blob: bec981d49a878734483b6d8a678edd8fc1444dfe [file] [log] [blame]
/*
* 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 org.apache.batchee.cli.classloader.ChildFirstURLClassLoader;
import org.apache.batchee.cli.command.api.Option;
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 = "sharedLibs", description = "folder containing shared libraries, the folder is added too to the loader")
private String sharedLibs = null;
@Option(name = "addFolderToLoader", description = "force shared lib and libs folders to be added to the classloader")
private boolean addFolderToLoader = false;
protected JobOperator operator;
protected JobOperator operator() {
if (operator != null) {
return operator;
}
if (baseUrl == null) {
return operator = 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 operator = 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(".");
}
}
}