org.openspaces.pu.container.standalone.StandaloneProcessingUnitContainerProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.openspaces.pu.container.standalone.StandaloneProcessingUnitContainerProvider.java

Source

/*
 * Copyright (c) 2008-2016, GigaSpaces Technologies, Inc. All Rights Reserved.
 *
 * 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.openspaces.pu.container.standalone;

import com.gigaspaces.internal.io.BootIOUtils;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openspaces.core.cluster.ClusterInfo;
import org.openspaces.pu.container.CannotCreateContainerException;
import org.openspaces.pu.container.ProcessingUnitContainer;
import org.openspaces.pu.container.spi.ApplicationContextProcessingUnitContainerProvider;
import org.openspaces.pu.container.support.ClusterInfoParser;
import org.openspaces.pu.container.support.CompoundProcessingUnitContainer;
import org.springframework.core.io.Resource;
import org.springframework.util.FileCopyUtils;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * A {@link StandaloneProcessingUnitContainer} provider. A standalone processing unit container is a
 * container that understands a processing unit archive structure (both when working with an
 * "exploded" directory and when working with a zip/jar archive of it). It is provided with the
 * location of the processing unit using {@link StandaloneProcessingUnitContainerProvider#StandaloneProcessingUnitContainerProvider(String)}.
 * The location itself follows Spring resource loader syntax.
 *
 * <p> When creating the container a thread is started with {@link org.openspaces.pu.container.standalone.StandaloneContainerRunnable}.
 * This is done since a custom class loader is created taking into account the processing unit
 * archive structure, and in order to allows using the standlone container within other
 * environments, the new class loader is only set on the newly created thread context.
 *
 * <p> At its core the integrated processing unit container is built around Spring {@link
 * org.springframework.context.ApplicationContext} configured based on a set of config locations.
 *
 * <p> The provider allows for programmatic configuration of different processing unit aspects. It
 * allows to configure where the processing unit Spring context xml descriptors are located (by
 * default it uses <code>classpath*:/META-INF/spring/pu.xml</code>). It also allows to set {@link
 * org.openspaces.core.properties.BeanLevelProperties} and {@link org.openspaces.core.cluster.ClusterInfo}
 * that will be injected to beans configured within the processing unit.
 *
 * <p> For a runnable "main" processing unit container please see {@link
 * StandaloneProcessingUnitContainer#main(String[])}.
 *
 * @author kimchy
 */
public class StandaloneProcessingUnitContainerProvider extends ApplicationContextProcessingUnitContainerProvider {

    private static Log logger = LogFactory.getLog(StandaloneProcessingUnitContainerProvider.class);

    private String location;

    private List<String> configLocations = new ArrayList<String>();

    private boolean addedSharedLibToClassLoader = false;

    /**
     * Constructs a new standalone container provider using the provided location as the location of
     * the processing unit archive (either an exploded archive or a jar/zip archive). The location
     * syntax follows Spring {@link org.springframework.core.io.Resource} syntax.
     *
     * @param location The location of the processing unit archive
     */
    public StandaloneProcessingUnitContainerProvider(String location) {
        this.location = location;
    }

    /**
     * Adds a config location based on a String description using Springs {@link
     * org.springframework.core.io.support.PathMatchingResourcePatternResolver}.
     *
     * @see org.springframework.core.io.support.PathMatchingResourcePatternResolver
     */
    public void addConfigLocation(String configLocation) {
        configLocations.add(configLocation);
    }

    public void addConfigLocation(Resource resource) throws IOException {
        throw new UnsupportedOperationException();
    }

    /**
     * <p> Creates a new {@link StandaloneProcessingUnitContainer} based on the configured
     * parameters. A standalone processing unit container is a container that understands a
     * processing unit archive structure (both when working with an "exploded" directory and when
     * working with a zip/jar archive of it). It is provided with the location of the processing
     * unit using {@link org.openspaces.pu.container.standalone.StandaloneProcessingUnitContainerProvider#StandaloneProcessingUnitContainerProvider(String)}.
     * The location itself follows Spring resource loader syntax.
     *
     * <p> If {@link #addConfigLocation(String)} is used, the Spring xml context will be read based
     * on the provided locations. If no config location was provided the default config location
     * will be <code>classpath*:/META-INF/spring/pu.xml</code>.
     *
     * <p> If {@link #setBeanLevelProperties(org.openspaces.core.properties.BeanLevelProperties)} is
     * set will use the configured bean level properties in order to configure the application
     * context and specific beans within it based on properties. This is done by adding {@link
     * org.openspaces.core.properties.BeanLevelPropertyBeanPostProcessor} and {@link
     * org.openspaces.core.properties.BeanLevelPropertyPlaceholderConfigurer} to the application
     * context.
     *
     * <p> If {@link #setClusterInfo(org.openspaces.core.cluster.ClusterInfo)} is set will use it to
     * inject {@link org.openspaces.core.cluster.ClusterInfo} into beans that implement {@link
     * org.openspaces.core.cluster.ClusterInfoAware}.
     *
     * @return An {@link StandaloneProcessingUnitContainer} instance
     */
    public ProcessingUnitContainer createContainer() throws CannotCreateContainerException {
        File fileLocation = new File(location);
        if (!fileLocation.exists()) {
            throw new CannotCreateContainerException("Failed to locate pu location [" + location + "]");
        }

        // in case we don't have a cluster info specific members
        final ClusterInfo clusterInfo = getClusterInfo();
        if (clusterInfo != null && clusterInfo.getInstanceId() == null) {
            ClusterInfo origClusterInfo = clusterInfo;
            List<ProcessingUnitContainer> containers = new ArrayList<ProcessingUnitContainer>();
            for (int i = 0; i < clusterInfo.getNumberOfInstances(); i++) {
                ClusterInfo containerClusterInfo = clusterInfo.copy();
                containerClusterInfo.setInstanceId(i + 1);
                containerClusterInfo.setBackupId(null);
                setClusterInfo(containerClusterInfo);
                containers.add(createContainer());
                if (clusterInfo.getNumberOfBackups() != null) {
                    for (int j = 0; j < clusterInfo.getNumberOfBackups(); j++) {
                        containerClusterInfo = containerClusterInfo.copy();
                        containerClusterInfo.setBackupId(j + 1);
                        setClusterInfo(containerClusterInfo);
                        containers.add(createContainer());
                    }
                }
            }
            setClusterInfo(origClusterInfo);
            return new CompoundProcessingUnitContainer(
                    containers.toArray(new ProcessingUnitContainer[containers.size()]));
        }

        if (clusterInfo != null) {
            ClusterInfoParser.guessSchema(clusterInfo);
        }

        if (logger.isInfoEnabled()) {
            logger.info("Starting a Standalone processing unit container "
                    + (clusterInfo != null ? "with " + clusterInfo : ""));
        }

        List<URL> urls = new ArrayList<URL>();
        List<URL> sharedUrls = new ArrayList<URL>();
        if (fileLocation.isDirectory()) {
            if (fileLocation.exists()) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Adding pu directory location [" + location + "] to classpath");
                }
                try {
                    urls.add(fileLocation.toURL());
                } catch (MalformedURLException e) {
                    throw new CannotCreateContainerException(
                            "Failed to add classes to class loader with location [" + location + "]", e);
                }
            }
            addJarsLocation(fileLocation, urls, "lib");
            addJarsLocation(fileLocation, sharedUrls, "shared-lib");
        } else {
            JarFile jarFile;
            try {
                jarFile = new JarFile(fileLocation);
            } catch (IOException e) {
                throw new CannotCreateContainerException("Failed to open pu file [" + location + "]", e);
            }
            // add the root to the classpath
            try {
                urls.add(new URL("jar:" + fileLocation.toURL() + "!/"));
            } catch (MalformedURLException e) {
                throw new CannotCreateContainerException(
                        "Failed to add pu location [" + location + "] to classpath", e);
            }
            // add jars in lib and shared-lib to the classpath
            for (Enumeration<JarEntry> entries = jarFile.entries(); entries.hasMoreElements();) {
                JarEntry jarEntry = entries.nextElement();
                if (isWithinDir(jarEntry, "lib") || isWithinDir(jarEntry, "shared-lib")) {
                    // extract the jar into a temp location
                    if (logger.isDebugEnabled()) {
                        logger.debug("Adding jar [" + jarEntry.getName() + "] with pu location [" + location + "]");
                    }
                    File tempLocation = new File(System.getProperty("java.io.tmpdir") + "/openspaces");
                    tempLocation.mkdirs();
                    File tempJar;
                    String tempJarName = jarEntry.getName();
                    if (tempJarName.indexOf('/') != -1) {
                        tempJarName = tempJarName.substring(tempJarName.lastIndexOf('/') + 1);
                    }
                    try {
                        tempJar = File.createTempFile(tempJarName, ".jar", tempLocation);
                    } catch (IOException e) {
                        throw new CannotCreateContainerException("Failed to create temp jar at location ["
                                + tempLocation + "] with name [" + tempJarName + "]", e);
                    }
                    tempJar.deleteOnExit();
                    if (logger.isTraceEnabled()) {
                        logger.trace("Extracting jar [" + jarEntry.getName() + "] to temporary jar ["
                                + tempJar.getAbsolutePath() + "]");
                    }

                    FileOutputStream fos;
                    try {
                        fos = new FileOutputStream(tempJar);
                    } catch (FileNotFoundException e) {
                        throw new CannotCreateContainerException(
                                "Failed to find temp jar [" + tempJar.getAbsolutePath() + "]", e);
                    }
                    InputStream is = null;
                    try {
                        is = jarFile.getInputStream(jarEntry);
                        FileCopyUtils.copy(is, fos);
                    } catch (IOException e) {
                        throw new CannotCreateContainerException(
                                "Failed to create temp jar [" + tempJar.getAbsolutePath() + "]");
                    } finally {
                        if (is != null) {
                            try {
                                is.close();
                            } catch (IOException e1) {
                                // do nothing
                            }
                        }
                        try {
                            fos.close();
                        } catch (IOException e1) {
                            // do nothing
                        }
                    }

                    try {
                        if (isWithinDir(jarEntry, "lib")) {
                            urls.add(tempJar.toURL());
                        } else if (isWithinDir(jarEntry, "shared-lib")) {
                            sharedUrls.add(tempJar.toURL());
                        }
                    } catch (MalformedURLException e) {
                        throw new CannotCreateContainerException("Failed to add pu entry [" + jarEntry.getName()
                                + "] with location [" + location + "]", e);
                    }
                }
            }
        }

        List<URL> allUrls = new ArrayList<URL>();
        allUrls.addAll(sharedUrls);
        allUrls.addAll(urls);

        addUrlsToContextClassLoader(allUrls.toArray(new URL[allUrls.size()]));

        StandaloneContainerRunnable containerRunnable = new StandaloneContainerRunnable(getBeanLevelProperties(),
                clusterInfo, configLocations);
        Thread standaloneContainerThread = new Thread(containerRunnable, "Standalone Container Thread");
        standaloneContainerThread.setDaemon(false);
        standaloneContainerThread.start();

        while (!containerRunnable.isInitialized()) {
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                logger.warn("Interrupted while waiting for standalone container to initialize");
            }
        }

        if (containerRunnable.hasException()) {
            throw new CannotCreateContainerException("Failed to start container", containerRunnable.getException());
        }

        return new StandaloneProcessingUnitContainer(containerRunnable);
    }

    private boolean isWithinDir(JarEntry jarEntry, String dir) {
        return jarEntry.getName().startsWith(dir + "/") && jarEntry.getName().length() > (dir + "/").length();
    }

    private void addJarsLocation(File fileLocation, List<URL> urls, String dir) {
        File libLocation = new File(fileLocation, dir);
        if (libLocation.exists()) {
            File[] jarFiles = BootIOUtils.listFiles(libLocation);
            for (File jarFile : jarFiles) {
                if (logger.isDebugEnabled()) {
                    logger.debug("Adding jar [" + jarFile.getAbsolutePath() + "] with pu directory location ["
                            + location + "] to classpath");
                }
                try {
                    urls.add(jarFile.toURL());
                } catch (MalformedURLException e) {
                    throw new CannotCreateContainerException(
                            "Failed to add jar file [" + jarFile.getAbsolutePath() + "] to classs loader", e);
                }
            }
        }
    }

    /**
     * Adds the shared lib to the thread context class loader (they need to be added to where the
     * openspaces.jar class exists).
     */
    private void addUrlsToContextClassLoader(URL[] urls) {
        if (addedSharedLibToClassLoader) {
            return;
        }
        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class clazz = classLoader.getClass();
        while (clazz != Object.class && !URLClassLoader.class.equals(clazz)) {
            clazz = clazz.getSuperclass();
        }
        if (clazz == Object.class) {
            throw new CannotCreateContainerException(
                    "Failed to find URLClassLoader to add shared lib for " + classLoader.getClass());
        }

        try {
            Method addURL = clazz.getDeclaredMethod("addURL", URL.class);
            addURL.setAccessible(true);
            for (URL url : urls) {
                addURL.invoke(classLoader, url);
            }
        } catch (Exception e) {
            throw new CannotCreateContainerException(
                    "Failed to add shared lib to thread context class loader [" + classLoader + "]", e);
        }
        addedSharedLibToClassLoader = true;
    }
}