org.apache.batchee.cli.command.JobOperatorCommand.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.batchee.cli.command.JobOperatorCommand.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.batchee.cli.command;

import io.airlift.command.Option;
import org.apache.batchee.cli.classloader.ChildFirstURLClassLoader;
import org.apache.batchee.cli.lifecycle.Lifecycle;
import org.apache.batchee.cli.zip.Zips;
import org.apache.batchee.container.exception.BatchContainerRuntimeException;
import org.apache.batchee.jaxrs.client.BatchEEJAXRSClientFactory;
import org.apache.batchee.jaxrs.client.ClientConfiguration;
import org.apache.batchee.jaxrs.client.ClientSecurity;
import org.apache.batchee.jaxrs.client.ClientSslConfiguration;
import org.apache.commons.io.FileUtils;

import javax.batch.operations.JobOperator;
import javax.batch.runtime.BatchRuntime;
import java.io.File;
import java.io.FileFilter;
import java.io.FilenameFilter;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.LinkedList;

import static java.lang.Thread.currentThread;

/**
 * base class handling:
 * - classloader enriched with libs folders (and subfolders)
 * - Lifecycle (allow to start/stop a container)
 *
 * Note: the classloader is created from libs command, it is handy to organize batches
 *       by folders to be able to run them contextual using this command.
*/
public abstract class JobOperatorCommand implements Runnable {
    // Remote config

    @Option(name = "-url", description = "when using JAXRS the batchee resource url")
    protected String baseUrl = null;

    @Option(name = "-json", description = "when using JAXRS the json provider")
    private String jsonProvider = null;

    @Option(name = "-user", description = "when using JAXRS the username")
    private String username = null;

    @Option(name = "-password", description = "when using JAXRS the password")
    private String password = null;

    @Option(name = "-auth", description = "when using JAXRS the authentication type (Basic)")
    private String type = "Basic";

    @Option(name = "-hostnameVerifier", description = "when using JAXRS the hostname verifier")
    private String hostnameVerifier = null;

    @Option(name = "-keystorePassword", description = "when using JAXRS the keystorePassword")
    private String keystorePassword = null;

    @Option(name = "-keystoreType", description = "when using JAXRS the keystoreType (JKS)")
    private String keystoreType = "JKS";

    @Option(name = "-keystorePath", description = "when using JAXRS the keystorePath")
    private String keystorePath = null;

    @Option(name = "-sslContextType", description = "when using JAXRS the sslContextType (TLS)")
    private String sslContextType = "TLS";

    @Option(name = "-keyManagerType", description = "when using JAXRS the keyManagerType (SunX509)")
    private String keyManagerType = "SunX509";

    @Option(name = "-keyManagerPath", description = "when using JAXRS the keyManagerPath")
    private String keyManagerPath = null;

    @Option(name = "-trustManagerAlgorithm", description = "when using JAXRS the trustManagerAlgorithm")
    private String trustManagerAlgorithm = null;

    @Option(name = "-trustManagerProvider", description = "when using JAXRS the trustManagerProvider")
    private String trustManagerProvider = null;

    // local config

    @Option(name = "-lifecycle", description = "the lifecycle class to use")
    private String lifecycle = null;

    @Option(name = "-libs", description = "folder containing additional libraries, the folder is added too to the loader")
    private String libs = null;

    @Option(name = "-archive", description = "a bar archive")
    private String archive = null;

    @Option(name = "-work", description = "work directory (default to java.io.tmp/work)")
    private String work = System.getProperty("batchee.home", System.getProperty("java.io.tmpdir")) + "/work";

    @Option(name = "-shared-libs", description = "folder containing shared libraries, the folder is added too to the loader")
    private String sharedLibs = null;

    @Option(name = "-add-folder-to-loader", description = "force shared lib and libs folders to be added to the classloader")
    private boolean addFolderToLoader = false;

    protected JobOperator operator() {
        if (baseUrl == null) {
            return BatchRuntime.getJobOperator();
        }

        final ClientConfiguration configuration = new ClientConfiguration();
        configuration.setBaseUrl(baseUrl);
        configuration.setJsonProvider(jsonProvider);
        if (hostnameVerifier != null || keystorePath != null || keyManagerPath != null) {
            final ClientSslConfiguration ssl = new ClientSslConfiguration();
            configuration.setSsl(ssl);
            ssl.setHostnameVerifier(hostnameVerifier);
            ssl.setKeystorePassword(keystorePassword);
            ssl.setKeyManagerPath(keyManagerPath);
            ssl.setKeyManagerType(keyManagerType);
            ssl.setKeystorePath(keystorePath);
            ssl.setKeystoreType(keystoreType);
            ssl.setSslContextType(sslContextType);
            ssl.setTrustManagerAlgorithm(trustManagerAlgorithm);
            ssl.setTrustManagerProvider(trustManagerProvider);
        }
        final ClientSecurity security = new ClientSecurity();
        configuration.setSecurity(security);
        security.setUsername(username);
        security.setPassword(password);
        security.setType(type);

        return BatchEEJAXRSClientFactory.newClient(configuration);
    }

    protected void info(final String text) {
        System.out.println(text);
    }

    protected abstract void doRun();

    @Override
    public final void run() {
        System.setProperty("org.apache.batchee.init.verbose.sysout", "true");

        final ClassLoader oldLoader = currentThread().getContextClassLoader();
        final ClassLoader loader;
        try {
            loader = createLoader(oldLoader);
        } catch (final MalformedURLException e) {
            throw new BatchContainerRuntimeException(e);
        }

        if (loader != oldLoader) {
            currentThread().setContextClassLoader(loader);
        }

        try {
            final Lifecycle<Object> lifecycleInstance;
            final Object state;
            if (lifecycle != null) {
                lifecycleInstance = createLifecycle(loader);
                state = lifecycleInstance.start();
            } else {
                lifecycleInstance = null;
                state = null;
            }

            try {
                doRun();
            } finally {
                if (lifecycleInstance != null) {
                    lifecycleInstance.stop(state);
                }
            }
        } finally {
            currentThread().setContextClassLoader(oldLoader);
        }
    }

    private Lifecycle<Object> createLifecycle(final ClassLoader loader) {
        // some shortcuts are nicer to use from CLI
        if ("openejb".equalsIgnoreCase(lifecycle)) {
            lifecycle = "org.apache.batchee.cli.lifecycle.impl.OpenEJBLifecycle";
        } else if ("cdi".equalsIgnoreCase(lifecycle)) {
            lifecycle = "org.apache.batchee.cli.lifecycle.impl.CdiCtrlLifecycle";
        } else if ("spring".equalsIgnoreCase(lifecycle)) {
            lifecycle = "org.apache.batchee.cli.lifecycle.impl.SpringLifecycle";
        }

        try {
            return (Lifecycle<Object>) loader.loadClass(lifecycle).newInstance();
        } catch (final Exception e) {
            throw new BatchContainerRuntimeException(e);
        }
    }

    private ClassLoader createLoader(final ClassLoader parent) throws MalformedURLException {
        final Collection<URL> urls = new LinkedList<URL>();

        if (libs != null) {
            final File folder = new File(libs);
            if (folder.exists()) {
                addFolder(folder, urls);
            }
        }

        // we add libs/*.jar and libs/xxx/*.jar to be able to sort libs but only one level to keep it simple
        File resources = null;
        File exploded = null;
        if (archive != null) {
            final File bar = new File(archive);
            if (bar.exists()) {
                if (bar.isFile()) { // bar to unzip
                    exploded = new File(work, bar.getName());
                } else if (bar.isDirectory()) { // already unpacked
                    exploded = bar;
                } else {
                    throw new IllegalArgumentException("unsupported archive type for: '" + archive + "'");
                }

                final File timestamp = new File(exploded, "timestamp.txt");

                long ts = Long.MIN_VALUE;
                if (exploded.exists()) {
                    if (timestamp.exists()) {
                        try {
                            ts = Long.parseLong(FileUtils.readFileToString(timestamp).trim());
                        } catch (final IOException e) {
                            ts = Long.MIN_VALUE;
                        }
                    }
                }

                if (ts == Long.MIN_VALUE || ts < bar.lastModified()) {
                    explode(bar, exploded, timestamp, bar.lastModified());
                }

                if (archive.endsWith(".bar") || new File(exploded, "BATCH-INF").exists()) {
                    // bar archives are split accross 3 folders
                    addFolder(new File(exploded, "BATCH-INF/classes"), urls);
                    addFolderIfExist(new File(exploded, "BATCH-INF/lib"), urls);
                    resources = new File(exploded, "BATCH-INF");
                } else if (archive.endsWith(".war") || new File(exploded, "WEB-INF").exists()) {
                    addFolderIfExist(new File(exploded, "WEB-INF/classes"), urls);
                    addLibs(new File(exploded, "WEB-INF/lib"), urls);
                } else {
                    throw new IllegalArgumentException("unknown or unsupported archive type: " + archive);
                }
            } else {
                throw new IllegalArgumentException("'" + archive + "' doesn't exist");
            }
        }

        final ClassLoader sharedClassLoader = createSharedClassLoader(parent);
        if (libs == null && archive == null) {
            return sharedClassLoader;
        }

        final ChildFirstURLClassLoader classLoader = new ChildFirstURLClassLoader(
                urls.toArray(new URL[urls.size()]), sharedClassLoader);
        if (resources != null && resources.exists()) {
            classLoader.addResource(resources);
        }
        if (exploded != null) {
            classLoader.setApplicationFolder(exploded);
        }
        return classLoader;
    }

    private static void addFolderIfExist(final File file, final Collection<URL> urls) throws MalformedURLException {
        if (file.isDirectory()) {
            urls.add(file.toURI().toURL());
        }
    }

    private ClassLoader createSharedClassLoader(final ClassLoader parent) throws MalformedURLException {
        final ClassLoader usedParent;
        if (sharedLibs != null) { // add it later to let specific libs be taken before
            final Collection<URL> sharedUrls = new LinkedList<URL>();
            final File folder = new File(sharedLibs);
            addJars(folder, sharedUrls);
            if (ChildFirstURLClassLoader.class.isInstance(parent)) { // merge it
                ChildFirstURLClassLoader.class.cast(parent).addUrls(sharedUrls);
                usedParent = parent;
            } else {
                usedParent = new ChildFirstURLClassLoader(sharedUrls.toArray(new URL[sharedUrls.size()]), parent);
            }
        } else {
            usedParent = parent;
        }
        return usedParent;
    }

    private void addJars(final File folder, final Collection<URL> urls) throws MalformedURLException {
        if (!folder.isDirectory()) {
            return;
        }

        addLibs(folder, urls);
        if (addFolderToLoader) {
            urls.add(folder.toURI().toURL());
        }
    }

    private void addFolder(File folder, Collection<URL> urls) throws MalformedURLException {
        if (!folder.exists()) {
            return;
        }

        addJars(folder, urls);

        final File[] subFolders = folder.listFiles(DirFilter.INSTANCE);
        if (subFolders != null) {
            for (final File f : subFolders) {
                addJars(f, urls);
            }
        }
    }

    private static void addLibs(final File folder, final Collection<URL> urls) throws MalformedURLException {
        if (!folder.isDirectory()) {
            return;
        }

        final File[] additionals = folder.listFiles(JarFilter.INSTANCE);
        if (additionals != null) {
            for (final File toAdd : additionals) {
                urls.add(toAdd.toURI().toURL());
            }
        }
    }

    private static void explode(final File source, final File target, final File timestampFile, final long time) {
        try {
            FileUtils.deleteDirectory(target);
            Zips.unzip(source, target);
            FileUtils.write(timestampFile, Long.toString(time));
        } catch (final IOException e) {
            // no-op
        }
    }

    private static class JarFilter implements FilenameFilter {
        public static final FilenameFilter INSTANCE = new JarFilter();

        private JarFilter() {
            // no-op
        }

        @Override
        public boolean accept(final File dir, final String name) {
            return name.endsWith(".jar") || name.endsWith(".zip");
        }
    }

    private static class DirFilter implements FileFilter {
        public static final FileFilter INSTANCE = new DirFilter();

        private DirFilter() {
            // no-op
        }

        @Override
        public boolean accept(final File dir) {
            return dir.isDirectory() && !dir.getName().startsWith(".");
        }
    }
}