/*
 * 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.naming;

import static org.jboss.as.naming.util.NamingUtils.cannotProceedException;
import static org.jboss.as.naming.util.NamingUtils.emptyNameException;
import static org.jboss.as.naming.util.NamingUtils.getLastComponent;
import static org.jboss.as.naming.util.NamingUtils.isEmpty;
import static org.jboss.as.naming.util.NamingUtils.isLastComponentEmpty;
import static org.jboss.as.naming.util.NamingUtils.nameAlreadyBoundException;
import static org.jboss.as.naming.util.NamingUtils.nameNotFoundException;
import static org.jboss.as.naming.util.NamingUtils.notAContextException;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import java.util.concurrent.locks.ReentrantLock;

import javax.naming.Binding;
import javax.naming.CannotProceedException;
import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.NameClassPair;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.Reference;
import javax.naming.event.EventContext;
import javax.naming.event.NamingEvent;
import javax.naming.event.NamingListener;
import javax.naming.spi.ResolveResult;
import org.jboss.as.naming.logging.NamingLogger;

/**
 * In-memory implementation of the NamingStore.  The backing for the entries is a basic tree structure with either context
 * nodes or binding nodes.  The context nodes are allowed to have children and can be represented by a NamingContext.  A
 * binding node is only allowed to have a normal object binding.
 *
 * @author John E. Bailey
 */
public class InMemoryNamingStore implements WritableNamingStore {

    /* The root node of the tree.  Represents a JNDI name of "" */
    private final ContextNode root = new ContextNode(null, null, new CompositeName(), new NamingContext(this, null));

    /* Naming Event Coordinator */
    private final NamingEventCoordinator eventCoordinator;

    private final ReentrantLock writeLock = new ReentrantLock();

    private final Name baseName;

    /**
     * Construct instance with no event support, and an empty base name.
     */
    public InMemoryNamingStore() {
        this(null, new CompositeName());
    }

    /**
     * Construct instance with an event coordinator, and an empty base name.
     *
     * @param eventCoordinator The event coordinator
     */
    public InMemoryNamingStore(final NamingEventCoordinator eventCoordinator) {
        this(eventCoordinator, new CompositeName());
    }

    /**
     * Construct instance with no event support, and the specified base name.
     *
     * @param baseName
     */
    public InMemoryNamingStore(final Name baseName) {
        this(null, baseName);
    }

    /**
     * Construct instance with an event coordinator, and the specified base name.
     *
     * @param eventCoordinator
     * @param baseName
     */
    public InMemoryNamingStore(final NamingEventCoordinator eventCoordinator, final Name baseName) {
        this.eventCoordinator = eventCoordinator;
        if(baseName == null) {
            throw new NullPointerException(NamingLogger.ROOT_LOGGER.cannotBeNull("baseName"));
        }
        this.baseName = baseName;
    }

    /** {@inheritDoc} */
    public Name getBaseName() throws NamingException {
        return baseName;
    }

    /** {@inheritDoc} */
    public void bind(Name name, Object object) throws NamingException {
        bind(name, object, object.getClass());
    }

    /** {@inheritDoc} */
    public void bind(final Name name, final Object object, final Class<?> bindType) throws NamingException {
        if (isLastComponentEmpty(name)) {
            throw emptyNameException();
        }

        writeLock.lock();
        try {
            root.accept(new BindVisitor(true, name, object, bindType.getName()));
        } finally {
            writeLock.unlock();
        }
    }

    /** {@inheritDoc} */
    public void rebind(Name name, Object object) throws NamingException {
        rebind(name, object, object.getClass());
    }

    /** {@inheritDoc} */
    public void rebind(final Name name, final Object object, final Class<?> bindType) throws NamingException {
        if (isLastComponentEmpty(name)) {
            throw emptyNameException();
        }

        writeLock.lock();
        try {
            root.accept(new RebindVisitor(name, object, bindType.getName()));
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * Unbind the entry in the provided location.  This will remove the node in the tree and no longer manage it.
     *
     * @param name The entry name
     * @throws NamingException
     */
    public void unbind(final Name name) throws NamingException {
        if (isLastComponentEmpty(name)) {
            throw emptyNameException();
        }

        writeLock.lock();
        try {
            root.accept(new UnbindVisitor(name));
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * Lookup the object value of a binding node in the tree.
     *
     * @param name The entry name
     * @return The object value of the binding
     * @throws NamingException
     */
    public Object lookup(final Name name) throws NamingException {
        if (isEmpty(name)) {
            final Name emptyName = new CompositeName("");
            return new NamingContext(emptyName, this, new Hashtable<String, Object>());
        }
        return root.accept(new LookupVisitor(name));
    }

    @Override
    public Object lookup(Name name, boolean dereference) throws NamingException {
        // ignoring dereference arg, it's not relevant to this store impl
        return lookup(name);
    }

    /**
     * List all NameClassPair instances at a given location in the tree.
     *
     * @param name The entry name
     * @return The NameClassPair instances
     * @throws NamingException
     */
    public List<NameClassPair> list(final Name name) throws NamingException {
        final Name nodeName = name.isEmpty() ? new CompositeName("") : name;
        return root.accept(new ListVisitor(nodeName));
    }

    /**
     * List all the Binding instances at a given location in the tree.
     *
     * @param name The entry name
     * @return The Binding instances
     * @throws NamingException
     */
    public List<Binding> listBindings(final Name name) throws NamingException {
        final Name nodeName = name.isEmpty() ? new CompositeName("") : name;
        return root.accept(new ListBindingsVisitor(nodeName));
    }

    public Context createSubcontext(final Name name) throws NamingException {
        if (isLastComponentEmpty(name)) {
            throw emptyNameException();
        }
        return root.accept(new CreateSubContextVisitor(name));
    }

    /**
     * Close the store.  This will clear all children from the root node.
     *
     * @throws NamingException
     */
    public void close() throws NamingException {
        writeLock.lock();
        try {
            root.clear();
        } finally {
            writeLock.unlock();
        }
    }

    /**
     * Add a {@code NamingListener} to the naming event coordinator.
     *
     * @param target The target name to add the listener to
     * @param scope The listener scope
     * @param listener The listener
     */
    public void addNamingListener(final Name target, final int scope, final NamingListener listener) {
        final NamingEventCoordinator coordinator = eventCoordinator;
        if (coordinator != null) {
            coordinator.addListener(target.toString(), scope, listener);
        }
    }

    /**
     * Remove a {@code NamingListener} from the naming event coordinator.
     *
     * @param listener The listener
     */
    public void removeNamingListener(final NamingListener listener) {
        final NamingEventCoordinator coordinator = eventCoordinator;
        if (coordinator != null) {
            coordinator.removeListener(listener);
        }
    }

    private void fireEvent(final ContextNode contextNode, final Name name, final Binding existingBinding, final Binding newBinding, final int type, final String changeInfo) {
        final NamingEventCoordinator coordinator = eventCoordinator;
        if (eventCoordinator != null) {
            final Context context = Context.class.cast(contextNode.binding.getObject());
            if(context instanceof EventContext) {
                coordinator.fireEvent(EventContext.class.cast(context), name, existingBinding, newBinding, type, changeInfo, NamingEventCoordinator.DEFAULT_SCOPES);
            }
        }
    }

    private void checkReferenceForContinuation(final Name name, final Object object) throws CannotProceedException {
        if (object instanceof Reference) {
            if (((Reference) object).get("nns") != null) {
                throw cannotProceedException(object, name);
            }
        }
    }

    private abstract class TreeNode {
        protected final Name fullName;
        protected final Binding binding;

        private TreeNode(final Name fullName, final Binding binding) {
            this.fullName = fullName;
            this.binding = binding;
        }

        protected abstract <T> T accept(NodeVisitor<T> visitor) throws NamingException;
    }

    private static final AtomicMapFieldUpdater<ContextNode, String, TreeNode> childrenUpdater = AtomicMapFieldUpdater.newMapUpdater(AtomicReferenceFieldUpdater.newUpdater(ContextNode.class, Map.class, "children"));

    private class ContextNode extends TreeNode {
        volatile Map<String, TreeNode> children = Collections.emptyMap();
        protected final String name;
        protected final ContextNode parentNode;

        private ContextNode(final ContextNode parentNode, final String name, final Name fullName, final NamingContext context) {
            super(fullName, new Binding(getLastComponent(fullName), Context.class.getName(), context));
            this.name = name;
            this.parentNode = parentNode;
        }

        private void addChild(final String childName, final TreeNode childNode) throws NamingException {
            if (childrenUpdater.putIfAbsent(this, childName, childNode) != null) {
                throw nameAlreadyBoundException(fullName.add(childName));
            }
        }

        private TreeNode replaceChild(final String childName, final TreeNode childNode) throws NamingException {
            return childrenUpdater.put(this, childName, childNode);
        }

        private TreeNode removeChild(final String childName) throws NameNotFoundException {
            TreeNode old = childrenUpdater.remove(this, childName);
            if (old == null) {
                throw nameNotFoundException(childName, fullName);
            }
            if(parentNode != null && children.isEmpty()) {
                childrenUpdater.remove(parentNode, name);
            }
            return old;
        }

        private void clear() {
            childrenUpdater.clear(this);
        }

        protected final <T> T accept(NodeVisitor<T> visitor) throws NamingException {
            return visitor.visit(this);
        }

        public TreeNode addOrGetChild(final String childName, final TreeNode childNode) {
            TreeNode appearing = childrenUpdater.putIfAbsent(this, childName, childNode);
            return appearing == null ? childNode : appearing;
        }
    }

    private class BindingNode extends TreeNode {
        private BindingNode(final Name fullName, final Binding binding) {
            super(fullName, binding);
        }

        protected final <T> T accept(NodeVisitor<T> visitor) throws NamingException {
            return visitor.visit(this);
        }
    }

    private interface NodeVisitor<T> {
        T visit(BindingNode bindingNode) throws NamingException;

        T visit(ContextNode contextNode) throws NamingException;
    }

    private abstract class NodeTraversingVisitor<T> implements NodeVisitor<T> {
        private final boolean createIfMissing;
        private Name currentName;
        private Name traversedName;
        protected final Name targetName;

        protected NodeTraversingVisitor(final boolean createIfMissing, final Name targetName) {
            this.createIfMissing = createIfMissing;
            this.targetName = currentName = targetName;
            this.traversedName = new CompositeName();
        }

        protected NodeTraversingVisitor(final Name targetName) {
            this(false, targetName);
        }

        public final T visit(final BindingNode bindingNode) throws NamingException {
            if (isEmpty(currentName)) {
                return found(bindingNode);
            }
            return foundReferenceInsteadOfContext(bindingNode);
        }

        public final T visit(final ContextNode contextNode) throws NamingException {
            if (isEmpty(currentName)) {
                return found(contextNode);
            }
            final String childName = currentName.get(0);
            traversedName.add(childName);
            currentName = currentName.getSuffix(1);
            final TreeNode node = contextNode.children.get(childName);
            if (node == null) {
                if (createIfMissing) {
                    final NamingContext subContext = new NamingContext((Name)traversedName.clone(), InMemoryNamingStore.this, new Hashtable<String, Object>());
                    return contextNode.addOrGetChild(childName, new ContextNode(contextNode, childName, (Name)traversedName.clone(), subContext)).accept(this);
                } else {
                    throw nameNotFoundException(childName, contextNode.fullName);
                }
            }
            return node.accept(this);
        }

        protected abstract T found(ContextNode contextNode) throws NamingException;

        protected abstract T found(BindingNode bindingNode) throws NamingException;

        protected T foundReferenceInsteadOfContext(BindingNode bindingNode) throws NamingException {
            final Object object = bindingNode.binding.getObject();
            checkReferenceForContinuation(currentName, object);
            throw notAContextException(bindingNode.fullName);
        }
    }

    private abstract class BindingContextVisitor<T> extends NodeTraversingVisitor<T> {
        protected final Name targetName;

        protected BindingContextVisitor(final boolean createIfMissing, final Name targetName) {
            super(createIfMissing, targetName.getPrefix(targetName.size() - 1));
            this.targetName = targetName;
        }

        protected BindingContextVisitor(final Name targetName) {
            this(false, targetName);
        }

        protected final T found(final ContextNode contextNode) throws NamingException {
            return foundBindContext(contextNode);
        }

        protected final T found(final BindingNode bindingNode) throws NamingException {
            checkReferenceForContinuation(targetName.getSuffix(bindingNode.fullName.size()), bindingNode.binding.getObject());
            throw notAContextException(targetName);
        }

        protected abstract T foundBindContext(final ContextNode contextNode) throws NamingException;
    }

    private final class BindVisitor extends BindingContextVisitor<Void> {
        private final Object object;
        private final String className;

        private BindVisitor(final boolean createIfMissing, final Name name, final Object object, final String className) {
            super(createIfMissing, name);
            this.object = object;
            this.className = className;
        }

        protected Void foundBindContext(final ContextNode contextNode) throws NamingException {
            final String childName = getLastComponent(targetName);
            final Binding binding = new Binding(childName, className, object, true);
            final BindingNode bindingNode = new BindingNode(targetName, binding);
            contextNode.addChild(childName, bindingNode);
            fireEvent(contextNode, targetName, null, binding, NamingEvent.OBJECT_ADDED, "bind");
            return null;
        }
    }

    private final class RebindVisitor extends BindingContextVisitor<Void> {
        private final Object object;
        private final String className;

        private RebindVisitor(final Name name, final Object object, final String className) {
            super(name);
            this.object = object;
            this.className = className;
        }

        protected Void foundBindContext(final ContextNode contextNode) throws NamingException {
            final String childName = getLastComponent(targetName);
            final Binding binding = new Binding(childName, className, object, true);
            final BindingNode bindingNode = new BindingNode(targetName, binding);
            final TreeNode previous = contextNode.replaceChild(childName, bindingNode);

            final Binding previousBinding = previous != null ? previous.binding : null;
            fireEvent(contextNode, targetName, previousBinding, binding, previousBinding != null ? NamingEvent.OBJECT_CHANGED : NamingEvent.OBJECT_ADDED, "rebind");
            return null;
        }
    }

    private final class UnbindVisitor extends BindingContextVisitor<Void> {

        private UnbindVisitor(final Name targetName) throws NamingException {
            super(targetName);
        }

        protected Void foundBindContext(final ContextNode contextNode) throws NamingException {
            final TreeNode previous = contextNode.removeChild(getLastComponent(targetName));
            fireEvent(contextNode, targetName, previous.binding, null, NamingEvent.OBJECT_REMOVED, "unbind");
            return null;
        }
    }

    private final class LookupVisitor extends NodeTraversingVisitor<Object> {
        private LookupVisitor(final Name targetName) {
            super(targetName);
        }

        protected Object found(final ContextNode contextNode) throws NamingException {
            return contextNode.binding.getObject();
        }

        protected Object found(final BindingNode bindingNode) throws NamingException {
            return bindingNode.binding.getObject();
        }

        protected Object foundReferenceInsteadOfContext(final BindingNode bindingNode) throws NamingException {
            final Name remainingName = targetName.getSuffix(bindingNode.fullName.size());
            final Object boundObject = bindingNode.binding.getObject();
            checkReferenceForContinuation(remainingName, boundObject);
            return new ResolveResult(boundObject, remainingName);
        }
    }

    private final class ListVisitor extends NodeTraversingVisitor<List<NameClassPair>> {
        private ListVisitor(final Name targetName) {
            super(targetName);
        }

        protected List<NameClassPair> found(final ContextNode contextNode) throws NamingException {
            final List<NameClassPair> nameClassPairs = new ArrayList<NameClassPair>();
            for (TreeNode childNode : contextNode.children.values()) {
                final Binding binding = childNode.binding;
                nameClassPairs.add(new NameClassPair(binding.getName(), binding.getClassName(), true));
            }
            return nameClassPairs;
        }

        protected List<NameClassPair> found(final BindingNode bindingNode) throws NamingException {
            checkReferenceForContinuation(new CompositeName(), bindingNode.binding.getObject());
            throw notAContextException(targetName);
        }
    }

    private final class ListBindingsVisitor extends NodeTraversingVisitor<List<Binding>> {
        private ListBindingsVisitor(final Name targetName) {
            super(targetName);
        }

        protected List<Binding> found(final ContextNode contextNode) throws NamingException {
            final List<Binding> bindings = new ArrayList<Binding>();
            for (TreeNode childNode : contextNode.children.values()) {
                bindings.add(childNode.binding);
            }
            return bindings;
        }

        protected List<Binding> found(final BindingNode bindingNode) throws NamingException {
            checkReferenceForContinuation(new CompositeName(), bindingNode.binding.getObject());
            throw notAContextException(targetName);
        }
    }

    private final class CreateSubContextVisitor extends BindingContextVisitor<Context> {
        private CreateSubContextVisitor(final Name targetName) throws NamingException {
            super(targetName);
        }

        protected Context foundBindContext(ContextNode contextNode) throws NamingException {
            final NamingContext subContext = new NamingContext(targetName, InMemoryNamingStore.this, new Hashtable<String, Object>());
            final String childName = getLastComponent(targetName);
            final ContextNode subContextNode = new ContextNode(contextNode, childName, targetName, subContext);
            contextNode.addChild(getLastComponent(targetName), subContextNode);
            fireEvent(contextNode, targetName, null, subContextNode.binding, NamingEvent.OBJECT_ADDED, "createSubcontext");
            return subContext;
        }
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2017, 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.undertow.security;

import io.undertow.security.idm.Account;
import io.undertow.security.idm.Credential;

import org.wildfly.extension.undertow.security.digest.DigestCredential;

import io.undertow.security.idm.IdentityManager;
import io.undertow.security.idm.PasswordCredential;
import io.undertow.security.idm.X509CertificateCredential;

import java.security.Principal;
import java.security.acl.Group;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;

import javax.security.auth.Subject;

import org.jboss.as.security.plugins.SecurityDomainContext;
import org.jboss.security.AuthenticationManager;
import org.jboss.security.AuthorizationManager;
import org.jboss.security.SecurityConstants;
import org.jboss.security.SecurityContext;
import org.jboss.security.auth.callback.CallbackHandlerPolicyContextHandler;
import org.jboss.security.auth.callback.DigestCallbackHandler;
import org.jboss.security.callbacks.SecurityContextCallbackHandler;
import org.jboss.security.identity.Role;
import org.jboss.security.identity.RoleGroup;
import org.wildfly.extension.undertow.logging.UndertowLogger;

/**
 * @author Stuart Douglas
 * @author Darran Lofthouse
 */
public class JAASIdentityManagerImpl implements IdentityManager {

    private final SecurityDomainContext securityDomainContext;

    public JAASIdentityManagerImpl(final SecurityDomainContext securityDomainContext) {
        this.securityDomainContext = securityDomainContext;
    }

    @Override
    public Account verify(Account account) {
        // This method is called for previously verfified accounts so just accept it for the moment.
        if (!(account instanceof AccountImpl)) {
            UndertowLogger.ROOT_LOGGER.tracef("Account is not an AccountImpl", account);
            return null;
        }
        AccountImpl accountImpl = (AccountImpl) account;
        return verifyCredential(accountImpl, accountImpl.getCredential());
    }

    @Override
    public Account verify(String id, Credential credential) {
        AccountImpl account = getAccount(id);
        if (credential instanceof DigestCredential) {
            DigestCredential digestCredential = (DigestCredential) credential;
            DigestCallbackHandler handler = new DigestCallbackHandler(id, digestCredential.getNonce(), digestCredential.getNonceCount(),
                    digestCredential.getClientNonce(), digestCredential.getQop(), digestCredential.getRealm(),
                    digestCredential.getHA2());
            CallbackHandlerPolicyContextHandler.setCallbackHandler(handler);

            return verifyCredential(account, digestCredential.getClientDigest());
        } else if(credential instanceof PasswordCredential) {
            final char[] password = ((PasswordCredential) credential).getPassword();
            // The original array may be cleared, this integration relies on it being cached for use later.
            final char[] duplicate = Arrays.copyOf(password, password.length);
            return verifyCredential(account, duplicate);
        } else {
            return verifyCredential(account, credential);
        }
    }

    @Override
    public Account verify(Credential credential) {
        if (credential instanceof X509CertificateCredential) {
            X509CertificateCredential certCredential = (X509CertificateCredential) credential;
            X509Certificate certificate = certCredential.getCertificate();
            AccountImpl account = getAccount(certificate.getSubjectDN().getName());

            return verifyCredential(account, certificate);
        }
        throw new IllegalArgumentException("Parameter must be a X509CertificateCredential");
    }

    private AccountImpl getAccount(final String id) {
        return new AccountImpl(id);
    }

    private Account verifyCredential(final AccountImpl account, final Object credential) {
        final AuthenticationManager authenticationManager = securityDomainContext.getAuthenticationManager();
        final AuthorizationManager authorizationManager = securityDomainContext.getAuthorizationManager();
        final SecurityContext sc = SecurityActions.getSecurityContext();
        Principal incomingPrincipal = account.getOriginalPrincipal();
        Subject subject = new Subject();
        try {
            boolean isValid = authenticationManager.isValid(incomingPrincipal, credential, subject);
            if (isValid) {
                UndertowLogger.ROOT_LOGGER.tracef("User: %s is authenticated", incomingPrincipal);
                if (sc == null) {
                    throw UndertowLogger.ROOT_LOGGER.noSecurityContext();
                }
                Principal userPrincipal = getPrincipal(subject);
                sc.getUtil().createSubjectInfo(incomingPrincipal, credential, subject);
                SecurityContextCallbackHandler scb = new SecurityContextCallbackHandler(sc);
                RoleGroup roles = authorizationManager.getSubjectRoles(subject, scb);
                Set<String> roleSet = new HashSet<>();
                for (Role role : roles.getRoles()) {
                    roleSet.add(role.getRoleName());
                }
                return new AccountImpl(userPrincipal, roleSet, credential, account.getOriginalPrincipal());
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return null;
    }

    /**
     * Get the Principal given the authenticated Subject. Currently the first principal that is not of type {@code Group} is
     * considered or the single principal inside the CallerPrincipal group.
     *
     * @param subject
     * @return the authenticated principal
     */
    private Principal getPrincipal(Subject subject) {
        Principal principal = null;
        Principal callerPrincipal = null;
        if (subject != null) {
            Set<Principal> principals = subject.getPrincipals();
            if (principals != null && !principals.isEmpty()) {
                for (Principal p : principals) {
                    if (!(p instanceof Group) && principal == null) {
                        principal = p;
                    }
                    if (p instanceof Group) {
                        Group g = Group.class.cast(p);
                        if (g.getName().equals(SecurityConstants.CALLER_PRINCIPAL_GROUP) && callerPrincipal == null) {
                            Enumeration<? extends Principal> e = g.members();
                            if (e.hasMoreElements())
                                callerPrincipal = e.nextElement();
                        }
                    }
                }
            }
        }
        return callerPrincipal == null ? principal : callerPrincipal;
    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, 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.appclient.subsystem.parsing;

import static javax.xml.stream.XMLStreamConstants.END_ELEMENT;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.ADD;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.NAME;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.OP_ADDR;
import static org.jboss.as.controller.descriptions.ModelDescriptionConstants.SOCKET_BINDING_GROUP;
import static org.jboss.as.controller.parsing.Namespace.DOMAIN_1_0;
import static org.jboss.as.controller.parsing.ParseUtils.isNoNamespaceAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.missingRequired;
import static org.jboss.as.controller.parsing.ParseUtils.nextElement;
import static org.jboss.as.controller.parsing.ParseUtils.requireNamespace;
import static org.jboss.as.controller.parsing.ParseUtils.requireNoAttributes;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedAttribute;
import static org.jboss.as.controller.parsing.ParseUtils.unexpectedElement;

import java.util.ArrayList;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.xml.stream.XMLStreamException;

import org.jboss.as.appclient.logging.AppClientLogger;
import org.jboss.as.controller.extension.ExtensionRegistry;
import org.jboss.as.controller.logging.ControllerLogger;
import org.jboss.as.controller.operations.common.Util;
import org.jboss.as.controller.parsing.Attribute;
import org.jboss.as.controller.parsing.Element;
import org.jboss.as.controller.parsing.ExtensionXml;
import org.jboss.as.controller.parsing.Namespace;
import org.jboss.as.controller.parsing.ParseUtils;
import org.jboss.as.server.parsing.CommonXml;
import org.jboss.as.server.parsing.SocketBindingsXml;
import org.jboss.as.server.services.net.SocketBindingGroupResourceDefinition;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.modules.ModuleLoader;
import org.jboss.staxmapper.XMLExtendedStreamReader;
import org.jboss.staxmapper.XMLExtendedStreamWriter;

/**
 * A mapper between an AS server's configuration model and XML representations, particularly {@code appclient.xml}.
 *
 * @author Stuart Douglas
 */
public class AppClientXml extends CommonXml {

    private final ExtensionXml extensionXml;

    public AppClientXml(final ModuleLoader loader, final ExtensionRegistry extensionRegistry) {
        super(new AppClientSocketBindingsXml());
        extensionXml = new ExtensionXml(loader, null, extensionRegistry);
    }

    public void readElement(final XMLExtendedStreamReader reader, final List<ModelNode> operationList)
            throws XMLStreamException {
        final ModelNode address = new ModelNode().setEmptyList();

        if (Element.forName(reader.getLocalName()) != Element.SERVER) {
            throw unexpectedElement(reader);
        }

        Namespace readerNS = Namespace.forUri(reader.getNamespaceURI());
        switch (readerNS) {
            case DOMAIN_1_0:
                readServerElement_1_0(reader, address, operationList);
                break;
            default:
                // Instead of having to list the remaining versions we just check it is actually a valid version.
                for (Namespace current : Namespace.domainValues()) {
                    if (readerNS.equals(current)) {
                        readServerElement_1_1(readerNS, reader, address, operationList);
                        return;
                    }
                }
                throw unexpectedElement(reader);
        }
    }

    /**
     * Read the <server/> element based on version 1.0 of the schema.
     *
     * @param reader  the xml stream reader
     * @param address address of the parent resource of any resources this method will add
     * @param list the list of boot operations to which any new operations should be added
     * @throws XMLStreamException if a parsing error occurs
     */
    private void readServerElement_1_0(final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list)
            throws XMLStreamException {

        parseNamespaces(reader, address, list);

        String serverName = null;

        // attributes
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            switch (Namespace.forUri(reader.getAttributeNamespace(i))) {
                case NONE: {
                    final String value = reader.getAttributeValue(i);
                    final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
                    switch (attribute) {
                        case NAME: {
                            serverName = value;
                            break;
                        }
                        default:
                            throw unexpectedAttribute(reader, i);
                    }
                    break;
                }
                case XML_SCHEMA_INSTANCE: {
                    switch (Attribute.forName(reader.getAttributeLocalName(i))) {
                        case SCHEMA_LOCATION: {
                            parseSchemaLocations(reader, address, list, i);
                            break;
                        }
                        case NO_NAMESPACE_SCHEMA_LOCATION: {
                            // todo, jeez
                            break;
                        }
                        default: {
                            throw unexpectedAttribute(reader, i);
                        }
                    }
                    break;
                }
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }

        setServerName(address, list, serverName);

        // elements - sequence

        Element element = nextElement(reader, DOMAIN_1_0);
        if (element == Element.EXTENSIONS) {
            extensionXml.parseExtensions(reader, address, DOMAIN_1_0, list);
            element = nextElement(reader, DOMAIN_1_0);
        }
        // System properties
        if (element == Element.SYSTEM_PROPERTIES) {
            parseSystemProperties(reader, address, DOMAIN_1_0, list, true);
            element = nextElement(reader, DOMAIN_1_0);
        }
        if (element == Element.PATHS) {
            parsePaths(reader, address, DOMAIN_1_0, list, true);
            element = nextElement(reader, DOMAIN_1_0);
        }

        // Single profile
        if (element == Element.PROFILE) {
            parseServerProfile(reader, address, list);
            element = nextElement(reader, DOMAIN_1_0);
        }

        // Interfaces
        final Set<String> interfaceNames = new HashSet<String>();
        if (element == Element.INTERFACES) {
            parseInterfaces(reader, interfaceNames, address, DOMAIN_1_0, list, true);
            element = nextElement(reader, DOMAIN_1_0);
        }
        // Single socket binding group
        if (element == Element.SOCKET_BINDING_GROUP) {
            parseSocketBindingGroup(reader, interfaceNames, address, DOMAIN_1_0, list);
            element = nextElement(reader, DOMAIN_1_0);
        }
        if (element != null) {
            throw unexpectedElement(reader);
        }
    }

    /**
     * Read the <server/> element based on version 1.1 of the schema.
     *
     * @param reader  the xml stream reader
     * @param address address of the parent resource of any resources this method will add
     * @param list the list of boot operations to which any new operations should be added
     * @throws XMLStreamException if a parsing error occurs
     */
    private void readServerElement_1_1(final Namespace namespace, final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list)
            throws XMLStreamException {

        parseNamespaces(reader, address, list);

        String serverName = null;

        // attributes
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            switch (Namespace.forUri(reader.getAttributeNamespace(i))) {
                case NONE: {
                    final String value = reader.getAttributeValue(i);
                    final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
                    switch (attribute) {
                        case NAME: {
                            serverName = value;
                            break;
                        }
                        default:
                            throw unexpectedAttribute(reader, i);
                    }
                    break;
                }
                case XML_SCHEMA_INSTANCE: {
                    switch (Attribute.forName(reader.getAttributeLocalName(i))) {
                        case SCHEMA_LOCATION: {
                            parseSchemaLocations(reader, address, list, i);
                            break;
                        }
                        case NO_NAMESPACE_SCHEMA_LOCATION: {
                            // todo, jeez
                            break;
                        }
                        default: {
                            throw unexpectedAttribute(reader, i);
                        }
                    }
                    break;
                }
                default:
                    throw unexpectedAttribute(reader, i);
            }
        }

        setServerName(address, list, serverName);

        // elements - sequence

        Element element = nextElement(reader, namespace);
        if (element == Element.EXTENSIONS) {
            extensionXml.parseExtensions(reader, address, namespace, list);
            element = nextElement(reader, namespace);
        }
        // System properties
        if (element == Element.SYSTEM_PROPERTIES) {
            parseSystemProperties(reader, address, namespace, list, true);
            element = nextElement(reader, namespace);
        }
        if (element == Element.PATHS) {
            parsePaths(reader, address, namespace, list, true);
            element = nextElement(reader, namespace);
        }

        if (element == Element.VAULT) {
            parseVault(reader, address, namespace, list);
            element = nextElement(reader, namespace);
        }
        // Single profile
        if (element == Element.PROFILE) {
            parseServerProfile(reader, address, list);
            element = nextElement(reader, namespace);
        }

        // Interfaces
        final Set<String> interfaceNames = new HashSet<String>();
        if (element == Element.INTERFACES) {
            parseInterfaces(reader, interfaceNames, address, namespace, list, true);
            element = nextElement(reader, namespace);
        }
        // Single socket binding group
        if (element == Element.SOCKET_BINDING_GROUP) {
            parseSocketBindingGroup(reader, interfaceNames, address, namespace, list);
            element = nextElement(reader, namespace);
        }

        if (element != null) {
            throw unexpectedElement(reader);
        }
    }

    private void parseSocketBindingGroup(final XMLExtendedStreamReader reader, final Set<String> interfaces,
            final ModelNode address, final Namespace expectedNs, final List<ModelNode> updates) throws XMLStreamException {

        // unique names for both socket-binding and outbound-socket-binding(s)
        final Set<String> uniqueBindingNames = new HashSet<String>();

        ModelNode op = Util.getEmptyOperation(ADD, null);
        // Handle attributes
        String socketBindingGroupName = null;

        final EnumSet<Attribute> required = EnumSet.of(Attribute.NAME, Attribute.DEFAULT_INTERFACE);
        final int count = reader.getAttributeCount();
        for (int i = 0; i < count; i++) {
            final String value = reader.getAttributeValue(i);
            if (!isNoNamespaceAttribute(reader, i)) {
                throw unexpectedAttribute(reader, i);
            }
            final Attribute attribute = Attribute.forName(reader.getAttributeLocalName(i));
            switch (attribute) {
                case NAME: {
                    socketBindingGroupName = value;
                    required.remove(attribute);
                    break;
                }
                case DEFAULT_INTERFACE: {
                    SocketBindingGroupResourceDefinition.DEFAULT_INTERFACE.parseAndSetParameter(value, op, reader);
                    required.remove(attribute);
                    break;
                }
                case PORT_OFFSET: {
                    SocketBindingGroupResourceDefinition.PORT_OFFSET.parseAndSetParameter(value, op, reader);
                    break;
                }
                default:
                    throw ParseUtils.unexpectedAttribute(reader, i);
            }
        }

        if (!required.isEmpty()) {
            throw missingRequired(reader, required);
        }

        ModelNode groupAddress = address.clone().add(SOCKET_BINDING_GROUP, socketBindingGroupName);
        op.get(OP_ADDR).set(groupAddress);

        updates.add(op);

        // Handle elements
        while (reader.nextTag() != END_ELEMENT) {
            requireNamespace(reader, expectedNs);
            final Element element = Element.forName(reader.getLocalName());
            switch (element) {
                case SOCKET_BINDING: {
                    // FIXME JBAS-8825
                    final String bindingName = parseSocketBinding(reader, interfaces, groupAddress, updates);
                    if (!uniqueBindingNames.add(bindingName)) {
                        throw ControllerLogger.ROOT_LOGGER.alreadyDeclared(Element.SOCKET_BINDING.getLocalName(), Element.OUTBOUND_SOCKET_BINDING.getLocalName(), bindingName, Element.SOCKET_BINDING_GROUP.getLocalName(), socketBindingGroupName, reader.getLocation());
                    }
                    break;
                }
                case OUTBOUND_SOCKET_BINDING: {
                    final String bindingName = parseOutboundSocketBinding(reader, interfaces, groupAddress, updates);
                    if (!uniqueBindingNames.add(bindingName)) {
                        throw ControllerLogger.ROOT_LOGGER.alreadyDeclared(Element.SOCKET_BINDING.getLocalName(), Element.OUTBOUND_SOCKET_BINDING.getLocalName(), bindingName, Element.SOCKET_BINDING_GROUP.getLocalName(), socketBindingGroupName, reader.getLocation());
                    }
                    break;
                }
                default:
                    throw unexpectedElement(reader);
            }
        }
    }

    private void parseServerProfile(final XMLExtendedStreamReader reader, final ModelNode address, final List<ModelNode> list)
            throws XMLStreamException {
        // Attributes
        requireNoAttributes(reader);

        // Content
        final Set<String> configuredSubsystemTypes = new HashSet<String>();
        while (reader.hasNext() && reader.nextTag() != END_ELEMENT) {
            if (Element.forName(reader.getLocalName()) != Element.SUBSYSTEM) {
                throw unexpectedElement(reader);
            }
            if (!configuredSubsystemTypes.add(reader.getNamespaceURI())) {
                throw AppClientLogger.ROOT_LOGGER.duplicateSubsystemDeclaration(reader.getLocation());
            }
            // parse subsystem
            final List<ModelNode> subsystems = new ArrayList<ModelNode>();
            reader.handleAny(subsystems);

            // Process subsystems
            for (final ModelNode update : subsystems) {
                // Process relative subsystem path address
                final ModelNode subsystemAddress = address.clone();
                for (final Property path : update.get(OP_ADDR).asPropertyList()) {
                    subsystemAddress.add(path.getName(), path.getValue().asString());
                }
                update.get(OP_ADDR).set(subsystemAddress);
                list.add(update);
            }
        }
    }

    private void setServerName(final ModelNode address, final List<ModelNode> operationList, final String value) {
        if (value != null && value.length() > 0) {
            final ModelNode update = Util.getWriteAttributeOperation(address, NAME, value);
            operationList.add(update);
        }
    }

    static class AppClientSocketBindingsXml extends SocketBindingsXml {
        @Override
        protected void writeExtraAttributes(XMLExtendedStreamWriter writer, ModelNode bindingGroup) throws XMLStreamException {
            SocketBindingGroupResourceDefinition.PORT_OFFSET.marshallAsAttribute(bindingGroup, writer);
        }
    }

}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2011, 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 org.jboss.as.connector.metadata.xmldescriptors.ConnectorXmlDescriptor;
import org.jboss.as.connector.subsystems.resourceadapters.ModifiableResourceAdapter;
import org.jboss.as.connector.subsystems.resourceadapters.ResourceAdaptersSubsystemService;
import org.jboss.as.connector.util.CopyOnWriteArrayListMultiMap;
import org.jboss.as.server.deployment.AttachmentKey;
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.msc.service.ServiceName;

public class RaXmlDependencyProcessor implements DeploymentUnitProcessor {

    /**
     * Add dependencies for modules required for ra deployments
     *
     * @param phaseContext the deployment unit context
     * @throws org.jboss.as.server.deployment.DeploymentUnitProcessingException
     */
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {

        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();

        if (phaseContext.getDeploymentUnit().getAttachment(ConnectorXmlDescriptor.ATTACHMENT_KEY) == null) {
            return;  // Skip non ra deployments
        }
        CopyOnWriteArrayListMultiMap<String,ServiceName> resourceAdaptersMap = phaseContext.getDeploymentUnit().getAttachment(ResourceAdaptersSubsystemService.ATTACHMENT_KEY);
        String deploymentUnitPrefix = "";
        if (deploymentUnit.getParent() != null) {
            deploymentUnitPrefix = deploymentUnit.getParent().getName() + "#";
        }

        final String deploymentUnitName = deploymentUnitPrefix + deploymentUnit.getName();
        if (resourceAdaptersMap != null && resourceAdaptersMap.get(deploymentUnitName) != null) {
            for (ServiceName serviceName : resourceAdaptersMap.get(deploymentUnitName)) {

                phaseContext.addDeploymentDependency(serviceName, AttachmentKey
                        .create(ModifiableResourceAdapter.class));
            }
        }
    }

    public void undeploy(final DeploymentUnit context) {
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2015, 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.ejb3.component.stateful;

import org.jboss.ejb.client.EJBClient;
import org.jboss.marshalling.ObjectResolver;

/**
 * @author Paul Ferraro
 */
public class EJBClientContextIdentifierResolver implements ObjectResolver {

    /**
     * {@inheritDoc}
     */
    @Override
    public Object readResolve(Object replacement) {
        return replacement;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object writeReplace(Object object) {
        return EJBClient.isEJBProxy(object) ? new SerializableEJBProxy(object) : object;
    }
}


/*
 * 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.jboss.as.ee.concurrent;

import org.jboss.as.ee.concurrent.handle.ResetContextHandle;
import org.jboss.as.ee.concurrent.handle.SetupContextHandle;
import org.jboss.as.ee.logging.EeLogger;
import org.jboss.as.ee.concurrent.handle.ContextHandleFactory;
import org.jboss.as.naming.util.ThreadLocalStack;
import org.jboss.as.server.CurrentServiceContainer;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleIdentifier;
import org.jboss.msc.service.ServiceContainer;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;

import javax.enterprise.concurrent.ContextService;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.SortedSet;
import java.util.TreeSet;

import static java.lang.Thread.currentThread;

/**
 * Manages context handle factories, it is used by EE Context Services to save the invocation context.
 *
 * @author Eduardo Martins
 */
public class ConcurrentContext {

    /**
     * the name of the factory used by the chained context handles
     */
    public static final String CONTEXT_HANDLE_FACTORY_NAME = "CONCURRENT_CONTEXT";

    /**
     * a thread local stack with the contexts pushed
     */
    private static final ThreadLocalStack<ConcurrentContext> current = new ThreadLocalStack<ConcurrentContext>();

    /**
     * Sets the specified context as the current one, in the current thread.
     *
     * @param context The current context
     */
    public static void pushCurrent(final ConcurrentContext context) {
        current.push(context);
    }

    /**
     * Pops the current context in the current thread.
     *
     * @return
     */
    public static ConcurrentContext popCurrent() {
        return current.pop();
    }

    /**
     * Retrieves the current context in the current thread.
     *
     * @return
     */
    public static ConcurrentContext current() {
        return current.peek();
    }

    private final Map<String, ContextHandleFactory> factoryMap = new HashMap<>();
    private List<ContextHandleFactory> factoryOrderedList;

    private volatile ServiceName serviceName;

    /**
     *
     * @param serviceName
     */
    public void setServiceName(ServiceName serviceName) {
        this.serviceName = serviceName;
    }

    /**
     * Adds a new factory.
     * @param factory
     */
    public synchronized void addFactory(ContextHandleFactory factory) {
        final String factoryName = factory.getName();
        if(factoryMap.containsKey(factoryName)) {
            throw EeLogger.ROOT_LOGGER.factoryAlreadyExists(this, factoryName);
        }
        factoryMap.put(factoryName, factory);
        final Comparator<ContextHandleFactory> comparator = new Comparator<ContextHandleFactory>() {
            @Override
            public int compare(ContextHandleFactory o1, ContextHandleFactory o2) {
                return Integer.compare(o1.getChainPriority(),o2.getChainPriority());
            }
        };
        SortedSet<ContextHandleFactory> sortedSet = new TreeSet<>(comparator);
        sortedSet.addAll(factoryMap.values());
        factoryOrderedList = new ArrayList<>(sortedSet);
    }

    /**
     * Saves the current invocation context on a chained context handle.
     * @param contextService
     * @param contextObjectProperties
     * @return
     */
    public SetupContextHandle saveContext(ContextService contextService, Map<String, String> contextObjectProperties) {
        final List<SetupContextHandle> handles = new ArrayList<>(factoryOrderedList.size());
        for (ContextHandleFactory factory : factoryOrderedList) {
            handles.add(factory.saveContext(contextService, contextObjectProperties));
        }
        return new ChainedSetupContextHandle(this, handles);
    }

    /**
     * A setup context handle that is a chain of other setup context handles
     */
    private static class ChainedSetupContextHandle implements SetupContextHandle {

        private transient ConcurrentContext concurrentContext;
        private transient List<SetupContextHandle> setupHandles;

        private ChainedSetupContextHandle(ConcurrentContext concurrentContext, List<SetupContextHandle> setupHandles) {
            this.concurrentContext = concurrentContext;
            this.setupHandles = setupHandles;
        }

        @Override
        public ResetContextHandle setup() throws IllegalStateException {
            final LinkedList<ResetContextHandle> resetHandles = new LinkedList<>();
            final ResetContextHandle resetContextHandle = new ChainedResetContextHandle(resetHandles);
            try {
                ConcurrentContext.pushCurrent(concurrentContext);
                for (SetupContextHandle handle : setupHandles) {
                    resetHandles.addFirst(handle.setup());
                }
            } catch (Error | RuntimeException e) {
                resetContextHandle.reset();
                throw e;
            }
            return resetContextHandle;
        }

        @Override
        public String getFactoryName() {
            return CONTEXT_HANDLE_FACTORY_NAME;
        }

        private void writeObject(ObjectOutputStream out) throws IOException {
            out.defaultWriteObject();
            // write the concurrent context service name
            out.writeObject(concurrentContext.serviceName);
            // write the number of setup handles
            out.write(setupHandles.size());
            // write each handle
            ContextHandleFactory factory = null;
            String factoryName = null;
            for(SetupContextHandle handle : setupHandles) {
                factoryName = handle.getFactoryName();
                factory = concurrentContext.factoryMap.get(factoryName);
                if(factory == null) {
                    throw EeLogger.ROOT_LOGGER.factoryNotFound(concurrentContext, factoryName);
                }
                out.writeUTF(factoryName);
                factory.writeSetupContextHandle(handle, out);
            }
        }

        private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
            // switch to EE module classloader, otherwise, due to serialization, deployments would need to have dependencies to other internal modules
            final Module module;
            try {
                module = Module.getBootModuleLoader().loadModule(ModuleIdentifier.create("org.jboss.as.ee"));
            } catch (Throwable e) {
                throw new IOException(e);
            }
            final SecurityManager sm = System.getSecurityManager();
            final ClassLoader classLoader;
            if (sm == null) {
                classLoader = currentThread().getContextClassLoader();
            } else {
                classLoader = AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
                    @Override
                    public ClassLoader run() {
                        return currentThread().getContextClassLoader();
                    }
                });
            }
            if (sm == null) {
                currentThread().setContextClassLoader(module.getClassLoader());
            } else {
                AccessController.doPrivileged(new PrivilegedAction<Object>() {
                    @Override
                    public Object run() {
                        currentThread().setContextClassLoader(module.getClassLoader());
                        return null;
                    }
                });
            }
            try {
                in.defaultReadObject();
                // restore concurrent context from msc
                final ServiceName serviceName = (ServiceName) in.readObject();
                final ServiceController<?> serviceController = currentServiceContainer().getService(serviceName);
                if(serviceController == null) {
                    throw EeLogger.ROOT_LOGGER.concurrentContextServiceNotInstalled(serviceName);
                }
                concurrentContext = (ConcurrentContext) serviceController.getValue();
                // read setup handles
                setupHandles = new ArrayList<>();
                ContextHandleFactory factory = null;
                String factoryName = null;
                for(int i = in.read(); i > 0; i--) {
                    factoryName = in.readUTF();
                    factory = concurrentContext.factoryMap.get(factoryName);
                    if(factory == null) {
                        throw EeLogger.ROOT_LOGGER.factoryNotFound(concurrentContext, factoryName);
                    }
                    setupHandles.add(factory.readSetupContextHandle(in));
                }
            } finally {
                if (sm == null) {
                    currentThread().setContextClassLoader(classLoader);
                } else {
                    AccessController.doPrivileged(new PrivilegedAction<Object>() {
                        @Override
                        public Object run() {
                            currentThread().setContextClassLoader(classLoader);
                            return null;
                        }
                    });
                }
            }
        }
    }

    /**
     * A reset context handle that is a chain of other reset context handles
     */
    private static class ChainedResetContextHandle implements ResetContextHandle {

        private transient List<ResetContextHandle> resetHandles;

        private ChainedResetContextHandle(List<ResetContextHandle> resetHandles) {
            this.resetHandles = resetHandles;
        }

        @Override
        public void reset() {
            if(resetHandles != null) {
                for (ResetContextHandle handle : resetHandles) {
                    try {
                        handle.reset();
                    } catch (Throwable e) {
                        EeLogger.ROOT_LOGGER.debug("failed to reset handle",e);
                    }
                }
                resetHandles = null;
                ConcurrentContext.popCurrent();
            }
        }

        @Override
        public String getFactoryName() {
            return CONTEXT_HANDLE_FACTORY_NAME;
        }

    }

    private static ServiceContainer currentServiceContainer() {
        if(System.getSecurityManager() == null) {
            return CurrentServiceContainer.getServiceContainer();
        }
        return AccessController.doPrivileged(CurrentServiceContainer.GET_ACTION);
    }
}


/*
 * 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.naming.interfaces.java;

import org.jboss.as.naming.NamingContext;

import javax.naming.CompositeName;
import javax.naming.Context;
import javax.naming.Name;
import javax.naming.spi.ObjectFactory;
import java.util.Hashtable;

/**
 * Implementation of {@code ObjectFactory} used to create a {@code NamingContext} instances to support the java: namespace.
 *
 * @author John E. Bailey
 */
public class javaURLContextFactory implements ObjectFactory {

    /** {@inheritDoc} */
    public Object getObjectInstance(Object obj, Name name, Context nameCtx, Hashtable<?, ?> environment) throws Exception {
        return new NamingContext(name != null ? name : new CompositeName(""), environment);
    }
}


/*
 * JBoss, Home of Professional Open Source
 * Copyright 2010, Red Hat Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.weld.deployment;

import java.security.PrivilegedAction;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.CopyOnWriteArraySet;

import org.jboss.as.weld.WeldModuleResourceLoader;
import org.jboss.as.weld.spi.WildFlyBeanDeploymentArchive;
import org.jboss.as.weld.util.Reflections;
import org.jboss.as.weld.util.ServiceLoaders;
import org.jboss.modules.DependencySpec;
import org.jboss.modules.Module;
import org.jboss.modules.ModuleDependencySpec;
import org.jboss.modules.ModuleLoadException;
import org.jboss.modules.ModuleLoader;
import org.jboss.weld.bootstrap.api.Service;
import org.jboss.weld.bootstrap.api.ServiceRegistry;
import org.jboss.weld.bootstrap.api.helpers.SimpleServiceRegistry;
import org.jboss.weld.bootstrap.spi.BeanDeploymentArchive;
import org.jboss.weld.bootstrap.spi.BeansXml;
import org.jboss.weld.ejb.spi.EjbDescriptor;
import org.jboss.weld.resources.spi.ResourceLoader;
import org.wildfly.security.manager.WildFlySecurityManager;

/**
 * Implementation of {@link BeanDeploymentArchive}.
 *

 * Thread Safety: This class is thread safe and does not require a happens before action between construction and usage
 *
 * @author Stuart Douglas
 *
 */
public class BeanDeploymentArchiveImpl implements WildFlyBeanDeploymentArchive {

    public enum BeanArchiveType {
        IMPLICIT, EXPLICIT, EXTERNAL, SYNTHETIC;
    }

    private final Set<String> beanClasses;

    private final Set<String> allKnownClasses;

    private final Set<BeanDeploymentArchive> beanDeploymentArchives;

    private final BeansXml beansXml;

    private final String id;

    private final ServiceRegistry serviceRegistry;

    private final Module module;

    private final WeldModuleResourceLoader resourceLoader;

    private final Set<EjbDescriptor<?>> ejbDescriptors;

    private final boolean root; // indicates whether this is a root BDA

    private final BeanArchiveType beanArchiveType;

    public BeanDeploymentArchiveImpl(Set<String> beanClasses, Set<String> allClasses, BeansXml beansXml, Module module, String id, BeanArchiveType beanArchiveType) {
        this(beanClasses, allClasses, beansXml, module, id, beanArchiveType, false);
    }

    public BeanDeploymentArchiveImpl(Set<String> beanClasses, Set<String> allClasses, BeansXml beansXml, Module module, String id, BeanArchiveType beanArchiveType, boolean root) {
        this.beanClasses = new ConcurrentSkipListSet<String>(beanClasses);
        this.allKnownClasses = new ConcurrentSkipListSet<String>(allClasses);
        this.beanDeploymentArchives = new CopyOnWriteArraySet<BeanDeploymentArchive>();
        this.beansXml = beansXml;
        this.id = id;
        this.serviceRegistry = new SimpleServiceRegistry();
        this.resourceLoader = new WeldModuleResourceLoader(module);
        this.serviceRegistry.add(ResourceLoader.class, resourceLoader);

        for (Entry<Class<? extends Service>, Service> entry : ServiceLoaders.loadBeanDeploymentArchiveServices(BeanDeploymentArchiveImpl.class, this)
                .entrySet()) {
            this.serviceRegistry.add(entry.getKey(), Reflections.cast(entry.getValue()));
        }

        this.module = module;
        this.ejbDescriptors = new HashSet<EjbDescriptor<?>>();
        this.beanArchiveType = beanArchiveType;
        this.root = root;
    }

    /**
     * Adds an accessible {@link BeanDeploymentArchive}.
     */
    public void addBeanDeploymentArchive(BeanDeploymentArchive archive) {
        if (archive == this) {
            return;
        }
        beanDeploymentArchives.add(archive);
    }

    /**
     * Adds multiple accessible {@link BeanDeploymentArchive}s
     */
    public void addBeanDeploymentArchives(Collection<? extends BeanDeploymentArchive> archives) {
        for (BeanDeploymentArchive bda : archives) {
            if (bda != this) {
                beanDeploymentArchives.add(bda);
            }
        }
    }

    public void addBeanClass(String clazz) {
        this.beanClasses.add(clazz);
        this.allKnownClasses.add(clazz);
    }

    public void addBeanClass(Class<?> clazz) {
        this.resourceLoader.addAdditionalClass(clazz);
    }

    /**
     * returns an unmodifiable copy of the bean classes in this BDA
     */
    @Override
    public Collection<String> getBeanClasses() {
        return Collections.unmodifiableSet(new HashSet<String>(beanClasses));
    }

    /**
     * Returns an unmodifiable copy of the bean deployment archives set
     */
    @Override
    public Collection<BeanDeploymentArchive> getBeanDeploymentArchives() {
        return Collections.unmodifiableCollection(new HashSet<BeanDeploymentArchive>(beanDeploymentArchives));
    }

    @Override
    public BeansXml getBeansXml() {
        return beansXml;
    }

    public void addEjbDescriptor(EjbDescriptor<?> descriptor) {
        ejbDescriptors.add(descriptor);
    }

    @Override
    public Collection<EjbDescriptor<?>> getEjbs() {
        return Collections.unmodifiableSet(ejbDescriptors);
    }

    @Override
    public String getId() {
        return id;
    }

    @Override
    public ServiceRegistry getServices() {
        return serviceRegistry;
    }

    public Module getModule() {
        return module;
    }

    public ClassLoader getClassLoader() {
        if (module != null) {
            if(WildFlySecurityManager.isChecking()) {
                return WildFlySecurityManager.doUnchecked(new PrivilegedAction<ClassLoader>() {
                    @Override
                    public ClassLoader run() {
                        return module.getClassLoader();
                    }
                });
            } else {
                return module.getClassLoader();
            }
        }
        return null;
    }

    public boolean isRoot() {
        return root;
    }

    /**
     * Determines if a class from this {@link BeanDeploymentArchiveImpl} instance can access a class in the
     * {@link BeanDeploymentArchive} instance represented by the specified BeanDeploymentArchive parameter
     * according to the Java EE class accessibility requirements.
     *
     * @param target
     * @return true if an only if a class this archive can access a class from the archive represented by the specified parameter
     */
    public boolean isAccessible(BeanDeploymentArchive target) {
        if (this == target) {
            return true;
        }

        BeanDeploymentArchiveImpl that = (BeanDeploymentArchiveImpl) target;

        if (that.getModule() == null) {
            /*
             * The target BDA is the bootstrap BDA - it bundles classes loaded by the bootstrap classloader.
             * Everyone can see the bootstrap classloader.
             */
            return true;
        }
        if (module == null) {
            /*
             * This BDA is the bootstrap BDA - it bundles classes loaded by the bootstrap classloader. We assume that a
             * bean whose class is loaded by the bootstrap classloader can only see other beans in the "bootstrap BDA".
             */
            return that.getModule() == null;
        }

        if (module.equals(that.getModule())) {
            return true;
        }

        // basic check whether the module is our dependency
        for (DependencySpec dependency : module.getDependencies()) {
            if (dependency instanceof ModuleDependencySpec) {
                ModuleDependencySpec moduleDependency = (ModuleDependencySpec) dependency;
                if (moduleDependency.getIdentifier().equals(that.getModule().getIdentifier())) {
                    return true;
                }

                // moduleDependency might be an alias - try to load it to get lined module
                Module module = loadModule(moduleDependency);
                if (module != null && module.getIdentifier().equals(that.getModule().getIdentifier())) {
                    return true;
                }
            }
        }

        /*
         * full check - we try to load a class from the target bean archive and check whether its module
         * is the same as the one of the bean archive
         * See WFLY-4250 for more info
         */
        Iterator<String> iterator = target.getBeanClasses().iterator();
        if (iterator.hasNext()) {
            Class<?> clazz = Reflections.loadClass(iterator.next(), module.getClassLoader());
            if (clazz != null) {
                Module classModule = Module.forClass(clazz);
                return classModule != null && classModule.equals(that.getModule());
            }
        }
        return false;
    }

    private Module loadModule(ModuleDependencySpec moduleDependency) {
        try {
            ModuleLoader moduleLoader = moduleDependency.getModuleLoader();
            if (moduleLoader == null) {
                return null;
            } else {
                return moduleLoader.loadModule(moduleDependency.getIdentifier());
            }
        } catch (ModuleLoadException e) {
            return null;
        }
    }

    public BeanArchiveType getBeanArchiveType() {
        return beanArchiveType;
    }

    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder(this.beanArchiveType.toString());
        builder.append(" BeanDeploymentArchive (");
        builder.append(this.id);
        builder.append(")");
        return builder.toString();
    }

    @Override
    public Collection<String> getKnownClasses(){
        return allKnownClasses;
    }
}