/*
*
* Copyright 2012,2013 International Business Machines Corp.
*
* See the NOTICE file distributed with this work for additional information
* regarding copyright ownership. 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.apache.batchee.container.services;
import org.apache.batchee.container.exception.BatchContainerRuntimeException;
import org.apache.batchee.container.exception.BatchContainerServiceException;
import org.apache.batchee.container.services.executor.DefaultThreadPoolService;
import org.apache.batchee.container.services.factory.CDIBatchArtifactFactory;
import org.apache.batchee.container.services.factory.DefaultBatchArtifactFactory;
import org.apache.batchee.container.services.kernel.DefaultBatchKernel;
import org.apache.batchee.container.services.loader.DefaultJobXMLLoaderService;
import org.apache.batchee.container.services.locator.SingletonLocator;
import org.apache.batchee.container.services.persistence.MemoryPersistenceManager;
import org.apache.batchee.container.services.security.DefaultSecurityService;
import org.apache.batchee.container.services.status.DefaultJobStatusManager;
import org.apache.batchee.container.services.transaction.DefaultBatchTransactionService;
import org.apache.batchee.container.util.BatchContainerConstants;
import org.apache.batchee.spi.BatchArtifactFactory;
import org.apache.batchee.spi.BatchService;
import org.apache.batchee.spi.BatchThreadPoolService;
import org.apache.batchee.spi.JobXMLLoaderService;
import org.apache.batchee.spi.PersistenceManagerService;
import org.apache.batchee.spi.SecurityService;
import org.apache.batchee.spi.TransactionManagementService;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Logger;
public class ServicesManager implements BatchContainerConstants {
private final static Logger LOGGER = Logger.getLogger(ServicesManager.class.getName());
private static final String SERVICES_CONFIGURATION_FILE = "/batchee.properties";
// Use class names instead of Class objects to not drag in any dependencies and to easily interact with properties
private static final Map<String, String> SERVICE_IMPL_CLASS_NAMES = new ConcurrentHashMap<String, String>();
static {
SERVICE_IMPL_CLASS_NAMES.put(TransactionManagementService.class.getName(), DefaultBatchTransactionService.class.getName());
// SERVICE_IMPL_CLASS_NAMES.put(PersistenceManagerService.class.getName(), JDBCPersistenceManager.class.getName()); // too slow in embedded mode to be usable because of init step
SERVICE_IMPL_CLASS_NAMES.put(PersistenceManagerService.class.getName(), MemoryPersistenceManager.class.getName());
SERVICE_IMPL_CLASS_NAMES.put(JobStatusManagerService.class.getName(), DefaultJobStatusManager.class.getName());
SERVICE_IMPL_CLASS_NAMES.put(BatchThreadPoolService.class.getName(), DefaultThreadPoolService.class.getName());
SERVICE_IMPL_CLASS_NAMES.put(BatchKernelService.class.getName(), DefaultBatchKernel.class.getName());
SERVICE_IMPL_CLASS_NAMES.put(JobXMLLoaderService.class.getName(), DefaultJobXMLLoaderService.class.getName());
SERVICE_IMPL_CLASS_NAMES.put(SecurityService.class.getName(), DefaultSecurityService.class.getName());
try {
Thread.currentThread().getContextClassLoader().loadClass("javax.enterprise.inject.spi.BeanManager");
SERVICE_IMPL_CLASS_NAMES.put(BatchArtifactFactory.class.getName(), CDIBatchArtifactFactory.class.getName());
} catch (final Throwable th) {
SERVICE_IMPL_CLASS_NAMES.put(BatchArtifactFactory.class.getName(), DefaultBatchArtifactFactory.class.getName());
}
setServicesManagerLocator(SingletonLocator.INSTANCE); // default init
}
private static ServicesManagerLocator servicesManagerLocator;
// designed to be used from app or a server
public static void setServicesManagerLocator(final ServicesManagerLocator locator) {
servicesManagerLocator = locator;
}
public static <T extends BatchService> T service(final Class<T> api) {
return api.cast(Proxy.newProxyInstance(ServicesManager.class.getClassLoader(), new Class<?>[]{ api }, new ServiceHandler<T>(api)));
}
public static String value(final String key, final String defaultValue) {
return servicesManagerLocator.find().batchRuntimeConfig.getProperty(key, defaultValue);
}
// Declared 'volatile' to allow use in double-checked locking. This 'isInited'
// refers to whether the configuration has been hardened and possibly the
// first service impl loaded, not whether the instance has merely been instantiated.
private final byte[] isInitedLock = new byte[0];
private volatile boolean isInited = false;
private Properties batchRuntimeConfig;
private boolean logServices;
// Registry of all current services
private final ConcurrentHashMap<String, BatchService> serviceRegistry = new ConcurrentHashMap<String, BatchService>();
/**
* Init doesn't actually load the service impls, which are still loaded lazily. What it does is it
* hardens the config. This is necessary since the batch runtime by and large is not dynamically
* configurable, (e.g. via MBeans). Things like the database config used by the batch runtime's
* persistent store are hardened then, as are the names of the service impls to use.
*/
public void init(final Properties props) {
// Use double-checked locking with volatile.
if (!isInited) {
synchronized (isInitedLock) {
if (!isInited) {
batchRuntimeConfig = new Properties();
batchRuntimeConfig.putAll(SERVICE_IMPL_CLASS_NAMES); // defaults
// file in the classloader
final InputStream batchServicesListInputStream = getClass().getResourceAsStream(SERVICES_CONFIGURATION_FILE);
if (batchServicesListInputStream != null) {
try {
batchRuntimeConfig.load(batchServicesListInputStream);
} catch (final Exception e) {
LOGGER.config("Error loading " + SERVICES_CONFIGURATION_FILE + " Exception=" + e.toString());
} finally {
try {
batchServicesListInputStream.close();
} catch (final IOException e) {
// no-op
}
}
}
// API overriding
if (props != null) {
batchRuntimeConfig.putAll(props);
}
// JVM instance overriding
batchRuntimeConfig.putAll(System.getProperties());
logServices = Boolean.parseBoolean(batchRuntimeConfig.getProperty("batchee.service-manager.log", "false"));
isInited = Boolean.TRUE;
}
}
}
}
private <T extends BatchService> T getService(final Class<T> clazz) throws BatchContainerServiceException {
T service = clazz.cast(serviceRegistry.get(clazz.getName()));
if (service == null) {
// Probably don't want to be loading two on two different threads so lock the whole table.
synchronized (serviceRegistry) {
service = clazz.cast(serviceRegistry.get(clazz.getName()));
if (service == null) {
service = loadService(clazz);
service.init(batchRuntimeConfig);
serviceRegistry.putIfAbsent(clazz.getName(), service);
}
}
}
return service;
}
private <T extends BatchService> T loadService(final Class<T> serviceType) {
T service = null;
String className = batchRuntimeConfig.getProperty(serviceType.getSimpleName());
try {
if (className != null) {
service = load(serviceType, className);
} else {
className = batchRuntimeConfig.getProperty(serviceType.getName());
if (className != null) {
service = load(serviceType, className);
}
}
} catch (final Throwable e1) {
throw new IllegalArgumentException("Could not instantiate service " + className + " due to exception: " + e1);
}
if (service == null) {
throw new BatchContainerRuntimeException("Instantiate of service=: " + className + " returned null. Aborting...");
}
if (logServices) {
LOGGER.info("Using " + service + " (" + className + ") as " + serviceType.getName());
}
return service;
}
private static <T> T load(final Class<T> expected, final String className) throws Exception {
final Class<?> cls = Class.forName(className);
if (cls != null) {
if (cls.getConstructor() != null) {
return expected.cast(cls.newInstance());
}
throw new BatchContainerRuntimeException("Service class " + className + " should have a default constructor defined");
}
throw new Exception("Exception loading Service class " + className + " make sure it exists");
}
// just an handler getting the right service instance using the ServicesManagerLocator
private static class ServiceHandler<T extends BatchService> implements InvocationHandler {
private final Class<T> service;
public ServiceHandler(final Class<T> api) {
this.service = api;
}
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args) throws Throwable {
final T instance = servicesManagerLocator.find().getService(service);
try {
return method.invoke(instance, args);
} catch (final InvocationTargetException ite) {
throw ite.getCause();
}
}
}
}