/*
 * JBoss, Home of Professional Open Source.
 * Copyright 2008, 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.wildfly.iiop.openjdk.rmi.marshal.strategy;

import java.rmi.NoSuchObjectException;
import java.rmi.Remote;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.rmi.UnexpectedException;

import javax.rmi.PortableRemoteObject;

import org.omg.CORBA.UserException;
import org.omg.CORBA.portable.IDLEntity;
import org.omg.CORBA_2_3.portable.InputStream;
import org.omg.CORBA_2_3.portable.OutputStream;
import org.jboss.javax.rmi.RemoteObjectSubstitutionManager;
import org.wildfly.iiop.openjdk.logging.IIOPLogger;
import org.wildfly.iiop.openjdk.rmi.marshal.CDRStream;
import org.wildfly.iiop.openjdk.rmi.marshal.CDRStreamReader;
import org.wildfly.iiop.openjdk.rmi.marshal.CDRStreamWriter;
import org.wildfly.security.manager.WildFlySecurityManager;

/**
 * An StubStrategy for a given method knows how to marshal
 * the sequence of method parameters into a CDR output stream, how to unmarshal
 * from a CDR input stream the return value of the method, and how to unmarshal
 * from a CDR input stream an application exception thrown by the method.
 *
 * @author Francisco Reverbel
 * @version $Revision: 81018 $
 */
public class StubStrategy {

    /**
     * Each CDRStreamWriter in the array marshals a method
     * parameter.
     */
    private CDRStreamWriter[] paramWriters;

    /**
     * List of exception classes.
     */
    private List<Class<?>> exceptionList;

    /**
     * Maps exception repository ids into ExceptionReader instances.
     */
    private Map<String, ExceptionReader> exceptionMap;

    /**
     * A CDRStreamReader that unmarshals the return value of the
     * method.
     */
    private CDRStreamReader retvalReader;

    /**
     * If this StubStrategy is for a method that returns a
     * remote interface, this field contains the remote interface's
     * Class. Otherwise it contains null.
     */
    private Class<?> retvalRemoteInterface;

    /**
     * Returns an StubStrategy for a method, given descriptions
     * of the method parameters, exceptions, and return value. Parameter and
     * return value descriptions are "marshaller abbreviated names".
     *
     * @param paramTypes a string array with marshaller abbreviated names for
     *                   the method parameters
     * @param excepIds   a string array with the CORBA repository ids of the
     *                   exceptions thrown by the method
     * @param excepTypes a string array with the Java class names of the
     *                   exceptions thrown by the method
     * @param retvalType marshaller abbreaviated name for the return value of
     *                   the method
     * @param cl         a ClassLoader to load value classes
     *                   (if null, the current thread's context class loader
     *                   will be used)
     * @return an StubStrategy for the operation with the
     *         parameters, exceptions, and return value specified.
     * @see org.wildfly.iiop.openjdk.rmi.marshal.CDRStream#abbrevFor(Class clz)
     */
    public static StubStrategy forMethod(String[] paramTypes,
                                         String[] excepIds,
                                         String[] excepTypes,
                                         String retvalType,
                                         ClassLoader cl) {
        // This "factory method" exists just because I have found it easier
        // to invoke a static method (rather than invoking operator new)
        // from a stub class dynamically assembled by an instance of
        // org.jboss.proxy.ProxyAssembler.

        return new StubStrategy(paramTypes, excepIds,
                excepTypes, retvalType, cl);
    }

    /**
     * Constructs an StubStrategy for a method, given
     * descriptions of the method parameters, exceptions, and return value.
     * Parameter and return value descriptions are "marshaller abbreviated
     * names".
     *
     * @param paramTypes a string array with marshaller abbreviated names for
     *                   the method parameters
     * @param excepIds   a string array with the CORBA repository ids of the
     *                   exceptions thrown by the method
     * @param excepTypes a string array with the Java class names of the
     *                   exceptions thrown by the method
     * @param retvalType marshaller abbreaviated name for the return value of
     *                   the method
     * @param cl         a ClassLoader to load value classes
     *                   (if null, the current thread's context class loader
     *                   will be used)
     * @see org.wildfly.iiop.openjdk.rmi.marshal.CDRStream#abbrevFor(Class clz)
     */
    private StubStrategy(String[] paramTypes, String[] excepIds,
                         String[] excepTypes, String retvalType,
                         ClassLoader cl) {
        if (cl == null) {
            cl = WildFlySecurityManager.getCurrentContextClassLoaderPrivileged();
        }

        // Initialize paramWriters
        int len = paramTypes.length;
        paramWriters = new CDRStreamWriter[len];
        for (int i = 0; i < len; i++) {
            paramWriters[i] = CDRStream.writerFor(paramTypes[i], cl);
        }

        // Initialize exception list and exception map
        exceptionList = new ArrayList<Class<?>>();
        exceptionMap = new HashMap<String, ExceptionReader>();
        len = excepIds.length;
        for (int i = 0; i < len; i++) {
            try {
                Class<?> clz = cl.loadClass(excepTypes[i]);
                exceptionList.add(clz);
                ExceptionReader exceptionReader =
                        new ExceptionReader(clz, excepIds[i]);
                exceptionMap.put(exceptionReader.getReposId(), exceptionReader);
            } catch (ClassNotFoundException e) {
                throw IIOPLogger.ROOT_LOGGER.errorLoadingClass(excepTypes[i], e);
            }
        }

        // Initialize retvalReader
        retvalReader = CDRStream.readerFor(retvalType, cl);

        // Initialize retvalRemoteInterface
        if (retvalType.charAt(0) == 'R') {
            try {
                retvalRemoteInterface = cl.loadClass(retvalType.substring(1));
            } catch (ClassNotFoundException e) {
                throw IIOPLogger.ROOT_LOGGER.errorLoadingClass(retvalType.substring(1), e);
            }
        }
    }

    /**
     * Marshals the sequence of method parameters into an output stream.
     *
     * @param out    a CDR output stream
     * @param params an object array with the parameters.
     */
    public void writeParams(OutputStream out, Object[] params) {
        int len = params.length;

        if (len != paramWriters.length) {
            throw IIOPLogger.ROOT_LOGGER.errorMashalingParams();
        }
        for (int i = 0; i < len; i++) {
            Object param = params[i];
            if (param instanceof PortableRemoteObject) {
                try {
                    param = PortableRemoteObject.toStub((Remote) param);
                } catch (NoSuchObjectException e) {
                    throw new RuntimeException(e);
                }
            }
            paramWriters[i].write(out, RemoteObjectSubstitutionManager.writeReplaceRemote(param));
        }
    }

    /**
     * Returns true if this StubStrategy's method is non void.
     */
    public boolean isNonVoid() {
        return (retvalReader != null);
    }

    /**
     * Unmarshals from an input stream the return value of the method.
     *
     * @param in a CDR input stream
     * @return a value unmarshaled from the stream.
     */
    public Object readRetval(InputStream in) {
        return retvalReader.read(in);
    }

    /**
     * Unmarshals from an input stream an exception thrown by the method.
     *
     * @param id the repository id of the exception to unmarshal
     * @param in a CDR input stream
     * @return an exception unmarshaled from the stream.
     */
    public Exception readException(String id, InputStream in) {
        ExceptionReader exceptionReader = (ExceptionReader) exceptionMap.get(id);
        if (exceptionReader == null) {
            return new UnexpectedException(id);
        } else {
            return exceptionReader.read(in);
        }
    }

    /**
     * Checks if a given Throwable instance corresponds to an
     * exception declared by this StubStrategy's method.
     *
     * @param t an exception class
     * @return true if t is an instance of any of the
     *         exceptions declared by this StubStrategy's
     *         method, false otherwise.
     */
    public boolean isDeclaredException(Throwable t) {
        Iterator<Class<?>> iterator = exceptionList.iterator();
        while (iterator.hasNext()) {
            if (((Class<?>) iterator.next()).isInstance(t)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Converts the return value of a local invocation into the expected type.
     * A conversion is needed if the return value is a remote interface
     * (in this case PortableRemoteObject.narrow() must be called).
     *
     * @param obj the return value to be converted
     * @return the converted value.
     */
    public Object convertLocalRetval(Object obj) {
        if (retvalRemoteInterface == null)
            return obj;
        else
            return PortableRemoteObject.narrow(obj, retvalRemoteInterface);
    }

    // Static inner class (private) --------------------------------------------

    /**
     * An ExceptionReader knows how to read exceptions of a given
     * class from a CDR input stream.
     */
    private static class ExceptionReader {
        /**
         * The exception class.
         */
        private Class<?> clz;

        /**
         * The CORBA repository id of the exception class.
         */
        private String reposId;

        /*
        * If the exception class corresponds to an IDL-defined exception, this
        * field contains the read method of the associated helper class.
        * A null value indicates that the exception class does not correspond
        * to an IDL-defined exception.
        */
        private java.lang.reflect.Method readMethod = null;

        /**
         * Constructs an ExceptionReader for a given exception
         * class.
         */
        ExceptionReader(Class<?> clz, String reposId) {
            this.clz = clz;
            if (IDLEntity.class.isAssignableFrom(clz)
                    && UserException.class.isAssignableFrom(clz)) {

                // This ExceptionReader corresponds to an IDL-defined exception
                String helperClassName = clz.getName() + "Helper";
                try {
                    Class<?> helperClass = clz.getClassLoader().loadClass(helperClassName);
                    Class<?>[] paramTypes = {org.omg.CORBA.portable.InputStream.class};
                    readMethod = helperClass.getMethod("read", paramTypes);

                    // Ignore the reposId parameter and use the id
                    // returned by the IDL-generated helper class
                    java.lang.reflect.Method idMethod = helperClass.getMethod("id", (Class[])null);
                    this.reposId = (String) idMethod.invoke(null, (Object[])null);
                } catch (ClassNotFoundException e) {
                    throw IIOPLogger.ROOT_LOGGER.errorLoadingClass(helperClassName, e);
                } catch (NoSuchMethodException e) {
                    throw IIOPLogger.ROOT_LOGGER.noReadMethodInHelper(helperClassName, e);
                } catch (IllegalAccessException e) {
                    throw IIOPLogger.ROOT_LOGGER.unexpectedException(e);
                } catch (java.lang.reflect.InvocationTargetException e) {
                    throw IIOPLogger.ROOT_LOGGER.unexpectedException(e.getTargetException());
                }
            } else {
                // This ExceptionReader does not correspond to an IDL-defined
                // exception: store the reposId parameter
                this.reposId = reposId;
            }
        }

        public String getReposId() {
            return reposId;
        }

        /**
         * Reads an exception from a CDR input stream.
         */
        public Exception read(InputStream in) {
            if (readMethod != null) {
                try {
                    return (Exception) readMethod.invoke(null, new Object[]{in});
                } catch (IllegalAccessException e) {
                    throw IIOPLogger.ROOT_LOGGER.unexpectedException(e);
                } catch (java.lang.reflect.InvocationTargetException e) {
                    throw IIOPLogger.ROOT_LOGGER.errorUnmarshaling(IDLEntity.class, e.getTargetException());
                }
            } else {
                in.read_string(); // read and discard the repository id
                return (Exception) in.read_value(clz);
            }
        }

    } // end of inner class ExceptionReader

}


/*
 * 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.wildfly.extension.messaging.activemq;

import java.util.function.Supplier;
import org.apache.activemq.artemis.api.core.SimpleString;
import org.apache.activemq.artemis.core.config.CoreQueueConfiguration;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
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.wildfly.extension.messaging.activemq.logging.MessagingLogger;

/**
 * Service responsible for create ActiveMQ core queues.
 *
 * @author Emanuel Muckenhuber
 */
class QueueService implements Service<Void> {

    private final Supplier<ActiveMQServer> activeMQServerSupplier;
    private final CoreQueueConfiguration queueConfiguration;
    private final boolean temporary;
    private final boolean createQueue;

    public QueueService(final Supplier<ActiveMQServer> activeMQServerSupplier, final CoreQueueConfiguration queueConfiguration, final boolean temporary, final boolean createQueue) {
        if(queueConfiguration == null) {
            throw MessagingLogger.ROOT_LOGGER.nullVar("queueConfiguration");
        }
        this.activeMQServerSupplier = activeMQServerSupplier;
        this.queueConfiguration = queueConfiguration;
        this.temporary = temporary;
        this.createQueue = createQueue;
    }

    /** {@inheritDoc} */
    @Override
    public synchronized void start(StartContext context) throws StartException {
        if (createQueue) {
            try {
                final ActiveMQServer server = this.activeMQServerSupplier.get();
                MessagingLogger.ROOT_LOGGER.debugf("Deploying queue on server %s with address: %s ,  name: %s, filter: %s ands durable: %s, temporary: %s",
                        server.getNodeID(), new SimpleString(queueConfiguration.getAddress()), new SimpleString(queueConfiguration.getName()),
                        SimpleString.toSimpleString(queueConfiguration.getFilterString()), queueConfiguration.isDurable(), temporary);
                final SimpleString resourceName = new SimpleString(queueConfiguration.getName());
                final SimpleString address = new SimpleString(queueConfiguration.getAddress());
                final SimpleString filterString = SimpleString.toSimpleString(queueConfiguration.getFilterString());
                server.createQueue(address,
                        queueConfiguration.getRoutingType(),
                        resourceName,
                        filterString,
                        queueConfiguration.isDurable(),
                        temporary);
            } catch (Exception e) {
                throw new StartException(e);
            }
        }
    }

    /** {@inheritDoc} */
    @Override
    public synchronized void stop(StopContext context) {
        try {
            final ActiveMQServer server = this.activeMQServerSupplier.get();
            server.destroyQueue(new SimpleString(queueConfiguration.getName()), null, false);
            MessagingLogger.ROOT_LOGGER.debugf("Destroying queue from server %s queue with name: %s",server.getNodeID() , new SimpleString(queueConfiguration.getName()));
        } catch(Exception e) {
            MessagingLogger.ROOT_LOGGER.failedToDestroy("queue", queueConfiguration.getName());
        }
    }

    /** {@inheritDoc} */
    @Override
    public Void getValue() throws IllegalStateException {
        return null;
    }
}


/*
 * JBoss, Home of Professional Open Source.
 * Copyright (c) 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.ejb3.deployment.processors;

import org.jboss.jandex.ClassInfo;
import org.jboss.jandex.DotName;

import java.io.Externalizable;
import java.io.Serializable;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

/**
 * @author Carlo de Wolf
 */
class ViewInterfaces {
    /**
     * Returns all interfaces implemented by a bean that are eligible to be view interfaces
     *
     * @param beanClass The bean class
     * @return A collection of all potential view interfaces
     */
    static Set<Class<?>> getPotentialViewInterfaces(Class<?> beanClass) {
        Class<?>[] interfaces = beanClass.getInterfaces();
        if (interfaces == null) {
            return Collections.emptySet();
        }
        final Set<Class<?>> potentialBusinessInterfaces = new HashSet<Class<?>>();
        for (Class<?> klass : interfaces) {
            // EJB 3.1 FR 4.9.7 bullet 5.3
            if (klass.equals(Serializable.class) ||
                    klass.equals(Externalizable.class) ||
                    klass.getName().startsWith("javax.ejb.") ||
                    klass.getName().startsWith("groovy.lang.")) {
                continue;
            }
            potentialBusinessInterfaces.add(klass);
        }
        return potentialBusinessInterfaces;
    }

    /**
     * Returns all interfaces implemented by a bean that are eligible to be view interfaces
     *
     * @param beanClass The bean class
     * @return A collection of all potential view interfaces
     */
    static Set<DotName> getPotentialViewInterfaces(ClassInfo beanClass) {
        DotName[] interfaces = beanClass.interfaces();
        if (interfaces == null) {
            return Collections.emptySet();
        }
        final Set<DotName> names = new HashSet<DotName>();
        for (DotName dotName : interfaces) {
            String name = dotName.toString();
            // EJB 3.1 FR 4.9.7 bullet 5.3
            // & FR 5.4.2
            if (name.equals(Serializable.class.getName()) ||
                    name.equals(Externalizable.class.getName()) ||
                    name.startsWith("javax.ejb.") ||
                    name.startsWith("groovy.lang.")) {
                continue;
            }
            names.add(dotName);
        }
        return names;
    }
}


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

import static java.security.AccessController.doPrivileged;
import static org.jboss.as.jpa.messages.JpaLogger.ROOT_LOGGER;

import java.security.PrivilegedAction;
import java.util.EnumSet;

import javax.persistence.EntityManager;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;

import org.jboss.as.jpa.container.ExtendedEntityManager;
import org.jboss.as.jpa.messages.JpaLogger;
import org.jboss.tm.TxUtils;
import org.jboss.tm.listener.EventType;
import org.wildfly.transaction.client.AbstractTransaction;
import org.wildfly.transaction.client.AssociationListener;
import org.wildfly.transaction.client.ContextTransactionManager;

/**
 * Transaction utilities for JPA
 *
 * @author Scott Marlow (forked from code by Gavin King)
 */
public class TransactionUtil {

    private static final EnumSet<EventType> eventTypes = EnumSet.of(EventType.ASSOCIATED, EventType.DISASSOCIATING);

    public static boolean isInTx(TransactionManager transactionManager) {
        Transaction tx = getTransaction(transactionManager);
        if (tx == null || !TxUtils.isActive(tx))
            return false;
        return true;
    }

    /**
     * Get current persistence context.  Only call while a transaction is active in the current thread.
     *
     * @param puScopedName
     * @return
     */
    public static EntityManager getTransactionScopedEntityManager(String puScopedName, TransactionSynchronizationRegistry tsr) {
        return getEntityManagerInTransactionRegistry(puScopedName, tsr);
    }

    public static void registerSynchronization(EntityManager entityManager, String puScopedName, TransactionSynchronizationRegistry tsr, TransactionManager transactionManager) {
        SessionSynchronization sessionSynchronization = new SessionSynchronization(entityManager, puScopedName);
        tsr.registerInterposedSynchronization(sessionSynchronization);
        final AbstractTransaction transaction = ((ContextTransactionManager) transactionManager).getTransaction();
        doPrivileged((PrivilegedAction<Void>) () -> {
            transaction.registerAssociationListener(sessionSynchronization);
            return null;
        });
    }

    public static Transaction getTransaction(TransactionManager transactionManager) {
        try {
            return transactionManager.getTransaction();
        } catch (SystemException e) {
            throw JpaLogger.ROOT_LOGGER.errorGettingTransaction(e);
        }
    }

    private static String currentThread() {
        return Thread.currentThread().getName();
    }

    public static String getEntityManagerDetails(EntityManager manager, String scopedPuName) {
        String result = currentThread() + ":";  // show the thread for correlation with other modules
        if (manager instanceof ExtendedEntityManager) {
            result += manager.toString();
        }
        else {
            result += "transaction scoped EntityManager [" + scopedPuName + "]";
        }
        return result;
    }

    private static EntityManager getEntityManagerInTransactionRegistry(String scopedPuName, TransactionSynchronizationRegistry tsr) {
        return (EntityManager)tsr.getResource(scopedPuName);
    }

    /**
     * Save the specified EntityManager in the local threads active transaction.  The TransactionSynchronizationRegistry
     * will clear the reference to the EntityManager when the transaction completes.
     *
     * @param scopedPuName
     * @param entityManager
     */
    public static void putEntityManagerInTransactionRegistry(String scopedPuName, EntityManager entityManager, TransactionSynchronizationRegistry tsr) {
        tsr.putResource(scopedPuName, entityManager);
    }

    /**
     * The AssociationListener helps protect against a non-application thread closing the entity manager at the same
     * time that the application thread may be using the entity manager.  We only close the entity manager after the
     * Synchronization.afterCompletion has been triggered and zero threads are associated with the transaction.
     *
     * We know when the application thread is associated with the transaction and can defer closing the EntityManager
     * until both conditions are met:
     *
     *   1. application thread is disassociated from transaction
     *   2. Synchronization.afterCompletion has been called
     *
     *   Note that entity managers do not get propagated on remote EJB invocations.
     *
     * See discussions for more details about how we arrived at using the AssociationListener (TransactionListener):
     *     https://developer.jboss.org/message/919807
     *     https://developer.jboss.org/thread/252572
     */
    private static class SessionSynchronization implements Synchronization, AssociationListener {
        private EntityManager manager;  // the underlying entity manager
        private String scopedPuName;
        private boolean afterCompletionCalled = false;
        private int associationCounter = 1; // set to one since transaction is associated with current thread already.
                                            // incremented when a thread is associated with transaction,
                                            // decremented when a thread is disassociated from transaction.
                                            // synchronization on this object protects associationCounter.

        public SessionSynchronization(EntityManager session, String scopedPuName) {
            this.manager = session;
            this.scopedPuName = scopedPuName;
        }

        public void beforeCompletion() {
            afterCompletionCalled = false;
        }

        public void afterCompletion(int status) {
            /**
             * Note: synchronization is to protect against two concurrent threads from closing the EntityManager (manager)
             * at the same time.
             */
            synchronized (this) {
                afterCompletionCalled = true;
                safeCloseEntityManager();
            }
        }

        /**
         * After the JTA transaction is ended (Synchronization.afterCompletion has been called) and
         * the JTA transaction is no longer associated with application thread (application thread called
         * transaction.rollback/commit/suspend), the entity manager can safely be closed.
         *
         * NOTE: caller must call with synchronized(this), where this == instance of SessionSynchronization associated with
         * the JTA transaction.
         */
        private void safeCloseEntityManager() {
            if ( afterCompletionCalled == true && associationCounter == 0) {
                if (manager != null) {
                    try {
                        if (ROOT_LOGGER.isDebugEnabled())
                            ROOT_LOGGER.debugf("%s: closing entity managersession", getEntityManagerDetails(manager, scopedPuName));
                        manager.close();
                    } catch (Exception ignored) {
                        if (ROOT_LOGGER.isDebugEnabled())
                            ROOT_LOGGER.debugf(ignored, "ignoring error that occurred while closing EntityManager for %s (", scopedPuName);
                    }
                    manager = null;
                }
            }
        }

        public void associationChanged(final AbstractTransaction transaction, final boolean associated) {
            synchronized (this) {
                // associationCounter is set to zero when application thread is no longer associated with JTA transaction.
                // We are tracking when the application thread
                // is no longer associated with the transaction, as that indicates that it is safe to
                // close the entity manager (since the application is no longer using the entity manager).
                //
                // Expected values for associationCounter:
                // 1 - application thread is associated with transaction
                // 0 - application thread is not associated with transaction (e.g. tm.suspend called)
                //
                // Expected values for TM Reaper thread timing out transaction
                // 1 - application thread is associated with transaction
                // 2 - TM reaper thread is associated with transaction (tx timeout handling)
                // 1 - either TM reaper or application thread disassociated from transaction
                // 0 - both TM reaper and application thread are disassociated from transaction
                //
                // the safeCloseEntityManager() may close the entity manager in the (background) reaper thread or
                // application thread (whichever thread reaches associationCounter == 0).
                associationCounter += associated ? 1 : -1;
                if (ROOT_LOGGER.isTraceEnabled()) {
                    ROOT_LOGGER.tracef("transaction association counter = %d for %s: ", associationCounter, getEntityManagerDetails(manager, scopedPuName));
                }
                safeCloseEntityManager();
            }
        }
    }

}


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

import static org.jboss.as.messaging.ClusterConnectionDefinition.ALLOW_DIRECT_CONNECTIONS_ONLY;
import static org.jboss.as.messaging.ClusterConnectionDefinition.CONNECTOR_REFS;
import static org.jboss.as.messaging.CommonAttributes.ACCEPTOR;
import static org.jboss.as.messaging.CommonAttributes.ADDRESS_SETTING;
import static org.jboss.as.messaging.CommonAttributes.BROADCAST_GROUP;
import static org.jboss.as.messaging.CommonAttributes.CONNECTION_FACTORY;
import static org.jboss.as.messaging.CommonAttributes.CONNECTOR;
import static org.jboss.as.messaging.CommonAttributes.DEFAULT;
import static org.jboss.as.messaging.CommonAttributes.DISCOVERY_GROUP;
import static org.jboss.as.messaging.CommonAttributes.DIVERT;
import static org.jboss.as.messaging.CommonAttributes.DURABLE;
import static org.jboss.as.messaging.CommonAttributes.GROUPING_HANDLER;
import static org.jboss.as.messaging.CommonAttributes.HORNETQ_SERVER;
import static org.jboss.as.messaging.CommonAttributes.HTTP_ACCEPTOR;
import static org.jboss.as.messaging.CommonAttributes.HTTP_CONNECTOR;
import static org.jboss.as.messaging.CommonAttributes.IN_VM_ACCEPTOR;
import static org.jboss.as.messaging.CommonAttributes.IN_VM_CONNECTOR;
import static org.jboss.as.messaging.CommonAttributes.JMS_BRIDGE;
import static org.jboss.as.messaging.CommonAttributes.JMS_CONNECTION_FACTORIES;
import static org.jboss.as.messaging.CommonAttributes.JMS_DESTINATIONS;
import static org.jboss.as.messaging.CommonAttributes.JMS_QUEUE;
import static org.jboss.as.messaging.CommonAttributes.JMS_TOPIC;
import static org.jboss.as.messaging.CommonAttributes.PARAM;
import static org.jboss.as.messaging.CommonAttributes.POOLED_CONNECTION_FACTORY;
import static org.jboss.as.messaging.CommonAttributes.REMOTE_ACCEPTOR;
import static org.jboss.as.messaging.CommonAttributes.REMOTE_CONNECTOR;
import static org.jboss.as.messaging.CommonAttributes.ROLE;
import static org.jboss.as.messaging.Element.SOURCE;
import static org.jboss.as.messaging.Element.TARGET;
import static org.jboss.as.messaging.Namespace.CURRENT;
import static org.jboss.as.messaging.PathDefinition.PATHS;
import static org.jboss.as.messaging.PathDefinition.RELATIVE_TO;

import java.util.ArrayList;
import java.util.List;

import javax.xml.stream.XMLStreamException;

import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.descriptions.ModelDescriptionConstants;
import org.jboss.as.controller.persistence.SubsystemMarshallingContext;
import org.jboss.as.messaging.jms.ConnectionFactoryAttribute;
import org.jboss.as.messaging.jms.ConnectionFactoryDefinition;
import org.jboss.as.messaging.jms.JMSQueueDefinition;
import org.jboss.as.messaging.jms.JMSTopicDefinition;
import org.jboss.as.messaging.jms.PooledConnectionFactoryDefinition;
import org.jboss.as.messaging.jms.bridge.JMSBridgeDefinition;
import org.jboss.as.messaging.logging.MessagingLogger;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.staxmapper.XMLElementWriter;
import org.jboss.staxmapper.XMLExtendedStreamWriter;

public class MessagingXMLWriter implements XMLElementWriter<SubsystemMarshallingContext> {

    static final MessagingXMLWriter INSTANCE = new MessagingXMLWriter();

    private static final char[] NEW_LINE = new char[]{'\n'};

    private MessagingXMLWriter() {
    }

    @Override
    public void writeContent(XMLExtendedStreamWriter writer, SubsystemMarshallingContext context) throws XMLStreamException {
        context.startSubsystemElement(CURRENT.getUriString(), false);
        final ModelNode node = context.getModelNode();

        if (node.hasDefined(HORNETQ_SERVER)) {
            final ModelNode servers = node.get(HORNETQ_SERVER);
            boolean first = true;
            for (String name : servers.keys()) {
                writeHornetQServer(writer, name, servers.get(name));
                if (!first) {
                    writeNewLine(writer);
                } else {
                    first = false;
                }
            }
        }

        if (node.hasDefined(JMS_BRIDGE)) {
            final ModelNode jmsBridges = node.get(JMS_BRIDGE);
            boolean first = true;
            for (String name : jmsBridges.keys()) {
                writeJmsBridge(writer, name, jmsBridges.get(name));
                if (!first) {
                    writeNewLine(writer);
                } else {
                    first = false;
                }
            }
        }
        writer.writeEndElement();
    }

    private static void writeHornetQServer(final XMLExtendedStreamWriter writer, final String serverName, final ModelNode node) throws XMLStreamException {

        writer.writeStartElement(Element.HORNETQ_SERVER.getLocalName());

        if (!DEFAULT.equals(serverName)) {
            writer.writeAttribute(Attribute.NAME.getLocalName(), serverName);
        }

        for (AttributeDefinition simpleAttribute : CommonAttributes.SIMPLE_ROOT_RESOURCE_ATTRIBUTES) {
            simpleAttribute.marshallAsElement(node, writer);
        }

        final ModelNode paths = node.get(ModelDescriptionConstants.PATH);
        writeDirectory(writer, Element.PAGING_DIRECTORY, paths);
        writeDirectory(writer, Element.BINDINGS_DIRECTORY, paths);
        writeDirectory(writer, Element.JOURNAL_DIRECTORY, paths);
        writeDirectory(writer, Element.LARGE_MESSAGES_DIRECTORY, paths);

        // New line after the simpler elements
        writeNewLine(writer);

        writeConnectors(writer, node);
        writeAcceptors(writer, node);
        writeBroadcastGroups(writer, node.get(BROADCAST_GROUP));
        writeDiscoveryGroups(writer, node.get(DISCOVERY_GROUP));
        writeDiverts(writer, node.get(DIVERT));
        writeQueues(writer, node.get(CommonAttributes.QUEUE));
        writeBridges(writer, node.get(CommonAttributes.BRIDGE));
        writeClusterConnections(writer, node.get(CommonAttributes.CLUSTER_CONNECTION));
        writeGroupingHandler(writer, node.get(GROUPING_HANDLER));
        writeSecuritySettings(writer, node.get(CommonAttributes.SECURITY_SETTING));
        writeAddressSettings(writer, node.get(ADDRESS_SETTING));
        writeConnectorServices(writer, node.get(CommonAttributes.CONNECTOR_SERVICE));

        if (node.hasDefined(CONNECTION_FACTORY) || node.hasDefined(POOLED_CONNECTION_FACTORY)) {
            ModelNode cf = node.get(CONNECTION_FACTORY);
            ModelNode pcf = node.get(POOLED_CONNECTION_FACTORY);
            boolean hasCf = cf.isDefined() && cf.keys().size() > 0;
            boolean hasPcf = pcf.isDefined() && pcf.keys().size() > 0;
            if (hasCf || hasPcf) {
                writer.writeStartElement(JMS_CONNECTION_FACTORIES);
                writeConnectionFactories(writer, cf);
                writePooledConnectionFactories(writer, pcf);
                writer.writeEndElement();
                writeNewLine(writer);
            }
        }

        if (node.hasDefined(JMS_QUEUE) || node.hasDefined(JMS_TOPIC)) {
            ModelNode queue = node.get(JMS_QUEUE);
            ModelNode topic = node.get(JMS_TOPIC);
            boolean hasQueue = queue.isDefined() && queue.keys().size() > 0;
            boolean hasTopic = topic.isDefined() && topic.keys().size() > 0;
            if (hasQueue || hasTopic) {
                writer.writeStartElement(JMS_DESTINATIONS);
                writeJmsQueues(writer, node.get(JMS_QUEUE));
                writeTopics(writer, node.get(JMS_TOPIC));
                writer.writeEndElement();
            }
        }

        writer.writeEndElement();
    }

    private static void writeConnectors(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (node.hasDefined(CONNECTOR) || node.hasDefined(REMOTE_CONNECTOR) || node.hasDefined(IN_VM_CONNECTOR)) {
            writer.writeStartElement(Element.CONNECTORS.getLocalName());
            if(node.hasDefined(HTTP_CONNECTOR)) {
                for(final Property property : node.get(HTTP_CONNECTOR).asPropertyList()) {
                    writer.writeStartElement(Element.HTTP_CONNECTOR.getLocalName());
                    writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                    HTTPConnectorDefinition.SOCKET_BINDING.marshallAsAttribute(property.getValue(), writer);
                    writeTransportParam(writer, property.getValue().get(PARAM));
                    writer.writeEndElement();
                }
            }
            if(node.hasDefined(REMOTE_CONNECTOR)) {
                for(final Property property : node.get(REMOTE_CONNECTOR).asPropertyList()) {
                    writer.writeStartElement(Element.NETTY_CONNECTOR.getLocalName());
                    writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                    RemoteTransportDefinition.SOCKET_BINDING.marshallAsAttribute(property.getValue(), writer);
                    writeTransportParam(writer, property.getValue().get(PARAM));
                    writer.writeEndElement();
                }
            }
            if(node.hasDefined(IN_VM_CONNECTOR)) {
                for(final Property property : node.get(IN_VM_CONNECTOR).asPropertyList()) {
                    writer.writeStartElement(Element.IN_VM_CONNECTOR.getLocalName());
                    writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                    InVMTransportDefinition.SERVER_ID.marshallAsAttribute(property.getValue(), writer);
                    writeTransportParam(writer, property.getValue().get(PARAM));
                    writer.writeEndElement();
                }
            }
            if(node.hasDefined(CONNECTOR)) {
                for(final Property property : node.get(CONNECTOR).asPropertyList()) {
                    writer.writeStartElement(Element.CONNECTOR.getLocalName());
                    writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                    GenericTransportDefinition.SOCKET_BINDING.marshallAsAttribute(property.getValue(), writer);

                    writeTransportParam(writer, property.getValue().get(PARAM));

                    CommonAttributes.FACTORY_CLASS.marshallAsElement(property.getValue(), writer);

                    writer.writeEndElement();
                }
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeAcceptors(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (node.hasDefined(ACCEPTOR) || node.hasDefined(REMOTE_ACCEPTOR) || node.hasDefined(IN_VM_ACCEPTOR)) {
            writer.writeStartElement(Element.ACCEPTORS.getLocalName());
            if(node.hasDefined(HTTP_ACCEPTOR)) {
                for(final Property property : node.get(HTTP_ACCEPTOR).asPropertyList()) {
                    writer.writeStartElement(Element.HTTP_ACCEPTOR.getLocalName());
                    HTTPAcceptorDefinition.HTTP_LISTENER.marshallAsAttribute(property.getValue(), writer);
                    writeAcceptorContent(writer, property);
                    writer.writeEndElement();
                }
            }
            if(node.hasDefined(REMOTE_ACCEPTOR)) {
                for(final Property property : node.get(REMOTE_ACCEPTOR).asPropertyList()) {
                    writer.writeStartElement(Element.NETTY_ACCEPTOR.getLocalName());
                    writeAcceptorContent(writer, property);
                    writer.writeEndElement();
                }
            }
            if(node.hasDefined(IN_VM_ACCEPTOR)) {
                for(final Property property : node.get(IN_VM_ACCEPTOR).asPropertyList()) {
                    writer.writeStartElement(Element.IN_VM_ACCEPTOR.getLocalName());
                    writeAcceptorContent(writer, property);
                    writer.writeEndElement();
                }
            }
            if(node.hasDefined(ACCEPTOR)) {
                for(final Property property : node.get(ACCEPTOR).asPropertyList()) {
                    writer.writeStartElement(Element.ACCEPTOR.getLocalName());
                    writeAcceptorContent(writer, property);
                    writer.writeEndElement();
                }
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeAcceptorContent(final XMLExtendedStreamWriter writer, final Property property) throws XMLStreamException {
        writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
        final ModelNode value = property.getValue();

        RemoteTransportDefinition.SOCKET_BINDING.marshallAsAttribute(value, writer);
        InVMTransportDefinition.SERVER_ID.marshallAsAttribute(value, writer);

        writeTransportParam(writer, value.get(PARAM));

        CommonAttributes.FACTORY_CLASS.marshallAsElement(value, writer);
    }

    private static void writeTransportParam(final XMLExtendedStreamWriter writer, final ModelNode param) throws XMLStreamException {
        if (param.isDefined()) {
            for(final Property parameter : param.asPropertyList()) {
                writer.writeStartElement(Element.PARAM.getLocalName());
                writer.writeAttribute(Attribute.KEY.getLocalName(), parameter.getName());
                writer.writeAttribute(Attribute.VALUE.getLocalName(), parameter.getValue().get(TransportParamDefinition.VALUE.getName()).asString());
                writer.writeEndElement();
            }
        }
    }

    private static void writeBroadcastGroups(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.BROADCAST_GROUPS.getLocalName());
            for(final Property property : properties) {
                writer.writeStartElement(Element.BROADCAST_GROUP.getLocalName());
                writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                for (AttributeDefinition attribute : BroadcastGroupDefinition.ATTRIBUTES) {
                    attribute.marshallAsElement(property.getValue(), writer);
                }
                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeDiscoveryGroups(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.DISCOVERY_GROUPS.getLocalName());
            for(final Property property : properties) {
                writer.writeStartElement(Element.DISCOVERY_GROUP.getLocalName());
                writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                for (AttributeDefinition attribute : DiscoveryGroupDefinition.ATTRIBUTES) {
                    attribute.marshallAsElement(property.getValue(), writer);
                }
                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeDiverts(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.DIVERTS.getLocalName());
            for(final Property property : properties) {
                writer.writeStartElement(Element.DIVERT.getLocalName());
                writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                for (AttributeDefinition attribute : DivertDefinition.ATTRIBUTES) {
                    if (CommonAttributes.FILTER == attribute) {
                        writeFilter(writer, property.getValue());
                    } else {
                        attribute.marshallAsElement(property.getValue(), writer);
                    }
                }
                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeQueues(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        if (node.asInt() > 0) {
            writer.writeStartElement(Element.CORE_QUEUES.getLocalName());
            for (String queueName : node.keys()) {
                writer.writeStartElement(Element.QUEUE.getLocalName());
                writer.writeAttribute(Attribute.NAME.getLocalName(), queueName);
                final ModelNode queue = node.get(queueName);
                QueueDefinition.ADDRESS.marshallAsElement(queue, writer);
                writeFilter(writer, queue);
                DURABLE.marshallAsElement(queue, writer);

                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeBridges(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.BRIDGES.getLocalName());
            for(final Property property : node.asPropertyList()) {
                writer.writeStartElement(Element.BRIDGE.getLocalName());
                writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                for (AttributeDefinition attribute : BridgeDefinition.ATTRIBUTES) {
                    if (CommonAttributes.FILTER == attribute) {
                        writeFilter(writer, property.getValue());
                    } else {
                        attribute.marshallAsElement(property.getValue(), writer);
                    }
                }
                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeClusterConnections(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.CLUSTER_CONNECTIONS.getLocalName());
            for(final Property property : node.asPropertyList()) {
                writer.writeStartElement(Element.CLUSTER_CONNECTION.getLocalName());
                writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                final ModelNode cluster = property.getValue();
                for (AttributeDefinition attribute : ClusterConnectionDefinition.ATTRIBUTES) {
                    if (attribute == ClusterConnectionDefinition.ALLOW_DIRECT_CONNECTIONS_ONLY) {
                        // we nest it in static-connectors
                        continue;
                    }
                    if (attribute == CONNECTOR_REFS) {
                        if (attribute.isMarshallable(cluster)) {
                            writer.writeStartElement(Element.STATIC_CONNECTORS.getLocalName());
                            ALLOW_DIRECT_CONNECTIONS_ONLY.marshallAsAttribute(cluster, writer);
                            CONNECTOR_REFS.marshallAsElement(cluster, writer);
                            writer.writeEndElement();
                        } else if (ALLOW_DIRECT_CONNECTIONS_ONLY.isMarshallable(cluster)) {
                            writer.writeEmptyElement(Element.STATIC_CONNECTORS.getLocalName());
                            ALLOW_DIRECT_CONNECTIONS_ONLY.marshallAsAttribute(cluster, writer);
                        }
                    } else {
                        attribute.marshallAsElement(property.getValue(), writer);
                    }
                }
                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeGroupingHandler(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        boolean wroteHandler = false;
        for (Property handler : node.asPropertyList()) {
            if (wroteHandler) {
                throw MessagingLogger.ROOT_LOGGER.multipleChildrenFound(GROUPING_HANDLER);
            } else {
                wroteHandler = true;
            }
            writer.writeStartElement(Element.GROUPING_HANDLER.getLocalName());
            writer.writeAttribute(Attribute.NAME.getLocalName(), handler.getName());
            final ModelNode resourceModel = handler.getValue();
            for (AttributeDefinition attr : GroupingHandlerDefinition.ATTRIBUTES) {
                attr.marshallAsElement(resourceModel, writer);
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    // TODO use a custom attribute marshaller
    private static void writeFilter(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (node.hasDefined(CommonAttributes.FILTER.getName())) {
            writer.writeEmptyElement(CommonAttributes.FILTER.getXmlName());
            writer.writeAttribute(CommonAttributes.STRING, node.get(CommonAttributes.FILTER.getName()).asString());
        }
    }

    private static void writeDirectory(final XMLExtendedStreamWriter writer, final Element element, final ModelNode node) throws XMLStreamException {
        final String localName = element.getLocalName();
        if(node.hasDefined(localName)) {
            final ModelNode localNode = node.get(localName);
            if (RELATIVE_TO.isMarshallable(localNode) ||  PATHS.get(localName).isMarshallable(localNode)) {
                writer.writeEmptyElement(localName);
                PATHS.get(localName).marshallAsAttribute(node.get(localName), writer);
                RELATIVE_TO.marshallAsAttribute(node.get(localName), writer);
            }
        }
    }

    private static void writeSecuritySettings(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.SECURITY_SETTINGS.getLocalName());

            for (Property matchRoles : properties) {
                writer.writeStartElement(Element.SECURITY_SETTING.getLocalName());
                writer.writeAttribute(Attribute.MATCH.getLocalName(), matchRoles.getName());

                // TODO use a custom attribute marshaller
                if (matchRoles.getValue().hasDefined(ROLE)) {

                    ArrayList<String> send = new ArrayList<String>();
                    ArrayList<String> consume = new ArrayList<String>();
                    ArrayList<String> createDurableQueue = new ArrayList<String>();
                    ArrayList<String> deleteDurableQueue = new ArrayList<String>();
                    ArrayList<String> createNonDurableQueue = new ArrayList<String>();
                    ArrayList<String> deleteNonDurableQueue = new ArrayList<String>();
                    ArrayList<String> manageRoles = new ArrayList<String>();

                    for (Property rolePerms : matchRoles.getValue().get(ROLE).asPropertyList()) {
                        final String role = rolePerms.getName();
                        final ModelNode perms = rolePerms.getValue();
                        if (perms.get(SecurityRoleDefinition.SEND.getName()).asBoolean(false)) {
                            send.add(role);
                        }
                        if (perms.get(SecurityRoleDefinition.CONSUME.getName()).asBoolean(false)) {
                            consume.add(role);
                        }
                        if (perms.get(SecurityRoleDefinition.CREATE_DURABLE_QUEUE.getName()).asBoolean(false)) {
                            createDurableQueue.add(role);
                        }
                        if (perms.get(SecurityRoleDefinition.DELETE_DURABLE_QUEUE.getName()).asBoolean(false)) {
                            deleteDurableQueue.add(role);
                        }
                        if (perms.get(SecurityRoleDefinition.CREATE_NON_DURABLE_QUEUE.getName()).asBoolean(false)) {
                            createNonDurableQueue.add(role);
                        }
                        if (perms.get(SecurityRoleDefinition.DELETE_NON_DURABLE_QUEUE.getName()).asBoolean(false)) {
                            deleteNonDurableQueue.add(role);
                        }
                        if (perms.get(SecurityRoleDefinition.MANAGE.getName()).asBoolean(false)) {
                            manageRoles.add(role);
                        }
                    }

                    writePermission(writer, SecurityRoleDefinition.SEND.getXmlName(), send);
                    writePermission(writer, SecurityRoleDefinition.CONSUME.getXmlName(), consume);
                    writePermission(writer, SecurityRoleDefinition.CREATE_DURABLE_QUEUE.getXmlName(), createDurableQueue);
                    writePermission(writer, SecurityRoleDefinition.DELETE_DURABLE_QUEUE.getXmlName(), deleteDurableQueue);
                    writePermission(writer, SecurityRoleDefinition.CREATE_NON_DURABLE_QUEUE.getXmlName(), createNonDurableQueue);
                    writePermission(writer, SecurityRoleDefinition.DELETE_NON_DURABLE_QUEUE.getXmlName(), deleteNonDurableQueue);
                    writePermission(writer, SecurityRoleDefinition.MANAGE.getXmlName(), manageRoles);
                }

                writer.writeEndElement();
            }

            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writePermission(final XMLExtendedStreamWriter writer, final String type, final List<String> roles) throws XMLStreamException {
        if (roles.size() == 0) {
            return;
        }
        writer.writeStartElement(Element.PERMISSION_ELEMENT_NAME.getLocalName());
        StringBuilder sb = new StringBuilder();
        for (String role : roles) {
            if (sb.length() > 0) {
                sb.append(" ");
            }
            sb.append(role);
        }
        writer.writeAttribute(Attribute.TYPE_ATTR_NAME.getLocalName(), type);
        writer.writeAttribute(Attribute.ROLES_ATTR_NAME.getLocalName(), sb.toString());
        writer.writeEndElement();
    }

    private static void writeAddressSettings(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.ADDRESS_SETTINGS.getLocalName());
            for (Property matchSetting : properties) {
                writer.writeStartElement(Element.ADDRESS_SETTING.getLocalName());
                writer.writeAttribute(Attribute.MATCH.getLocalName(), matchSetting.getName());
                final ModelNode setting = matchSetting.getValue();

                for (AttributeDefinition attribute : AddressSettingDefinition.ATTRIBUTES) {
                    attribute.marshallAsElement(setting, writer);
                }

                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeConnectorServices(XMLExtendedStreamWriter writer, ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        List<Property> properties = node.asPropertyList();
        if (!properties.isEmpty()) {
            writer.writeStartElement(Element.CONNECTOR_SERVICES.getLocalName());
            for(final Property property : node.asPropertyList()) {
                writer.writeStartElement(Element.CONNECTOR_SERVICE.getLocalName());
                writer.writeAttribute(Attribute.NAME.getLocalName(), property.getName());
                final ModelNode service = property.getValue();
                for (AttributeDefinition attribute : ConnectorServiceDefinition.ATTRIBUTES) {
                    attribute.marshallAsElement(property.getValue(), writer);
                }
                // TODO use a custom attribute marshaller
                if (service.hasDefined(CommonAttributes.PARAM)) {
                    for (Property param : service.get(CommonAttributes.PARAM).asPropertyList()) {
                        writer.writeEmptyElement(Element.PARAM.getLocalName());
                        writer.writeAttribute(Attribute.KEY.getLocalName(), param.getName());
                        writer.writeAttribute(Attribute.VALUE.getLocalName(), param.getValue().get(ConnectorServiceParamDefinition.VALUE.getName()).asString());
                    }
                }
                writer.writeEndElement();
            }
            writer.writeEndElement();
            writeNewLine(writer);
        }
    }

    private static void writeConnectionFactories(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        if (node.asInt() > 0) {
            for (String name : node.keys()) {
                final ModelNode factory = node.get(name);
                if (factory.isDefined()) {
                    writer.writeStartElement(Element.CONNECTION_FACTORY.getLocalName());
                    writer.writeAttribute(Attribute.NAME.getLocalName(), name);

                    for (AttributeDefinition attribute : ConnectionFactoryDefinition.ATTRIBUTES) {
                        attribute.marshallAsElement(factory, writer);
                    }

                    writer.writeEndElement();
                }
            }
        }
    }

    private static void writePooledConnectionFactories(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        if (node.asInt() > 0) {
            for (String name : node.keys()) {
                final ModelNode factory = node.get(name);
                if (factory.isDefined()) {
                    writer.writeStartElement(Element.POOLED_CONNECTION_FACTORY.getLocalName());

                    writer.writeAttribute(Attribute.NAME.getLocalName(), name);

                    // write inbound config attributes first...
                    if(hasDefinedInboundConfigAttributes(factory)) {
                        writer.writeStartElement(Element.INBOUND_CONFIG.getLocalName());
                        for (ConnectionFactoryAttribute attribute : PooledConnectionFactoryDefinition.ATTRIBUTES) {
                            if (attribute.isInboundConfig()) {
                                attribute.getDefinition().marshallAsElement(factory, writer);
                            }
                        }
                        writer.writeEndElement();
                    }

                    // ... then the attributes that are not part of the inbound config
                    for (ConnectionFactoryAttribute attribute : PooledConnectionFactoryDefinition.ATTRIBUTES) {
                        if (!attribute.isInboundConfig()) {
                            attribute.getDefinition().marshallAsElement(factory, writer);
                        }
                    }

                    writer.writeEndElement();
                }
            }
        }
    }

    private static boolean hasDefinedInboundConfigAttributes(ModelNode pcf) {
        for (ConnectionFactoryAttribute attribute : PooledConnectionFactoryDefinition.ATTRIBUTES) {
            if (attribute.isInboundConfig() && pcf.hasDefined(attribute.getDefinition().getName())) {
                return true;
            }
        }
        return false;
    }

    private static void writeJmsQueues(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        if (node.asInt() > 0) {
            for (String name : node.keys()) {
                final ModelNode queue = node.get(name);
                if (queue.isDefined()) {
                    writer.writeStartElement(Element.JMS_QUEUE.getLocalName());
                    writer.writeAttribute(Attribute.NAME.getLocalName(), name);

                    for (AttributeDefinition attribute : JMSQueueDefinition.ATTRIBUTES) {
                        attribute.marshallAsElement(queue, writer);
                    }

                    writer.writeEndElement();
                }
            }
        }
    }

    private static void writeTopics(final XMLExtendedStreamWriter writer, final ModelNode node) throws XMLStreamException {
        if (!node.isDefined()) {
            return;
        }
        if (node.asInt() > 0) {
            for (String name : node.keys()) {
                final ModelNode topic = node.get(name);
                if (topic.isDefined()) {
                    writer.writeStartElement(Element.JMS_TOPIC.getLocalName());
                    writer.writeAttribute(Attribute.NAME.getLocalName(), name);

                    for (AttributeDefinition attribute : JMSTopicDefinition.ATTRIBUTES) {
                        attribute.marshallAsElement(topic, writer);
                    }

                    writer.writeEndElement();
                }
            }
        }
    }

    private void writeJmsBridge(XMLExtendedStreamWriter writer, String bridgeName, ModelNode value) throws XMLStreamException {
        writer.writeStartElement(Element.JMS_BRIDGE.getLocalName());

        if (!DEFAULT.equals(bridgeName)) {
            writer.writeAttribute(Attribute.NAME.getLocalName(), bridgeName);
        }

        JMSBridgeDefinition.MODULE.marshallAsAttribute(value, writer);

        writer.writeStartElement(SOURCE.getLocalName());
        for (AttributeDefinition attr : JMSBridgeDefinition.JMS_SOURCE_ATTRIBUTES) {
            attr.marshallAsElement(value, writer);
        }
        writer.writeEndElement();

        writer.writeStartElement(TARGET.getLocalName());
        for (AttributeDefinition attr : JMSBridgeDefinition.JMS_TARGET_ATTRIBUTES) {
            attr.marshallAsElement(value, writer);
        }
        writer.writeEndElement();

        for (AttributeDefinition attr : JMSBridgeDefinition.JMS_BRIDGE_ATTRIBUTES) {
            if (attr == JMSBridgeDefinition.MODULE) {
                // handled as a XML attribute
                continue;
            }
            attr.marshallAsElement(value, writer);
        }

        writer.writeEndElement();
    }

    private static void writeNewLine(XMLExtendedStreamWriter writer) throws XMLStreamException {
        writer.writeCharacters(NEW_LINE, 0, 1);
    }

}


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

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import org.jboss.as.ee.component.DeploymentDescriptorEnvironment;
import org.jboss.as.ee.component.EEModuleDescription;
import org.jboss.as.ee.metadata.MetadataCompleteMarker;
import org.jboss.as.ee.structure.DeploymentType;
import org.jboss.as.ee.structure.DeploymentTypeMarker;
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.as.web.common.WarMetaData;
import org.jboss.metadata.ear.spec.EarMetaData;
import org.jboss.metadata.javaee.spec.EmptyMetaData;
import org.jboss.metadata.javaee.spec.SecurityRolesMetaData;
import org.jboss.metadata.merge.javaee.spec.SecurityRolesMetaDataMerger;
import org.jboss.metadata.merge.web.jboss.JBossWebMetaDataMerger;
import org.jboss.metadata.merge.web.spec.WebCommonMetaDataMerger;
import org.jboss.metadata.web.jboss.JBossWebMetaData;
import org.jboss.metadata.web.spec.AbsoluteOrderingMetaData;
import org.jboss.metadata.web.spec.OrderingElementMetaData;
import org.jboss.metadata.web.spec.WebCommonMetaData;
import org.jboss.metadata.web.spec.WebFragmentMetaData;
import org.jboss.metadata.web.spec.WebMetaData;
import org.jboss.vfs.VirtualFile;
import org.wildfly.extension.undertow.logging.UndertowLogger;

/**
 * Merge all metadata into a main JBossWebMetaData.
 *
 * @author Remy Maucherat
 * @author Thomas.Diesler@jboss.com
 */
public class WarMetaDataProcessor implements DeploymentUnitProcessor {

    static final String VERSION = "4.0";

    @Override
    public void deploy(DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        if (!DeploymentTypeMarker.isType(DeploymentType.WAR, deploymentUnit)) {
            return; // Skip non web deployments
        }
        WarMetaData warMetaData = deploymentUnit.getAttachment(WarMetaData.ATTACHMENT_KEY);
        assert warMetaData != null;

        boolean isComplete = false;
        WebMetaData specMetaData = warMetaData.getWebMetaData();
        if (specMetaData != null) {
            isComplete = specMetaData.isMetadataComplete();
        }

        // Find all fragments that have been processed by deployers, and place
        // them in a map keyed by location
        LinkedList<String> order = new LinkedList<String>();
        List<WebOrdering> orderings = new ArrayList<WebOrdering>();
        HashSet<String> jarsSet = new HashSet<String>();
        Set<VirtualFile> overlays = new HashSet<VirtualFile>();
        Map<String, VirtualFile> scis = new HashMap<String, VirtualFile>();
        boolean fragmentFound = false;
        Map<String, WebFragmentMetaData> webFragments = warMetaData.getWebFragmentsMetaData();
        List<ResourceRoot> resourceRoots = deploymentUnit.getAttachmentList(Attachments.RESOURCE_ROOTS);
        for (ResourceRoot resourceRoot : resourceRoots) {
            if (resourceRoot.getRoot().getName().toLowerCase(Locale.ENGLISH).endsWith(".jar")) {
                jarsSet.add(resourceRoot.getRootName());
                // Find overlays
                VirtualFile overlay = resourceRoot.getRoot().getChild("META-INF/resources");
                if (overlay.exists()) {
                    overlays.add(overlay);
                }
            }

            //we load SCI's directly from the war not just from jars
            //not required by spec but other containers do it
            //see WFLY-9081
            // Find ServletContainerInitializer services
            VirtualFile sci = resourceRoot.getRoot().getChild("META-INF/services/javax.servlet.ServletContainerInitializer");
            if (sci.exists()) {
                scis.put(resourceRoot.getRootName(), sci);
            }
        }

        if (!isComplete) {
            HashSet<String> jarsWithoutFragmentsSet = new HashSet<String>();
            jarsWithoutFragmentsSet.addAll(jarsSet);
            for (String jarName : webFragments.keySet()) {
                fragmentFound = true;
                WebFragmentMetaData fragmentMetaData = webFragments.get(jarName);
                webFragments.put(jarName, fragmentMetaData);
                WebOrdering webOrdering = new WebOrdering();
                webOrdering.setName(fragmentMetaData.getName());
                webOrdering.setJar(jarName);
                jarsWithoutFragmentsSet.remove(jarName);
                if (fragmentMetaData.getOrdering() != null) {
                    if (fragmentMetaData.getOrdering().getAfter() != null) {
                        for (OrderingElementMetaData orderingElementMetaData : fragmentMetaData.getOrdering().getAfter()
                                .getOrdering()) {
                            if (orderingElementMetaData.isOthers()) {
                                webOrdering.setAfterOthers(true);
                            } else {
                                webOrdering.addAfter(orderingElementMetaData.getName());
                            }
                        }
                    }
                    if (fragmentMetaData.getOrdering().getBefore() != null) {
                        for (OrderingElementMetaData orderingElementMetaData : fragmentMetaData.getOrdering().getBefore()
                                .getOrdering()) {
                            if (orderingElementMetaData.isOthers()) {
                                webOrdering.setBeforeOthers(true);
                            } else {
                                webOrdering.addBefore(orderingElementMetaData.getName());
                            }
                        }
                    }
                }
                orderings.add(webOrdering);
            }
            // If there is no fragment, still consider it for ordering as a
            // fragment specifying no name and no order
            for (String jarName : jarsWithoutFragmentsSet) {
                WebOrdering ordering = new WebOrdering();
                ordering.setJar(jarName);
                orderings.add(ordering);
            }

        }

        if (!fragmentFound) {
            // Drop the order as there is no fragment in the webapp
            orderings.clear();
        }

        // Generate web fragments parsing order
        AbsoluteOrderingMetaData absoluteOrderingMetaData = null;
        if (!isComplete && specMetaData != null) {
            absoluteOrderingMetaData = specMetaData.getAbsoluteOrdering();
        }
        if (absoluteOrderingMetaData != null) {
            // Absolute ordering from web.xml, any relative fragment ordering is ignored
            int otherPos = -1;
            int i = 0;
            for (OrderingElementMetaData orderingElementMetaData : absoluteOrderingMetaData.getOrdering()) {
                if (orderingElementMetaData.isOthers()) {
                    if (otherPos >= 0) {
                        throw new DeploymentUnitProcessingException(UndertowLogger.ROOT_LOGGER.invalidMultipleOthers());
                    }
                    otherPos = i;
                } else {
                    boolean found = false;
                    for (WebOrdering ordering : orderings) {
                        if (orderingElementMetaData.getName().equals(ordering.getName())) {
                            order.add(ordering.getJar());
                            jarsSet.remove(ordering.getJar());
                            found = true;
                            break;
                        }
                    }
                    if (!found) {
                        UndertowLogger.ROOT_LOGGER.invalidAbsoluteOrdering(orderingElementMetaData.getName());
                    } else {
                        i++;
                    }
                }
            }
            if (otherPos >= 0) {
                order.addAll(otherPos, jarsSet);
                jarsSet.clear();
            }
        } else if (orderings.size() > 0) {
            // Resolve relative ordering
            try {
                resolveOrder(orderings, order);
            } catch (IllegalStateException e) {
                throw new DeploymentUnitProcessingException(UndertowLogger.ROOT_LOGGER.invalidRelativeOrdering(), e);
            }
            jarsSet.clear();
        } else {
            // No order specified
            order.addAll(jarsSet);
            jarsSet.clear();
            warMetaData.setNoOrder(true);
        }

        if (UndertowLogger.ROOT_LOGGER.isDebugEnabled()) {
            StringBuilder builder = new StringBuilder();
            builder.append("Resolved order: [ ");
            for (String jar : order) {
                builder.append(jar).append(' ');
            }
            builder.append(']');
            UndertowLogger.ROOT_LOGGER.debug(builder.toString());
        }

        warMetaData.setOrder(order);
        warMetaData.setOverlays(overlays);
        warMetaData.setScis(scis);

        Map<String, WebMetaData> annotationsMetaData = warMetaData.getAnnotationsMetaData();

        // The fragments and corresponding annotations will need to be merged in order
        // For each JAR in the order:
        // - Merge the annotation metadata into the fragment meta data (unless the fragment exists and is meta data complete)
        // - Merge the fragment metadata into merged fragment meta data
        WebCommonMetaData mergedFragmentMetaData = new WebCommonMetaData();
        if (specMetaData == null) {
            // If there is no web.xml, it has to be considered to be the latest version
            specMetaData = new WebMetaData();
            specMetaData.setVersion(VERSION);
        }
        // Augment with meta data from annotations in /WEB-INF/classes
        WebMetaData annotatedMetaData = annotationsMetaData.get("classes");
        if (annotatedMetaData != null) {
            if (isComplete) {
                // Discard @WebFilter, @WebListener and @WebServlet
                annotatedMetaData.setFilters(null);
                annotatedMetaData.setFilterMappings(null);
                annotatedMetaData.setListeners(null);
                annotatedMetaData.setServlets(null);
                annotatedMetaData.setServletMappings(null);
            }
            WebCommonMetaDataMerger.augment(specMetaData, annotatedMetaData, null, true);
        }
        // Augment with meta data from fragments and annotations from the corresponding JAR
        for (String jar : order) {
            WebFragmentMetaData webFragmentMetaData = webFragments.get(jar);
            if (webFragmentMetaData == null || isComplete) {
                webFragmentMetaData = new WebFragmentMetaData();
                // Add non overriding default distributable flag
                webFragmentMetaData.setDistributable(new EmptyMetaData());
            }
            WebMetaData jarAnnotatedMetaData = annotationsMetaData.get(jar);
            if ((isComplete || webFragmentMetaData.isMetadataComplete()) && jarAnnotatedMetaData != null) {
                // Discard @WebFilter, @WebListener and @WebServlet
                jarAnnotatedMetaData.setFilters(null);
                jarAnnotatedMetaData.setFilterMappings(null);
                jarAnnotatedMetaData.setListeners(null);
                jarAnnotatedMetaData.setServlets(null);
                jarAnnotatedMetaData.setServletMappings(null);
            }
            if (jarAnnotatedMetaData != null) {
                // Merge annotations corresponding to the JAR
                WebCommonMetaDataMerger.augment(webFragmentMetaData, jarAnnotatedMetaData, null, true);
            }
            // Merge fragment meta data according to the conflict rules
            try {
                WebCommonMetaDataMerger.augment(mergedFragmentMetaData, webFragmentMetaData, specMetaData, false);
            } catch (Exception e) {
                throw new DeploymentUnitProcessingException(UndertowLogger.ROOT_LOGGER.invalidWebFragment(jar), e);
            }
        }
        // Augment with meta data from annotations from JARs excluded from the order
        for (String jar : jarsSet) {
            WebFragmentMetaData webFragmentMetaData = new WebFragmentMetaData();
            // Add non overriding default distributable flag
            webFragmentMetaData.setDistributable(new EmptyMetaData());
            WebMetaData jarAnnotatedMetaData = annotationsMetaData.get(jar);
            if (jarAnnotatedMetaData != null) {
                // Discard @WebFilter, @WebListener and @WebServlet
                jarAnnotatedMetaData.setFilters(null);
                jarAnnotatedMetaData.setFilterMappings(null);
                jarAnnotatedMetaData.setListeners(null);
                jarAnnotatedMetaData.setServlets(null);
                jarAnnotatedMetaData.setServletMappings(null);
            }
            if (jarAnnotatedMetaData != null) {
                // Merge annotations corresponding to the JAR
                WebCommonMetaDataMerger.augment(webFragmentMetaData, jarAnnotatedMetaData, null, true);
            }
            // Merge fragment meta data according to the conflict rules
            try {
                WebCommonMetaDataMerger.augment(mergedFragmentMetaData, webFragmentMetaData, specMetaData, false);
            } catch (Exception e) {
                throw new DeploymentUnitProcessingException(UndertowLogger.ROOT_LOGGER.invalidWebFragment(jar), e);
            }
        }

        WebCommonMetaDataMerger.augment(specMetaData, mergedFragmentMetaData, null, true);

        List<WebMetaData> additional = warMetaData.getAdditionalModuleAnnotationsMetadata();
        if (additional != null && !isComplete) {
            //augument with annotations from additional modules
            for (WebMetaData annotations : additional) {
                // Merge annotations corresponding to the JAR
                WebCommonMetaDataMerger.augment(specMetaData, annotations, null, true);
            }
        }

        // Override with meta data (JBossWebMetaData) Create a merged view
        JBossWebMetaData mergedMetaData = new JBossWebMetaData();
        JBossWebMetaData metaData = warMetaData.getJBossWebMetaData();
        JBossWebMetaDataMerger.merge(mergedMetaData, metaData, specMetaData);

        // FIXME: Incorporate any ear level overrides
        warMetaData.setMergedJBossWebMetaData(mergedMetaData);

        if (mergedMetaData.isMetadataComplete()) {
            MetadataCompleteMarker.setMetadataComplete(deploymentUnit, true);
        }

        //now attach any JNDI binding related information to the deployment
        if (mergedMetaData.getJndiEnvironmentRefsGroup() != null) {
            final DeploymentDescriptorEnvironment bindings = new DeploymentDescriptorEnvironment("java:module/env/", mergedMetaData.getJndiEnvironmentRefsGroup());
            deploymentUnit.putAttachment(org.jboss.as.ee.component.Attachments.MODULE_DEPLOYMENT_DESCRIPTOR_ENVIRONMENT, bindings);
        }

        //override module name if applicable
        if (mergedMetaData.getModuleName() != null && !mergedMetaData.getModuleName().isEmpty()) {
            final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION);
            description.setModuleName(mergedMetaData.getModuleName());
        }

        //WFLY-3102 EJB in WAR should inherit WAR's security domain
        if(mergedMetaData.getSecurityDomain() != null) {
            final EEModuleDescription description = deploymentUnit.getAttachment(org.jboss.as.ee.component.Attachments.EE_MODULE_DESCRIPTION);
            description.setDefaultSecurityDomain(mergedMetaData.getSecurityDomain());
        }

        //merge security roles from the ear
        DeploymentUnit parent = deploymentUnit.getParent();
        if (parent != null) {
            final EarMetaData earMetaData = parent.getAttachment(org.jboss.as.ee.structure.Attachments.EAR_METADATA);
            if (earMetaData != null) {
                SecurityRolesMetaData earSecurityRolesMetaData = earMetaData.getSecurityRoles();
                if(earSecurityRolesMetaData != null) {
                    if(mergedMetaData.getSecurityRoles() == null) {
                        mergedMetaData.setSecurityRoles(new SecurityRolesMetaData());
                    }
                    SecurityRolesMetaDataMerger.merge(mergedMetaData.getSecurityRoles(), mergedMetaData.getSecurityRoles(), earSecurityRolesMetaData);
                }
            }
        }
    }

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

    /**
     * Utility class to associate the logical name with the JAR name, needed
     * during the order resolving.
     */
    protected static class WebOrdering implements Serializable {

        private static final long serialVersionUID = 5603203103871892211L;

        protected String jar = null;
        protected String name = null;
        protected List<String> after = new ArrayList<String>();
        protected List<String> before = new ArrayList<String>();
        protected boolean afterOthers = false;
        protected boolean beforeOthers = false;

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public List<String> getAfter() {
            return after;
        }

        public void addAfter(String name) {
            after.add(name);
        }

        public List<String> getBefore() {
            return before;
        }

        public void addBefore(String name) {
            before.add(name);
        }

        public String getJar() {
            return jar;
        }

        public void setJar(String jar) {
            this.jar = jar;
        }

        public boolean isAfterOthers() {
            return afterOthers;
        }

        public void setAfterOthers(boolean afterOthers) {
            this.afterOthers = afterOthers;
        }

        public boolean isBeforeOthers() {
            return beforeOthers;
        }

        public void setBeforeOthers(boolean beforeOthers) {
            this.beforeOthers = beforeOthers;
        }

    }

    protected static class Ordering {
        protected WebOrdering ordering;
        protected Set<Ordering> after = new HashSet<Ordering>();
        protected Set<Ordering> before = new HashSet<Ordering>();
        protected boolean afterOthers = false;
        protected boolean beforeOthers = false;

        public boolean addAfter(Ordering ordering) {
            return after.add(ordering);
        }

        public boolean addBefore(Ordering ordering) {
            return before.add(ordering);
        }

        public void validate() {
            isBefore(new Ordering());
            isAfter(new Ordering());
        }

        /**
         * Check (recursively) if a fragment is before the specified fragment.
         *
         * @param ordering
         * @return
         */
        public boolean isBefore(Ordering ordering) {
            return isBeforeInternal(ordering, new HashSet<Ordering>());
        }

        protected boolean isBeforeInternal(Ordering ordering, Set<Ordering> checked) {
            checked.add(this);
            if (before.contains(ordering)) {
                return true;
            }
            Iterator<Ordering> beforeIterator = before.iterator();
            while (beforeIterator.hasNext()) {
                Ordering check = beforeIterator.next();
                if (checked.contains(check)) {
                    throw new IllegalStateException(UndertowLogger.ROOT_LOGGER.invalidRelativeOrdering(this.ordering.getJar()));
                }
                if (check.isBeforeInternal(ordering, checked)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Check (recursively) if a fragment is after the specified fragment.
         *
         * @param ordering
         * @return
         */
        public boolean isAfter(Ordering ordering) {
            return isAfterInternal(ordering, new HashSet<Ordering>());
        }

        protected boolean isAfterInternal(Ordering ordering, Set<Ordering> checked) {
            checked.add(this);
            if (after.contains(ordering)) {
                return true;
            }
            Iterator<Ordering> afterIterator = after.iterator();
            while (afterIterator.hasNext()) {
                Ordering check = afterIterator.next();
                if (checked.contains(check)) {
                    throw new IllegalStateException(UndertowLogger.ROOT_LOGGER.invalidRelativeOrdering(this.ordering.getJar()));
                }
                if (check.isAfterInternal(ordering, checked)) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Check is a fragment marked as before others is after a fragment that
         * is not.
         *
         * @return true if a fragment marked as before others is after a
         *         fragment that is not
         */
        public boolean isLastBeforeOthers() {
            if (!beforeOthers) {
                throw new IllegalStateException();
            }
            Iterator<Ordering> beforeIterator = before.iterator();
            while (beforeIterator.hasNext()) {
                Ordering check = beforeIterator.next();
                if (!check.beforeOthers) {
                    return true;
                } else if (check.isLastBeforeOthers()) {
                    return true;
                }
            }
            return false;
        }

        /**
         * Check is a fragment marked as after others is before a fragment that
         * is not.
         *
         * @return true if a fragment marked as after others is before a
         *         fragment that is not
         */
        public boolean isFirstAfterOthers() {
            if (!afterOthers) {
                throw new IllegalStateException();
            }
            Iterator<Ordering> afterIterator = after.iterator();
            while (afterIterator.hasNext()) {
                Ordering check = afterIterator.next();
                if (!check.afterOthers) {
                    return true;
                } else if (check.isFirstAfterOthers()) {
                    return true;
                }
            }
            return false;
        }

    }

    /**
     * Generate the Jar processing order.
     *
     * @param webOrderings The list of orderings, as parsed from the fragments
     * @param order        The generated order list
     */
    protected static void resolveOrder(List<WebOrdering> webOrderings, List<String> order) {
        List<Ordering> work = new ArrayList<Ordering>();

        // Populate the work Ordering list
        Iterator<WebOrdering> webOrderingsIterator = webOrderings.iterator();
        while (webOrderingsIterator.hasNext()) {
            WebOrdering webOrdering = webOrderingsIterator.next();
            Ordering ordering = new Ordering();
            ordering.ordering = webOrdering;
            ordering.afterOthers = webOrdering.isAfterOthers();
            ordering.beforeOthers = webOrdering.isBeforeOthers();
            if (ordering.afterOthers && ordering.beforeOthers) {
                // Cannot be both after and before others
                throw new IllegalStateException(UndertowLogger.ROOT_LOGGER.invalidRelativeOrderingBeforeAndAfter(webOrdering.getJar()));
            }
            work.add(ordering);
        }

        // Create double linked relationships between the orderings,
        // and resolve names
        Iterator<Ordering> workIterator = work.iterator();
        while (workIterator.hasNext()) {
            Ordering ordering = workIterator.next();
            WebOrdering webOrdering = ordering.ordering;
            Iterator<String> after = webOrdering.getAfter().iterator();
            while (after.hasNext()) {
                String name = after.next();
                Iterator<Ordering> workIterator2 = work.iterator();
                boolean found = false;
                while (workIterator2.hasNext()) {
                    Ordering ordering2 = workIterator2.next();
                    if (name.equals(ordering2.ordering.getName())) {
                        if (found) {
                            // Duplicate name
                            throw new IllegalStateException(UndertowLogger.ROOT_LOGGER.invalidRelativeOrderingDuplicateName(webOrdering.getJar()));
                        }
                        ordering.addAfter(ordering2);
                        ordering2.addBefore(ordering);
                        found = true;
                    }
                }
                if (!found) {
                    // Unknown name
                    UndertowLogger.ROOT_LOGGER.invalidRelativeOrderingUnknownName(webOrdering.getJar());
                }
            }
            Iterator<String> before = webOrdering.getBefore().iterator();
            while (before.hasNext()) {
                String name = before.next();
                Iterator<Ordering> workIterator2 = work.iterator();
                boolean found = false;
                while (workIterator2.hasNext()) {
                    Ordering ordering2 = workIterator2.next();
                    if (name.equals(ordering2.ordering.getName())) {
                        if (found) {
                            // Duplicate name
                            throw new IllegalStateException(UndertowLogger.ROOT_LOGGER.invalidRelativeOrderingDuplicateName(webOrdering.getJar()));
                        }
                        ordering.addBefore(ordering2);
                        ordering2.addAfter(ordering);
                        found = true;
                    }
                }
                if (!found) {
                    // Unknown name
                    UndertowLogger.ROOT_LOGGER.invalidRelativeOrderingUnknownName(webOrdering.getJar());
                }
            }
        }

        // Validate ordering
        workIterator = work.iterator();
        while (workIterator.hasNext()) {
            workIterator.next().validate();
        }

        // Create three ordered lists that will then be merged
        List<Ordering> tempOrder = new ArrayList<Ordering>();

        // Create the ordered list of fragments which are before others
        workIterator = work.iterator();
        while (workIterator.hasNext()) {
            Ordering ordering = workIterator.next();
            if (ordering.beforeOthers) {
                // Insert at the first possible position
                int insertAfter = -1;
                boolean last = ordering.isLastBeforeOthers();
                int lastBeforeOthers = -1;
                for (int i = 0; i < tempOrder.size(); i++) {
                    if (ordering.isAfter(tempOrder.get(i))) {
                        insertAfter = i;
                    }
                    if (tempOrder.get(i).beforeOthers) {
                        lastBeforeOthers = i;
                    }
                }
                int pos = insertAfter;
                if (last && lastBeforeOthers > insertAfter) {
                    pos = lastBeforeOthers;
                }
                tempOrder.add(pos + 1, ordering);
            } else if (ordering.afterOthers) {
                // Insert at the last possible element
                int insertBefore = tempOrder.size();
                boolean first = ordering.isFirstAfterOthers();
                int firstAfterOthers = tempOrder.size();
                for (int i = tempOrder.size() - 1; i >= 0; i--) {
                    if (ordering.isBefore(tempOrder.get(i))) {
                        insertBefore = i;
                    }
                    if (tempOrder.get(i).afterOthers) {
                        firstAfterOthers = i;
                    }
                }
                int pos = insertBefore;
                if (first && firstAfterOthers < insertBefore) {
                    pos = firstAfterOthers;
                }
                tempOrder.add(pos, ordering);
            } else {
                // Insert according to other already inserted elements
                int insertAfter = -1;
                int insertBefore = tempOrder.size();
                for (int i = 0; i < tempOrder.size(); i++) {
                    if (ordering.isAfter(tempOrder.get(i)) || tempOrder.get(i).beforeOthers) {
                        insertAfter = i;
                    }
                    if (ordering.isBefore(tempOrder.get(i)) || tempOrder.get(i).afterOthers) {
                        insertBefore = i;
                    }
                }
                if (insertAfter > insertBefore) {
                    // Conflicting order (probably caught earlier)
                    throw new IllegalStateException(UndertowLogger.ROOT_LOGGER.invalidRelativeOrderingConflict(ordering.ordering.getJar()));
                }
                // Insert somewhere in the range
                tempOrder.add(insertAfter + 1, ordering);
            }
        }

        // Create the final ordered list
        Iterator<Ordering> tempOrderIterator = tempOrder.iterator();
        while (tempOrderIterator.hasNext()) {
            Ordering ordering = tempOrderIterator.next();
            order.add(ordering.ordering.getJar());
        }

    }

}