com.adaptris.core.runtime.AdapterRegistry.java Source code

Java tutorial

Introduction

Here is the source code for com.adaptris.core.runtime.AdapterRegistry.java

Source

/*
 * Copyright 2015 Adaptris Ltd.
 * 
 * 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 com.adaptris.core.runtime;

import java.beans.Transient;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import javax.management.InstanceNotFoundException;
import javax.management.JMX;
import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.validation.constraints.NotNull;

import org.apache.commons.io.IOUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.adaptris.annotation.AdvancedConfig;
import com.adaptris.annotation.AutoPopulated;
import com.adaptris.annotation.ComponentProfile;
import com.adaptris.annotation.DisplayOrder;
import com.adaptris.annotation.InputFieldDefault;
import com.adaptris.core.Adapter;
import com.adaptris.core.CoreException;
import com.adaptris.core.DefaultMarshaller;
import com.adaptris.core.EventFactory;
import com.adaptris.core.XStreamJsonMarshaller;
import com.adaptris.core.event.AdapterShutdownEvent;
import com.adaptris.core.fs.FsHelper;
import com.adaptris.core.util.JmxHelper;
import com.adaptris.util.URLString;
import com.thoughtworks.xstream.annotations.XStreamAlias;

import io.github.classgraph.ClassGraph;
import io.github.classgraph.ScanResult;

@SuppressWarnings("deprecation")
public class AdapterRegistry implements AdapterRegistryMBean {

    // Packages for fast class path we never want to scan
    private static final String[] FCS_BLACKLIST = { "javax", "java", "org.apache", "org.codehaus", "org.hibernate",
            "org.springframework", "com.mchange", "com.sun", "org.bouncycastle", "org.eclipse", "org.jboss",
            "org.slf4j", "net.sf.saxon", "com.google.guava", "com.fasterxml", "io.github.classgraph", "com.jcraft",
            "com.thoughtworks", "org.quartz" };
    private static final String EXCEPTION_MSG_XML_NULL = "XML String is null";
    private static final String EXCEPTION_MSG_URL_NULL = "URL is null";
    private static final String EXCEPTION_MSG_MBEAN_NULL = "AdapterManagerMBean is null";
    private static final String EXCEPTION_MSG_NO_VCR = "No Runtime Version Control";

    private final Set<ObjectName> registeredAdapters = new HashSet<ObjectName>();
    private transient ObjectName myObjectName;
    private transient MBeanServer mBeanServer;
    private static transient Logger log = LoggerFactory.getLogger(AdapterRegistry.class);
    private transient Map<ObjectName, URLString> configurationURLs = new HashMap<>();
    private transient Map<ObjectName, AdapterBuilder> builderByObjectName = new HashMap<>();
    private transient Map<Properties, AdapterBuilder> builderByProps = new HashMap<>();
    private transient AdapterBuilder defaultBuilder;

    private AdapterRegistry() throws MalformedObjectNameException {
        mBeanServer = JmxHelper.findMBeanServer();
        myObjectName = ObjectName.getInstance(STANDARD_REGISTRY_JMX_NAME);
    }

    public static AdapterRegistryMBean findInstance(Properties cfg)
            throws MalformedObjectNameException, CoreException {
        MBeanServer mbs = JmxHelper.findMBeanServer();
        AdapterRegistryMBean result = null;
        ObjectName objName = ObjectName.getInstance(STANDARD_REGISTRY_JMX_NAME);
        if (mbs.isRegistered(objName)) {
            result = JMX.newMBeanProxy(mbs, objName, AdapterRegistryMBean.class);
            result.addConfiguration(cfg);
        } else {
            result = new AdapterRegistry();
            result.registerMBean();
            result.addConfiguration(cfg);
        }
        return result;
    }

    @Override
    public ObjectName createObjectName() {
        return myObjectName;
    }

    @Override
    public void registerMBean() throws CoreException {
        try {
            JmxHelper.register(createObjectName(), this);
        } catch (Exception e) {
            throw new CoreException(e);
        }

    }

    @Override
    public void unregisterMBean() throws CoreException {
        try {
            JmxHelper.unregister(createObjectName());
            for (AdapterBuilder builder : builderByProps.values()) {
                builder.unregisterMBean();
            }
        } catch (Exception e) {
            throw new CoreException(e);
        }
    }

    @Override
    public Set<ObjectName> getAdapters() {
        return new HashSet<ObjectName>(registeredAdapters);
    }

    @Override
    public URLString getConfigurationURL(ObjectName adapterName) {
        return configurationURLs.get(adapterName);
    }

    @Override
    public String getConfigurationURLString(ObjectName adapterName) {
        URLString url = getConfigurationURL(adapterName);
        if (url != null) {
            return url.toString();
        }
        return null;
    }

    @Override
    public boolean removeConfigurationURL(ObjectName adapterName) {
        URLString removed = null;
        synchronized (configurationURLs) {
            removed = configurationURLs.remove(adapterName);
        }
        return removed != null;
    }

    @Override
    public void putConfigurationURL(ObjectName adapterName, URLString configUrl) {
        if (configUrl != null) {
            synchronized (configurationURLs) {
                configurationURLs.put(adapterName, configUrl);
            }
        }
    }

    @Override
    public void putConfigurationURL(ObjectName adapterName, String configUrl) throws IOException {
        putConfigurationURL(adapterName, new URLString(configUrl));
    }

    @Override
    public void persistAdapter(AdapterManagerMBean adapter, URLString url) throws CoreException, IOException {
        assertNotNull(adapter, EXCEPTION_MSG_MBEAN_NULL);
        persist(adapter.getConfiguration(), url);
    }

    @Override
    public void persistAdapter(AdapterManagerMBean adapter, String url) throws CoreException, IOException {
        assertNotNull(url, EXCEPTION_MSG_URL_NULL);
        persistAdapter(adapter, new URLString(url));
    }

    @Override
    public void persistAdapter(ObjectName adapter, URLString url) throws CoreException, IOException {
        persistAdapter(JMX.newMBeanProxy(mBeanServer, adapter, AdapterManagerMBean.class), url);
    }

    @Override
    public void persistAdapter(ObjectName adapter, String url) throws CoreException, IOException {
        assertNotNull(url, EXCEPTION_MSG_URL_NULL);
        persistAdapter(adapter, new URLString(url));
    }

    @Override
    public void persist(String data, URLString configUrl) throws CoreException, IOException {
        assertNotNull(configUrl, EXCEPTION_MSG_URL_NULL);
        URL url = configUrl.getURL();
        OutputStream out = null;
        try {
            if ("file".equalsIgnoreCase(url.getProtocol())) {
                out = new FileOutputStream(FsHelper.createFileReference(url));
                persist(data, out);
            } else {
                URLConnection urlConnection = url.openConnection();
                urlConnection.setDoOutput(true);
                out = urlConnection.getOutputStream();
                persist(data, out);
            }
        } finally {
            IOUtils.closeQuietly(out);
        }
    }

    private void persist(String data, OutputStream out) throws IOException {
        try (PrintStream ps = new PrintStream(out)) {
            ps.println(data);
        }
    }

    @Override
    public ObjectName createAdapter(URLString url) throws IOException, MalformedObjectNameException, CoreException {
        assertNotNull(url, EXCEPTION_MSG_URL_NULL);
        return defaultBuilder.createAdapter(url);
    }

    @Override
    public ObjectName createAdapterFromUrl(String url)
            throws IOException, MalformedObjectNameException, CoreException {
        assertNotNull(url, EXCEPTION_MSG_URL_NULL);
        return createAdapter(new URLString(url));
    }

    @Override
    public ObjectName createAdapter(String xml) throws IOException, MalformedObjectNameException, CoreException {
        assertNotNull(xml, EXCEPTION_MSG_XML_NULL);
        return defaultBuilder.createAdapter(xml);
    }

    @Override
    public void addAdapter(AdapterManagerMBean adapterManager) throws MalformedObjectNameException, CoreException {
        assertNotNull(adapterManager, EXCEPTION_MSG_MBEAN_NULL);
        addRegisteredAdapter(adapterManager.createObjectName());
    }

    private void addRegisteredAdapter(ObjectName adapterName) throws CoreException {
        synchronized (registeredAdapters) {
            if (registeredAdapters.contains(adapterName)) {
                throw new CoreException("[" + adapterName + "] already exists in the registry, remove it first");
            }
            registeredAdapters.remove(adapterName);
            registeredAdapters.add(adapterName);
        }
    }

    private void removeRegisteredAdapter(ObjectName adapterName) throws CoreException {
        synchronized (registeredAdapters) {
            registeredAdapters.remove(adapterName);
        }
    }

    ObjectName register(AdapterBuilder builder, Adapter adapter, URLString configUrl)
            throws CoreException, MalformedObjectNameException {
        AdapterManager manager = new AdapterManager(adapter);
        ObjectName adapterName = manager.createObjectName();
        addRegisteredAdapter(adapterName);
        manager.registerMBean();
        putConfigurationURL(adapterName, configUrl);
        synchronized (builderByObjectName) {
            builderByObjectName.put(adapterName, builder);
        }
        return adapterName;
    }

    @Override
    public void destroyAdapter(AdapterManagerMBean adapter) throws CoreException, MalformedObjectNameException {
        assertNotNull(adapter, EXCEPTION_MSG_MBEAN_NULL);
        ObjectName name = adapter.createObjectName();
        adapter.requestClose();
        adapter.unregisterMBean();
        removeRegisteredAdapter(name);
        synchronized (builderByObjectName) {
            builderByObjectName.remove(name);
        }
    }

    @Override
    public void destroyAdapter(ObjectName adapterName) throws MalformedObjectNameException, CoreException {
        destroyAdapter(JMX.newMBeanProxy(mBeanServer, adapterName, AdapterManagerMBean.class));
    }

    /**
     * Convenience method for sending a shutdown event for every adapter.
     * 
     * @param adapterManagers set of managers.
     */
    public static void sendShutdownEvent(Set<ObjectName> adapterManagers) {
        for (ObjectName obj : adapterManagers) {
            AdapterManagerMBean mgr = JMX.newMBeanProxy(JmxHelper.findMBeanServer(), obj,
                    AdapterManagerMBean.class);
            try {
                mgr.sendLifecycleEvent(EventFactory.create(AdapterShutdownEvent.class));
            } catch (CoreException e) {
                log.warn("Failed to send ShutdownEvent for " + mgr.getUniqueId());
            }
        }
    }

    /**
     * Convenience method to start a set of adapter managers that uses {@link AdapterManagerMBean#requestStart()}
     *
     * @param adapterManagers set of managers.
     */
    public static void start(Set<ObjectName> adapterManagers) throws CoreException {
        for (ObjectName obj : adapterManagers) {
            AdapterManagerMBean mgr = JMX.newMBeanProxy(JmxHelper.findMBeanServer(), obj,
                    AdapterManagerMBean.class);
            mgr.requestStart();
        }
    }

    /**
     * Convenience method to close a set of adapter managers that uses
     * {@link AdapterManagerMBean#requestClose()}
     *
     * @param adapterManagers set of managers.
     * @throws CoreException wrapping other exceptions
     */
    public static void close(Set<ObjectName> adapterManagers) throws CoreException {
        for (ObjectName obj : adapterManagers) {
            AdapterManagerMBean mgr = JMX.newMBeanProxy(JmxHelper.findMBeanServer(), obj,
                    AdapterManagerMBean.class);
            mgr.requestClose();
        }
    }

    /**
     * Convenience method to close a set of adapter managers that uses
     * {@link AdapterManagerMBean#requestStop()}
     *
     * @param adapterManagers set of managers.
     * @throws CoreException wrapping other exceptions
     */
    public static void stop(Set<ObjectName> adapterManagers) throws CoreException {
        for (ObjectName obj : adapterManagers) {
            AdapterManagerMBean mgr = JMX.newMBeanProxy(JmxHelper.findMBeanServer(), obj,
                    AdapterManagerMBean.class);
            mgr.requestStop();
        }
    }

    /**
     * Convenience method to close a set of adapter managers that uses
     * {@link AdapterManagerMBean#unregisterMBean()}
     *
     * @param adapterManagers set of managers.
     * @throws CoreException wrapping other exceptions
     */
    public static void unregister(Set<ObjectName> adapterManagers) throws CoreException {
        for (ObjectName obj : adapterManagers) {
            AdapterManagerMBean mgr = JMX.newMBeanProxy(JmxHelper.findMBeanServer(), obj,
                    AdapterManagerMBean.class);
            mgr.unregisterMBean();
        }
    }

    private static void assertNotNull(Object o, String msg) throws CoreException {
        if (o == null) {
            throw new CoreException(msg);
        }
    }

    @Override
    public Properties getConfiguration() {
        return defaultBuilder.getConfiguration();
    }

    @Override
    public String getVersionControl() {
        String name = null;
        for (AdapterBuilder builder : builderByProps.values()) {
            name = builder.getVersionControl();
            if (name != null)
                break;
        }
        return name;
    }

    @Override
    public void reloadFromVersionControl()
            throws MalformedObjectNameException, CoreException, MalformedURLException, IOException {

        assertNotNull(getVersionControl(), EXCEPTION_MSG_NO_VCR);
        for (ObjectName o : getAdapters()) {
            destroyAdapter(o);
        }
        for (AdapterBuilder builder : builderByProps.values()) {
            builder.updateVCS();
            builder.createAdapter();
        }
    }

    @Override
    public Set<ObjectName> reloadFromConfig()
            throws MalformedObjectNameException, CoreException, MalformedURLException, IOException {
        for (ObjectName o : getAdapters()) {
            destroyAdapter(o);
        }
        Set<ObjectName> result = new HashSet<>();
        for (AdapterBuilder builder : builderByProps.values()) {
            result.add(builder.createAdapter());
        }
        return result;
    }

    @Override
    public ObjectName reloadFromConfig(ObjectName obj)
            throws MalformedObjectNameException, CoreException, IOException {
        return reload(obj, false);
    }

    @Override
    public ObjectName reloadFromVersionControl(ObjectName obj)
            throws MalformedObjectNameException, CoreException, IOException {
        return reload(obj, true);
    }

    private ObjectName reload(ObjectName obj, boolean vcsUpdate)
            throws MalformedObjectNameException, CoreException, IOException {
        destroyAdapter(obj);
        ObjectName newAdapter = null;
        synchronized (builderByObjectName) {
            AdapterBuilder builder = builderByObjectName.containsKey(obj) ? builderByObjectName.get(obj)
                    : defaultBuilder;
            if (vcsUpdate)
                builder.updateVCS();
            newAdapter = builder.createAdapter();
            builderByObjectName.put(newAdapter, builder);
        }
        return newAdapter;
    }

    @Override
    public void addConfiguration(Properties cfg) throws MalformedObjectNameException, CoreException {
        synchronized (builderByProps) {
            AdapterBuilder builder = builderByProps.containsKey(cfg) ? builderByProps.get(cfg)
                    : new AdapterBuilder(this, cfg);
            if (defaultBuilder == null) {
                defaultBuilder = builder;
            }
            if (!mBeanServer.isRegistered(builder.createObjectName())) {
                builder.registerMBean();
            }
            builderByProps.put(cfg, builder);
        }
    }

    @Override
    public void validateConfig(String config) throws CoreException {
        try {
            assertNotNull(config, EXCEPTION_MSG_XML_NULL);
            String xml = defaultBuilder.loadPreProcessors().process(config);
            DefaultMarshaller.getDefaultMarshaller().unmarshal(xml);
        } catch (CoreException e) {
            // We do this so that we don't have nested causes as it's possible that
            // some exceptions may not be serializable for the UI.
            throw new CoreException(e.getMessage());
        }
    }

    @Override
    public String getClassDefinition(String className) throws CoreException {
        final ClassDescriptor classDescriptor = new ClassDescriptor(className);
        try {
            Class<?> clazz = Class.forName(className);

            classDescriptor.setClassType(ClassDescriptor.ClassType.getTypeForClass(clazz).name().toLowerCase());

            List<String> displayOrder = new ArrayList<>();
            for (Annotation annotation : clazz.getAnnotations()) {
                if (XStreamAlias.class.isAssignableFrom(annotation.annotationType())) {
                    classDescriptor.setAlias(((XStreamAlias) annotation).value());
                } else if (ComponentProfile.class.isAssignableFrom(annotation.annotationType())) {
                    classDescriptor.setTags(((ComponentProfile) annotation).tag());
                    classDescriptor.setSummary(((ComponentProfile) annotation).summary());
                } else if (DisplayOrder.class.isAssignableFrom(annotation.annotationType())) {
                    displayOrder = Arrays.asList(((DisplayOrder) annotation).order());
                }
            }

            for (Field field : clazz.getDeclaredFields()) {
                if ((!Modifier.isStatic(field.getModifiers()))
                        && (field.getDeclaredAnnotation(Transient.class) == null)) { // if we're not transient
                    ClassDescriptorProperty fieldProperty = new ClassDescriptorProperty();
                    fieldProperty.setOrder(
                            displayOrder.contains(field.getName()) ? displayOrder.indexOf(field.getName()) + 1
                                    : 999);
                    fieldProperty.setAdvanced(false);
                    fieldProperty.setClassName(field.getType().getName());
                    fieldProperty.setType(field.getType().getSimpleName());
                    fieldProperty.setName(field.getName());
                    fieldProperty.setAutoPopulated(field.getDeclaredAnnotation(AutoPopulated.class) != null);
                    fieldProperty.setNullAllowed(field.getDeclaredAnnotation(NotNull.class) != null);

                    for (Annotation annotation : field.getDeclaredAnnotations()) {
                        if (AdvancedConfig.class.isAssignableFrom(annotation.annotationType())) {
                            fieldProperty.setAdvanced(true);
                        } else if (InputFieldDefault.class.isAssignableFrom(annotation.annotationType())) {
                            fieldProperty.setDefaultValue(((InputFieldDefault) annotation).value());
                        }
                    }
                    classDescriptor.getClassDescriptorProperties().add(fieldProperty);
                }
            }

            try (ScanResult result = new ClassGraph().enableAllInfo().blacklistPackages(FCS_BLACKLIST).scan()) {

                List<String> subclassNames = result.getSubclasses(className).getNames();

                for (String subclassName : subclassNames) {
                    classDescriptor.getSubTypes().add(subclassName);
                }
            }

        } catch (ClassNotFoundException e) {
            throw new CoreException(e);
        }
        return new XStreamJsonMarshaller().marshal(classDescriptor);
    }

    @Override
    public Set<ObjectName> getBuilders() {
        Set<ObjectName> result = new HashSet<>();
        for (AdapterBuilder b : builderByProps.values()) {
            result.add(b.createObjectName());
        }
        return result;
    }

    @Override
    public ObjectName getBuilder(ObjectName p) throws InstanceNotFoundException {
        AdapterBuilder b = builderByObjectName.get(p);
        if (b == null) {
            throw new InstanceNotFoundException("No Builder for " + p);
        }
        return b.createObjectName();
    }

    @Override
    public AdapterBuilderMBean getBuilderMBean(ObjectName p) throws InstanceNotFoundException {
        AdapterBuilderMBean b = builderByObjectName.get(p);
        if (b == null) {
            throw new InstanceNotFoundException("No Builder for " + p);
        }
        return b;
    }

    @Override
    public AdapterBuilderMBean getBuilder(Properties p) throws InstanceNotFoundException {
        AdapterBuilder b = builderByProps.get(p);
        if (b == null) {
            throw new InstanceNotFoundException("No Builder for " + p);
        }
        return b;
    }

    Set<AdapterBuilder> builders() {
        return new HashSet<AdapterBuilder>(builderByProps.values());
    }
}