com.mirth.connect.server.ExtensionLoader.java Source code

Java tutorial

Introduction

Here is the source code for com.mirth.connect.server.ExtensionLoader.java

Source

/*
 * Copyright (c) Mirth Corporation. All rights reserved.
 * 
 * http://www.mirthcorp.com
 * 
 * The software in this package is published under the terms of the MPL license a copy of which has
 * been included with this distribution in the LICENSE.txt file.
 */

package com.mirth.connect.server;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.filefilter.AndFileFilter;
import org.apache.commons.io.filefilter.FileFilterUtils;
import org.apache.commons.io.filefilter.IOFileFilter;
import org.apache.commons.io.filefilter.NameFileFilter;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;

import com.mirth.connect.model.ConnectorMetaData;
import com.mirth.connect.model.MetaData;
import com.mirth.connect.model.PluginClass;
import com.mirth.connect.model.PluginMetaData;
import com.mirth.connect.model.converters.ObjectXMLSerializer;
import com.mirth.connect.server.tools.ClassPathResource;
import com.mirth.connect.server.util.ResourceUtil;

public final class ExtensionLoader {
    private final static ExtensionLoader instance = new ExtensionLoader();

    public static ExtensionLoader getInstance() {
        return instance;
    }

    private Map<String, ConnectorMetaData> connectorMetaDataMap = new HashMap<String, ConnectorMetaData>();
    private Map<String, PluginMetaData> pluginMetaDataMap = new HashMap<String, PluginMetaData>();
    private Map<String, ConnectorMetaData> connectorProtocolsMap = new HashMap<String, ConnectorMetaData>();
    private Map<String, MetaData> invalidMetaDataMap = new HashMap<String, MetaData>();
    private boolean loadedExtensions = false;
    private ObjectXMLSerializer serializer = ObjectXMLSerializer.getInstance();
    private static Logger logger = Logger.getLogger(ExtensionLoader.class);

    private ExtensionLoader() {
    }

    public Map<String, ConnectorMetaData> getConnectorMetaData() {
        loadExtensions();
        return connectorMetaDataMap;
    }

    public Map<String, PluginMetaData> getPluginMetaData() {
        loadExtensions();
        return pluginMetaDataMap;
    }

    public Map<String, ConnectorMetaData> getConnectorProtocols() {
        loadExtensions();
        return connectorProtocolsMap;
    }

    public Map<String, MetaData> getInvalidMetaData() {
        loadExtensions();
        return invalidMetaDataMap;
    }

    @SuppressWarnings("unchecked")
    public <T> Class<T> getControllerClass(Class<T> abstractClass) {
        Class<T> overrideClass = null;
        PluginClass highestPluginClassModel = null;

        for (PluginMetaData pluginMetaData : getPluginMetaData().values()) {
            List<PluginClass> controllerClasses = pluginMetaData.getControllerClasses();

            if (controllerClasses != null) {
                for (PluginClass controllerClassModel : controllerClasses) {
                    try {
                        Class<?> pluginClass = Class.forName(controllerClassModel.getName());

                        if (abstractClass.isAssignableFrom(pluginClass) && (highestPluginClassModel == null
                                || highestPluginClassModel.getWeight() < controllerClassModel.getWeight())) {
                            highestPluginClassModel = controllerClassModel;
                            overrideClass = (Class<T>) pluginClass;
                        }
                    } catch (Exception e) {
                        logger.error("An error occurred while attempting to load \""
                                + controllerClassModel.getName() + "\" from plugin: " + pluginMetaData.getName(),
                                e);
                    }
                }
            }
        }

        return overrideClass;
    }

    public <T> T getControllerInstance(Class<T> abstractClass) {
        Class<T> overrideClass = getControllerClass(abstractClass);

        if (overrideClass != null) {
            try {
                T instance = overrideClass.newInstance();
                logger.debug("Using custom " + abstractClass.getSimpleName() + ": " + overrideClass.getName());
                return instance;
            } catch (Exception e) {
                logger.error("An error occurred while attempting to instantiate " + abstractClass.getSimpleName()
                        + " implementation: " + overrideClass.getName(), e);
            }
        }

        logger.debug("Using default " + abstractClass.getSimpleName());
        return null;
    }

    public boolean isExtensionCompatible(MetaData metaData) {
        String serverMirthVersion;
        try {
            serverMirthVersion = getServerVersion();
        } catch (Exception e) {
            logger.error("An error occurred while attempting to determine the current server version.", e);
            return false;
        }

        String[] extensionMirthVersions = metaData.getMirthVersion().split(",");

        logger.debug("checking extension \"" + metaData.getName() + "\" version compatability: versions="
                + ArrayUtils.toString(extensionMirthVersions) + ", server=" + serverMirthVersion);

        // if there is no build version, just use the patch version
        if (serverMirthVersion.split("\\.").length == 4) {
            serverMirthVersion = serverMirthVersion.substring(0, serverMirthVersion.lastIndexOf('.'));
        }

        for (int i = 0; i < extensionMirthVersions.length; i++) {
            if (extensionMirthVersions[i].trim().equals(serverMirthVersion)) {
                return true;
            }
        }

        return false;
    }

    /**
     * Loads the metadata files (plugin.xml, source.xml, destination.xml) for all extensions of the
     * specified type. If this function fails to parse the metadata file for an extension, it will
     * skip it and continue.
     */
    private synchronized void loadExtensions() {
        if (!loadedExtensions) {
            try {
                // match all of the file names for the extension
                IOFileFilter nameFileFilter = new NameFileFilter(
                        new String[] { "plugin.xml", "source.xml", "destination.xml" });
                // this is probably not needed, but we dont want to pick up directories,
                // so we AND the two filters
                IOFileFilter andFileFilter = new AndFileFilter(nameFileFilter, FileFilterUtils.fileFileFilter());
                // this is directory where extensions are located
                File extensionPath = new File(getExtensionsPath());
                // do a recursive scan for extension files
                Collection<File> extensionFiles = FileUtils.listFiles(extensionPath, andFileFilter,
                        FileFilterUtils.trueFileFilter());

                for (File extensionFile : extensionFiles) {
                    try {
                        MetaData metaData = (MetaData) serializer
                                .deserialize(FileUtils.readFileToString(extensionFile), MetaData.class);

                        if (isExtensionCompatible(metaData)) {
                            if (metaData instanceof ConnectorMetaData) {
                                ConnectorMetaData connectorMetaData = (ConnectorMetaData) metaData;
                                connectorMetaDataMap.put(connectorMetaData.getName(), connectorMetaData);

                                if (StringUtils.contains(connectorMetaData.getProtocol(), ":")) {
                                    for (String protocol : connectorMetaData.getProtocol().split(":")) {
                                        connectorProtocolsMap.put(protocol, connectorMetaData);
                                    }
                                } else {
                                    connectorProtocolsMap.put(connectorMetaData.getProtocol(), connectorMetaData);
                                }
                            } else if (metaData instanceof PluginMetaData) {
                                pluginMetaDataMap.put(metaData.getName(), (PluginMetaData) metaData);
                            }
                        } else {
                            logger.error("Extension \"" + metaData.getName()
                                    + "\" is not compatible with this version of Mirth Connect and was not loaded. Please install a compatible version.");
                            invalidMetaDataMap.put(metaData.getName(), metaData);
                        }
                    } catch (Exception e) {
                        logger.error("Error reading or parsing extension metadata file: " + extensionFile.getName(),
                                e);
                    }
                }
            } catch (Exception e) {
                logger.error("Error loading extension metadata.", e);
            } finally {
                loadedExtensions = true;
            }
        }
    }

    /**
     * If in an IDE, extensions will be on the classpath as a resource. If that's the case, use that
     * directory. Otherwise, use the mirth home directory and append extensions.
     * 
     * @return
     */
    private String getExtensionsPath() {
        if (ClassPathResource.getResourceURI("extensions") != null) {
            return ClassPathResource.getResourceURI("extensions").getPath() + File.separator;
        } else {
            return new File(ClassPathResource.getResourceURI("mirth.properties")).getParentFile().getParent()
                    + File.separator + "extensions" + File.separator;
        }
    }

    private String getServerVersion() throws FileNotFoundException, ConfigurationException {
        PropertiesConfiguration versionConfig = new PropertiesConfiguration();
        versionConfig.setDelimiterParsingDisabled(true);
        InputStream versionPropertiesStream = ResourceUtil.getResourceStream(ExtensionLoader.class,
                "version.properties");
        versionConfig.load(versionPropertiesStream);
        IOUtils.closeQuietly(versionPropertiesStream);
        return versionConfig.getString("mirth.version");
    }
}