/** * 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.camel.scr; import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.net.URI; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import org.apache.camel.CamelContext; import org.apache.camel.RoutesBuilder; import org.apache.camel.component.properties.PropertiesComponent; import org.apache.camel.core.osgi.OsgiCamelContextPublisher; import org.apache.camel.core.osgi.OsgiDefaultCamelContext; import org.apache.camel.core.osgi.utils.BundleDelegatingClassLoader; import org.apache.camel.impl.DefaultCamelContext; import org.apache.camel.impl.ExplicitCamelContextNameStrategy; import org.apache.camel.impl.SimpleRegistry; import org.apache.camel.spi.Registry; import org.apache.camel.util.ReflectionHelper; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public abstract class AbstractCamelRunner implements Runnable { public static final int START_DELAY = 5000; public static final String PROPERTY_PREFIX = "camel.scr.properties.prefix"; protected Logger log = LoggerFactory.getLogger(getClass()); // Configured fields private String camelContextId; private boolean active; private CamelContext context; private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(); private ScheduledFuture starter; private volatile boolean activated; private volatile boolean started; public final synchronized void activate(final BundleContext bundleContext, final Map<String, String> props) throws Exception { if (activated) { return; } log.debug("activated!"); activated = true; prepare(bundleContext, props); runWithDelay(this); } public final synchronized void prepare(final BundleContext bundleContext, final Map<String, String> props) throws Exception { createCamelContext(bundleContext, props); // Configure fields from properties configure(context, this, log, true); setupCamelContext(bundleContext, camelContextId); } protected void createCamelContext(final BundleContext bundleContext, final Map<String, String> props) { if (bundleContext != null) { context = new OsgiDefaultCamelContext(bundleContext, createRegistry(bundleContext)); // Setup the application context classloader with the bundle classloader context.setApplicationContextClassLoader(new BundleDelegatingClassLoader(bundleContext.getBundle())); // and make sure the TCCL is our classloader Thread.currentThread().setContextClassLoader(context.getApplicationContextClassLoader()); } else { context = new DefaultCamelContext(createRegistry()); } setupPropertiesComponent(context, props, log); } protected void setupCamelContext(final BundleContext bundleContext, final String camelContextId) throws Exception { // Set up CamelContext if (camelContextId != null) { context.setNameStrategy(new ExplicitCamelContextNameStrategy(camelContextId)); } // Add routes for (RoutesBuilder route : getRouteBuilders()) { context.addRoutes(configure(context, route, log)); } // ensure we publish this CamelContext to the OSGi service registry context.getManagementStrategy().addEventNotifier(new OsgiCamelContextPublisher(bundleContext)); } public static void setupPropertiesComponent(final CamelContext context, final Map<String, String> props, Logger log) { // Set up PropertiesComponent PropertiesComponent pc = new PropertiesComponent(); if (context.getComponentNames().contains("properties")) { pc = context.getComponent("properties", PropertiesComponent.class); } else { context.addComponent("properties", pc); } // Set property prefix if (System.getProperty(PROPERTY_PREFIX) != null) { pc.setPropertyPrefix(System.getProperty(PROPERTY_PREFIX) + "."); } if (props != null) { Properties initialProps = new Properties(); initialProps.putAll(props); log.debug(String.format("Added %d initial properties", props.size())); pc.setInitialProperties(initialProps); } } protected abstract List<RoutesBuilder> getRouteBuilders() throws Exception; // Run after a delay unless the method is called again private void runWithDelay(final Runnable runnable) { if (activated && !started) { cancelDelayedRun(); // Run after a delay starter = executor.schedule(runnable, START_DELAY, TimeUnit.MILLISECONDS); } } private void cancelDelayedRun() { if (null != starter) { // Cancel but don't interrupt starter.cancel(false); } } public final synchronized void run() { startCamelContext(); } public final synchronized void deactivate() { if (!activated) { return; } log.debug("deactivated!"); activated = false; cancelDelayedRun(); stop(); } public final synchronized void stop() { stopCamelContext(); } private void startCamelContext() { if (started) { return; } try { if (!active) { context.setAutoStartup(false); } context.start(); started = true; } catch (Exception e) { // we should have a better way - than just try every 5th second to try to start the bundle log.warn("Failed to start Camel context. Will try again when more Camel components have been registered.", e); } } private void stopCamelContext() { if (!started) { return; } try { context.stop(); } catch (Exception e) { log.warn("Failed to stop Camel context.", e); } finally { // Even if stopping failed we consider Camel context stopped started = false; } } public final CamelContext getContext() { return context; } protected final void gotCamelComponent(final ServiceReference serviceReference) { log.trace("Got a new Camel Component."); runWithDelay(this); } protected final void lostCamelComponent(final ServiceReference serviceReference) { log.trace("Lost a Camel Component."); } public static <T> T configure(final CamelContext context, final T target, final Logger log) { return configure(context, target, log, false); } public static <T> T configure(final CamelContext context, final T target, final Logger log, final boolean deep) { Class<?> clazz = target.getClass(); log.debug("Configuring {}", clazz.getName()); Collection<Field> fields = new ArrayList<Field>(); fields.addAll(Arrays.asList(clazz.getDeclaredFields())); if (deep) { fields.addAll(Arrays.asList(clazz.getFields())); fields.addAll(Arrays.asList(clazz.getSuperclass().getDeclaredFields())); } for (Field field : fields) { String propertyValue; try { propertyValue = context.resolvePropertyPlaceholders("{{" + field.getName() + "}}"); } catch (Exception e) { log.debug("Skipped field {}", field.getName()); continue; } try { if (!propertyValue.isEmpty()) { // Try to convert the value and set the field Object convertedValue = convertValue(propertyValue, field.getGenericType()); ReflectionHelper.setField(field, target, convertedValue); log.debug("Configured field {} with value {}", field.getName(), propertyValue); } } catch (Exception e) { log.warn("Error setting field " + field.getName() + " due: " + e.getMessage() + ". This exception is ignored.", e); } } return target; } public static Object convertValue(final String value, final Type type) throws Exception { Class<?> clazz = null; if (type instanceof ParameterizedType) { clazz = (Class<?>) ((ParameterizedType) type).getRawType(); } else if (type instanceof Class) { clazz = (Class<?>) type; } if (clazz != null && value != null) { if (clazz.isAssignableFrom(value.getClass())) { return clazz.cast(value); } else if (clazz == Boolean.class || clazz == boolean.class) { return Boolean.parseBoolean(value); } else if (clazz == Integer.class || clazz == int.class) { return Integer.parseInt(value); } else if (clazz == Long.class || clazz == long.class) { return Long.parseLong(value); } else if (clazz == Double.class || clazz == double.class) { return Double.parseDouble(value); } else if (clazz == File.class) { return new File(value); } else if (clazz == URI.class) { return new URI(value); } else if (clazz == URL.class) { return new URL(value); } else { throw new IllegalArgumentException("Unsupported type: " + clazz.getName()); } } else { return null; } } public static <T extends Registry> T getRegistry(CamelContext context, Class<T> type) throws Exception { T result = context.getRegistry(type); if (result == null) { throw new Exception(type.getName() + " not available in " + context.getName()); } return result; } protected Registry createRegistry() { return new SimpleRegistry(); } protected Registry createRegistry(BundleContext bundleContext) { return createRegistry(); } }