/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2016, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.naming.java.permission;

import java.security.Permission;
import java.security.PermissionCollection;
import java.util.Iterator;

import org.jboss.as.naming.logging.NamingLogger;
import org.wildfly.common.Assert;

/**
 * Permission to access an object within the "java:" JNDI namespace.
 *

 * This permission does not span into bound nested contexts; such contexts may be governed by their own permission scheme.
 *

 * The {@code name} segment of the permission is a JNDI path whose segments are separated by {@code /} characters.  The
 * name may be preceded with the string {@code java:} for compatibility with previous schemes.  A name of
 * {@code <<ALL BINDINGS>>} is translated to {@code -} for compatibility reasons.
 */
public final class JndiPermission extends Permission {
    private static final long serialVersionUID = 1272655825146515997L;

    private final int actionBits;
    private String actionString;

    /**
     * The bitwise encoding of the {@code bind} action.
     */
    public static final int ACTION_BIND                 = 0b000000001;
    /**
     * The bitwise encoding of the {@code rebind} action.
     */
    public static final int ACTION_REBIND               = 0b000000010;
    /**
     * The bitwise encoding of the {@code unbind} action.
     */
    public static final int ACTION_UNBIND               = 0b000000100;
    /**
     * The bitwise encoding of the {@code lookup} action.
     */
    public static final int ACTION_LOOKUP               = 0b000001000;
    /**
     * The bitwise encoding of the {@code list} action.
     */
    public static final int ACTION_LIST                 = 0b000010000;
    /**
     * The bitwise encoding of the {@code listBindings} action.
     */
    public static final int ACTION_LIST_BINDINGS        = 0b000100000;
    /**
     * The bitwise encoding of the {@code createSubcontext} action.
     */
    public static final int ACTION_CREATE_SUBCONTEXT    = 0b001000000;
    /**
     * The bitwise encoding of the {@code destroySubcontext} action.
     */
    public static final int ACTION_DESTROY_SUBCONTEXT   = 0b010000000;
    /**
     * The bitwise encoding of the {@code addNamingListener} action.
     */
    public static final int ACTION_ADD_NAMING_LISTENER  = 0b100000000;
    /**
     * The bitwise encoding of the {@code *} action.  This value is the bitwise-OR of all of the other action bits.
     */
    public static final int ACTION_ALL                  = 0b111111111;

    /**
     * Construct a new instance.
     *
     * @param name the path name (must not be {@code null})
     * @param actions the actions (must not be {@code null})
     */
    public JndiPermission(final String name, final String actions) {
        this(name, parseActions(Assert.checkNotNullParam("actions", actions)));
    }

    /**
     * Construct a new instance using an action bitmask.  If a bit in the mask falls outside of {@link #ACTION_ALL}, it
     * is stripped.
     *
     * @param name the path name (must not be {@code null})
     * @param actionBits the action bits
     */
    public JndiPermission(final String name, final int actionBits) {
        super(canonicalize1(Assert.checkNotNullParam("name", name)));
        this.actionBits = actionBits & ACTION_ALL;
    }

    /**
     * Determine if this permission implies the other permission.
     *
     * @param permission the other permission
     * @return {@code true} if this permission implies the other, {@code false} if it does not or {@code permission} is {@code null}
     */
    public boolean implies(final Permission permission) {
        return permission instanceof JndiPermission && implies((JndiPermission) permission);
    }

    /**
     * Determine if this permission implies the other permission.
     *
     * @param permission the other permission
     * @return {@code true} if this permission implies the other, {@code false} if it does not or {@code permission} is {@code null}
     */
    public boolean implies(final JndiPermission permission) {
        return permission != null && ((actionBits & permission.actionBits) == permission.actionBits) && impliesPath(permission.getName());
    }

    /**
     * Determine if this permission implies the given {@code actions} on the given {@code name}.
     *
     * @param name the name (must not be {@code null})
     * @param actions the actions (must not be {@code null})
     * @return {@code true} if this permission implies the {@code actions} on the {@code name}, {@code false} otherwise
     */
    public boolean implies(final String name, final String actions) {
        return implies(name, parseActions(actions));
    }

    /**
     * Determine if this permission implies the given {@code actionsBits} on the given {@code name}.
     *
     * @param name the name (must not be {@code null})
     * @param actionBits the action bits
     * @return {@code true} if this permission implies the {@code actionBits} on the {@code name}, {@code false} otherwise
     */
    public boolean implies(final String name, final int actionBits) {
        Assert.checkNotNullParam("name", name);
        final int maskedBits = actionBits & ACTION_ALL;
        return (this.actionBits & maskedBits) == maskedBits && impliesPath(name);
    }

    /**
     * Determine whether this object is equal to another.
     *
     * @param other the other object
     * @return {@code true} if they are equal, {@code false} otherwise
     */
    public boolean equals(Object other) {
        return other instanceof JndiPermission && equals((JndiPermission)other);
    }

    /**
     * Determine whether this object is equal to another.
     *
     * @param other the other object
     * @return {@code true} if they are equal, {@code false} otherwise
     */
    public boolean equals(JndiPermission other) {
        return this == other || other != null && getName().equals(other.getName()) && actionBits == other.actionBits;
    }

    /**
     * Get the hash code of this permission.
     *
     * @return the hash code of this permission
     */
    public int hashCode() {
        return actionBits * 23 + getName().hashCode();
    }

    /**
     * Get the actions string.  The actions string will be a canonical version of the one passed in at construction.
     *
     * @return the actions string (not {@code null})
     */
    public String getActions() {
        final String actionString = this.actionString;
        if (actionString != null) {
            return actionString;
        }
        int actionBits = this.actionBits;
        if (actionBits == ACTION_ALL) {
            return this.actionString = "*";
        }
        int m = Integer.lowestOneBit(actionBits);
        if (m != 0) {
            StringBuilder b = new StringBuilder();
            b.append(getAction(m));
            actionBits &= ~m;
            while (actionBits != 0) {
                m = Integer.lowestOneBit(actionBits);
                b.append(',').append(getAction(m));
                actionBits &= ~m;
            }
            return this.actionString = b.toString();
        } else {
            return this.actionString = "";
        }
    }

    /**
     * Get the action bits.
     *
     * @return the action bits
     */
    public int getActionBits() {
        return actionBits;
    }

    /**
     * Return a permission which is equal to this one except with its actions reset to {@code actionBits}.  If the given
     * {@code actionBits} equals the current bits of this permission, then this permission instance is returned; otherwise
     * a new permission is constructed. Any action bits which fall outside of {@link #ACTION_ALL} are silently ignored.
     *
     * @param actionBits the action bits to use
     * @return a permission with only the given action bits (not {@code null})
     */
    public JndiPermission withNewActions(int actionBits) {
        actionBits &= ACTION_ALL;
        if (actionBits == this.actionBits) {
            return this;
        } else {
            return new JndiPermission(getName(), actionBits);
        }
    }

    /**
     * Return a permission which is equal to this one except with its actions reset to {@code actions}.  If the given
     * {@code actions} equals the current actions of this permission, then this permission instance is returned; otherwise
     * a new permission is constructed.
     *
     * @param actions the actions to use (must not be {@code null})
     * @return a permission with only the given action bits (not {@code null})
     */
    public JndiPermission withNewActions(String actions) {
        return withNewActions(parseActions(Assert.checkNotNullParam("actions", actions)));
    }

    /**
     * Return a permission which is equal to this one except with additional action bits.  If the given {@code actionBits}
     * do not add any new actions, then this permission instance is returned; otherwise a new permission is constructed.
     * Any action bits which fall outside of {@link #ACTION_ALL} are silently ignored.
     *
     * @param actionBits the action bits to add
     * @return a permission with the union of permissions from this instance and the given bits (not {@code null})
     */
    public JndiPermission withActions(int actionBits) {
        return withNewActions(actionBits & ACTION_ALL | this.actionBits);
    }

    /**
     * Return a permission which is equal to this one except with additional actions.  If the given {@code actionBits}
     * do not add any new actions, then this permission instance is returned; otherwise a new permission is constructed.
     *
     * @param actions the actions to add (must not be {@code null})
     * @return a permission with the union of permissions from this instance and the given bits (not {@code null})
     */
    public JndiPermission withActions(String actions) {
        return withActions(parseActions(Assert.checkNotNullParam("actions", actions)));
    }

    /**
     * Return a permission which is equal to this one except without some action bits.  If the given {@code actionBits}
     * do not remove any actions, then this permission instance is returned; otherwise a new permission is constructed.
     * Any action bits which fall outside of {@link #ACTION_ALL} are silently ignored.
     *
     * @param actionBits the action bits to remove
     * @return a permission with the given bits subtracted from this instance (not {@code null})
     */
    public JndiPermission withoutActions(int actionBits) {
        return withNewActions(this.actionBits & ~(actionBits & ACTION_ALL));
    }

    /**
     * Return a permission which is equal to this one except without some actions.  If the given {@code actions}
     * do not remove any actions, then this permission instance is returned; otherwise a new permission is constructed.
     *
     * @param actions the actions to remove (must not be {@code null})
     * @return a permission with the given bits subtracted from this instance (not {@code null})
     */
    public JndiPermission withoutActions(String actions) {
        return withoutActions(parseActions(Assert.checkNotNullParam("actions", actions)));
    }

    /**
     * Construct a new type-specific permission collection.
     *
     * @return the new permission collection instance (not {@code null})
     */
    public PermissionCollection newPermissionCollection() {
        return new JndiPermissionCollection();
    }

    // semi-private

    Object writeReplace() {
        return new SerializedJndiPermission(getName(), getActions());
    }

    boolean impliesPath(final String yourName) {
        return yourName.startsWith("java:") ? impliesPath0(yourName.substring(5)) : impliesPath0(yourName);
    }

    // private

    private boolean impliesPath0(final String yourName) {
        // segment-by-segment comparison
        final String myName = getName();
        final Iterator<String> myIter = JndiPermissionNameParser.nameIterator(myName);
        final Iterator<String> yourIter = JndiPermissionNameParser.nameIterator(yourName);
        // even if it's just "", there is always a first element
        assert myIter.hasNext() && yourIter.hasNext();
        String myNext;
        String yourNext;
        for (;;) {
            myNext = myIter.next();
            yourNext = yourIter.next();
            if (myNext.equals("-")) {
                // "-" implies everything including ""
                return true;
            }
            if (! myNext.equals("*") && ! myNext.equals(yourNext)) {
                // "foo/bar" does not imply "foo/baz"
                return false;
            }
            if (myIter.hasNext()) {
                if (! yourIter.hasNext()) {
                    // "foo/bar" does not imply "foo"
                    return false;
                }
            } else {
                // if neither has next, "foo/bar" implies "foo/bar", else "foo" does not imply "foo/bar"
                return ! yourIter.hasNext();
            }
        }
    }

    private static String canonicalize1(String name) {
        Assert.checkNotNullParam("name", name);
        return name.equalsIgnoreCase("<<ALL BINDINGS>>") ? "-" : canonicalize2(name);
    }

    private static String canonicalize2(String name) {
        return name.startsWith("java:") ? name.substring(5) : name;
    }

    private static int parseActions(final String actionsString) {
        // TODO: switch to Elytron utility methods to do this
        int actions = 0;
        int pos = 0;
        int idx = actionsString.indexOf(',');
        for (;;) {
            String str;
            if (idx == -1) {
                str = actionsString.substring(pos, actionsString.length()).trim();
                if (! str.isEmpty()) actions |= parseAction(str);
                return actions;
            } else {
                str = actionsString.substring(pos, idx).trim();
                pos = idx + 1;
                if (! str.isEmpty()) actions |= parseAction(str);
                idx = actionsString.indexOf(',', pos);
            }
        }
    }

    private static int parseAction(final String str) {
        switch (str) {
            case "*":
            case "all": return ACTION_ALL;
            case "bind": return ACTION_BIND;
            case "rebind": return ACTION_REBIND;
            case "unbind": return ACTION_UNBIND;
            case "lookup": return ACTION_LOOKUP;
            case "list": return ACTION_LIST;
            case "listBindings": return ACTION_LIST_BINDINGS;
            case "createSubcontext": return ACTION_CREATE_SUBCONTEXT;
            case "destroySubcontext": return ACTION_DESTROY_SUBCONTEXT;
            case "addNamingListener": return ACTION_ADD_NAMING_LISTENER;
            default: {
                throw NamingLogger.ROOT_LOGGER.invalidPermissionAction(str);
            }
        }
    }

    private String getAction(final int bit) {
        switch (bit) {
            case ACTION_BIND: return "bind";
            case ACTION_REBIND: return "rebind";
            case ACTION_UNBIND: return "unbind";
            case ACTION_LOOKUP: return "lookup";
            case ACTION_LIST: return "list";
            case ACTION_LIST_BINDINGS: return "listBindings";
            case ACTION_CREATE_SUBCONTEXT: return "createSubcontext";
            case ACTION_DESTROY_SUBCONTEXT: return "destroySubcontext";
            case ACTION_ADD_NAMING_LISTENER: return "addNamingListener";
            default: throw Assert.impossibleSwitchCase(bit);
        }
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.system;

import java.util.concurrent.atomic.AtomicLong;

import javax.management.AttributeChangeNotification;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.NotificationBroadcasterSupport;
import javax.management.ObjectName;

import org.jboss.logging.Logger;
import org.jboss.system.logging.ServiceMBeanLogger;

/**
 * An abstract base class JBoss services can subclass to implement a service that conforms to the ServiceMBean interface.
 * Subclasses must override {@link #getName} method and should override {@link #startService}, and {@link #stopService} as
 * approriate.
 *
 * @see ServiceMBean
 *
 * @author Rickard Öberg
 * @author Scott.Stark@jboss.org
 * @author Andreas Schaefer
 * @author Jason Dillon
 * @author Eduardo Martins (AS7)
 */
public class ServiceMBeanSupport extends NotificationBroadcasterSupport implements ServiceMBean, MBeanRegistration {

    protected Logger log;

    /** The MBeanServer which we have been register with. */
    protected MBeanServer server;

    /** The object name which we are registered under. */
    protected ObjectName serviceName;

    /** The current state this service is in. */
    private int state = UNREGISTERED;

    /** Sequence number for jmx notifications we send out */
    private final AtomicLong sequenceNumber = new AtomicLong(0);

    // on AS7 MBean lifecycle is CREATED -> STARTED -> REGISTERED -> UNREGISTERED -> STOP -> DESTROY,
    // on previous AS versions is REGISTERED -> CREATED -> STARTED -> STOP -> DESTROYED -> UNREGISTERED
    // to maintain compatibility with old ServiceMBeanSupport, we ignore some state changes, but redo
    // these when proper state change happens
    // the flags below are used to mark ignored lifecycle methods invocations
    private boolean createIgnored = false;
    private boolean startIgnored = false;
    private boolean stopIgnored = false;
    private boolean destroyIgnored = false;
    private boolean unregisterIgnored = false;

    /**
     * Construct a <t>ServiceMBeanSupport</tt>.
     *
     *

     * Sets up logging.
     */
    public ServiceMBeanSupport() {
        // can not call this(Class) because we need to call getClass()
        this.log = Logger.getLogger(getClass().getName());
        log.trace("Constructing");
    }

    /**
     * Construct a <t>ServiceMBeanSupport</tt>.
     *
     *

     * Sets up logging.
     *
     * @param type The class type to determine category name from.
     */
    public ServiceMBeanSupport(final Class<?> type) {
        this(type.getName());
    }

    /**
     * Construct a <t>ServiceMBeanSupport</tt>.
     *
     *

     * Sets up logging.
     *
     * @param category The logger category name.
     */
    public ServiceMBeanSupport(final String category) {
        this(Logger.getLogger(category));
    }

    /**
     * Construct a <t>ServiceMBeanSupport</tt>.
     *
     * @param log The logger to use.
     */
    public ServiceMBeanSupport(final Logger log) {
        this.log = log;
        log.trace("Constructing");
    }

    /**
     * Use the short class name as the default for the service name.
     *
     * @return a description of the mbean
     */
    public String getName() {
        final String s = log.getName();
        final int i = s.lastIndexOf(".");
        return i != -1 ? s.substring(i + 1, s.length()) : s;
    }

    public ObjectName getServiceName() {
        return serviceName;
    }

    public MBeanServer getServer() {
        return server;
    }

    public int getState() {
        return state;
    }

    public String getStateString() {
        return states[state];
    }

    public Logger getLog() {
        return log;
    }

    // /////////////////////////////////////////////////////////////////////////
    // State Mutators //
    // /////////////////////////////////////////////////////////////////////////

    public void create() throws Exception {
        jbossInternalCreate();
    }

    public void start() throws Exception {
        jbossInternalStart();
    }

    public void stop() {
        try {
            jbossInternalStop();
        } catch (Throwable t) {
            log.warn(ServiceMBeanLogger.ROOT_LOGGER.errorInStop(jbossInternalDescription()), t);
        }
    }

    public void destroy() {
        try {
            jbossInternalDestroy();
        } catch (Throwable t) {
            log.warn(ServiceMBeanLogger.ROOT_LOGGER.errorInDestroy(jbossInternalDescription()), t);
        }
    }

    protected String jbossInternalDescription() {
        if (serviceName != null)
            return serviceName.toString();
        else
            return getName();
    }

    public void jbossInternalLifecycle(String method) throws Exception {
        if (method == null)
            throw ServiceMBeanLogger.ROOT_LOGGER.nullMethodName();

        if (method.equals("create"))
            jbossInternalCreate();
        else if (method.equals("start"))
            jbossInternalStart();
        else if (method.equals("stop"))
            jbossInternalStop();
        else if (method.equals("destroy"))
            jbossInternalDestroy();
        else
            throw ServiceMBeanLogger.ROOT_LOGGER.unknownLifecycleMethod(method);
    }

    protected void jbossInternalCreate() throws Exception {
        // if (state == CREATED || state == STARTING || state == STARTED
        // || state == STOPPING || state == STOPPED)
        if (state != REGISTERED) {
            createIgnored = true;
            if (log.isDebugEnabled()) {
                log.debug("Ignoring create call; current state is " + getStateString());
            }
            return;
        }

        createIgnored = false;
        if (log.isDebugEnabled()) {
            log.debug("Creating " + jbossInternalDescription());
        }

        try {
            createService();
            state = CREATED;
        } catch (Exception e) {
            log.warn(ServiceMBeanLogger.ROOT_LOGGER.initializationFailed(jbossInternalDescription()), e);
            throw e;
        }

        if (log.isDebugEnabled()) {
            log.debug("Created " + jbossInternalDescription());
        }

        if (startIgnored) {
            start();
        }
    }

    protected void jbossInternalStart() throws Exception {
        if (state != CREATED && state != STOPPED) {
            startIgnored = true;
            if (log.isDebugEnabled()) {
                log.debug("Ignoring start call; current state is " + getStateString());
            }
            return;
        }

        startIgnored = false;

        state = STARTING;
        sendStateChangeNotification(STOPPED, STARTING, getName() + " starting", null);
        if (log.isDebugEnabled()) {
            log.debug("Starting " + jbossInternalDescription());
        }

        try {
            startService();
        } catch (Exception e) {
            state = FAILED;
            sendStateChangeNotification(STARTING, FAILED, getName() + " failed", e);
            log.warn(ServiceMBeanLogger.ROOT_LOGGER.startingFailed(jbossInternalDescription()), e);
            throw e;
        }

        state = STARTED;
        sendStateChangeNotification(STARTING, STARTED, getName() + " started", null);
        if (log.isDebugEnabled()) {
            log.debug("Started " + jbossInternalDescription());
        }

        if (stopIgnored) {
            stop();
        }
    }

    protected void jbossInternalStop() {
        if (state != STARTED) {
            stopIgnored = true;
            if (log.isDebugEnabled()) {
                log.debug("Ignoring stop call; current state is " + getStateString());
            }
            return;
        }

        stopIgnored = false;
        state = STOPPING;
        sendStateChangeNotification(STARTED, STOPPING, getName() + " stopping", null);
        if (log.isDebugEnabled()) {
            log.debug("Stopping " + jbossInternalDescription());
        }

        try {
            stopService();
        } catch (Throwable e) {
            state = FAILED;
            sendStateChangeNotification(STOPPING, FAILED, getName() + " failed", e);
            log.warn(ServiceMBeanLogger.ROOT_LOGGER.stoppingFailed(jbossInternalDescription()), e);
            return;
        }

        state = STOPPED;
        sendStateChangeNotification(STOPPING, STOPPED, getName() + " stopped", null);
        if (log.isDebugEnabled()) {
            log.debug("Stopped " + jbossInternalDescription());
        }

        if (destroyIgnored) {
            destroy();
        }
    }

    protected void jbossInternalDestroy() {
        if (state != STOPPED) {
            destroyIgnored = true;
            if (log.isDebugEnabled()) {
                log.debug("Ignoring destroy call; current state is " + getStateString());
            }
            return;
        }

        destroyIgnored = false;
        if (log.isDebugEnabled()) {
            log.debug("Destroying " + jbossInternalDescription());
        }

        try {
            destroyService();
        } catch (Throwable t) {
            log.warn(ServiceMBeanLogger.ROOT_LOGGER.destroyingFailed(jbossInternalDescription()), t);
        }
        state = DESTROYED;
        if (log.isDebugEnabled()) {
            log.debug("Destroyed " + jbossInternalDescription());
        }

        if (unregisterIgnored) {
            postDeregister();
        }
    }

    // /////////////////////////////////////////////////////////////////////////
    // JMX Hooks //
    // /////////////////////////////////////////////////////////////////////////

    /**
     * Callback method of {@link javax.management.MBeanRegistration} before the MBean is registered at the JMX Agent.
     *
     *

     * Attention: Always call this method when you overwrite it in a subclass because it saves the Object Name of the
     * MBean.
     *
     * @param server Reference to the JMX Agent this MBean is registered on
     * @param name Name specified by the creator of the MBean. Note that you can overwrite it when the given ObjectName is null
     *        otherwise the change is discarded (maybe a bug in JMX-RI).
     * @return the ObjectName
     * @throws Exception for any error
     */
    public ObjectName preRegister(MBeanServer server, ObjectName name) throws Exception {
        this.server = server;

        serviceName = getObjectName(server, name);

        return serviceName;
    }

    public void postRegister(Boolean registrationDone) {
        if (!registrationDone.booleanValue()) {
            log.debug("Registration is not done -> stop");
            stop();
        } else {
            state = REGISTERED;
            if (createIgnored) {
                try {
                    create();
                } catch (Exception e) {
                    log.error(ServiceMBeanLogger.ROOT_LOGGER.postRegisterInitializationFailed(), e);
                }
            }
        }
    }

    public void preDeregister() throws Exception {}

    public void postDeregister() {
        if (state != DESTROYED) {
            unregisterIgnored = true;
            if (log.isDebugEnabled()) {
                log.debug("Ignoring postDeregister call; current state is " + getStateString());
            }
            return;
        }
        unregisterIgnored = false;
        server = null;
        serviceName = null;
        state = UNREGISTERED;
    }

    // /////////////////////////////////////////////////////////////////////////
    // Concrete Service Overrides //
    // /////////////////////////////////////////////////////////////////////////

    /**
     * Sub-classes should override this method if they only need to set their object name during MBean pre-registration.
     *
     * @param server the mbeanserver
     * @param name the suggested name, maybe null
     * @return the object name
     * @throws javax.management.MalformedObjectNameException for a bad object name
     */
    protected ObjectName getObjectName(MBeanServer server, ObjectName name) throws MalformedObjectNameException {
        return name;
    }

    /**
     * Sub-classes should override this method to provide custum 'create' logic.
     *
     *

     * This method is empty, and is provided for convenience when concrete service classes do not need to perform anything
     * specific for this state change.
     *
     * @throws Exception for any error
     */
    protected void createService() throws Exception {}

    /**
     * Sub-classes should override this method to provide custum 'start' logic.
     *
     *

     * This method is empty, and is provided for convenience when concrete service classes do not need to perform anything
     * specific for this state change.
     *
     * @throws Exception for any error
     */
    protected void startService() throws Exception {}

    /**
     * Sub-classes should override this method to provide custum 'stop' logic.
     *
     *

     * This method is empty, and is provided for convenience when concrete service classes do not need to perform anything
     * specific for this state change.
     *
     * @throws Exception for any error
     */
    protected void stopService() throws Exception {}

    /**
     * Sub-classes should override this method to provide custum 'destroy' logic.
     *
     *

     * This method is empty, and is provided for convenience when concrete service classes do not need to perform anything
     * specific for this state change.
     *
     * @throws Exception for any error
     */
    protected void destroyService() throws Exception {}

    /**
     * The nextNotificationSequenceNumber method returns the next sequence number for use in notifications.
     *
     * @return a long value
     */
    public long nextNotificationSequenceNumber() {
        return sequenceNumber.incrementAndGet();
    }

    /**
     * The getNextNotificationSequenceNumber method returns the next sequence number for use in notifications.
     *
     * @return a long value
     */
    protected long getNextNotificationSequenceNumber() {
        return nextNotificationSequenceNumber();
    }

    /**
     * Helper for sending out state change notifications
     */
    private void sendStateChangeNotification(int oldState, int newState, String msg, Throwable t) {
        long now = System.currentTimeMillis();
        AttributeChangeNotification stateChangeNotification = new AttributeChangeNotification(this,
                getNextNotificationSequenceNumber(), now, msg, "State", "java.lang.Integer", new Integer(oldState),
                new Integer(newState));
        stateChangeNotification.setUserData(t);
        sendNotification(stateChangeNotification);
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat Middleware LLC, and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.pojo;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamReader;

import org.jboss.as.pojo.descriptor.KernelDeploymentXmlDescriptor;
import org.jboss.as.pojo.descriptor.KernelDeploymentXmlDescriptorParser;
import org.jboss.as.pojo.descriptor.LegacyKernelDeploymentXmlDescriptorParser;
import org.jboss.as.pojo.logging.PojoLogger;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.staxmapper.XMLMapper;
import org.jboss.vfs.VFSUtils;
import org.jboss.vfs.VirtualFile;
import org.jboss.vfs.VirtualFileFilter;
import org.jboss.vfs.util.SuffixMatchFilter;

/**
 * DeploymentUnitProcessor responsible for parsing a jboss-beans.xml
 * descriptor and attaching the corresponding KernelDeploymentXmlDescriptor.
 *
 * @author Ales Justin
 */
public class KernelDeploymentParsingProcessor implements DeploymentUnitProcessor {

    private final XMLMapper xmlMapper = XMLMapper.Factory.create();
    private final XMLInputFactory inputFactory = XMLInputFactory.newInstance();

    public KernelDeploymentParsingProcessor() {
        final KernelDeploymentXmlDescriptorParser parser = new KernelDeploymentXmlDescriptorParser();
        xmlMapper.registerRootElement(new QName(KernelDeploymentXmlDescriptorParser.NAMESPACE, "deployment"), parser);
        // old MC parser -- just a warning / info atm
        final LegacyKernelDeploymentXmlDescriptorParser legacy = new LegacyKernelDeploymentXmlDescriptorParser();
        xmlMapper.registerRootElement(new QName(LegacyKernelDeploymentXmlDescriptorParser.MC_NAMESPACE_1_0, "deployment"), legacy);
        xmlMapper.registerRootElement(new QName(LegacyKernelDeploymentXmlDescriptorParser.MC_NAMESPACE_2_0, "deployment"), legacy);
    }

    /**
     * Process a deployment for jboss-beans.xml files.
     * Will parse the xml file and attach a configuration discovered during processing.
     *
     * @param phaseContext the deployment unit context
     * @throws DeploymentUnitProcessingException
     *
     */
    @Override
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        DeploymentUnit unit = phaseContext.getDeploymentUnit();
        final VirtualFile deploymentRoot = unit.getAttachment(Attachments.DEPLOYMENT_ROOT).getRoot();
        parseDescriptors(unit, deploymentRoot);
        final List<ResourceRoot> resourceRoots = unit.getAttachmentList(Attachments.RESOURCE_ROOTS);
        for (ResourceRoot root : resourceRoots)
            parseDescriptors(unit, root.getRoot());
    }

    /**
     * Find and parse -jboss-beans.xml files.
     *
     * @param unit the deployment unit
     * @param root the root
     * @throws DeploymentUnitProcessingException
     *          for any error
     */
    protected void parseDescriptors(DeploymentUnit unit, VirtualFile root) throws DeploymentUnitProcessingException {
        if (root == null || root.exists() == false)
            return;

        Collection<VirtualFile> beans;
        final String name = root.getName();
        if (name.endsWith("jboss-beans.xml")) {
            beans = Collections.singleton(root);
        } else {
            VirtualFileFilter filter = new SuffixMatchFilter("jboss-beans.xml");
            beans = new ArrayList<VirtualFile>();
            try {
                // try plain .jar/META-INF
                VirtualFile metainf = root.getChild("META-INF");
                if (metainf.exists())
                    beans.addAll(metainf.getChildren(filter));

                // allow for WEB-INF/*-jboss-beans.xml
                VirtualFile webinf = root.getChild("WEB-INF");
                if (webinf.exists()) {
                    beans.addAll(webinf.getChildren(filter));

                    // allow WEB-INF/classes/META-INF
                    metainf = webinf.getChild("classes/META-INF");
                    if (metainf.exists())
                        beans.addAll(metainf.getChildren(filter));
                }
            } catch (IOException e) {
                throw new DeploymentUnitProcessingException(e);
            }
        }
        for (VirtualFile beansXmlFile : beans)
            parseDescriptor(unit, beansXmlFile);
    }

    /**
     * Parse -jboss-beans.xml file.
     *
     * @param unit         the deployment unit
     * @param beansXmlFile the beans xml file
     * @throws DeploymentUnitProcessingException
     *          for any error
     */
    protected void parseDescriptor(DeploymentUnit unit, VirtualFile beansXmlFile) throws DeploymentUnitProcessingException {
        if (beansXmlFile == null || beansXmlFile.exists() == false)
            return;

        InputStream xmlStream = null;
        try {
            xmlStream = beansXmlFile.openStream();
            final XMLStreamReader reader = inputFactory.createXMLStreamReader(xmlStream);
            final ParseResult<KernelDeploymentXmlDescriptor> result = new ParseResult<KernelDeploymentXmlDescriptor>();
            xmlMapper.parseDocument(result, reader);
            final KernelDeploymentXmlDescriptor xmlDescriptor = result.getResult();
            if (xmlDescriptor != null)
                unit.addToAttachmentList(KernelDeploymentXmlDescriptor.ATTACHMENT_KEY, xmlDescriptor);
            else
                throw PojoLogger.ROOT_LOGGER.failedToParse(beansXmlFile);
        } catch (DeploymentUnitProcessingException e) {
            throw e;
        } catch (Exception e) {
            throw PojoLogger.ROOT_LOGGER.parsingException(beansXmlFile, e);
        } finally {
            VFSUtils.safeClose(xmlStream);
        }
    }

    @Override
    public void undeploy(DeploymentUnit context) {
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2012, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.connector.subsystems.resourceadapters;

import static org.jboss.as.connector.logging.ConnectorLogger.SUBSYSTEM_RA_LOGGER;
import static org.jboss.as.connector.subsystems.common.pool.Constants.BACKGROUNDVALIDATION;
import static org.jboss.as.connector.subsystems.common.pool.Constants.BACKGROUNDVALIDATIONMILLIS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.BLOCKING_TIMEOUT_WAIT_MILLIS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_DECREMENTER_CLASS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_DECREMENTER_PROPERTIES;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_INCREMENTER_CLASS;
import static org.jboss.as.connector.subsystems.common.pool.Constants.CAPACITY_INCREMENTER_PROPERTIES;
import static org.jboss.as.connector.subsystems.common.pool.Constants.IDLETIMEOUTMINUTES;
import static org.jboss.as.connector.subsystems.common.pool.Constants.INITIAL_POOL_SIZE;
import static org.jboss.as.connector.subsystems.common.pool.Constants.MAX_POOL_SIZE;
import static org.jboss.as.connector.subsystems.common.pool.Constants.MIN_POOL_SIZE;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_FAIR;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_FLUSH_STRATEGY;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_PREFILL;
import static org.jboss.as.connector.subsystems.common.pool.Constants.POOL_USE_STRICT_MIN;
import static org.jboss.as.connector.subsystems.common.pool.Constants.USE_FAST_FAIL;
import static org.jboss.as.connector.subsystems.common.pool.Constants.VALIDATE_ON_MATCH;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ADMIN_OBJECTS_NAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ALLOCATION_RETRY;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ALLOCATION_RETRY_WAIT_MILLIS;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.APPLICATION;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ARCHIVE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.AUTHENTICATION_CONTEXT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.AUTHENTICATION_CONTEXT_AND_APPLICATION;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.BEANVALIDATION_GROUPS;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.BOOTSTRAP_CONTEXT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.CLASS_NAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.CONFIG_PROPERTIES;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.CONNECTABLE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.CONNECTIONDEFINITIONS_NAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ELYTRON_ENABLED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ENABLED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ENLISTMENT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.ENLISTMENT_TRACE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.INTERLEAVING;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.JNDINAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.MCP;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.MODULE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.NOTXSEPARATEPOOL;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.NO_RECOVERY;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.PAD_XID;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERLUGIN_CLASSNAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERLUGIN_PROPERTIES;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_AUTHENTICATION_CONTEXT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_CREDENTIAL_REFERENCE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_ELYTRON_ENABLED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_PASSWORD;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RECOVERY_USERNAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RESOURCEADAPTERS_NAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.RESOURCEADAPTER_NAME;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SAME_RM_OVERRIDE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SECURITY_DOMAIN_AND_APPLICATION;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.SHARABLE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.STATISTICS_ENABLED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.TRACKING;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.TRANSACTION_SUPPORT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.USE_CCM;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.USE_JAVA_CONTEXT;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_ELYTRON_SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_DEFAULT_GROUP;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_DEFAULT_GROUPS;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_DEFAULT_PRINCIPAL;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_DOMAIN;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_MAPPING_FROM;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_MAPPING_GROUPS;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_MAPPING_REQUIRED;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_MAPPING_TO;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WM_SECURITY_MAPPING_USERS;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.WRAP_XA_RESOURCE;
import static org.jboss.as.connector.subsystems.resourceadapters.Constants.XA_RESOURCE_TIMEOUT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;

import java.util.List;

import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;

import org.jboss.as.connector.metadata.api.resourceadapter.WorkManagerSecurity;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.as.controller.persistence.SubsystemMarshallingContext;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.jca.common.api.metadata.common.Capacity;
import org.jboss.jca.common.api.metadata.common.Pool;
import org.jboss.jca.common.api.metadata.common.Recovery;
import org.jboss.jca.common.api.metadata.common.TransactionSupportEnum;
import org.jboss.jca.common.api.metadata.resourceadapter.Activation;
import org.jboss.jca.common.api.metadata.resourceadapter.Activations;
import org.jboss.jca.common.api.metadata.resourceadapter.ConnectionDefinition;
import org.jboss.jca.common.api.metadata.resourceadapter.WorkManager;
import org.jboss.staxmapper.XMLElementReader;
import org.jboss.staxmapper.XMLElementWriter;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.jboss.staxmapper.XMLExtendedStreamWriter;

/**
* @author Tomaz Cerar (c) 2012 Red Hat Inc.
*/
public final class ResourceAdapterSubsystemParser implements XMLStreamConstants, XMLElementReader<List<ModelNode>>,
        XMLElementWriter<SubsystemMarshallingContext> {

    static final ResourceAdapterSubsystemParser INSTANCE = new ResourceAdapterSubsystemParser();

    /** {@inheritDoc} */
    @Override
    public void writeContent(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
        ModelNode node = context.getModelNode();
        boolean hasChildren = node.hasDefined(RESOURCEADAPTER_NAME) && node.get(RESOURCEADAPTER_NAME).asPropertyList().size() > 0;

        context.startSubsystemElement(Namespace.CURRENT.getUriString(), !hasChildren);

        if (hasChildren) {
            writer.writeStartElement(Element.RESOURCE_ADAPTERS.getLocalName());
            ModelNode ras = node.get(RESOURCEADAPTER_NAME);
            for (String name : ras.keys()) {
                final ModelNode ra = ras.get(name);
                writeRaElement(writer, ra, name);
            }
            writer.writeEndElement();
            // Close the subsystem element
            writer.writeEndElement();
        }
    }

    private void writeRaElement(XMLExtendedStreamWriter streamWriter, ModelNode ra, final String name) throws XMLStreamException {
        streamWriter.writeStartElement(Activations.Tag.RESOURCE_ADAPTER.getLocalName());
        streamWriter.writeAttribute(ResourceAdapterParser.Attribute.ID.getLocalName(), name);
        STATISTICS_ENABLED.marshallAsAttribute(ra, streamWriter);
        ARCHIVE.marshallAsElement(ra, streamWriter);
        MODULE.marshallAsElement(ra, streamWriter);

        BOOTSTRAP_CONTEXT.marshallAsElement(ra, streamWriter);

        if (ra.hasDefined(BEANVALIDATION_GROUPS.getName())) {
            streamWriter.writeStartElement(Activation.Tag.BEAN_VALIDATION_GROUPS.getLocalName());
            for (ModelNode bvg : ra.get(BEANVALIDATION_GROUPS.getName()).asList()) {
                streamWriter.writeStartElement(BEANVALIDATION_GROUPS.getXmlName());
                streamWriter.writeCharacters(bvg.asString());
                streamWriter.writeEndElement();
            }
            streamWriter.writeEndElement();
        }

        TRANSACTION_SUPPORT.marshallAsElement(ra, streamWriter);
        writeNewConfigProperties(streamWriter, ra);
        TransactionSupportEnum transactionSupport = ra.hasDefined(TRANSACTION_SUPPORT.getName()) ? TransactionSupportEnum
            .valueOf(ra.get(TRANSACTION_SUPPORT.getName()).asString()) : null;
        boolean isXa = false;
        if (transactionSupport == TransactionSupportEnum.XATransaction) {
            isXa = true;
        }
        if (ra.hasDefined(WM_SECURITY.getName()) && ra.get(WM_SECURITY.getName()).asBoolean()) {
            streamWriter.writeStartElement(Activation.Tag.WORKMANAGER.getLocalName());
            streamWriter.writeStartElement(WorkManager.Tag.SECURITY.getLocalName());
            WM_SECURITY_MAPPING_REQUIRED.marshallAsElement(ra, streamWriter);
            WM_SECURITY_DOMAIN.marshallAsElement(ra, streamWriter);
            WM_ELYTRON_SECURITY_DOMAIN.marshallAsElement(ra, streamWriter);
            WM_SECURITY_DEFAULT_PRINCIPAL.marshallAsElement(ra, streamWriter);
            if (ra.hasDefined(WM_SECURITY_DEFAULT_GROUPS.getName())) {
                streamWriter.writeStartElement(WM_SECURITY_DEFAULT_GROUPS.getXmlName());
                for (ModelNode group : ra.get(WM_SECURITY_DEFAULT_GROUPS.getName()).asList()) {
                    streamWriter.writeStartElement(WM_SECURITY_DEFAULT_GROUP.getXmlName());
                    streamWriter.writeCharacters(group.asString());
                    streamWriter.writeEndElement();
                }
                streamWriter.writeEndElement();
            }
            if (ra.hasDefined(WM_SECURITY_MAPPING_USERS.getName()) || ra.hasDefined(WM_SECURITY_MAPPING_GROUPS.getName())) {
                streamWriter.writeStartElement(WorkManagerSecurity.Tag.MAPPINGS.getLocalName());
                if (ra.hasDefined(WM_SECURITY_MAPPING_USERS.getName())) {
                    streamWriter.writeStartElement(WorkManagerSecurity.Tag.USERS.getLocalName());
                    for (ModelNode node : ra.get(WM_SECURITY_MAPPING_USERS.getName()).asList()) {
                        streamWriter.writeStartElement(WorkManagerSecurity.Tag.MAP.getLocalName());
                        WM_SECURITY_MAPPING_FROM.marshallAsAttribute(node, streamWriter);
                        WM_SECURITY_MAPPING_TO.marshallAsAttribute(node, streamWriter);
                        streamWriter.writeEndElement();

                    }
                    streamWriter.writeEndElement();
                }
                if (ra.hasDefined(WM_SECURITY_MAPPING_GROUPS.getName())) {
                    streamWriter.writeStartElement(WorkManagerSecurity.Tag.GROUPS.getLocalName());
                    for (ModelNode node : ra.get(WM_SECURITY_MAPPING_GROUPS.getName()).asList()) {
                        streamWriter.writeStartElement(WorkManagerSecurity.Tag.MAP.getLocalName());
                        WM_SECURITY_MAPPING_FROM.marshallAsAttribute(node, streamWriter);
                        WM_SECURITY_MAPPING_TO.marshallAsAttribute(node, streamWriter);
                        streamWriter.writeEndElement();
                    }
                    streamWriter.writeEndElement();
                }
                streamWriter.writeEndElement();
            }
            streamWriter.writeEndElement();
            streamWriter.writeEndElement();
        }
        if (ra.hasDefined(CONNECTIONDEFINITIONS_NAME)) {
            streamWriter.writeStartElement(Activation.Tag.CONNECTION_DEFINITIONS.getLocalName());
            for (Property conDef : ra.get(CONNECTIONDEFINITIONS_NAME).asPropertyList()) {
                writeConDef(streamWriter, conDef.getValue(), conDef.getName(), isXa);
            }
            streamWriter.writeEndElement();
        }

        if (ra.hasDefined(ADMIN_OBJECTS_NAME)) {
            streamWriter.writeStartElement(Activation.Tag.ADMIN_OBJECTS.getLocalName());
            for (Property adminObject : ra.get(ADMIN_OBJECTS_NAME).asPropertyList()) {
                writeAdminObject(streamWriter, adminObject.getValue(), adminObject.getName());
            }
            streamWriter.writeEndElement();
        }
        streamWriter.writeEndElement();

    }

    private void writeNewConfigProperties(XMLExtendedStreamWriter streamWriter, ModelNode ra) throws XMLStreamException {
        if (ra.hasDefined(CONFIG_PROPERTIES.getName())) {
            for (Property connectionProperty : ra.get(CONFIG_PROPERTIES.getName()).asPropertyList()) {
                writeProperty(streamWriter, ra, connectionProperty.getName(), connectionProperty
                        .getValue().get("value").asString(), Activation.Tag.CONFIG_PROPERTY.getLocalName());
            }

        }
    }

    private void writeProperty(XMLExtendedStreamWriter writer, ModelNode node, String name, String value, String localName)
            throws XMLStreamException {

        writer.writeStartElement(localName);
        writer.writeAttribute("name", name);
        writer.writeCharacters(value);
        writer.writeEndElement();

    }

    private void writeAdminObject(XMLExtendedStreamWriter streamWriter, ModelNode adminObject, final String poolName) throws XMLStreamException {
        streamWriter.writeStartElement(Activation.Tag.ADMIN_OBJECT.getLocalName());
        CLASS_NAME.marshallAsAttribute(adminObject, streamWriter);
        JNDINAME.marshallAsAttribute(adminObject, streamWriter);
        ENABLED.marshallAsAttribute(adminObject, streamWriter);
        USE_JAVA_CONTEXT.marshallAsAttribute(adminObject, streamWriter);
        streamWriter.writeAttribute("pool-name", poolName);

        writeNewConfigProperties(streamWriter, adminObject);
        streamWriter.writeEndElement();

    }

    private void writeConDef(XMLExtendedStreamWriter streamWriter, ModelNode conDef, final String poolName, final boolean isXa) throws XMLStreamException {
        streamWriter.writeStartElement(Activation.Tag.CONNECTION_DEFINITION.getLocalName());
        CLASS_NAME.marshallAsAttribute(conDef, streamWriter);
        JNDINAME.marshallAsAttribute(conDef, streamWriter);
        ENABLED.marshallAsAttribute(conDef, streamWriter);
        CONNECTABLE.marshallAsAttribute(conDef, streamWriter);
        TRACKING.marshallAsAttribute(conDef, streamWriter);
        USE_JAVA_CONTEXT.marshallAsAttribute(conDef, streamWriter);
        streamWriter.writeAttribute("pool-name", poolName);
        USE_CCM.marshallAsAttribute(conDef, streamWriter);
        SHARABLE.marshallAsAttribute(conDef, streamWriter);
        ENLISTMENT.marshallAsAttribute(conDef, streamWriter);
        MCP.marshallAsAttribute(conDef, streamWriter);
        ENLISTMENT_TRACE.marshallAsAttribute(conDef, streamWriter);

        writeNewConfigProperties(streamWriter, conDef);

        boolean poolRequired = INITIAL_POOL_SIZE.isMarshallable(conDef) || MAX_POOL_SIZE.isMarshallable(conDef) || MIN_POOL_SIZE.isMarshallable(conDef) ||
                POOL_USE_STRICT_MIN.isMarshallable(conDef) || POOL_PREFILL.isMarshallable(conDef) || POOL_FAIR.isMarshallable(conDef) || POOL_FLUSH_STRATEGY.isMarshallable(conDef);
        final boolean capacityRequired = CAPACITY_INCREMENTER_CLASS.isMarshallable(conDef) ||
                CAPACITY_INCREMENTER_PROPERTIES.isMarshallable(conDef) ||
                CAPACITY_DECREMENTER_CLASS.isMarshallable(conDef) ||
                CAPACITY_DECREMENTER_PROPERTIES.isMarshallable(conDef);
        poolRequired = poolRequired || capacityRequired;

        if (poolRequired) {
            if (isXa) {

                streamWriter.writeStartElement(ConnectionDefinition.Tag.XA_POOL.getLocalName());
                MIN_POOL_SIZE.marshallAsElement(conDef, streamWriter);
                INITIAL_POOL_SIZE.marshallAsElement(conDef, streamWriter);
                MAX_POOL_SIZE.marshallAsElement(conDef, streamWriter);
                POOL_PREFILL.marshallAsElement(conDef, streamWriter);
                POOL_FAIR.marshallAsElement(conDef, streamWriter);
                POOL_USE_STRICT_MIN.marshallAsElement(conDef, streamWriter);
                POOL_FLUSH_STRATEGY.marshallAsElement(conDef, streamWriter);

                SAME_RM_OVERRIDE.marshallAsElement(conDef, streamWriter);
                if (conDef.hasDefined(INTERLEAVING.getName()) && conDef.get(INTERLEAVING.getName()).asBoolean()) {
                    streamWriter.writeEmptyElement(INTERLEAVING.getXmlName());
                } else {
                    INTERLEAVING.marshallAsElement(conDef, streamWriter);
                }
                if (conDef.hasDefined(NOTXSEPARATEPOOL.getName()) && conDef.get(NOTXSEPARATEPOOL.getName()).asBoolean()) {
                    streamWriter.writeEmptyElement(NOTXSEPARATEPOOL.getXmlName());
                } else {
                    NOTXSEPARATEPOOL.marshallAsElement(conDef, streamWriter);
                }
                PAD_XID.marshallAsElement(conDef, streamWriter);
                WRAP_XA_RESOURCE.marshallAsElement(conDef, streamWriter);

            } else {
                streamWriter.writeStartElement(ConnectionDefinition.Tag.POOL.getLocalName());
                MIN_POOL_SIZE.marshallAsElement(conDef, streamWriter);
                INITIAL_POOL_SIZE.marshallAsElement(conDef, streamWriter);
                MAX_POOL_SIZE.marshallAsElement(conDef, streamWriter);
                POOL_PREFILL.marshallAsElement(conDef, streamWriter);
                POOL_USE_STRICT_MIN.marshallAsElement(conDef, streamWriter);
                POOL_FLUSH_STRATEGY.marshallAsElement(conDef, streamWriter);

            }
            if (capacityRequired) {
                streamWriter.writeStartElement(Pool.Tag.CAPACITY.getLocalName());
                if (conDef.hasDefined(CAPACITY_INCREMENTER_CLASS.getName())) {
                    streamWriter.writeStartElement(Capacity.Tag.INCREMENTER.getLocalName());
                    CAPACITY_INCREMENTER_CLASS.marshallAsAttribute(conDef, streamWriter);
                    CAPACITY_INCREMENTER_PROPERTIES.marshallAsElement(conDef, streamWriter);

                    streamWriter.writeEndElement();
                }
                if (conDef.hasDefined(CAPACITY_DECREMENTER_CLASS.getName())) {
                    streamWriter.writeStartElement(Capacity.Tag.DECREMENTER.getLocalName());
                    CAPACITY_DECREMENTER_CLASS.marshallAsAttribute(conDef, streamWriter);
                    CAPACITY_DECREMENTER_PROPERTIES.marshallAsElement(conDef, streamWriter);

                    streamWriter.writeEndElement();
                }
                streamWriter.writeEndElement();
            }
            streamWriter.writeEndElement();
        }

        if (conDef.hasDefined(APPLICATION.getName()) || conDef.hasDefined(SECURITY_DOMAIN.getName())
                || conDef.hasDefined(SECURITY_DOMAIN_AND_APPLICATION.getName())
                || conDef.hasDefined(ELYTRON_ENABLED.getName())) {
            streamWriter.writeStartElement(ConnectionDefinition.Tag.SECURITY.getLocalName());
            if (conDef.hasDefined(APPLICATION.getName()) && conDef.get(APPLICATION.getName()).asBoolean()) {
                streamWriter.writeEmptyElement(APPLICATION.getXmlName());
            } else {
                APPLICATION.marshallAsElement(conDef, streamWriter);
            }
            SECURITY_DOMAIN.marshallAsElement(conDef, streamWriter);
            SECURITY_DOMAIN_AND_APPLICATION.marshallAsElement(conDef, streamWriter);
            ELYTRON_ENABLED.marshallAsElement(conDef, streamWriter);
            AUTHENTICATION_CONTEXT.marshallAsElement(conDef, streamWriter);
            AUTHENTICATION_CONTEXT_AND_APPLICATION.marshallAsElement(conDef, streamWriter);

            streamWriter.writeEndElement();
        }

        if (conDef.hasDefined(BLOCKING_TIMEOUT_WAIT_MILLIS.getName()) || conDef.hasDefined(IDLETIMEOUTMINUTES.getName()) || conDef.hasDefined(ALLOCATION_RETRY.getName())
                || conDef.hasDefined(ALLOCATION_RETRY_WAIT_MILLIS.getName()) || conDef.hasDefined(XA_RESOURCE_TIMEOUT.getName())) {
            streamWriter.writeStartElement(ConnectionDefinition.Tag.TIMEOUT.getLocalName());
            BLOCKING_TIMEOUT_WAIT_MILLIS.marshallAsElement(conDef, streamWriter);
            IDLETIMEOUTMINUTES.marshallAsElement(conDef, streamWriter);
            ALLOCATION_RETRY.marshallAsElement(conDef, streamWriter);
            ALLOCATION_RETRY_WAIT_MILLIS.marshallAsElement(conDef, streamWriter);
            XA_RESOURCE_TIMEOUT.marshallAsElement(conDef, streamWriter);
            streamWriter.writeEndElement();
        }

        if (conDef.hasDefined(BACKGROUNDVALIDATION.getName()) || conDef.hasDefined(BACKGROUNDVALIDATIONMILLIS.getName()) ||
                conDef.hasDefined(USE_FAST_FAIL.getName()) ||  conDef.hasDefined(VALIDATE_ON_MATCH.getName())) {
            streamWriter.writeStartElement(ConnectionDefinition.Tag.VALIDATION.getLocalName());
            BACKGROUNDVALIDATION.marshallAsElement(conDef, streamWriter);
            BACKGROUNDVALIDATIONMILLIS.marshallAsElement(conDef, streamWriter);
            USE_FAST_FAIL.marshallAsElement(conDef, streamWriter);
            VALIDATE_ON_MATCH.marshallAsElement(conDef, streamWriter);
            streamWriter.writeEndElement();
        }

        if (conDef.hasDefined(RECOVERY_USERNAME.getName()) || conDef.hasDefined(RECOVERY_PASSWORD.getName())
                || conDef.hasDefined(RECOVERY_SECURITY_DOMAIN.getName()) || conDef.hasDefined(RECOVERLUGIN_CLASSNAME.getName())
                || conDef.hasDefined(RECOVERLUGIN_PROPERTIES.getName()) || conDef.hasDefined(NO_RECOVERY.getName())
                || conDef.hasDefined(ELYTRON_ENABLED.getName())) {

            streamWriter.writeStartElement(ConnectionDefinition.Tag.RECOVERY.getLocalName());
            NO_RECOVERY.marshallAsAttribute(conDef, streamWriter);

            if (conDef.hasDefined(RECOVERY_USERNAME.getName()) || conDef.hasDefined(RECOVERY_PASSWORD.getName())
                    || conDef.hasDefined(RECOVERY_CREDENTIAL_REFERENCE.getName())
                    || conDef.hasDefined(RECOVERY_SECURITY_DOMAIN.getName())
                    || conDef.hasDefined(RECOVERY_ELYTRON_ENABLED.getName())) {
                streamWriter.writeStartElement(Recovery.Tag.RECOVER_CREDENTIAL.getLocalName());
                RECOVERY_USERNAME.marshallAsElement(conDef, streamWriter);
                RECOVERY_PASSWORD.marshallAsElement(conDef, streamWriter);
                RECOVERY_CREDENTIAL_REFERENCE.marshallAsElement(conDef, streamWriter);
                RECOVERY_SECURITY_DOMAIN.marshallAsElement(conDef, streamWriter);
                RECOVERY_ELYTRON_ENABLED.marshallAsElement(conDef, streamWriter);
                RECOVERY_AUTHENTICATION_CONTEXT.marshallAsElement(conDef, streamWriter);
                streamWriter.writeEndElement();
            }
            if (conDef.hasDefined(RECOVERLUGIN_CLASSNAME.getName()) || conDef.hasDefined(RECOVERLUGIN_PROPERTIES.getName())) {
                streamWriter.writeStartElement(Recovery.Tag.RECOVER_PLUGIN.getLocalName());
                RECOVERLUGIN_CLASSNAME.marshallAsAttribute(conDef, streamWriter);
                if (conDef.hasDefined(RECOVERLUGIN_PROPERTIES.getName())) {
                    for (Property property : conDef.get(RECOVERLUGIN_PROPERTIES.getName()).asPropertyList()) {
                        writeProperty(streamWriter, conDef, property.getName(), property
                                    .getValue().asString(), org.jboss.jca.common.api.metadata.common.Extension.Tag.CONFIG_PROPERTY.getLocalName());
                    }
                }
                streamWriter.writeEndElement();
            }
            streamWriter.writeEndElement();

        }

        streamWriter.writeEndElement();

    }

    @Override
    public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> list) throws XMLStreamException {

        final ModelNode address = new ModelNode();
        address.add(ModelDescriptionConstants.SUBSYSTEM, RESOURCEADAPTERS_NAME);
        address.protect();

        final ModelNode subsystem = new ModelNode();
        subsystem.get(OP).set(ADD);
        subsystem.get(OP_ADDR).set(address);

        list.add(subsystem);

        try {
            String localName;
            switch (Namespace.forUri(reader.getNamespaceURI())) {
                case RESOURCEADAPTERS_1_0:
                case RESOURCEADAPTERS_1_1:
                case RESOURCEADAPTERS_2_0:
                case RESOURCEADAPTERS_3_0:
                case RESOURCEADAPTERS_4_0:
                case RESOURCEADAPTERS_5_0:{
                    localName = reader.getLocalName();
                    final Element element = Element.forName(reader.getLocalName());
                    SUBSYSTEM_RA_LOGGER.tracef("%s -> %s", localName, element);
                    switch (element) {
                        case SUBSYSTEM: {
                            ResourceAdapterParser parser = new ResourceAdapterParser();
                            parser.parse(reader, list, address);
                            ParseUtils.requireNoContent(reader);
                            break;
                        }
                    }
                    break;
                }
            }
        } catch (Exception e) {
            throw new XMLStreamException(e);
        }

    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2013, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.wildfly.extension.picketlink.idm.service;

import org.hibernate.cfg.AvailableSettings;
import org.hibernate.engine.transaction.jta.platform.internal.JBossAppServerJtaPlatform;
import org.jboss.modules.Module;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StartException;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.picketlink.idm.jpa.internal.JPAIdentityStore;
import org.picketlink.idm.spi.ContextInitializer;
import org.picketlink.idm.spi.IdentityContext;
import org.picketlink.idm.spi.IdentityStore;
import org.wildfly.extension.picketlink.idm.config.JPAStoreSubsystemConfiguration;
import org.wildfly.extension.picketlink.idm.config.JPAStoreSubsystemConfigurationBuilder;
import org.wildfly.extension.picketlink.idm.jpa.transaction.TransactionalEntityManagerHelper;
import org.wildfly.transaction.client.ContextTransactionManager;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.persistence.metamodel.EntityType;
import javax.transaction.Status;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static java.lang.reflect.Modifier.isAbstract;
import static org.picketlink.common.util.StringUtil.isNullOrEmpty;
import static org.wildfly.extension.picketlink.logging.PicketLinkLogger.ROOT_LOGGER;

/**
 * @author Pedro Igor
 */
public class JPAIdentityStoreService implements Service<JPAIdentityStoreService> {

    private static final String JPA_ANNOTATION_PACKAGE = "org.picketlink.idm.jpa.annotations";
    public static final String DEFAULT_PERSISTENCE_UNIT_NAME = "identity";

    private final JPAStoreSubsystemConfigurationBuilder configurationBuilder;
    private JPAStoreSubsystemConfiguration storeConfig;
    private EntityManagerFactory emf;
    private InjectedValue<TransactionSynchronizationRegistry> transactionSynchronizationRegistry = new InjectedValue<TransactionSynchronizationRegistry>();
    private TransactionalEntityManagerHelper transactionalEntityManagerHelper;

    public JPAIdentityStoreService(JPAStoreSubsystemConfigurationBuilder configurationBuilder) {
        this.configurationBuilder = configurationBuilder;
    }

    @Override
    public void start(StartContext startContext) throws StartException {
        this.storeConfig = this.configurationBuilder.create();
        this.transactionalEntityManagerHelper = new TransactionalEntityManagerHelper(
            this.transactionSynchronizationRegistry.getValue(),
            ContextTransactionManager.getInstance());

        try {
            configureEntityManagerFactory();
            configureEntities();
        } catch (Exception e) {
            throw ROOT_LOGGER.idmJpaStartFailed(e);
        }

        this.configurationBuilder.addContextInitializer(new ContextInitializer() {
            @Override
            public void initContextForStore(IdentityContext context, IdentityStore<?> store) {
                if (store instanceof JPAIdentityStore) {
                    EntityManager entityManager = context.getParameter(JPAIdentityStore.INVOCATION_CTX_ENTITY_MANAGER);

                    if (entityManager == null || !entityManager.isOpen()) {
                        context.setParameter(JPAIdentityStore.INVOCATION_CTX_ENTITY_MANAGER, getEntityManager(ContextTransactionManager.getInstance()));
                    }
                }
            }
        });
    }

    @Override
    public void stop(StopContext stopContext) {
        if (this.storeConfig.getEntityManagerFactoryJndiName() == null) {
            this.emf.close();
        }
    }

    @Override
    public JPAIdentityStoreService getValue() throws IllegalStateException, IllegalArgumentException {
        return this;
    }

    public InjectedValue<TransactionSynchronizationRegistry> getTransactionSynchronizationRegistry() {
        return this.transactionSynchronizationRegistry;
    }

    private void configureEntityManagerFactory() {
        if (this.storeConfig.getEntityManagerFactoryJndiName() != null) {
            this.emf = lookupEntityManagerFactory();
        } else {
            this.emf = createEmbeddedEntityManagerFactory();
        }
    }

    private EntityManagerFactory lookupEntityManagerFactory() {
        ROOT_LOGGER.debugf("Looking up EntityManagerFactory from [%s]", this.storeConfig.getEntityManagerFactoryJndiName());
        try {
            return (EntityManagerFactory) new InitialContext().lookup(this.storeConfig.getEntityManagerFactoryJndiName());
        } catch (NamingException e) {
            throw ROOT_LOGGER.idmJpaEMFLookupFailed(this.storeConfig.getEntityManagerFactoryJndiName());
        }
    }

    private EntityManagerFactory createEmbeddedEntityManagerFactory() {
        ROOT_LOGGER.debugf("Creating embedded EntityManagerFactory.");
        ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();

        try {
            Map<Object, Object> properties = new HashMap<Object, Object>();
            String dataSourceJndiUrl = this.storeConfig.getDataSourceJndiUrl();

            if (!isNullOrEmpty(dataSourceJndiUrl)) {
                ROOT_LOGGER.debugf("Using datasource [%s] for embedded EntityManagerFactory.", dataSourceJndiUrl);
                properties.put("javax.persistence.jtaDataSource", dataSourceJndiUrl);
            }

            properties.put(AvailableSettings.JTA_PLATFORM, new JBossAppServerJtaPlatform());

            Module entityModule = this.storeConfig.getEntityModule();

            if (entityModule != null) {
                Thread.currentThread().setContextClassLoader(entityModule.getClassLoader());
            }

            return Persistence.createEntityManagerFactory(this.storeConfig.getEntityModuleUnitName(), properties);
        } finally {
            Thread.currentThread().setContextClassLoader(originalClassLoader);
        }
    }

    private void configureEntities() {
        Set<EntityType<?>> mappedEntities = this.emf.getMetamodel().getEntities();

        for (EntityType<?> entity : mappedEntities) {
            Class<?> javaType = entity.getJavaType();
            if (!isAbstract(javaType.getModifiers()) && isIdentityEntity(javaType)) {
                ROOT_LOGGER.debugf("Mapping entity [%s] to JPA Identity Store.", javaType.getName());
                this.configurationBuilder.mappedEntity(javaType);
            }
        }
    }

    private boolean isIdentityEntity(Class<?> cls) {
        Class<?> checkClass = cls;

        while (!checkClass.equals(Object.class)) {
            for (Annotation a : checkClass.getAnnotations()) {
                if (a.annotationType().getName().startsWith(JPA_ANNOTATION_PACKAGE)) {
                    return true;
                }
            }

            // No class annotation was found, check the fields
            for (Field f : checkClass.getDeclaredFields()) {
                for (Annotation a : f.getAnnotations()) {
                    if (a.annotationType().getName().startsWith(JPA_ANNOTATION_PACKAGE)) {
                        return true;
                    }
                }
            }

            // Check the superclass
            checkClass = checkClass.getSuperclass();
        }

        return false;
    }

    private EntityManager getEntityManager(TransactionManager transactionManager) {
        EntityManager entityManager = getOrCreateTransactionalEntityManager(transactionManager);

        if (entityManager == null || !entityManager.isOpen()) {
            entityManager = createEntityManager(transactionManager);
        }

        return entityManager;
    }

    /**
     *
Returns an {@link javax.persistence.EntityManager} associated with the actual {@link javax.transaction.Transaction}, if present.

     *
     *
If {@link javax.transaction.Transaction} is {@link Status#STATUS_ACTIVE}, this method tries to return an entity manager
     * already associated with it. If there is no entity manager a new one is created.

     *
     *
This method is specially useful when IDM is being called from an EJB or any other component that have already started
     * a transaction. In this case, the client code is responsible to commit or rollback the transaction accordingly.

     *
     *
The returned {@link EntityManager} is always closed before the transaction completes.

     *
     * @param transactionManager The transaction manager.
     *
     * @return The {@link EntityManager} associated with the actual transaction or null if there is no active transactions.
     */
    private EntityManager getOrCreateTransactionalEntityManager(TransactionManager transactionManager) {
        try {
            if (transactionManager.getStatus() == Status.STATUS_ACTIVE) {
                EntityManager entityManager = this.transactionalEntityManagerHelper.getTransactionScopedEntityManager(getPersistenceUnitName());

                if (entityManager == null) {
                    entityManager = createEntityManager(transactionManager);
                    this.transactionalEntityManagerHelper.putEntityManagerInTransactionRegistry(getPersistenceUnitName(), entityManager);
                }

                return entityManager;
            }
        } catch (Exception e) {
            throw ROOT_LOGGER.idmJpaFailedCreateTransactionEntityManager(e);
        }

        return null;
    }

    private String getPersistenceUnitName() {
        String persistenceUnitName = this.storeConfig.getEntityModuleUnitName();

        if (persistenceUnitName == null) {
            persistenceUnitName = DEFAULT_PERSISTENCE_UNIT_NAME;
        }

        return persistenceUnitName;
    }

    private EntityManager createEntityManager(TransactionManager transactionManager) {
        return (EntityManager) Proxy.newProxyInstance(Thread.currentThread()
            .getContextClassLoader(), new Class<?>[]{EntityManager.class}, new EntityManagerInvocationHandler(this.emf.createEntityManager(),
            this.storeConfig.getEntityModule(), transactionManager));
    }

    private class EntityManagerInvocationHandler implements InvocationHandler {

        private final Module entityModule;
        private final TransactionManager transactionManager;
        private final EntityManager entityManager;

        public EntityManagerInvocationHandler(EntityManager entityManager, Module entitiesModule, TransactionManager transactionManager) {
            this.entityManager = entityManager;
            this.entityModule = entitiesModule;
            this.transactionManager = transactionManager;
        }

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            boolean isManagedTransaction = false;

            if (isTxRequired(method)) {
                if (this.transactionManager.getStatus() == Status.STATUS_NO_TRANSACTION) {
                    this.transactionManager.begin();
                    isManagedTransaction = true;
                }

                this.entityManager.joinTransaction();
            }

            ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader();

            try {
                if (this.entityModule != null) {
                    Thread.currentThread().setContextClassLoader(this.entityModule.getClassLoader());
                }

                return method.invoke(this.entityManager, args);
            } finally {
                Thread.currentThread().setContextClassLoader(originalClassLoader);

                if (isManagedTransaction) {
                    if (this.transactionManager.getStatus() == Status.STATUS_ACTIVE) {
                        this.transactionManager.commit();
                        this.transactionManager.suspend();
                    }
                }
            }
        }

        private boolean isTxRequired(Method method) {
            String n = method.getName();
            return "flush".equals(n) || "getLockMode".equals(n) || "lock".equals(n) || "merge".equals(n) || "persist".equals(n) || "refresh".equals(n) || "remove".equals(n);
        }
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2010, Red Hat, Inc., and individual contributors
 * as indicated by the @author tags. See the copyright.txt file in the
 * distribution for a full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.jboss.as.connector.deployers.ra.processors;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.util.Locale;

import org.jboss.as.connector.deployers.Util;
import org.jboss.as.connector.logging.ConnectorLogger;
import org.jboss.as.connector.metadata.xmldescriptors.ConnectorXmlDescriptor;
import org.jboss.as.server.deployment.Attachments;
import org.jboss.as.server.deployment.DeploymentPhaseContext;
import org.jboss.as.server.deployment.DeploymentUnit;
import org.jboss.as.server.deployment.DeploymentUnitProcessingException;
import org.jboss.as.server.deployment.DeploymentUnitProcessor;
import org.jboss.as.server.deployment.module.ResourceRoot;
import org.jboss.jca.common.api.metadata.spec.Connector;
import org.jboss.jca.common.metadata.spec.RaParser;
import org.jboss.vfs.VFSUtils;
import org.jboss.vfs.VirtualFile;

/**
 * DeploymentUnitProcessor responsible for parsing a standard jca xml descriptor
 * and attaching the corresponding metadata. It take care also to register this
 * metadata into IronJacamar's MetadataRepository
 *
 * @author Stefano Maestri
 */
public class RaDeploymentParsingProcessor implements DeploymentUnitProcessor {

    /**
     * Construct a new instance.
     */
    public RaDeploymentParsingProcessor() {
    }

    /**
     * Process a deployment for standard ra deployment files. Will parse the xml
     * file and attach a configuration discovered during processing.
     *
     * @param phaseContext the deployment unit context
     * @throws DeploymentUnitProcessingException
     *
     */
    @Override
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        final ResourceRoot deploymentRoot = deploymentUnit.getAttachment(Attachments.DEPLOYMENT_ROOT);
        final boolean resolveProperties = Util.shouldResolveSpec(deploymentUnit);

        final VirtualFile file = deploymentRoot.getRoot();
        if (file == null || !file.exists())
            return;

        final String deploymentRootName = file.getName().toLowerCase(Locale.ENGLISH);
        if (!deploymentRootName.endsWith(".rar")) {
            return;
        }

        final VirtualFile alternateDescriptor = deploymentRoot.getAttachment(org.jboss.as.ee.structure.Attachments.ALTERNATE_CONNECTOR_DEPLOYMENT_DESCRIPTOR);
        String prefix = "";

        if (deploymentUnit.getParent() != null) {
            prefix = deploymentUnit.getParent().getName() + "#";
        }

        String deploymentName = prefix + file.getName();
        ConnectorXmlDescriptor xmlDescriptor = process(resolveProperties, file, alternateDescriptor, deploymentName);

        phaseContext.getDeploymentUnit().putAttachment(ConnectorXmlDescriptor.ATTACHMENT_KEY, xmlDescriptor);

    }

    public static ConnectorXmlDescriptor process(boolean resolveProperties, VirtualFile file, VirtualFile alternateDescriptor, String deploymentName) throws DeploymentUnitProcessingException {
        // Locate the descriptor
        final VirtualFile serviceXmlFile;
        if (alternateDescriptor != null) {
            serviceXmlFile = alternateDescriptor;
        } else {
            serviceXmlFile = file.getChild("/META-INF/ra.xml");
        }
        InputStream xmlStream = null;
        Connector result = null;
        try {
            if (serviceXmlFile != null && serviceXmlFile.exists()) {

                xmlStream = serviceXmlFile.openStream();
                RaParser raParser = new RaParser();
                raParser.setSystemPropertiesResolved(resolveProperties);
                result = raParser.parse(xmlStream);
                if (result == null)
                    throw ConnectorLogger.ROOT_LOGGER.failedToParseServiceXml(serviceXmlFile);
            }
            File root = file.getPhysicalFile();
            URL url = root.toURI().toURL();
            return new ConnectorXmlDescriptor(result, root, url, deploymentName);

        } catch (Exception e) {
            throw ConnectorLogger.ROOT_LOGGER.failedToParseServiceXml(e, serviceXmlFile);
        } finally {
            VFSUtils.safeClose(xmlStream);
        }
    }

    public void undeploy(final DeploymentUnit context) {
    }
}