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

import org.jboss.as.controller.services.path.PathManager;
import org.jboss.as.ejb3.component.stateful.CurrentSynchronizationCallback;
import org.jboss.as.ejb3.timerservice.TimerImpl;
import org.jboss.as.ejb3.timerservice.TimerServiceImpl;
import org.jboss.as.ejb3.timerservice.TimerState;
import org.jboss.as.ejb3.timerservice.persistence.TimerPersistence;
import org.jboss.marshalling.MarshallerFactory;
import org.jboss.marshalling.MarshallingConfiguration;
import org.jboss.marshalling.ModularClassResolver;
import org.jboss.marshalling.river.RiverMarshallerFactory;
import org.jboss.modules.ModuleLoader;
import org.jboss.msc.service.Service;
import org.jboss.msc.service.StartContext;
import org.jboss.msc.service.StopContext;
import org.jboss.msc.value.InjectedValue;
import org.jboss.staxmapper.XMLExtendedStreamWriter;
import org.jboss.staxmapper.XMLMapper;
import org.wildfly.security.manager.WildFlySecurityManager;
import org.wildfly.transaction.client.ContextTransactionManager;

import javax.transaction.Status;
import javax.transaction.Synchronization;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import javax.transaction.TransactionSynchronizationRegistry;
import javax.xml.namespace.QName;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLStreamWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FilePermission;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import static java.security.AccessController.doPrivileged;
import static org.jboss.as.ejb3.logging.EjbLogger.EJB3_TIMER_LOGGER;

/**
 * File based persistent timer store.
 * <p/>
 * TODO: this is fairly hackey at the moment, it should be registered as an XA resource to support proper XA semantics
 *
 * @author Stuart Douglas
 */
public class FileTimerPersistence implements TimerPersistence, Service<FileTimerPersistence> {

    private static final FilePermission FILE_PERMISSION = new FilePermission("<<ALL FILES>>", "read,write,delete");
    private static final XMLInputFactory INPUT_FACTORY = XMLInputFactory.newInstance();

    private final boolean createIfNotExists;
    private MarshallerFactory factory;
    private MarshallingConfiguration configuration;
    private final InjectedValue<TransactionSynchronizationRegistry> transactionSynchronizationRegistry = new InjectedValue<TransactionSynchronizationRegistry>();
    private final InjectedValue<ModuleLoader> moduleLoader = new InjectedValue<ModuleLoader>();
    private final InjectedValue<PathManager> pathManager = new InjectedValue<PathManager>();
    private final String path;
    private final String pathRelativeTo;
    private File baseDir;
    private PathManager.Callback.Handle callbackHandle;

    private final ConcurrentMap<String, Lock> locks = new ConcurrentHashMap<String, Lock>();
    private final ConcurrentMap<String, String> directories = new ConcurrentHashMap<String, String>();

    public FileTimerPersistence(final boolean createIfNotExists, final String path, final String pathRelativeTo) {
        final SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            sm.checkPermission(FILE_PERMISSION);
        }
        this.createIfNotExists = createIfNotExists;
        this.path = path;
        this.pathRelativeTo = pathRelativeTo;
    }

    @Override
    public void start(final StartContext context) {
        if (WildFlySecurityManager.isChecking()) {
            WildFlySecurityManager.doUnchecked(new PrivilegedAction<Void>() {
                public Void run() {
                    doStart();
                    return null;
                }
            });
        } else {
            doStart();
        }
    }

    private void doStart() {
        final RiverMarshallerFactory factory = new RiverMarshallerFactory();
        final MarshallingConfiguration configuration = new MarshallingConfiguration();
        configuration.setClassResolver(ModularClassResolver.getInstance(moduleLoader.getValue()));
        configuration.setVersion(3);

        this.configuration = configuration;
        this.factory = factory;
        if (pathRelativeTo != null) {
            callbackHandle = pathManager.getValue().registerCallback(pathRelativeTo, PathManager.ReloadServerCallback.create(), PathManager.Event.UPDATED, PathManager.Event.REMOVED);
        }
        baseDir = new File(pathManager.getValue().resolveRelativePathEntry(path, pathRelativeTo));
        if (!baseDir.exists()) {
            if (createIfNotExists) {
                if (!baseDir.mkdirs()) {
                    throw EJB3_TIMER_LOGGER.failToCreateTimerFileStoreDir(baseDir);
                }
            } else {
                throw EJB3_TIMER_LOGGER.timerFileStoreDirNotExist(baseDir);
            }
        }
        if (!baseDir.isDirectory()) {
            throw EJB3_TIMER_LOGGER.invalidTimerFileStoreDir(baseDir);
        }
    }

    @Override
    public void stop(final StopContext context) {
        locks.clear();
        directories.clear();
        if (callbackHandle != null) {
            callbackHandle.remove();
        }
        factory = null;
        configuration = null;
    }

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

    @Override
    public void addTimer(final TimerImpl TimerImpl) {
        if(WildFlySecurityManager.isChecking()) {
            WildFlySecurityManager.doUnchecked(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    persistTimer(TimerImpl, true);
                    return null;
                }
            });
        } else {
            persistTimer(TimerImpl, true);
        }
    }

    @Override
    public void persistTimer(final TimerImpl TimerImpl) {
        if(WildFlySecurityManager.isChecking()) {
            WildFlySecurityManager.doUnchecked(new PrivilegedAction<Object>() {
                @Override
                public Object run() {
                    persistTimer(TimerImpl, false);
                    return null;
                }
            });
        } else {
            persistTimer(TimerImpl, false);
        }
    }

    @Override
    public boolean shouldRun(TimerImpl timer, TransactionManager tm) {
        return true;
    }

    private void persistTimer(final TimerImpl timer, boolean newTimer) {
        final Lock lock = getLock(timer.getTimedObjectId());
        try {
            final int status = ContextTransactionManager.getInstance().getStatus();
            if (status == Status.STATUS_MARKED_ROLLBACK || status == Status.STATUS_ROLLEDBACK ||
                    status == Status.STATUS_ROLLING_BACK) {
                //no need to persist anyway
                return;
            }

            lock.lock();
            if (status == Status.STATUS_NO_TRANSACTION ||
                    status == Status.STATUS_UNKNOWN || isBeforeCompletion()
                    || status == Status.STATUS_COMMITTED) {
                Map<String, TimerImpl> map = getTimers(timer.getTimedObjectId(), timer.getTimerService());
                if (timer.getState() == TimerState.CANCELED ||
                        timer.getState() == TimerState.EXPIRED) {
                    map.remove(timer.getId());
                    writeFile(timer);
                } else if (newTimer || map.containsKey(timer.getId())) {
                    //if it is not a new timer and is not in the map then it has
                    //been removed by another thread.
                    map.put(timer.getId(), timer);
                    writeFile(timer);
                }
            } else {

                final String key = timerTransactionKey(timer);
                Object existing = transactionSynchronizationRegistry.getValue().getResource(key);
                //check is there is already a persist sync for this timer
                if (existing == null) {
                    transactionSynchronizationRegistry.getValue().registerInterposedSynchronization(new PersistTransactionSynchronization(lock, key, newTimer));
                }
                //update the most recent version of the timer to be persisted
                transactionSynchronizationRegistry.getValue().putResource(key, timer);
            }
        } catch (SystemException e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }

    private String timerTransactionKey(final TimerImpl TimerImpl) {
        return "org.jboss.as.ejb3.timerTransactionKey." + TimerImpl.getId();
    }

    @Override
    public void timerUndeployed(final String timedObjectId) {
        final Lock lock = getLock(timedObjectId);
        try {
            lock.lock();
            locks.remove(timedObjectId);
            directories.remove(timedObjectId);
        } finally {
            lock.unlock();
        }

    }

    private boolean isBeforeCompletion() {
        final CurrentSynchronizationCallback.CallbackType type = CurrentSynchronizationCallback.get();
        if (type != null) {
            return type == CurrentSynchronizationCallback.CallbackType.BEFORE_COMPLETION;
        }
        return false;
    }

    @Override
    public List<TimerImpl> loadActiveTimers(final String timedObjectId, final TimerServiceImpl timerService) {
        final Lock lock = getLock(timedObjectId);
        try {
            lock.lock();
            final Map<String, TimerImpl> timers = getTimers(timedObjectId, timerService);

            final List<TimerImpl> entities = new ArrayList<TimerImpl>();
            for (Map.Entry<String, TimerImpl> entry : timers.entrySet()) {
                entities.add(mostRecentEntityVersion(entry.getValue()));
            }
            return entities;
        } finally {
            lock.unlock();
        }
    }

    @Override
    public Closeable registerChangeListener(String timedObjectId, TimerChangeListener listener) {
        return new Closeable() {
            @Override
            public void close() throws IOException {
            }
        };
    }

    /**
     * Returns either the loaded entity or the most recent version of the entity that has
     * been persisted in this transaction.
     */
    private TimerImpl mostRecentEntityVersion(final TimerImpl timerImpl) {
        try {
            final int status = ContextTransactionManager.getInstance().getStatus();
            if (status == Status.STATUS_UNKNOWN ||
                    status == Status.STATUS_NO_TRANSACTION) {
                return timerImpl;
            }
            final String key = timerTransactionKey(timerImpl);
            TimerImpl existing = (TimerImpl) transactionSynchronizationRegistry.getValue().getResource(key);
            return existing != null ? existing : timerImpl;
        } catch (SystemException e) {
            throw new RuntimeException(e);
        }
    }

    private Lock getLock(final String timedObjectId) {
        Lock lock = locks.get(timedObjectId);
        if (lock == null) {
            final Lock addedLock = new ReentrantLock();
            lock = locks.putIfAbsent(timedObjectId, addedLock);
            if (lock == null) {
                lock = addedLock;
            }
        }
        return lock;
    }

    /**
     * Gets the timer map, loading from the persistent store if necessary. Should be called under lock
     *
     * @param timedObjectId The timed object id
     * @return The timers for the object
     */
    private Map<String, TimerImpl> getTimers(final String timedObjectId, final TimerServiceImpl timerService) {
        return loadTimersFromFile(timedObjectId, timerService);
    }

    private Map<String, TimerImpl> loadTimersFromFile(String timedObjectId, TimerServiceImpl timerService) {
        Map<String, TimerImpl> timers = new HashMap<>();
        String directory = getDirectory(timedObjectId);

        timers.putAll(LegacyFileStore.loadTimersFromFile(timedObjectId, timerService, directory, factory, configuration));
        for(Map.Entry<String, TimerImpl> entry : timers.entrySet()) {
            writeFile(entry.getValue()); //write legacy timers into the new format
            //the legacy code handling code will write a marker file, to make sure that the old timers will not be loaded on next restart.
        }
        final File file = new File(directory);
        if (!file.exists()) {
            //no timers exist yet
            return timers;
        } else if (!file.isDirectory()) {
            EJB3_TIMER_LOGGER.failToRestoreTimers(file);
            return timers;
        }

        final XMLMapper mapper = createMapper(timerService);

        for (File timerFile : file.listFiles()) {
            if (!timerFile.getName().endsWith(".xml")) {
                continue;
            }
            FileInputStream in = null;

            try {
                in = new FileInputStream(timerFile);
                final XMLInputFactory inputFactory = INPUT_FACTORY;
                setIfSupported(inputFactory, XMLInputFactory.IS_VALIDATING, Boolean.FALSE);
                setIfSupported(inputFactory, XMLInputFactory.SUPPORT_DTD, Boolean.FALSE);
                final XMLStreamReader streamReader = inputFactory.createXMLStreamReader(in);
                try {
                    List<TimerImpl> timerList = new ArrayList<>();
                    mapper.parseDocument(timerList, streamReader);
                    for (TimerImpl timer : timerList) {
                        if (timer.getId().equals("deleted-timer")) {
                            timerFile.delete();
                            break;
                        }
                        timers.put(timer.getId(), timer);
                    }
                } finally {
                    safeClose(in);
                }

            } catch (Exception e) {
                EJB3_TIMER_LOGGER.failToRestoreTimersFromFile(timerFile, e);
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException e) {
                        EJB3_TIMER_LOGGER.failToCloseFile(e);
                    }
                }
            }
        }
        return timers;
    }

    private XMLMapper createMapper(TimerServiceImpl timerService) {
        final XMLMapper mapper = XMLMapper.Factory.create();
        mapper.registerRootElement(new QName(EjbTimerXmlParser_1_0.NAMESPACE, EjbTimerXmlPersister.TIMERS), new EjbTimerXmlParser_1_0(timerService, factory, configuration, timerService.getTimedObjectInvoker().getValue().getClassLoader()));
        return mapper;
    }

    private File fileName(String timedObjectId, String timerId) {
        return new File(getDirectory(timedObjectId) + File.separator + timerId.replace(File.separator, "-") + ".xml");
    }

    /**
     * Gets the directory for a given timed object, making sure it exists.
     *
     * @param timedObjectId The timed object
     * @return The directory
     */
    private String getDirectory(String timedObjectId) {
        String dirName = directories.get(timedObjectId);
        if (dirName == null) {
            dirName = baseDir.getAbsolutePath() + File.separator + timedObjectId.replace(File.separator, "-");
            File file = new File(dirName);
            if (!file.exists()) {
                if (!file.mkdirs()) {
                    EJB3_TIMER_LOGGER.failToCreateDirectoryForPersistTimers(file);
                }
            }
            directories.put(timedObjectId, dirName);
        }
        return dirName;
    }

    private final class PersistTransactionSynchronization implements Synchronization {

        private final String transactionKey;
        private final Lock lock;
        private final boolean newTimer;
        private volatile TimerImpl timer;

        public PersistTransactionSynchronization(final Lock lock, final String transactionKey, final boolean newTimer) {
            this.lock = lock;
            this.transactionKey = transactionKey;
            this.newTimer = newTimer;
        }

        @Override
        public void beforeCompletion() {
            //get the latest version of the entity
            timer = (TimerImpl) transactionSynchronizationRegistry.getValue().getResource(transactionKey);
            if (timer == null) {
                return;
            }
        }

        @Override
        public void afterCompletion(final int status) {
            doPrivileged(new PrivilegedAction<Void>() {
                public Void run() {
                    if (timer == null) {
                        return null;
                    }
                    try {
                        lock.lock();
                        if (status == Status.STATUS_COMMITTED) {
                            final Map<String, TimerImpl> map = getTimers(timer.getTimedObjectId(), timer.getTimerService());
                            if (timer.getState() == TimerState.CANCELED ||
                                    timer.getState() == TimerState.EXPIRED) {
                                map.remove(timer.getId());
                            } else {
                                if (newTimer || map.containsKey(timer.getId())) {
                                    //if an existing timer is not in the map it has been cancelled by another thread
                                    map.put(timer.getId(), timer);
                                }
                            }
                            writeFile(timer);
                        }
                    } finally {
                        lock.unlock();
                    }
                    return null;
                }
            });
        }

    }

    private void writeFile(TimerImpl timer) {
        final File file = fileName(timer.getTimedObjectId(), timer.getId());

        //if the timer is expired or cancelled delete the file
        if (timer.getState() == TimerState.CANCELED ||
                timer.getState() == TimerState.EXPIRED) {
            if (file.exists()) {
                file.delete();
            }
            return;
        }
        try {
            FileOutputStream out = new FileOutputStream(file);

            try {
                XMLStreamWriter writer = XMLOutputFactory.newInstance().createXMLStreamWriter(out);
                XMLMapper mapper = createMapper(timer.getTimerService());
                mapper.deparseDocument(new EjbTimerXmlPersister(factory, configuration), Collections.singletonList(timer), writer);
                writer.flush();
                writer.close();
            } finally {
                safeClose(out);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

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

    public InjectedValue<ModuleLoader> getModuleLoader() {
        return moduleLoader;
    }

    public InjectedValue<PathManager> getPathManager() {
        return pathManager;
    }

    private void setIfSupported(final XMLInputFactory inputFactory, final String property, final Object value) {
        if (inputFactory.isPropertySupported(property)) {
            inputFactory.setProperty(property, value);
        }
    }

    private static void safeClose(final Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException e) {
                // ignore
            }
        }
    }

    public static XMLExtendedStreamWriter create(XMLStreamWriter writer) throws Exception {
        // Use reflection to access package protected class FormattingXMLStreamWriter
        // TODO: at some point the staxmapper API could be enhanced to make this unnecessary
        Class<?> clazz = Class.forName("org.jboss.staxmapper.FormattingXMLStreamWriter");
        Constructor<?> ctr = clazz.getConstructor(XMLStreamWriter.class);
        ctr.setAccessible(true);
        return (XMLExtendedStreamWriter) ctr.newInstance(new Object[]{writer});
    }
}


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

package org.jboss.as.service;

import java.beans.PropertyEditor;
import java.util.function.Supplier;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;

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.reflect.ClassReflectionIndex;
import org.jboss.as.server.deployment.reflect.DeploymentReflectionIndex;
import org.jboss.as.service.component.ServiceComponentInstantiator;
import org.jboss.as.service.descriptor.JBossServiceAttributeConfig;
import org.jboss.as.service.descriptor.JBossServiceAttributeConfig.Inject;
import org.jboss.as.service.descriptor.JBossServiceAttributeConfig.ValueFactory;
import org.jboss.as.service.descriptor.JBossServiceAttributeConfig.ValueFactoryParameter;
import org.jboss.as.service.descriptor.JBossServiceConfig;
import org.jboss.as.service.descriptor.JBossServiceConstructorConfig;
import org.jboss.as.service.descriptor.JBossServiceConstructorConfig.Argument;
import org.jboss.as.service.descriptor.JBossServiceDependencyConfig;
import org.jboss.as.service.descriptor.JBossServiceDependencyListConfig;
import org.jboss.as.service.descriptor.JBossServiceXmlDescriptor;
import org.jboss.as.service.logging.SarLogger;
import org.jboss.common.beans.property.finder.PropertyEditorFinder;
import org.jboss.modules.Module;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceTarget;
import org.wildfly.security.manager.WildFlySecurityManager;

/**
 * DeploymentUnit processor responsible for taking JBossServiceXmlDescriptor configuration and creating the
 * corresponding services.
 *
 * @author John E. Bailey
 * @author Richard Opalka
 * @author Eduardo Martins
 */
public class ParsedServiceDeploymentProcessor implements DeploymentUnitProcessor {

    private final ServiceName mbeanServerServiceName;

    ParsedServiceDeploymentProcessor(ServiceName mbeanServerServiceName) {

        this.mbeanServerServiceName = mbeanServerServiceName;
    }

    /**
     * Process a deployment for JbossService configuration.  Will install a {@code JBossService} for each configured service.
     *
     * @param phaseContext the deployment unit context
     * @throws DeploymentUnitProcessingException
     */
    @Override
    public void deploy(final DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();
        final JBossServiceXmlDescriptor serviceXmlDescriptor = deploymentUnit.getAttachment(JBossServiceXmlDescriptor.ATTACHMENT_KEY);
        if (serviceXmlDescriptor == null) {
            // Skip deployments without a service xml descriptor
            return;
        }

        // assert module
        final Module module = deploymentUnit.getAttachment(Attachments.MODULE);
        if (module == null)
            throw SarLogger.ROOT_LOGGER.failedToGetAttachment("module", deploymentUnit);

        // assert reflection index
        final DeploymentReflectionIndex reflectionIndex = deploymentUnit.getAttachment(Attachments.REFLECTION_INDEX);
        if (reflectionIndex == null)
            throw SarLogger.ROOT_LOGGER.failedToGetAttachment("reflection index", deploymentUnit);

        // install services
        final ClassLoader classLoader = module.getClassLoader();
        final List<JBossServiceConfig> serviceConfigs = serviceXmlDescriptor.getServiceConfigs();
        final ServiceTarget target = phaseContext.getServiceTarget();
        final Map<String,ServiceComponentInstantiator> serviceComponents = deploymentUnit.getAttachment(ServiceAttachments.SERVICE_COMPONENT_INSTANTIATORS);
        for (final JBossServiceConfig serviceConfig : serviceConfigs) {
            addServices(target, serviceConfig, classLoader, reflectionIndex, serviceComponents != null ? serviceComponents.get(serviceConfig.getName()) : null, phaseContext);
        }
    }

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

    private void addServices(final ServiceTarget target, final JBossServiceConfig mBeanConfig, final ClassLoader classLoader, final DeploymentReflectionIndex index, ServiceComponentInstantiator componentInstantiator, final DeploymentPhaseContext phaseContext) throws DeploymentUnitProcessingException {
        final String mBeanClassName = mBeanConfig.getCode();
        final List<ClassReflectionIndex> mBeanClassHierarchy = ReflectionUtils.getClassHierarchy(mBeanClassName, index, classLoader);
        final Object mBeanInstance = newInstance(mBeanConfig, mBeanClassHierarchy, classLoader);
        final String mBeanName = mBeanConfig.getName();
        final DeploymentUnit deploymentUnit = phaseContext.getDeploymentUnit();

        final MBeanServices mBeanServices = new MBeanServices(mBeanName, mBeanInstance, mBeanClassHierarchy, target, componentInstantiator, deploymentUnit.getAttachmentList(org.jboss.as.ee.component.Attachments.WEB_SETUP_ACTIONS), classLoader, mbeanServerServiceName);

        final JBossServiceDependencyConfig[] dependencyConfigs = mBeanConfig.getDependencyConfigs();
        addDependencies(dependencyConfigs, mBeanClassHierarchy, mBeanServices, mBeanInstance);

        final JBossServiceDependencyListConfig[] dependencyListConfigs = mBeanConfig.getDependencyConfigLists();
        addDependencyLists(dependencyListConfigs, mBeanClassHierarchy, mBeanServices, mBeanInstance);

        final JBossServiceAttributeConfig[] attributeConfigs = mBeanConfig.getAttributeConfigs();
        addAttributes(attributeConfigs, mBeanClassHierarchy, mBeanServices, classLoader, mBeanInstance);

        // register all mBean related services
        mBeanServices.install();
    }

    private void addDependencies(final JBossServiceDependencyConfig[] dependencyConfigs, final List<ClassReflectionIndex> mBeanClassHierarchy, final MBeanServices mBeanServices, final Object mBeanInstance) throws DeploymentUnitProcessingException {
        if (dependencyConfigs != null) {
            for (final JBossServiceDependencyConfig dependencyConfig : dependencyConfigs) {
                final String optionalAttributeName = dependencyConfig.getOptionalAttributeName();
                if(optionalAttributeName != null){
                    final Method setter = ReflectionUtils.getSetter(mBeanClassHierarchy, optionalAttributeName);
                    final ObjectName dependencyObjectName = createDependencyObjectName(dependencyConfig.getDependencyName());
                    final Supplier<Object> objectSupplier = new ObjectSupplier(dependencyObjectName);
                    mBeanServices.addValue(setter, objectSupplier);
                }
                mBeanServices.addDependency(dependencyConfig.getDependencyName());
            }
        }
    }

    private void addDependencyLists(final JBossServiceDependencyListConfig[] dependencyListConfigs, final List<ClassReflectionIndex> mBeanClassHierarchy, final MBeanServices mBeanServices, final Object mBeanInstance) throws DeploymentUnitProcessingException {
        if(dependencyListConfigs != null){
            for(final JBossServiceDependencyListConfig dependencyListConfig: dependencyListConfigs) {
                final List<ObjectName> dependencyObjectNames = new ArrayList<ObjectName>(dependencyListConfig.getDependencyConfigs().length);
                for(final JBossServiceDependencyConfig dependencyConfig: dependencyListConfig.getDependencyConfigs()){
                        final String dependencyName = dependencyConfig.getDependencyName();
                        mBeanServices.addDependency(dependencyName);
                        final ObjectName dependencyObjectName = createDependencyObjectName(dependencyName);
                        dependencyObjectNames.add(dependencyObjectName);
                }
                final String optionalAttributeName = dependencyListConfig.getOptionalAttributeName();
                if(optionalAttributeName != null){
                    final Method setter = ReflectionUtils.getSetter(mBeanClassHierarchy, optionalAttributeName);
                    final ObjectSupplier objectSupplier = new ObjectSupplier(dependencyObjectNames);
                    mBeanServices.addValue(setter, objectSupplier);
                }
            }
        }
    }

    private void addAttributes(final JBossServiceAttributeConfig[] attributeConfigs, final List<ClassReflectionIndex> mBeanClassHierarchy, final MBeanServices mBeanServices, final ClassLoader classLoader, final Object mBeanInstance) throws DeploymentUnitProcessingException {
        if (attributeConfigs != null) {
            for (final JBossServiceAttributeConfig attributeConfig : attributeConfigs) {
                final String propertyName = attributeConfig.getName();
                final Inject injectConfig = attributeConfig.getInject();
                final ValueFactory valueFactoryConfig = attributeConfig.getValueFactory();
                final Method setter = ReflectionUtils.getSetter(mBeanClassHierarchy, propertyName);

                if (injectConfig != null) {
                    final DelegatingSupplier propertySupplier = getObjectSupplier(injectConfig);
                    mBeanServices.addAttribute(injectConfig.getBeanName(), setter, propertySupplier);
                } else if (valueFactoryConfig != null) {
                    final DelegatingSupplier valueFactorySupplier = getObjectSupplier(valueFactoryConfig, classLoader);
                    mBeanServices.addAttribute(valueFactoryConfig.getBeanName(), setter, valueFactorySupplier);
                } else {
                    final Supplier<Object> value = getObjectSupplier(attributeConfig, mBeanClassHierarchy);
                    mBeanServices.addValue(setter, value);
                }
            }
        }
    }

    private ObjectName createDependencyObjectName(final String dependencyName) throws DeploymentUnitProcessingException {
        try {
            return new ObjectName(dependencyName);
        } catch(MalformedObjectNameException exception){
            throw SarLogger.ROOT_LOGGER.malformedDependencyName(exception, dependencyName);
        }
    }

    private static DelegatingSupplier getObjectSupplier(final Inject injectConfig) {
        return new PropertySupplier(injectConfig.getPropertyName());
    }

    private static DelegatingSupplier getObjectSupplier(final ValueFactory valueFactory, final ClassLoader classLoader) {
        final String methodName = valueFactory.getMethodName();
        final ValueFactoryParameter[] parameters = valueFactory.getParameters();
        final Class<?>[] paramTypes = new Class<?>[parameters.length];
        final Object[] args = new Object[parameters.length];
        int index = 0;
        for (ValueFactoryParameter parameter : parameters) {
            final Class<?> attributeType = ReflectionUtils.getClass(parameter.getType(), classLoader);
            paramTypes[index] = attributeType;
            args[index] = newValue(attributeType, parameter.getValue());
            index++;
        }
        return new ValueFactorySupplier(methodName, paramTypes, args);
    }

    private static Supplier<Object> getObjectSupplier(final JBossServiceAttributeConfig attributeConfig, final List<ClassReflectionIndex> mBeanClassHierarchy) {
        final String attributeName = attributeConfig.getName();
        final Method setterMethod = ReflectionUtils.getSetter(mBeanClassHierarchy, attributeName);
        final Class<?> setterType = setterMethod.getParameterTypes()[0];
        return new ObjectSupplier(newValue(setterType, attributeConfig.getValue()));
    }

    private static Object newInstance(final JBossServiceConfig serviceConfig, final List<ClassReflectionIndex> mBeanClassHierarchy, final ClassLoader deploymentClassLoader) throws DeploymentUnitProcessingException {
        // set TCCL so that the MBean instantiation happens in the deployment's classloader
        final ClassLoader oldTCCL = WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(deploymentClassLoader);
        try {
            final JBossServiceConstructorConfig constructorConfig = serviceConfig.getConstructorConfig();
            final int paramCount = constructorConfig != null ? constructorConfig.getArguments().length : 0;
            final Class<?>[] types = new Class<?>[paramCount];
            final Object[] params = new Object[paramCount];

            if (constructorConfig != null) {
                final Argument[] arguments = constructorConfig.getArguments();
                for (int i = 0; i < paramCount; i++) {
                    final Argument argument = arguments[i];
                    types[i] = ReflectionUtils.getClass(argument.getType(), deploymentClassLoader);
                    params[i] = newValue(ReflectionUtils.getClass(argument.getType(), deploymentClassLoader), argument.getValue());
                }
            }
            final Constructor<?> constructor = mBeanClassHierarchy.get(0).getConstructor(types);
            if(constructor == null){
                throw SarLogger.ROOT_LOGGER.defaultConstructorNotFound(mBeanClassHierarchy.get(0).getIndexedClass());
            }
            final Object mBeanInstance = ReflectionUtils.newInstance(constructor, params);

            return mBeanInstance;
        } finally {
            // switch back the TCCL
            WildFlySecurityManager.setCurrentContextClassLoaderPrivileged(oldTCCL);
        }
    }

    private static Object newValue(final Class<?> type, final String value) {
        final PropertyEditor editor = PropertyEditorFinder.getInstance().find(type);
        if (editor == null) {
            SarLogger.ROOT_LOGGER.propertyNotFound(type);
            return null;
        }
        editor.setAsText(value);

        return editor.getValue();
    }

}


package org.jboss.as.jdr.util;

import org.jboss.as.jdr.vfs.Filters;

/**
 * Provides the most commonly used sanitizers with their most common configurations.
 */
public class Sanitizers {

    /**
     * creates and returns a {@link Sanitizer} instance that only operates on
     * files that end with a {@code .properties} suffix.
     *
     * @param pattern {@link WildcardPattern} compatible pattern to search for
     * @param replacement text content to replace matches with
     * @return {@link Sanitizer} that only operates on files with names ending with {@code .properties}.
     * @throws Exception
     */
    public static Sanitizer pattern(String pattern, String replacement) throws Exception {
        return new PatternSanitizer(pattern, replacement, Filters.suffix(".properties"));
    }

    /**
     * creates and returns a {@link Sanitizer} instance that only operates on
     * files that end with a {@code .xml} suffix.
     *
     * @param xpath to search for and nullify
     * @return a {@link Sanitizer} instance that only operates on files that end with a {@code .xml} suffix.
     * @throws Exception
     */
    public static Sanitizer xml(String xpath) throws Exception {
        return new XMLSanitizer(xpath, Filters.suffix(".xml"));
    }
}


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

import org.jboss.as.controller.PathElement;
import org.jboss.as.controller.ReadResourceNameOperationStepHandler;
import org.jboss.as.controller.ReloadRequiredRemoveStepHandler;
import org.jboss.as.controller.ReloadRequiredWriteAttributeHandler;
import org.jboss.as.controller.SimpleAttributeDefinition;
import org.jboss.as.controller.SimpleAttributeDefinitionBuilder;
import org.jboss.as.controller.SimpleResourceDefinition;
import org.jboss.as.controller.registry.ManagementResourceRegistration;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.ModelType;

/**
 * Implementation of {@link org.jboss.as.controller.ResourceDefinition} for commit-markable-resource.
 *
 * @author Stefano Maestri (c) 2011 Red Hat Inc.
 */
public class CMResourceResourceDefinition extends SimpleResourceDefinition {

    public static final PathElement PATH_CM_RESOURCE = PathElement.pathElement(CommonAttributes.CM_RESOURCE);

    static SimpleAttributeDefinition JNDI_NAME =  new SimpleAttributeDefinitionBuilder(CommonAttributes.CM_JNDI_NAME, ModelType.STRING)
            .setAllowExpression(true)
            .setRequired(true)
            .setResourceOnly()
            .build();

    static SimpleAttributeDefinition CM_TABLE_BATCH_SIZE =  new SimpleAttributeDefinitionBuilder(CommonAttributes.CM_BATCH_SIZE, ModelType.INT)
            .setAllowExpression(true)
            .setRequired(false)
            .setDefaultValue(new ModelNode(CommonAttributes.CM_BATCH_SIZE_DEF_VAL))
            .setXmlName(CommonAttributes.CM_BATCH_SIZE)
            .build();

    static SimpleAttributeDefinition CM_TABLE_IMMEDIATE_CLEANUP =  new SimpleAttributeDefinitionBuilder(CommonAttributes.CM_IMMEDIATE_CLEANUP, ModelType.BOOLEAN)
            .setAllowExpression(true)
            .setRequired(false)
            .setDefaultValue(new ModelNode(CommonAttributes.CM_IMMEDIATE_CLEANUP_DEF_VAL))
            .setXmlName(CommonAttributes.CM_IMMEDIATE_CLEANUP)
            .build();

    static SimpleAttributeDefinition CM_TABLE_NAME = new SimpleAttributeDefinitionBuilder(CommonAttributes.CM_LOCATION_NAME, ModelType.STRING)
            .setAllowExpression(true)
            .setRequired(false)
            .setDefaultValue(new ModelNode(CommonAttributes.CM_LOCATION_NAME_DEF_VAL))
            .setXmlName(CommonAttributes.CM_LOCATION_NAME)
            .build();

    /**
     * Default constructure.
     * It set {@link org.jboss.as.txn.subsystem.CMResourceAdd} as add handler
     * and {@link org.jboss.as.controller.ReloadRequiredRemoveStepHandler} as remove handler
     */
    public CMResourceResourceDefinition() {
        super(PATH_CM_RESOURCE,
                TransactionExtension.getResourceDescriptionResolver(CommonAttributes.CM_RESOURCE),
                CMResourceAdd.INSTANCE,
                ReloadRequiredRemoveStepHandler.INSTANCE);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void registerAttributes(ManagementResourceRegistration resourceRegistration) {
        ReloadRequiredWriteAttributeHandler reloadWrtiteHandler = new ReloadRequiredWriteAttributeHandler(JNDI_NAME, CM_TABLE_NAME, CM_TABLE_BATCH_SIZE, CM_TABLE_IMMEDIATE_CLEANUP);
        resourceRegistration.registerReadWriteAttribute(CM_TABLE_NAME, null, reloadWrtiteHandler);
        resourceRegistration.registerReadWriteAttribute(CM_TABLE_BATCH_SIZE, null, reloadWrtiteHandler);
        resourceRegistration.registerReadWriteAttribute(CM_TABLE_IMMEDIATE_CLEANUP, null, reloadWrtiteHandler);

        //This comes from the address
        resourceRegistration.registerReadOnlyAttribute(JNDI_NAME, ReadResourceNameOperationStepHandler.INSTANCE);
    }
}


/*
 * 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.ejb3.component.stateful;

import java.lang.reflect.Method;

import org.jboss.as.ee.component.Component;
import org.jboss.as.ee.component.ComponentInstance;
import org.jboss.as.ee.component.ComponentView;
import org.jboss.as.ejb3.logging.EjbLogger;
import org.jboss.as.ejb3.component.EJBComponent;
import org.jboss.as.ejb3.component.Ejb2xViewType;
import org.jboss.ejb.client.SessionID;
import org.jboss.invocation.Interceptor;
import org.jboss.invocation.InterceptorContext;

/**
 * An interceptor which handles an invocation on a {@link javax.ejb.Remove} method of a stateful session bean. This interceptor
 * removes the stateful session once the method completes (either successfully or with an exception). If the remove method
 * was marked with "retainIfException" to true and if the remove method threw a {@link javax.ejb.ApplicationException} then
 * this interceptor does *not* remove the stateful session.
 *

 * User: Jaikiran Pai
 */
public class StatefulRemoveInterceptor implements Interceptor {

    private final boolean retainIfException;

    public StatefulRemoveInterceptor(final boolean retainIfException) {
        this.retainIfException = retainIfException;
    }

    @Override
    public Object processInvocation(InterceptorContext context) throws Exception {
        final Component component = context.getPrivateData(Component.class);

        //if a session bean is participating in a transaction, it
        //is an error for a client to invoke the remove method
        //on the session object's home or component interface.
        final ComponentView view = context.getPrivateData(ComponentView.class);
        if (view != null) {
            Ejb2xViewType viewType = view.getPrivateData(Ejb2xViewType.class);
            if (viewType != null) {
                //this means it is an EJB 2.x view
                //which is not allowed to remove while enrolled in a TX
                final StatefulTransactionMarker marker = context.getPrivateData(StatefulTransactionMarker.class);
                if(marker != null && !marker.isFirstInvocation()) {
                    throw EjbLogger.ROOT_LOGGER.cannotRemoveWhileParticipatingInTransaction();
                }
            }
        }
        // just log a WARN and throw back the original exception
        if (component instanceof StatefulSessionComponent == false) {
            throw EjbLogger.ROOT_LOGGER.unexpectedComponent(component, StatefulSessionComponent.class);
        }
        final StatefulSessionComponent statefulComponent = (StatefulSessionComponent) component;
        Object invocationResult = null;
        try {
            // proceed
            invocationResult = context.proceed();
        } catch (Exception e) {
            // Exception caught during call to @Remove method. Handle it appropriately

            // If it's an application exception and if the @Remove method has set "retainIfException" to true
            // then just throw back the exception and don't remove the session instance.
            if (this.isApplicationException(statefulComponent, e.getClass(), context.getMethod()) && this.retainIfException) {
                throw e;
            }
            // otherwise, just remove it and throw back the original exception
            final StatefulSessionComponentInstance statefulComponentInstance = (StatefulSessionComponentInstance) context.getPrivateData(ComponentInstance.class);
            final SessionID sessionId = statefulComponentInstance.getId();
            statefulComponent.removeSession(sessionId);
            throw e;
        }
        final StatefulSessionComponentInstance statefulComponentInstance = (StatefulSessionComponentInstance) context.getPrivateData(ComponentInstance.class);
        final SessionID sessionId = statefulComponentInstance.getId();
        // just remove the session because of a call to @Remove method
        statefulComponent.removeSession(sessionId);
        // return the invocation result
        return invocationResult;
    }

    /**
     * Returns true if the passed exceptionClass is an application exception. Else returns false.
     *
     * @param ejbComponent   The EJB component
     * @param exceptionClass The exception class
     * @return
     */
    private boolean isApplicationException(final EJBComponent ejbComponent, final Class<?> exceptionClass, final Method invokedMethod) {
        return ejbComponent.getApplicationException(exceptionClass, invokedMethod) != null;
    }

}


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

package org.jboss.as.naming;

import java.security.PrivilegedAction;
import java.util.concurrent.ThreadFactory;
import org.jboss.as.naming.util.FastCopyHashMap;

import javax.naming.Binding;
import javax.naming.Name;
import javax.naming.event.EventContext;
import javax.naming.event.NamespaceChangeListener;
import javax.naming.event.NamingEvent;
import javax.naming.event.NamingListener;
import javax.naming.event.ObjectChangeListener;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.jboss.threads.JBossThreadFactory;

import static java.security.AccessController.doPrivileged;

/**
 * Coordinator responsible for passing @(code NamingEvent} instances to registered @{code NamingListener} instances.  Two
 * maps are used to managed a mapping between a listener and its configuration as well as a mapping from target name to a list
 * of listener configurations.  These maps are updated atomically on listener add and remove.
 *
 * @author John E. Bailey
 */
public class NamingEventCoordinator {
    private volatile Map<TargetScope, List<ListenerHolder>> holdersByTarget = Collections.emptyMap();
    private volatile Map<NamingListener, ListenerHolder> holdersByListener = Collections.emptyMap();

    private final ThreadFactory threadFactory = doPrivileged(new PrivilegedAction<JBossThreadFactory>() {
        public JBossThreadFactory run() {
            return new JBossThreadFactory(new ThreadGroup("NamingEventCoordinator-threads"), Boolean.FALSE, null, "%G - %t", null, null);
        }
    });

    private final Executor executor = Executors.newSingleThreadExecutor(threadFactory);

    static final Integer[] DEFAULT_SCOPES = {EventContext.OBJECT_SCOPE, EventContext.ONELEVEL_SCOPE, EventContext.SUBTREE_SCOPE};

    /**
     * Add a listener to the coordinator with a given target name and event scope.  This information is used when an
     * event is fired to determine whether or not to fire this listener.
     *
     * @param target The target name to lister
     * @param scope The event scope
     * @param namingListener The listener
     */
    synchronized void addListener(final String target, final int scope, final NamingListener namingListener) {
        final TargetScope targetScope = new TargetScope(target, scope);
        // Do we have a holder for this listener
        ListenerHolder holder = holdersByListener.get(namingListener);
        if (holder == null) {
            holder = new ListenerHolder(namingListener, targetScope);
            final Map<NamingListener, ListenerHolder> byListenerCopy = new FastCopyHashMap<NamingListener, ListenerHolder>(holdersByListener);
            byListenerCopy.put(namingListener, holder);
            holdersByListener = byListenerCopy;
        } else {
            holder.addTarget(targetScope);
        }

        List<ListenerHolder> holdersForTarget = holdersByTarget.get(targetScope);
        if (holdersForTarget == null) {
            holdersForTarget = new CopyOnWriteArrayList<ListenerHolder>();
            final Map<TargetScope, List<ListenerHolder>> byTargetCopy = new FastCopyHashMap<TargetScope, List<ListenerHolder>>(holdersByTarget);
            byTargetCopy.put(targetScope, holdersForTarget);
            holdersByTarget = byTargetCopy;
        }
        holdersForTarget.add(holder);
    }

    /**
     * Remove a listener.  Will remove it from all target mappings.  Once this method returns, the listener will no longer
     * receive any events.
     *
     * @param namingListener The listener
     */
    synchronized void removeListener(final NamingListener namingListener) {
        // Do we have a holder for this listener
        final ListenerHolder holder = holdersByListener.get(namingListener);
        if (holder == null) {
            return;
        }

        final Map<NamingListener, ListenerHolder> byListenerCopy = new FastCopyHashMap<NamingListener, ListenerHolder>(holdersByListener);
        byListenerCopy.remove(namingListener);
        holdersByListener = byListenerCopy;

        final Map<TargetScope, List<ListenerHolder>> byTargetCopy = new FastCopyHashMap<TargetScope, List<ListenerHolder>>(holdersByTarget);
        for (TargetScope targetScope : holder.targets) {
            final List<ListenerHolder> holders = holdersByTarget.get(targetScope);
            holders.remove(holder);
            if (holders.isEmpty()) {
                byTargetCopy.remove(targetScope);
            }
        }
        holdersByTarget = byTargetCopy;
    }

    /**
     * Fire a naming event.  An event will be created with the provided information and sent to each listener that matches
     * the target and scope information.
     *
     * @param context The event context generating the event.
     * @param name The target name the event represents
     * @param existingBinding The existing binding at the provided name
     * @param newBinding The new binding at the provided name
     * @param type The event type
     * @param changeInfo The change info for the event
     * @param scopes The scopes this event should be fired against
     */
    void fireEvent(final EventContext context, final Name name, final Binding existingBinding, final Binding newBinding, int type, final String changeInfo, final Integer... scopes) {
        final String target = name.toString();
        final Set<Integer> scopeSet = new HashSet<Integer>(Arrays.asList(scopes));
        final NamingEvent event = new NamingEvent(context, type, newBinding, existingBinding, changeInfo);

        final Set<ListenerHolder> holdersToFire = new HashSet<ListenerHolder>();

        // Check for OBJECT_SCOPE based listeners
        if (scopeSet.contains(EventContext.OBJECT_SCOPE)) {
            final TargetScope targetScope = new TargetScope(target, EventContext.OBJECT_SCOPE);
            final List<ListenerHolder> holders = holdersByTarget.get(targetScope);
            if (holders != null) {
                for (ListenerHolder holder : holders) {
                    holdersToFire.add(holder);
                }
            }
        }

        // Check for ONELEVEL_SCOPE based listeners
        if (scopeSet.contains(EventContext.ONELEVEL_SCOPE) && !name.isEmpty()) {
            final TargetScope targetScope = new TargetScope(name.getPrefix(name.size() - 1).toString(), EventContext.ONELEVEL_SCOPE);
            final List<ListenerHolder> holders = holdersByTarget.get(targetScope);
            if (holders != null) {
                for (ListenerHolder holder : holders) {
                    holdersToFire.add(holder);
                }
            }
        }

        // Check for SUBTREE_SCOPE based listeners
        if (scopeSet.contains(EventContext.SUBTREE_SCOPE) && !name.isEmpty()) {
            for (int i = 1; i < name.size(); i++) {
                final Name parentName = name.getPrefix(i);
                final TargetScope targetScope = new TargetScope(parentName.toString(), EventContext.SUBTREE_SCOPE);
                final List<ListenerHolder> holders = holdersByTarget.get(targetScope);
                if (holders != null) {
                    for (ListenerHolder holder : holders) {
                        holdersToFire.add(holder);
                    }
                }
            }
        }

        executor.execute(new FireEventTask(holdersToFire, event));
    }

    private class FireEventTask implements Runnable {
        private final Set<ListenerHolder> listenerHolders;
        private final NamingEvent event;

        private FireEventTask(Set<ListenerHolder> listenerHolders, NamingEvent event) {
            this.listenerHolders = listenerHolders;
            this.event = event;
        }

        @Override
        public void run() {
            for (ListenerHolder holder : listenerHolders) {
                final NamingListener listener = holder.listener;
                switch (event.getType()) {
                    case NamingEvent.OBJECT_ADDED:
                        if (listener instanceof NamespaceChangeListener)
                            ((NamespaceChangeListener) listener).objectAdded(event);
                        break;
                    case NamingEvent.OBJECT_REMOVED:
                        if (listener instanceof NamespaceChangeListener)
                            ((NamespaceChangeListener) listener).objectRemoved(event);
                        break;
                    case NamingEvent.OBJECT_RENAMED:
                        if (listener instanceof NamespaceChangeListener)
                            ((NamespaceChangeListener) listener).objectRenamed(event);
                        break;
                    case NamingEvent.OBJECT_CHANGED:
                        if (listener instanceof ObjectChangeListener)
                            ((ObjectChangeListener) listener).objectChanged(event);
                        break;
                }
            }
        }
    }

    private class ListenerHolder {
        private volatile Set<TargetScope> targets = new HashSet<TargetScope>();
        private final NamingListener listener;

        private ListenerHolder(final NamingListener listener, final TargetScope initialTarget) {
            this.listener = listener;
            addTarget(initialTarget);
        }

        private synchronized void addTarget(final TargetScope targetScope) {
            targets.add(targetScope);
        }
    }

    private class TargetScope {
        private final String target;
        private final int scope;

        private TargetScope(String target, int scope) {
            this.target = target;
            this.scope = scope;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;

            TargetScope that = (TargetScope) o;

            if (scope != that.scope) return false;
            if (target != null ? !target.equals(that.target) : that.target != null) return false;

            return true;
        }

        @Override
        public int hashCode() {
            int result = target != null ? target.hashCode() : 0;
            result = 31 * result + scope;
            return result;
        }
    }
}


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

package org.jboss.as.clustering.function;

import java.util.function.Consumer;

import org.jboss.as.clustering.logging.ClusteringLogger;

/**
 * {@link Consumer} utility methods.
 * @author Paul Ferraro
 */
public class Consumers {

    /**
     * Returns a consumer that closes its input.
     * @return a consumer that closes its input.
     */
    public static <T extends AutoCloseable> Consumer<T> close() {
        return value -> {
            try {
                value.close();
            } catch (Throwable e) {
                ClusteringLogger.ROOT_LOGGER.failedToClose(e, value);
            }
        };
    }

    private Consumers() {
        // Hide
    }
}


/*
 * Copyright 2017 Red Hat, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.jboss.as.connector.security;

import org.ietf.jgss.GSSName;
import org.jboss.as.connector._private.Capabilities;
import org.jboss.as.controller.capability.RuntimeCapability;
import org.jboss.as.server.CurrentServiceContainer;
import org.jboss.jca.core.spi.security.SubjectFactory;
import org.jboss.msc.service.ServiceContainer;
import org.jboss.msc.service.ServiceName;
import org.wildfly.security.auth.callback.CredentialCallback;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
import org.wildfly.security.auth.principal.NamePrincipal;
import org.wildfly.security.credential.GSSKerberosCredential;
import org.wildfly.security.manager.WildFlySecurityManager;

import javax.resource.spi.security.PasswordCredential;
import javax.security.auth.Subject;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.kerberos.KerberosPrincipal;

import static org.jboss.as.connector.logging.ConnectorLogger.ROOT_LOGGER;

import java.net.URI;
import java.security.AccessController;
import java.security.PrivilegedAction;

/**
 * An Elytron based {@link SubjectFactory} implementation. It uses an {@link AuthenticationContext} to select a configuration
 * that matches the resource {@link URI} to obtain the username and password pair that will be inserted into the constructed
 * {@link Subject} instance.
 *
 * @author Flavia Rainone
 * @author Stefan Guilhen
 */
public class ElytronSubjectFactory implements SubjectFactory, Capabilities {

    private static final RuntimeCapability<Void> AUTHENTICATION_CONTEXT_RUNTIME_CAPABILITY = RuntimeCapability
            .Builder.of(AUTHENTICATION_CONTEXT_CAPABILITY, true, AuthenticationContext.class)
            .build();

    private static final AuthenticationContextConfigurationClient AUTH_CONFIG_CLIENT =
            AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);

    private final AuthenticationContext authenticationContext;
    private URI targetURI;

    /**
     * Constructor
     */
    public ElytronSubjectFactory() {
        this(null, null);
    }

    /**
     * Constructor.
     *
     * @param targetURI the {@link URI} of the target.
     */
    public ElytronSubjectFactory(final AuthenticationContext authenticationContext, final URI targetURI) {
        this.authenticationContext = authenticationContext;
        this.targetURI = targetURI;
    }

    /**
     * {@inheritDoc}
     */
    public Subject createSubject() {
        // If a authenticationContext was defined on the subsystem use that context, otherwise use capture the current
        // configuration.
        final Subject subject = this.createSubject(getAuthenticationContext());
        if (ROOT_LOGGER.isTraceEnabled()) {
            ROOT_LOGGER.subject(subject, Integer.toHexString(System.identityHashCode(subject)));
        }
        return subject;
    }

    /**
     * {@inheritDoc}
     */
    public Subject createSubject(final String authenticationContextName) {
        AuthenticationContext context;
        if (authenticationContextName != null && !authenticationContextName.isEmpty()) {
            final ServiceContainer container = this.currentServiceContainer();
            final ServiceName authContextServiceName = AUTHENTICATION_CONTEXT_RUNTIME_CAPABILITY.getCapabilityServiceName(authenticationContextName);
            context = (AuthenticationContext) container.getRequiredService(authContextServiceName).getValue();
        }
        else {
            context = getAuthenticationContext();
        }
        final Subject subject = this.createSubject(context);
        if (ROOT_LOGGER.isTraceEnabled()) {
            ROOT_LOGGER.subject(subject, Integer.toHexString(System.identityHashCode(subject)));
        }
        return subject;
    }

    /**
     * Create a {@link Subject} with the principal and password credential obtained from the authentication configuration
     * that matches the target {@link URI}.
     *
     * @param authenticationContext the {@link AuthenticationContext} used to select a configuration that matches the
     *                              target {@link URI}.
     * @return the constructed {@link Subject}. It contains a single principal and a {@link PasswordCredential}.
     */
    private Subject createSubject(final AuthenticationContext authenticationContext) {
        final AuthenticationConfiguration configuration = AUTH_CONFIG_CLIENT.getAuthenticationConfiguration(this.targetURI, authenticationContext);
        final CallbackHandler handler = AUTH_CONFIG_CLIENT.getCallbackHandler(configuration);
        final NameCallback nameCallback = new NameCallback("Username: ");
        final PasswordCallback passwordCallback = new PasswordCallback("Password: ", false);
        final CredentialCallback credentialCallback = new CredentialCallback(GSSKerberosCredential.class);
        try {
            handler.handle(new Callback[]{nameCallback, passwordCallback, credentialCallback});
            Subject subject = new Subject();
            // if a GSSKerberosCredential was found, add the enclosed GSSCredential and KerberosTicket to the private set in the Subject.
            if (credentialCallback.getCredential() != null) {
                GSSKerberosCredential kerberosCredential = GSSKerberosCredential.class.cast(credentialCallback.getCredential());
                this.addPrivateCredential(subject, kerberosCredential.getKerberosTicket());
                this.addPrivateCredential(subject, kerberosCredential.getGssCredential());
                // use the GSSName to build a kerberos principal and set it in the Subject.
                GSSName gssName = kerberosCredential.getGssCredential().getName();
                subject.getPrincipals().add(new KerberosPrincipal(gssName.toString()));
            }
            // use the name from the callback, if available, to build a principal and set it in the Subject.
            if (nameCallback.getName() != null) {
                subject.getPrincipals().add(new NamePrincipal(nameCallback.getName()));
            }
            // use the password from the callback, if available, to build a credential and set it as a private credential in the Subject.
            if (passwordCallback.getPassword() != null) {
                this.addPrivateCredential(subject, new PasswordCredential(nameCallback.getName(), passwordCallback.getPassword()));
            }
            return subject;
        } catch(Exception e) {
            throw new SecurityException(e);
        }
    }

    /**
     * Get a reference to the current {@link ServiceContainer}.
     *
     * @return a reference to the current {@link ServiceContainer}.
     */
    private ServiceContainer currentServiceContainer() {
        if(WildFlySecurityManager.isChecking()) {
            return AccessController.doPrivileged(CurrentServiceContainer.GET_ACTION);
        }
        return CurrentServiceContainer.getServiceContainer();
    }

    /**
     * Add the specified credential to the subject's private credentials set.
     *
     * @param subject the {@link Subject} to add the credential to.
     * @param credential a reference to the credential.
     */
    private void addPrivateCredential(final Subject subject, final Object credential) {
        if (!WildFlySecurityManager.isChecking()) {
            subject.getPrivateCredentials().add(credential);
        }
        else {
            AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
                subject.getPrivateCredentials().add(credential);
                return null;
            });
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();

        sb.append("ElytronSubjectFactory@").append(Integer.toHexString(System.identityHashCode(this)));
        sb.append("]");

        return sb.toString();
    }

    private AuthenticationContext getAuthenticationContext() {
        return authenticationContext == null ? AuthenticationContext.captureCurrent() : authenticationContext;
    }
}


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

import io.agroal.api.AgroalDataSource;
import io.agroal.api.AgroalDataSourceMetrics;
import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration;
import io.agroal.api.configuration.AgroalConnectionFactoryConfiguration.TransactionIsolation;
import io.agroal.api.configuration.AgroalConnectionPoolConfiguration;
import io.agroal.api.configuration.supplier.AgroalConnectionFactoryConfigurationSupplier;
import io.agroal.api.configuration.supplier.AgroalConnectionPoolConfigurationSupplier;
import io.agroal.api.security.NamePrincipal;
import io.agroal.api.security.SimplePassword;
import org.jboss.as.controller.AbstractWriteAttributeHandler;
import org.jboss.as.controller.AttributeDefinition;
import org.jboss.as.controller.OperationContext;
import org.jboss.as.controller.OperationFailedException;
import org.jboss.as.controller.OperationStepHandler;
import org.jboss.as.controller.security.CredentialReference;
import org.jboss.dmr.ModelNode;
import org.jboss.dmr.Property;
import org.jboss.msc.service.ServiceBuilder;
import org.jboss.msc.service.ServiceController;
import org.jboss.msc.service.ServiceName;
import org.jboss.msc.service.ServiceRegistry;
import org.wildfly.common.function.ExceptionSupplier;
import org.wildfly.extension.datasources.agroal.logging.AgroalLogger;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.credential.source.CredentialSource;

import java.sql.Connection;
import java.sql.SQLException;
import java.time.Duration;

import static io.agroal.api.configuration.AgroalConnectionPoolConfiguration.ConnectionValidator.defaultValidator;
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;

/**
 * Operations common to XA and non-XA DataSources
 *
 * @author Luis Barreiro
 */
class AbstractDataSourceOperations {

    static final OperationStepHandler STATISTICS_ENABLED_WRITE_OPERATION = new StatisticsEnabledAttributeWriter(AbstractDataSourceDefinition.STATISTICS_ENABLED_ATTRIBUTE);

    static final OperationStepHandler CONNECTION_FACTORY_WRITE_OPERATION = new ConnectionFactoryAttributeWriter(AbstractDataSourceDefinition.CONNECTION_FACTORY_ATTRIBUTE.getValueTypes());

    static final OperationStepHandler CONNECTION_POOL_WRITE_OPERATION = new ConnectionPoolAttributeWriter(AbstractDataSourceDefinition.CONNECTION_POOL_ATTRIBUTE.getValueTypes());

    // --- //

    static final OperationStepHandler FLUSH_ALL_OPERATION = new FlushOperation(AgroalDataSource.FlushMode.ALL);

    static final OperationStepHandler FLUSH_GRACEFUL_OPERATION = new FlushOperation(AgroalDataSource.FlushMode.GRACEFUL);

    static final OperationStepHandler FLUSH_INVALID_OPERATION = new FlushOperation(AgroalDataSource.FlushMode.INVALID);

    static final OperationStepHandler FLUSH_IDLE_OPERATION = new FlushOperation(AgroalDataSource.FlushMode.IDLE);

    static final OperationStepHandler RESET_STATISTICS_OPERATION = new ResetStatisticsOperation();

    static final OperationStepHandler STATISTICS_GET_OPERATION = new StatisticsGetOperation();

    static final OperationStepHandler TEST_CONNECTION_OPERATION = new TestConnectionOperation();

    // --- //

    protected static AgroalConnectionFactoryConfigurationSupplier connectionFactoryConfiguration(OperationContext context, ModelNode model) throws OperationFailedException {
        AgroalConnectionFactoryConfigurationSupplier configuration = new AgroalConnectionFactoryConfigurationSupplier();

        if (AbstractDataSourceDefinition.URL_ATTRIBUTE.resolveModelAttribute(context, model).isDefined()) {
            configuration.jdbcUrl(AbstractDataSourceDefinition.URL_ATTRIBUTE.resolveModelAttribute(context, model).asString());
        }

        if (AbstractDataSourceDefinition.NEW_CONNECTION_SQL_ATTRIBUTE.resolveModelAttribute(context, model).isDefined()) {
            configuration.initialSql(AbstractDataSourceDefinition.NEW_CONNECTION_SQL_ATTRIBUTE.resolveModelAttribute(context, model).asString());
        }

        if (AbstractDataSourceDefinition.TRANSACTION_ISOLATION_ATTRIBUTE.resolveModelAttribute(context, model).isDefined()) {
            configuration.jdbcTransactionIsolation(TransactionIsolation.valueOf(AbstractDataSourceDefinition.TRANSACTION_ISOLATION_ATTRIBUTE.resolveModelAttribute(context, model).asString()));
        }

        if (AbstractDataSourceDefinition.CONNECTION_PROPERTIES_ATTRIBUTE.resolveModelAttribute(context, model).isDefined()) {
            for (Property jdbcProperty : AbstractDataSourceDefinition.CONNECTION_PROPERTIES_ATTRIBUTE.resolveModelAttribute(context, model).asPropertyList()) {
                configuration.jdbcProperty(jdbcProperty.getName(), jdbcProperty.getValue().asString());
            }
        }

        if (AbstractDataSourceDefinition.USERNAME_ATTRIBUTE.resolveModelAttribute(context, model).isDefined()) {
            configuration.principal(new NamePrincipal(AbstractDataSourceDefinition.USERNAME_ATTRIBUTE.resolveModelAttribute(context, model).asString()));
        }
        if (AbstractDataSourceDefinition.PASSWORD_ATTRIBUTE.resolveModelAttribute(context, model).isDefined()) {
            configuration.credential(new SimplePassword(AbstractDataSourceDefinition.PASSWORD_ATTRIBUTE.resolveModelAttribute(context, model).asString()));
        }
        return configuration;
    }

    protected static void setupElytronSecurity(OperationContext context, ModelNode model, DataSourceService dataSourceService, ServiceBuilder<?> serviceBuilder) throws OperationFailedException {
        if (AbstractDataSourceDefinition.AUTHENTICATION_CONTEXT.resolveModelAttribute(context, model).isDefined()) {
            String authenticationContextName = AbstractDataSourceDefinition.AUTHENTICATION_CONTEXT.resolveModelAttribute(context, model).asString();
            ServiceName authenticationContextCapability = context.getCapabilityServiceName(AbstractDataSourceDefinition.AUTHENTICATION_CONTEXT_CAPABILITY, authenticationContextName, AuthenticationContext.class);
            serviceBuilder.addDependency(authenticationContextCapability, AuthenticationContext.class, dataSourceService.getAuthenticationContextInjector());
        }
        if (AbstractDataSourceDefinition.CREDENTIAL_REFERENCE.resolveModelAttribute(context, model).isDefined()) {
            ExceptionSupplier<CredentialSource, Exception> credentialSourceSupplier = CredentialReference.getCredentialSourceSupplier(context, AbstractDataSourceDefinition.CREDENTIAL_REFERENCE, model, serviceBuilder);
            dataSourceService.getCredentialSourceSupplierInjector().inject(credentialSourceSupplier);
        }
    }

    protected static AgroalConnectionPoolConfigurationSupplier connectionPoolConfiguration(OperationContext context, ModelNode model) throws OperationFailedException {
        AgroalConnectionPoolConfigurationSupplier configuration = new AgroalConnectionPoolConfigurationSupplier();

        configuration.maxSize(AbstractDataSourceDefinition.MAX_SIZE_ATTRIBUTE.resolveModelAttribute(context, model).asInt());
        configuration.minSize(AbstractDataSourceDefinition.MIN_SIZE_ATTRIBUTE.resolveModelAttribute(context, model).asInt());
        configuration.initialSize(AbstractDataSourceDefinition.INITIAL_SIZE_ATTRIBUTE.resolveModelAttribute(context, model).asInt());

        configuration.acquisitionTimeout(ofMillis(AbstractDataSourceDefinition.BLOCKING_TIMEOUT_MILLIS_ATTRIBUTE.resolveModelAttribute(context, model).asInt()));
        configuration.leakTimeout(ofMillis(AbstractDataSourceDefinition.LEAK_DETECTION_ATTRIBUTE.resolveModelAttribute(context, model).asInt()));
        configuration.validationTimeout(ofMillis(AbstractDataSourceDefinition.BACKGROUND_VALIDATION_ATTRIBUTE.resolveModelAttribute(context, model).asInt()));
        configuration.reapTimeout(ofMinutes(AbstractDataSourceDefinition.IDLE_REMOVAL_ATTRIBUTE.resolveModelAttribute(context, model).asInt()));
        configuration.connectionValidator(defaultValidator());

        return configuration;
    }

    // --- //

    private static AgroalDataSource getDataSource(OperationContext context) throws OperationFailedException {
        ServiceRegistry registry = context.getServiceRegistry(false);
        String dataSourceName = context.getCurrentAddressValue();

        switch (context.getCurrentAddress().getLastElement().getKey()) {
            case DataSourceOperations.DATASOURCE_SERVICE_NAME:
                ServiceController<?> controller = registry.getRequiredService(AbstractDataSourceDefinition.DATA_SOURCE_CAPABILITY.getCapabilityServiceName(dataSourceName));
                return ((AgroalDataSource) controller.getValue());
            case XADataSourceOperations.XADATASOURCE_SERVICE_NAME:
                ServiceController<?> xaController = registry.getRequiredService(AbstractDataSourceDefinition.DATA_SOURCE_CAPABILITY.getCapabilityServiceName(dataSourceName));
                return ((AgroalDataSource) xaController.getValue());
            default:
                throw AgroalLogger.SERVICE_LOGGER.unknownDatasourceServiceType(context.getCurrentAddress().getLastElement().getKey());
        }
    }

    // --- //

    private static class StatisticsEnabledAttributeWriter extends AbstractWriteAttributeHandler<Boolean> {

        private StatisticsEnabledAttributeWriter(AttributeDefinition... definitions) {
            super(definitions);
        }

        @Override
        protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<Boolean> handbackHolder) throws OperationFailedException {
            getDataSource(context).getConfiguration().setMetricsEnabled(resolvedValue.asBoolean());
            return false;
        }

        @Override
        protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, Boolean handback) throws OperationFailedException {
            getDataSource(context).getConfiguration().setMetricsEnabled(valueToRevert.asBoolean());
        }
    }

    private static class ConnectionFactoryAttributeWriter extends AbstractWriteAttributeHandler<AgroalConnectionFactoryConfiguration> {

        private ConnectionFactoryAttributeWriter(AttributeDefinition... definitions) {
            super(definitions);
        }

        @Override
        protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<AgroalConnectionFactoryConfiguration> handbackHolder) throws OperationFailedException {
            // At the moment no attributes support hot. Required
            return false;
        }

        @Override
        protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, AgroalConnectionFactoryConfiguration handback) throws OperationFailedException {
        }
    }

    private static class ConnectionPoolAttributeWriter extends AbstractWriteAttributeHandler<AgroalConnectionPoolConfiguration> {

        private ConnectionPoolAttributeWriter(AttributeDefinition... definitions) {
            super(definitions);
        }

        @Override
        protected boolean applyUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode resolvedValue, ModelNode currentValue, HandbackHolder<AgroalConnectionPoolConfiguration> handbackHolder) throws OperationFailedException {
            ModelNode newBlockingTimeout = resolvedValue.remove(AbstractDataSourceDefinition.BLOCKING_TIMEOUT_MILLIS_ATTRIBUTE.getName());
            ModelNode newMaxSize = resolvedValue.remove(AbstractDataSourceDefinition.MAX_SIZE_ATTRIBUTE.getName());
            ModelNode newMinSize = resolvedValue.remove(AbstractDataSourceDefinition.MIN_SIZE_ATTRIBUTE.getName());

            for (String attribute : resolvedValue.keys()) {
                if (!currentValue.hasDefined(attribute) || !resolvedValue.get(attribute).equals(currentValue.get(attribute))) {
                    // Other attributes changed. Restart required
                    return true;
                }
            }

            if (newBlockingTimeout != null) {
                getDataSource(context).getConfiguration().connectionPoolConfiguration().setAcquisitionTimeout(Duration.ofMillis(newBlockingTimeout.asInt()));
            }
            if (newMaxSize != null) {
                getDataSource(context).getConfiguration().connectionPoolConfiguration().setMaxSize(newMaxSize.asInt());
                // if max-size decreases Agroal will gracefully destroy connections when they are returned to the pool, so there is nothing to do here
            }
            if (newMinSize != null) {
                getDataSource(context).getConfiguration().connectionPoolConfiguration().setMinSize(newMinSize.asInt());
                // if min-size increases Agroal will create new connections when looking into the (shared) pool. FlushMode.FILL could be used here to enforce the new min-size
            }
            return false;
        }

        @Override
        protected void revertUpdateToRuntime(OperationContext context, ModelNode operation, String attributeName, ModelNode valueToRestore, ModelNode valueToRevert, AgroalConnectionPoolConfiguration handback) throws OperationFailedException {
            ModelNode newBlockingTimeout = valueToRevert.remove(AbstractDataSourceDefinition.BLOCKING_TIMEOUT_MILLIS_ATTRIBUTE.getName());
            ModelNode newMaxSize = valueToRevert.remove(AbstractDataSourceDefinition.MAX_SIZE_ATTRIBUTE.getName());
            ModelNode newMinSize = valueToRevert.remove(AbstractDataSourceDefinition.MIN_SIZE_ATTRIBUTE.getName());

            if (newBlockingTimeout != null) {
                getDataSource(context).getConfiguration().connectionPoolConfiguration().setAcquisitionTimeout(Duration.ofMillis(newBlockingTimeout.asInt()));
            }
            if (newMinSize != null) {
                getDataSource(context).getConfiguration().connectionPoolConfiguration().setMinSize(newMinSize.asInt());
            }
            if (newMaxSize != null) {
                getDataSource(context).getConfiguration().connectionPoolConfiguration().setMaxSize(newMaxSize.asInt());
            }
        }
    }

    // --- //

    private static class FlushOperation implements OperationStepHandler {

        private AgroalDataSource.FlushMode mode;

        private FlushOperation(AgroalDataSource.FlushMode mode) {
            this.mode = mode;
        }

        @Override
        public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
            if (context.isNormalServer()) {
                AgroalLogger.SERVICE_LOGGER.flushOperation(mode);
                getDataSource(context).flush(mode);
            }
        }
    }

    // --- //

    private static class StatisticsGetOperation implements OperationStepHandler {

        @Override
        public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
            if (context.isNormalServer()) {
                AgroalDataSourceMetrics metrics = getDataSource(context).getMetrics();

                ModelNode result = new ModelNode();

                result.get(AbstractDataSourceDefinition.STATISTICS_ACQUIRE_COUNT_ATTRIBUTE.getName()).set(metrics.acquireCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_ACTIVE_COUNT_ATTRIBUTE.getName()).set(metrics.activeCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_AVAILABLE_COUNT_ATTRIBUTE.getName()).set(metrics.availableCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_AWAITING_COUNT_ATTRIBUTE.getName()).set(metrics.awaitingCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_CREATION_COUNT_ATTRIBUTE.getName()).set(metrics.creationCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_DESTOY_COUNT_ATTRIBUTE.getName()).set(metrics.destroyCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_FLUSH_COUNT_ATTRIBUTE.getName()).set(metrics.flushCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_INVALID_COUNT_ATTRIBUTE.getName()).set(metrics.invalidCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_LEAK_DETECTION_COUNT_ATTRIBUTE.getName()).set(metrics.leakDetectionCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_MAX_USED_COUNT_ATTRIBUTE.getName()).set(metrics.maxUsedCount());
                result.get(AbstractDataSourceDefinition.STATISTICS_REAP_COUNT_ATTRIBUTE.getName()).set(metrics.reapCount());

                result.get(AbstractDataSourceDefinition.STATISTICS_BLOCKING_TIME_AVERAGE_ATTRIBUTE.getName()).set(metrics.blockingTimeAverage().toMillis());
                result.get(AbstractDataSourceDefinition.STATISTICS_BLOCKING_TIME_MAX_ATTRIBUTE.getName()).set(metrics.blockingTimeMax().toMillis());
                result.get(AbstractDataSourceDefinition.STATISTICS_BLOCKING_TIME_TOTAL_ATTRIBUTE.getName()).set(metrics.blockingTimeTotal().toMillis());

                result.get(AbstractDataSourceDefinition.STATISTICS_CREATION_TIME_AVERAGE_ATTRIBUTE.getName()).set(metrics.creationTimeAverage().toMillis());
                result.get(AbstractDataSourceDefinition.STATISTICS_CREATION_TIME_MAX_ATTRIBUTE.getName()).set(metrics.creationTimeMax().toMillis());
                result.get(AbstractDataSourceDefinition.STATISTICS_CREATION_TIME_TOTAL_ATTRIBUTE.getName()).set(metrics.creationTimeTotal().toMillis());

                context.getResult().set(result);
            }
        }
    }

    private static class ResetStatisticsOperation implements OperationStepHandler {

        @Override
        public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
            if (context.isNormalServer()) {
                getDataSource(context).getMetrics().reset();
            }
        }
    }

    // --- //

    private static class TestConnectionOperation implements OperationStepHandler {

        @Override
        public void execute(OperationContext context, ModelNode operation) throws OperationFailedException {
            if (context.isNormalServer()) {
                try (Connection connection = getDataSource(context).getConnection()) {
                    context.getResult().set(new ModelNode().add(connection.isValid(0)));
                } catch (SQLException e) {
                    throw AgroalLogger.SERVICE_LOGGER.invalidConnection(e, context.getCurrentAddressValue());
                }
            }
        }
    }
}


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

import org.jboss.as.ee.logging.EeLogger;
import org.jboss.as.naming.ManagedReference;
import org.jboss.invocation.Interceptor;
import org.jboss.invocation.InterceptorContext;

/**
 * An interceptor which releases a managed reference.
 *
 * @author David M. Lloyd
 */
class ManagedReferenceReleaseInterceptor implements Interceptor {

    private final Object contextKey;

    /**
     * Construct a new instance.
     *
     * @param contextKey the context key
     */
    ManagedReferenceReleaseInterceptor(final Object contextKey) {
        if (contextKey == null) {
            throw EeLogger.ROOT_LOGGER.nullVar("contextKey");
        }
        this.contextKey = contextKey;
    }

    /**
     * {@inheritDoc}
     */
    public Object processInvocation(final InterceptorContext context) throws Exception {
        try {
            return context.proceed();
        } finally {
            final ManagedReference managedReference = (ManagedReference) context.getPrivateData(ComponentInstance.class).getInstanceData(contextKey);
            if (managedReference != null) {
                managedReference.release();
            }
        }
    }
}