/* | |
* | |
* 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 static org.apache.batchee.container.util.ClassLoaderAwareHandler.makeLoaderAware; | |
import java.io.Closeable; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.lang.management.ManagementFactory; | |
import java.lang.reflect.Constructor; | |
import java.lang.reflect.InvocationTargetException; | |
import java.util.Map; | |
import java.util.Properties; | |
import java.util.concurrent.ConcurrentHashMap; | |
import java.util.logging.Level; | |
import java.util.logging.Logger; | |
import javax.management.MBeanServer; | |
import javax.management.ObjectName; | |
import org.apache.batchee.container.exception.BatchContainerRuntimeException; | |
import org.apache.batchee.container.exception.BatchContainerServiceException; | |
import org.apache.batchee.container.services.callback.SimpleJobExecutionCallbackService; | |
import org.apache.batchee.container.services.data.DefaultDataRepresentationService; | |
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.MemoryPersistenceManagerService; | |
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.jmx.BatchEE; | |
import org.apache.batchee.jmx.BatchEEMBean; | |
import org.apache.batchee.jmx.BatchEEMBeanImpl; | |
import org.apache.batchee.spi.BatchArtifactFactory; | |
import org.apache.batchee.spi.BatchService; | |
import org.apache.batchee.spi.BatchThreadPoolService; | |
import org.apache.batchee.spi.DataRepresentationService; | |
import org.apache.batchee.spi.JobExecutionCallbackService; | |
import org.apache.batchee.spi.JobXMLLoaderService; | |
import org.apache.batchee.spi.PersistenceManagerService; | |
import org.apache.batchee.spi.TransactionManagementService; | |
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(), MemoryPersistenceManagerService.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(JobExecutionCallbackService.class.getName(), SimpleJobExecutionCallbackService.class.getName()); | |
SERVICE_IMPL_CLASS_NAMES.put(DataRepresentationService.class.getName(), DefaultDataRepresentationService.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; | |
private ObjectName jmxName; | |
private ClassLoader loader = null; | |
// designed to be used from app or a server | |
public static void setServicesManagerLocator(final ServicesManagerLocator locator) { | |
servicesManagerLocator = locator; | |
} | |
public static ServicesManager find() { | |
return servicesManagerLocator.find(); | |
} | |
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 | |
loader = Thread.currentThread().getContextClassLoader(); | |
// file in the classloader | |
final InputStream batchServicesListInputStream = loader.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")); | |
if (Boolean.parseBoolean(batchRuntimeConfig.getProperty("org.apache.batchee.jmx", "true"))) { | |
try { | |
final MBeanServer platformMBeanServer = ManagementFactory.getPlatformMBeanServer(); | |
final String app = batchRuntimeConfig.getProperty("org.apache.batchee.jmx.application", ""); | |
if (app.isEmpty()) { | |
jmxName = new ObjectName(BatchEEMBean.DEFAULT_OBJECT_NAME); | |
} else { | |
jmxName = new ObjectName(BatchEEMBean.DEFAULT_OBJECT_NAME + ",application=" + app); | |
} | |
if (!platformMBeanServer.isRegistered(jmxName)) { | |
platformMBeanServer.registerMBean( | |
new BatchEE( | |
makeLoaderAware(BatchEEMBean.class, new Class<?>[]{ BatchEEMBean.class }, | |
BatchEEMBeanImpl.INSTANCE)), | |
jmxName); | |
} else { | |
jmxName = null; | |
LOGGER.warning("You didn't specify org.apache.batchee.jmx.application and JMX is already registered, skipping"); | |
} | |
} catch (final Exception e) { | |
throw new IllegalStateException(e); | |
} | |
} | |
isInited = Boolean.TRUE; | |
} | |
} | |
} | |
} | |
public void close() { | |
if (isInited) { | |
synchronized (isInitedLock) { | |
if (isInited) { | |
service(BatchThreadPoolService.class).shutdown(); | |
synchronized (serviceRegistry) { | |
for (final Object service : serviceRegistry.values()) { | |
if (Closeable.class.isInstance(service)) { | |
try { | |
Closeable.class.cast(service).close(); | |
} catch (IOException e) { // don't make it blocking, on j7 we can use suppressed maybe? | |
LOGGER.log(Level.SEVERE, e.getMessage(), e); | |
} | |
} | |
} | |
} | |
if (jmxName != null) { // unregister jmx bean if deployed in an app | |
final MBeanServer jmx = ManagementFactory.getPlatformMBeanServer(); | |
try { | |
jmx.unregisterMBean(jmxName); | |
} catch (final Exception e) { | |
// no-op | |
} | |
} | |
isInited = false; | |
} | |
} | |
} | |
} | |
public <T extends BatchService> T service(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) { | |
final Object existing = batchRuntimeConfig.get(serviceType.getName()); | |
if (serviceType.isInstance(existing)) { | |
return serviceType.cast(existing); | |
} | |
T service = null; | |
String className = batchRuntimeConfig.getProperty(serviceType.getSimpleName()); // short name first | |
try { | |
if (className != null) { | |
service = load(serviceType, className); | |
} else { | |
className = String.class.cast(existing); | |
if (className != null) { | |
service = load(serviceType, className); | |
} | |
} | |
} catch (final Throwable e1) { | |
handleBatchContainerRuntimeException(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 <T> T load(final Class<T> expected, final String className) throws Exception { | |
Class<?> cls = null; | |
try { | |
cls = getLoader().loadClass(className); | |
} catch (Exception e) { | |
handleBatchContainerRuntimeException(e); | |
throw new BatchContainerRuntimeException("Could not load Service class " + className + ". Make sure it exists", e); | |
} | |
Throwable problem = null; | |
try { | |
final Constructor<?> constructor = cls.getConstructor(ServicesManager.class); | |
return expected.cast(constructor.newInstance(this)); | |
} catch (final Throwable th) { | |
handleBatchContainerRuntimeException(th); | |
// remember the first problem and try no arg constructor | |
problem = th; | |
} | |
try { | |
if (cls.getConstructor() != null) { | |
return expected.cast(cls.newInstance()); | |
} | |
} catch (Throwable th) { | |
handleBatchContainerRuntimeException(th); | |
// default ct works neither, lets report the original problem | |
} | |
throw new BatchContainerRuntimeException("Service class " + className + " cannnot be loaded", problem); | |
} | |
/** | |
* prevent BatchContainerRuntimeExceptions to get swallowed | |
*/ | |
private void handleBatchContainerRuntimeException(Throwable e) throws BatchContainerRuntimeException { | |
if (e instanceof BatchContainerRuntimeException) { | |
throw (BatchContainerRuntimeException) e; | |
} | |
if (e instanceof InvocationTargetException) { | |
InvocationTargetException ite = (InvocationTargetException) e; | |
if (ite.getCause() instanceof BatchContainerRuntimeException) { | |
throw (BatchContainerRuntimeException) ite.getCause(); | |
} | |
} | |
// else all is fine | |
} | |
private ClassLoader getLoader() { | |
if (loader != null) { | |
return loader; | |
} | |
return Thread.currentThread().getContextClassLoader(); | |
} | |
} | |