| /* |
| * 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.felix.dm.impl.dependencies; |
| |
| import java.lang.reflect.InvocationTargetException; |
| import java.util.ArrayList; |
| import java.util.Dictionary; |
| import java.util.HashSet; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.Set; |
| |
| import org.apache.felix.dm.ComponentDependencyDeclaration; |
| import org.apache.felix.dm.ConfigurationDependency; |
| import org.apache.felix.dm.Dependency; |
| import org.apache.felix.dm.DependencyActivation; |
| import org.apache.felix.dm.DependencyService; |
| import org.apache.felix.dm.InvocationUtil; |
| import org.apache.felix.dm.PropertyMetaData; |
| import org.apache.felix.dm.impl.Logger; |
| import org.apache.felix.dm.impl.metatype.MetaTypeProviderImpl; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.service.cm.ConfigurationException; |
| import org.osgi.service.cm.ManagedService; |
| import org.osgi.service.log.LogService; |
| |
| /** |
| * Configuration dependency that can track the availability of a (valid) configuration. |
| * To use it, specify a PID for the configuration. The dependency is always required, |
| * because if it is not, it does not make sense to use the dependency manager. In that |
| * scenario, simply register your service as a <code>ManagedService(Factory)</code> and |
| * handle everything yourself. Also, only managed services are supported, not factories. |
| * There are a couple of things you need to be aware of when implementing the |
| * <code>updated(Dictionary)</code> method: |
| * <ul> |
| * <li>Make sure it throws a <code>ConfigurationException</code> when you get a |
| * configuration that is invalid. In this case, the dependency will not change: |
| * if it was not available, it will still not be. If it was available, it will |
| * remain available and implicitly assume you keep working with your old |
| * configuration.</li> |
| * <li>This method will be called before all required dependencies are available. |
| * Make sure you do not depend on these to parse your settings.</li> |
| * </ul> |
| * |
| * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a> |
| */ |
| public class ConfigurationDependencyImpl extends DependencyBase implements ConfigurationDependency, ManagedService, ComponentDependencyDeclaration, DependencyActivation { |
| private BundleContext m_context; |
| private String m_pid; |
| private ServiceRegistration m_registration; |
| protected List m_services = new ArrayList(); |
| private Dictionary m_settings; |
| private String m_callback; |
| private boolean m_isStarted; |
| private final Set m_updateInvokedCache = new HashSet(); |
| private MetaTypeProviderImpl m_metaType; |
| private boolean m_propagate; |
| private Object m_propagateCallbackInstance; |
| private String m_propagateCallbackMethod; |
| |
| public ConfigurationDependencyImpl(BundleContext context, Logger logger) { |
| super(logger); |
| m_context = context; |
| } |
| |
| public ConfigurationDependencyImpl(ConfigurationDependencyImpl prototype) { |
| super(prototype); |
| m_context = prototype.m_context; |
| m_pid = prototype.m_pid; |
| m_propagate = prototype.m_propagate; |
| m_callback = prototype.m_callback; |
| } |
| |
| public Dependency createCopy() { |
| return new ConfigurationDependencyImpl(this); |
| } |
| |
| public synchronized boolean isAvailable() { |
| return m_settings != null; |
| } |
| |
| /** |
| * Will always return <code>true</code> as optional configuration dependencies |
| * do not make sense. You might as well just implement <code>ManagedService</code> |
| * yourself in those cases. |
| */ |
| public boolean isRequired() { |
| return true; |
| } |
| |
| /** |
| * Returns <code>true</code> when configuration properties should be propagated |
| * as service properties. |
| */ |
| public boolean isPropagated() { |
| return m_propagate; |
| } |
| |
| public ConfigurationDependency setInstanceBound(boolean isInstanceBound) { |
| setIsInstanceBound(isInstanceBound); |
| return this; |
| } |
| |
| |
| public Dictionary getConfiguration() { |
| return m_settings; |
| } |
| |
| public void start(DependencyService service) { |
| boolean needsStarting = false; |
| synchronized (this) { |
| m_services.add(service); |
| if (!m_isStarted) { |
| m_isStarted = true; |
| needsStarting = true; |
| } |
| } |
| if (needsStarting) { |
| Properties props = new Properties(); |
| props.put(Constants.SERVICE_PID, m_pid); |
| ManagedService ms = this; |
| if (m_metaType != null) { |
| ms = m_metaType; |
| } |
| m_registration = m_context.registerService(ManagedService.class.getName(), ms, props); |
| } |
| } |
| |
| public void stop(DependencyService service) { |
| boolean needsStopping = false; |
| synchronized (this) { |
| if (m_services.size() == 1 && m_services.contains(service)) { |
| m_isStarted = false; |
| needsStopping = true; |
| } |
| } |
| if (needsStopping) { |
| m_registration.unregister(); |
| m_registration = null; |
| m_services.remove(service); |
| } |
| } |
| |
| public ConfigurationDependency setCallback(String callback) { |
| m_callback = callback; |
| return this; |
| } |
| |
| public void updated(Dictionary settings) throws ConfigurationException { |
| synchronized (m_updateInvokedCache) { |
| m_updateInvokedCache.clear(); |
| } |
| Dictionary oldSettings = null; |
| synchronized (this) { |
| oldSettings = m_settings; |
| } |
| |
| if (oldSettings == null && settings == null) { |
| // CM has started but our configuration is not still present in the CM database: ignore |
| return; |
| } |
| |
| Object[] services = m_services.toArray(); |
| for (int i = 0; i < services.length; i++) { |
| DependencyService ds = (DependencyService) services[i]; |
| // if non-null settings come in, we have to instantiate the service and |
| // apply these settings |
| ds.initService(); |
| Object service = ds.getService(); |
| |
| if (service != null) { |
| invokeUpdate(ds, service, settings); |
| } |
| else { |
| m_logger.log(Logger.LOG_ERROR, "Service " + ds + " with configuration dependency " + this + " could not be instantiated."); |
| return; |
| } |
| } |
| |
| synchronized (this) { |
| m_settings = settings; |
| } |
| |
| for (int i = 0; i < services.length; i++) { |
| DependencyService ds = (DependencyService) services[i]; |
| // If these settings did not cause a configuration exception, we determine if they have |
| // caused the dependency state to change |
| if ((oldSettings == null) && (settings != null)) { |
| ds.dependencyAvailable(this); |
| } |
| if ((oldSettings != null) && (settings == null)) { |
| ds.dependencyUnavailable(this); |
| } |
| if ((oldSettings != null) && (settings != null)) { |
| ds.dependencyChanged(this); |
| } |
| } |
| } |
| |
| public void invokeUpdate(DependencyService ds, Object service, Dictionary settings) throws ConfigurationException { |
| boolean wasAdded; |
| synchronized (m_updateInvokedCache) { |
| wasAdded = m_updateInvokedCache.add(ds); |
| } |
| if (wasAdded) { |
| String callback = (m_callback == null) ? "updated" : m_callback; |
| try { |
| // if exception is thrown here, what does that mean for the |
| // state of this dependency? how smart do we want to be?? |
| // it's okay like this, if the new settings contain errors, we |
| // remain in the state we were, assuming that any error causes |
| // the "old" configuration to stay in effect. |
| // CM will log any thrown exceptions. |
| InvocationUtil.invokeMethod(service, service.getClass(), callback, new Class[][] {{ Dictionary.class }}, new Object[][] {{ settings }}, false); |
| } |
| catch (InvocationTargetException e) { |
| // The component has thrown an exception during it's callback invocation. |
| if (e.getTargetException() instanceof ConfigurationException) { |
| // the callback threw an OSGi ConfigurationException: just re-throw it. |
| throw (ConfigurationException) e.getTargetException(); |
| } |
| else { |
| // wrap the callback exception into a ConfigurationException. |
| throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", e.getTargetException()); |
| } |
| } |
| catch (NoSuchMethodException e) { |
| // if the method does not exist, ignore it |
| } |
| catch (Throwable t) { |
| // wrap any other exception as a ConfigurationException. |
| throw new ConfigurationException(null, "Service " + ds + " with " + this.toString() + " could not be updated", t); |
| } |
| } |
| } |
| |
| /** |
| * Sets the <code>service.pid</code> of the configuration you |
| * are depending on. |
| */ |
| public ConfigurationDependency setPid(String pid) { |
| ensureNotActive(); |
| m_pid = pid; |
| return this; |
| } |
| |
| /** |
| * Sets propagation of the configuration properties to the service |
| * properties. Any additional service properties specified directly |
| * are merged with these. |
| */ |
| public ConfigurationDependency setPropagate(boolean propagate) { |
| ensureNotActive(); |
| m_propagate = propagate; |
| return this; |
| } |
| |
| private void ensureNotActive() { |
| if (m_services != null && m_services.size() > 0) { |
| throw new IllegalStateException("Cannot modify state while active."); |
| } |
| } |
| |
| public String toString() { |
| return "ConfigurationDependency[" + m_pid + "]"; |
| } |
| |
| public String getName() { |
| return m_pid; |
| } |
| |
| public int getState() { |
| return (isAvailable() ? 1 : 0) + (isRequired() ? 2 : 0); |
| } |
| |
| public String getType() { |
| return "configuration"; |
| } |
| |
| public Object getAutoConfigInstance() { |
| return getConfiguration(); |
| } |
| |
| public String getAutoConfigName() { |
| // TODO Auto-generated method stub |
| return null; |
| } |
| |
| public Class getAutoConfigType() { |
| return Dictionary.class; |
| } |
| |
| public void invokeAdded(DependencyService service) { |
| try { |
| invokeUpdate(service, service.getService(), getConfiguration()); |
| } |
| catch (ConfigurationException e) { |
| // if this happens, it's definitely an inconsistency, since we |
| // asked the instance the same question before (if this is a |
| // valid configuration) and then it was |
| e.printStackTrace(); |
| } |
| } |
| |
| public void invokeRemoved(DependencyService service) { |
| synchronized (m_updateInvokedCache) { |
| m_updateInvokedCache.remove(service); |
| } |
| } |
| |
| public boolean isAutoConfig() { |
| // TODO Auto-generated method stub |
| return false; |
| } |
| |
| public ConfigurationDependency setPropagate(Object instance, String method) { |
| setPropagate(instance != null && method != null); |
| m_propagateCallbackInstance = instance; |
| m_propagateCallbackMethod = method; |
| return this; |
| } |
| |
| public Dictionary getProperties() { |
| Dictionary config = getConfiguration(); |
| if (config != null) { |
| if (m_propagateCallbackInstance != null && m_propagateCallbackMethod != null) { |
| try { |
| return (Dictionary) InvocationUtil.invokeCallbackMethod(m_propagateCallbackInstance, m_propagateCallbackMethod, new Class[][] {{ Dictionary.class }, {}}, new Object[][] {{ config }, {}}); |
| } |
| catch (InvocationTargetException e) { |
| m_logger.log(LogService.LOG_WARNING, "Exception while invoking callback method", e.getCause()); |
| } |
| catch (Exception e) { |
| m_logger.log(LogService.LOG_WARNING, "Exception while trying to invoke callback method", e); |
| } |
| throw new IllegalStateException("Could not invoke callback"); |
| } |
| else { |
| return config; |
| } |
| } |
| else { |
| throw new IllegalStateException("cannot find configuration"); |
| } |
| } |
| |
| public BundleContext getBundleContext() { |
| return m_context; |
| } |
| |
| public Logger getLogger() { |
| return m_logger; |
| } |
| |
| public ConfigurationDependency add(PropertyMetaData properties) |
| { |
| createMetaTypeImpl(); |
| m_metaType.add(properties); |
| return this; |
| } |
| |
| public ConfigurationDependency setDescription(String description) |
| { |
| createMetaTypeImpl(); |
| m_metaType.setDescription(description); |
| return this; |
| } |
| |
| public ConfigurationDependency setHeading(String heading) |
| { |
| createMetaTypeImpl(); |
| m_metaType.setName(heading); |
| return this; |
| } |
| |
| public ConfigurationDependency setLocalization(String path) |
| { |
| createMetaTypeImpl(); |
| m_metaType.setLocalization(path); |
| return this; |
| } |
| |
| private synchronized void createMetaTypeImpl() { |
| if (m_metaType == null) { |
| m_metaType = new MetaTypeProviderImpl(getName(), getBundleContext(), getLogger(), this, null); |
| } |
| } |
| } |