ENTAXY-374 release 1.8.2

This commit is contained in:
2022-08-23 13:40:11 +03:00
parent b68642f81c
commit f495628c75
618 changed files with 61269 additions and 3202 deletions

View File

@ -0,0 +1,161 @@
<!-- 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. -->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-pom</artifactId>
<version>2.19.0-ENTAXY</version>
</parent>
<artifactId>artemis-server-osgi</artifactId>
<packaging>bundle</packaging>
<name>ActiveMQ Artemis Server OSGi ENTAXY</name>
<description>
Combines the commons, core-client and server jars as they contain too many duplicate packages
to be deployed separately.
</description>
<!-- <properties>-->
<!-- <activemq.basedir>${project.basedir}/..</activemq.basedir>-->
<!-- </properties>-->
<dependencies>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-commons</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-core-client</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-server</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-client</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jms-server</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-jdbc-store</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-journal</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-selector</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.apache.activemq</groupId>
<artifactId>artemis-service-extensions</artifactId>
<version>${activemq.version}</version>
</dependency>
<dependency>
<groupId>org.jboss.logmanager</groupId>
<artifactId>jboss-logmanager</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.wildfly.common</groupId>
<artifactId>wildfly-common</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-processor</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging-annotations</artifactId>
<scope>provided</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.errorprone</groupId>
<artifactId>error_prone_core</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>
<dependency>
<groupId>xalan</groupId>
<artifactId>xalan</artifactId>
<optional>true</optional>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.cmpn</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<extensions>true</extensions>
<configuration>
<instructions>
<Embed-Dependency>*;scope=compile|runtime;groupId=org.apache.activemq</Embed-Dependency>
<Import-Package>
org.postgresql*;resolution:=optional,
io.netty.buffer;io.netty.*;version="[4.1,5)",
org.apache.johnzon.core,
*
</Import-Package>
<_exportcontents>org.apache.activemq.artemis.*;-noimport:=true</_exportcontents>
</instructions>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,73 @@
/*
* 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.activemq.artemis.osgi;
import org.jboss.logging.BasicLogger;
import org.jboss.logging.Logger;
import org.jboss.logging.annotations.Cause;
import org.jboss.logging.annotations.LogMessage;
import org.jboss.logging.annotations.Message;
import org.jboss.logging.annotations.MessageLogger;
/**
* Logger code 58
*
* each message id must be 6 digits long starting with 58, the 3rd digit donates the level so
*
* INF0 1
* WARN 2
* DEBUG 3
* ERROR 4
* TRACE 5
* FATAL 6
*
* so an INFO message would be 581000 to 581999
*/
@MessageLogger(projectCode = "AMQ")
public interface ActiveMQOsgiLogger extends BasicLogger {
/**
* * The default logger.
*/
ActiveMQOsgiLogger LOGGER = Logger.getMessageLogger(ActiveMQOsgiLogger.class, ActiveMQOsgiLogger.class.getPackage().getName());
@LogMessage(level = Logger.Level.INFO)
@Message(id = 581000, value = "Broker config {0} found. Tracking protocols {1}", format = Message.Format.MESSAGE_FORMAT)
void brokerConfigFound(String name, String protocols);
@LogMessage(level = Logger.Level.INFO)
@Message(id = 581001, value = "Required protocol {0} was added for broker {1}. {2}", format = Message.Format.MESSAGE_FORMAT)
void protocolWasAddedForBroker(String protocol, String name, String message);
@LogMessage(level = Logger.Level.INFO)
@Message(id = 581002, value = "Required protocol {0} was removed for broker {1}. {2}", format = Message.Format.MESSAGE_FORMAT)
void protocolWasRemovedForBroker(String protocol, String name, String message);
@LogMessage(level = Logger.Level.WARN)
@Message(id = 582000, value = "Error starting broker: {0}", format = Message.Format.MESSAGE_FORMAT)
void errorStartingBroker(@Cause Exception e, String name);
@LogMessage(level = Logger.Level.WARN)
@Message(id = 582001, value = "Error stopping broker: {0}", format = Message.Format.MESSAGE_FORMAT)
void errorStoppingBroker(@Cause Exception e, String name);
@LogMessage(level = Logger.Level.WARN)
@Message(id = 582002, value = "Error getting dataSource provider infos.", format = Message.Format.MESSAGE_FORMAT)
void errorGettingDataSourceProviderInfo(@Cause Exception e);
}

View File

@ -0,0 +1,76 @@
/*
* 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.activemq.artemis.osgi;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import org.apache.activemq.artemis.core.config.storage.DatabaseStorageConfiguration;
import org.apache.activemq.artemis.jdbc.store.drivers.JDBCUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
public class DataSourceTracker implements ServiceTrackerCustomizer<DataSource, DataSource> {
private final String name;
private final BundleContext context;
private final DatabaseStorageConfiguration dsc;
private final ServerTrackerCallBack callback;
public DataSourceTracker(String name, BundleContext context, DatabaseStorageConfiguration dsc,
ServerTrackerCallBack callback) {
this.name = name;
this.context = context;
this.dsc = dsc;
this.callback = callback;
}
@Override
public DataSource addingService(ServiceReference<DataSource> reference) {
DataSource dataSource = context.getService(reference);
dsc.setDataSource(dataSource);
try (Connection conn = dataSource.getConnection()) {
dsc.setSqlProvider(JDBCUtils.getSQLProviderFactory(conn.getMetaData().getURL()));
} catch (SQLException ex) {
ActiveMQOsgiLogger.LOGGER.errorGettingDataSourceProviderInfo(ex);
}
callback.setDataSourceDependency(false);
try {
callback.start();
} catch (Exception ex) {
ActiveMQOsgiLogger.LOGGER.errorStartingBroker(ex, name);
}
return dataSource;
}
@Override
public void modifiedService(ServiceReference<DataSource> reference, DataSource service) {
// not supported
}
@Override
public void removedService(ServiceReference<DataSource> reference, DataSource service) {
callback.setDataSourceDependency(true);
try {
callback.stop();
} catch (Exception ex) {
ActiveMQOsgiLogger.LOGGER.errorStoppingBroker(ex, name);
}
}
}

View File

@ -0,0 +1,314 @@
/*
* 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.activemq.artemis.osgi;
import java.io.File;
import java.lang.management.ManagementFactory;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.activemq.artemis.api.core.Interceptor;
import org.apache.activemq.artemis.api.core.TransportConfiguration;
import org.apache.activemq.artemis.api.core.management.ObjectNameBuilder;
import org.apache.activemq.artemis.core.config.FileDeploymentManager;
import org.apache.activemq.artemis.core.config.StoreConfiguration;
import org.apache.activemq.artemis.core.config.StoreConfiguration.StoreType;
import org.apache.activemq.artemis.core.config.impl.FileConfiguration;
import org.apache.activemq.artemis.core.config.storage.DatabaseStorageConfiguration;
import org.apache.activemq.artemis.core.remoting.impl.netty.TransportConstants;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.core.server.ActiveMQServer;
import org.apache.activemq.artemis.core.server.management.ArtemisMBeanServerBuilder;
import org.apache.activemq.artemis.core.server.management.ArtemisMBeanServerGuard;
import org.apache.activemq.artemis.core.server.management.HawtioSecurityControl;
import org.apache.activemq.artemis.core.server.management.JMXAccessControlList;
import org.apache.activemq.artemis.core.server.management.impl.HawtioSecurityControlImpl;
import org.apache.activemq.artemis.jms.server.config.impl.FileJMSConfiguration;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory;
import org.apache.activemq.artemis.spi.core.security.ActiveMQJAASSecurityManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.component.ComponentContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.component.annotations.ConfigurationPolicy;
import org.osgi.service.component.annotations.Deactivate;
import org.osgi.util.tracker.ServiceTracker;
import javax.management.ObjectName;
@SuppressWarnings({"unchecked", "rawtypes"})
@Component(configurationPid = "org.apache.activemq.artemis", configurationPolicy = ConfigurationPolicy.REQUIRE)
public class OsgiBroker {
private String name;
private String configurationUrl;
private String rolePrincipalClass;
private Map<String, ActiveMQComponent> components;
private Map<String, ServiceRegistration<?>> registrations;
private ServiceTracker tracker;
private ServiceTracker dataSourceTracker;
@Activate
public void activate(ComponentContext cctx) throws Exception {
final BundleContext context = cctx.getBundleContext();
final Dictionary<String, Object> properties = cctx.getProperties();
configurationUrl = getMandatory(properties, "config");
name = getMandatory(properties, "name");
rolePrincipalClass = (String) properties.get("rolePrincipalClass");
String domain = getMandatory(properties, "domain");
ActiveMQJAASSecurityManager security = new ActiveMQJAASSecurityManager(domain);
if (rolePrincipalClass != null) {
security.setRolePrincipalClass(rolePrincipalClass);
}
String brokerInstance = null;
String artemisDataDir = System.getProperty("artemis.data");
if (artemisDataDir != null) {
brokerInstance = artemisDataDir + "/artemis/" + name;
} else {
String karafDataDir = System.getProperty("karaf.data");
if (karafDataDir != null) {
brokerInstance = karafDataDir + "/artemis/" + name;
}
}
// todo if we start to pullout more configs from the main config then we
// should pull out the configuration objects from factories if available
FileConfiguration configuration = new FileConfiguration();
if (brokerInstance != null) {
configuration.setBrokerInstance(new File(brokerInstance));
}
FileJMSConfiguration jmsConfiguration = new FileJMSConfiguration();
FileDeploymentManager fileDeploymentManager = new FileDeploymentManager(configurationUrl);
fileDeploymentManager.addDeployable(configuration).addDeployable(jmsConfiguration).readConfiguration();
components = fileDeploymentManager.buildService(security, ManagementFactory.getPlatformMBeanServer(), null);
final ActiveMQServer server = (ActiveMQServer) components.get("core");
String[] requiredProtocols = getRequiredProtocols(server.getConfiguration().getAcceptorConfigurations());
ServerTrackerCallBack callback = new ServerTrackerCallBackImpl(server, context, properties);
StoreConfiguration storeConfiguration = server.getConfiguration().getStoreConfiguration();
String dataSourceName = String.class.cast(properties.get("dataSourceName"));
if (storeConfiguration != null &&
storeConfiguration.getStoreType() == StoreType.DATABASE && dataSourceName != null &&
!dataSourceName.isEmpty()) {
callback.setDataSourceDependency(true);
String filter = "(&(objectClass=javax.sql.DataSource)(osgi.jndi.service.name=" + dataSourceName + "))";
DataSourceTracker trackerCust =
new DataSourceTracker(name, context, DatabaseStorageConfiguration.class.cast(storeConfiguration),
(ServerTrackerCallBack) callback);
dataSourceTracker = new ServiceTracker(context, context.createFilter(filter), trackerCust);
dataSourceTracker.open();
}
registerMBean();
ProtocolTracker trackerCust = new ProtocolTracker(name, context, requiredProtocols, callback);
tracker = new ServiceTracker(context, ProtocolManagerFactory.class, trackerCust);
tracker.open();
}
private void registerMBean() throws Exception {
ArtemisMBeanServerGuard artemisMBeanServerGuard = createArtemisMBeanServerGuard();
HawtioSecurityControl control = new HawtioSecurityControlImpl(artemisMBeanServerGuard, null);
ObjectName objectName = new ObjectName("artemis:area=jmx,name=ArtemisJMXSecurity,type=security");
if (!ManagementFactory.getPlatformMBeanServer().isRegistered(objectName)) {
ManagementFactory.getPlatformMBeanServer().registerMBean(control, objectName);
}
}
public ArtemisMBeanServerGuard createArtemisMBeanServerGuard() throws Exception {
JMXAccessControlList accessControlList = createJMXAccessControlList();
ArtemisMBeanServerGuard guardHandler = new ArtemisMBeanServerGuard();
guardHandler.setJMXAccessControlList(accessControlList);
ArtemisMBeanServerBuilder.setGuard(guardHandler);
return guardHandler;
}
private JMXAccessControlList createJMXAccessControlList() {
JMXAccessControlList accessControlList = new JMXAccessControlList();
accessControlList.addToAllowList("org.apache.activemq.artemis", null);
return accessControlList;
}
private String getMandatory(Dictionary<String, ?> properties, String key) {
String value = (String) properties.get(key);
if (value == null) {
throw new IllegalStateException("Property " + key + " must be set");
}
return value;
}
private String[] getRequiredProtocols(Set<TransportConfiguration> acceptors) {
ArrayList<String> protocols = new ArrayList<>();
for (TransportConfiguration acceptor : acceptors) {
Object protocolsFromAcceptor = acceptor.getParams().get(TransportConstants.PROTOCOLS_PROP_NAME);
if (protocolsFromAcceptor != null) {
String[] protocolsSplit = protocolsFromAcceptor.toString().split(",");
for (String protocol : protocolsSplit) {
if (!protocols.contains(protocol)) {
protocols.add(protocol);
}
}
}
}
return protocols.toArray(new String[protocols.size()]);
}
@Deactivate
public void stop() throws Exception {
tracker.close();
if (dataSourceTracker != null) {
dataSourceTracker.close();
}
}
public Map<String, ActiveMQComponent> getComponents() {
return components;
}
/*
* this makes sure the components are started in the correct order. Its
* simple at the mo as e only have core and jms but will need impproving if
* we get more.
*/
public ArrayList<ActiveMQComponent> getComponentsByStartOrder(Map<String, ActiveMQComponent> components) {
ArrayList<ActiveMQComponent> activeMQComponents = new ArrayList<>();
ActiveMQComponent jmsComponent = components.get("jms");
if (jmsComponent != null) {
activeMQComponents.add(jmsComponent);
}
activeMQComponents.add(components.get("core"));
return activeMQComponents;
}
public void register(BundleContext context, Dictionary<String, ?> properties) {
registrations = new HashMap<>();
for (Map.Entry<String, ActiveMQComponent> component : getComponents().entrySet()) {
String[] classes = getInterfaces(component.getValue());
Hashtable<String, Object> props = new Hashtable<>();
for (Enumeration<String> keyEnum = properties.keys(); keyEnum.hasMoreElements(); ) {
String key = keyEnum.nextElement();
Object val = properties.get(key);
props.put(key, val);
}
ServiceRegistration<?> registration = context.registerService(classes, component.getValue(), props);
registrations.put(component.getKey(), registration);
}
}
private String[] getInterfaces(ActiveMQComponent value) {
Set<String> interfaces = new HashSet<>();
getInterfaces(value.getClass(), interfaces);
return interfaces.toArray(new String[interfaces.size()]);
}
private void getInterfaces(Class<?> clazz, Set<String> interfaces) {
for (Class<?> itf : clazz.getInterfaces()) {
if (interfaces.add(itf.getName())) {
getInterfaces(itf, interfaces);
}
}
if (clazz.getSuperclass() != null) {
getInterfaces(clazz.getSuperclass(), interfaces);
}
}
public void unregister() {
if (registrations != null) {
for (ServiceRegistration<?> reg : registrations.values()) {
reg.unregister();
}
}
}
private class ServerTrackerCallBackImpl implements ServerTrackerCallBack {
private volatile boolean dataSourceDependency = false;
private final ActiveMQServer server;
private final BundleContext context;
private final Dictionary<String, Object> properties;
ServerTrackerCallBackImpl(ActiveMQServer server, BundleContext context,
Dictionary<String, Object> properties) {
this.server = server;
this.context = context;
this.properties = properties;
}
@Override
public void addFactory(ProtocolManagerFactory<Interceptor> pmf) {
server.addProtocolManagerFactory(pmf);
}
@Override
public void removeFactory(ProtocolManagerFactory<Interceptor> pmf) {
server.removeProtocolManagerFactory(pmf);
}
@Override
public void stop() throws Exception {
ActiveMQComponent[] mqComponents = new ActiveMQComponent[components.size()];
components.values().toArray(mqComponents);
for (int i = mqComponents.length - 1; i >= 0; i--) {
mqComponents[i].stop();
}
unregister();
}
@Override
public void start() throws Exception {
if (!dataSourceDependency) {
List<ActiveMQComponent> componentsByStartOrder = getComponentsByStartOrder(components);
for (ActiveMQComponent component : componentsByStartOrder) {
component.start();
}
register(context, properties);
}
}
@Override
public boolean isStarted() {
return server.isStarted();
}
@Override
public void setDataSourceDependency(boolean dataSourceDependency) {
this.dataSourceDependency = dataSourceDependency;
}
}
}

View File

@ -0,0 +1,142 @@
/*
* 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.activemq.artemis.osgi;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.activemq.artemis.api.core.Interceptor;
import org.apache.activemq.artemis.api.core.client.ActiveMQClient;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* Tracks the available ProtocolManagerFactory services as well as the required protocols.
* When a new service appears the factory is added to the server.
* When all needed protocols are present the server is started.
* When required a service disappears the server is stopped.
*/
@SuppressWarnings("rawtypes")
public class ProtocolTracker implements ServiceTrackerCustomizer<ProtocolManagerFactory<Interceptor>, ProtocolManagerFactory<Interceptor>> {
private String name;
private BundleContext context;
private Map<String, Boolean> protocols;
private ServerTrackerCallBack callback;
public ProtocolTracker(String name,
BundleContext context,
String[] requiredProtocols,
ServerTrackerCallBack callback) {
this.name = name;
this.context = context;
this.callback = callback;
this.protocols = new HashMap<>();
for (String requiredProtocol : requiredProtocols) {
this.protocols.put(requiredProtocol, false);
}
ActiveMQOsgiLogger.LOGGER.brokerConfigFound(name, Arrays.asList(requiredProtocols).toString());
//CORE is always registered as a protocol in RemoteServiceImpl
this.protocols.put(ActiveMQClient.DEFAULT_CORE_PROTOCOL, true);
//if no protocols are specified we need to start artemis
List<String> missing = getMissing();
if (missing.isEmpty()) {
try {
callback.start();
} catch (Exception e) {
ActiveMQOsgiLogger.LOGGER.errorStartingBroker(e, name);
}
}
}
@Override
public ProtocolManagerFactory addingService(ServiceReference<ProtocolManagerFactory<Interceptor>> reference) {
ProtocolManagerFactory<Interceptor> pmf = context.getService(reference);
callback.addFactory(pmf);
for (String protocol : pmf.getProtocols()) {
protocolAdded(protocol);
}
return pmf;
}
@Override
public void modifiedService(ServiceReference<ProtocolManagerFactory<Interceptor>> reference,
ProtocolManagerFactory<Interceptor> pmf) {
// Not supported
}
@Override
public void removedService(ServiceReference<ProtocolManagerFactory<Interceptor>> reference,
ProtocolManagerFactory<Interceptor> pmf) {
for (String protocol : pmf.getProtocols()) {
protocolRemoved(protocol);
}
callback.removeFactory(pmf);
}
private void protocolAdded(String protocol) {
Boolean present = this.protocols.get(protocol);
if (present != null && !present) {
this.protocols.put(protocol, true);
List<String> missing = getMissing();
ActiveMQOsgiLogger.LOGGER.protocolWasAddedForBroker(protocol, name, (missing.isEmpty() ? "Starting broker." : "Still waiting for " + missing));
if (missing.isEmpty()) {
try {
callback.start();
} catch (Exception e) {
ActiveMQOsgiLogger.LOGGER.errorStartingBroker(e, name);
}
}
}
}
private void protocolRemoved(String protocol) {
Boolean present = this.protocols.get(protocol);
if (present != null && present) {
List<String> missing = getMissing();
ActiveMQOsgiLogger.LOGGER.protocolWasRemovedForBroker(protocol, name, (missing.isEmpty() ? "Stopping broker. " : ""));
if (missing.isEmpty()) {
try {
callback.stop();
} catch (Exception e) {
ActiveMQOsgiLogger.LOGGER.errorStoppingBroker(e, name);
}
}
this.protocols.put(protocol, false);
}
}
private List<String> getMissing() {
List<String> missing = new ArrayList<>();
for (String protocol : protocols.keySet()) {
Boolean present = protocols.get(protocol);
if (!present) {
missing.add(protocol);
}
}
return missing;
}
}

View File

@ -0,0 +1,31 @@
/*
* 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.activemq.artemis.osgi;
import org.apache.activemq.artemis.api.core.Interceptor;
import org.apache.activemq.artemis.core.server.ActiveMQComponent;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory;
public interface ServerTrackerCallBack extends ActiveMQComponent {
void addFactory(ProtocolManagerFactory<Interceptor> pmf);
void removeFactory(ProtocolManagerFactory<Interceptor> pmf);
void setDataSourceDependency(boolean dataSourceDependency);
}

View File

@ -0,0 +1,76 @@
/*
* 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.activemq.artemis.osgi;
import org.apache.activemq.artemis.api.core.Interceptor;
import org.apache.activemq.artemis.spi.core.protocol.ProtocolManagerFactory;
import org.easymock.EasyMock;
import org.easymock.IMocksControl;
import org.junit.Test;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceReference;
import static org.easymock.EasyMock.expect;
@SuppressWarnings({"rawtypes", "unchecked"})
public class ProtocolTrackerTest {
@Test
public void testLifecycle() throws Exception {
IMocksControl c = EasyMock.createControl();
BundleContext context = c.createMock(BundleContext.class);
String[] requiredProtocols = {"a", "b"};
ServerTrackerCallBack callback = c.createMock(ServerTrackerCallBack.class);
RefFact protA = new RefFact(c, context, new String[]{"a"});
RefFact protB = new RefFact(c, context, new String[]{"b"});
callback.addFactory(protA.factory);
EasyMock.expectLastCall();
callback.addFactory(protB.factory);
EasyMock.expectLastCall();
callback.start();
EasyMock.expectLastCall();
callback.removeFactory(protA.factory);
EasyMock.expectLastCall();
callback.stop();
EasyMock.expectLastCall();
c.replay();
ProtocolTracker tracker = new ProtocolTracker("test", context, requiredProtocols, callback);
tracker.addingService(protA.ref);
tracker.addingService(protB.ref);
tracker.removedService(protA.ref, protA.factory);
c.verify();
}
class RefFact {
ServiceReference<ProtocolManagerFactory<Interceptor>> ref;
ProtocolManagerFactory factory;
RefFact(IMocksControl c, BundleContext context, String[] protocols) {
ref = c.createMock(ServiceReference.class);
factory = c.createMock(ProtocolManagerFactory.class);
expect(factory.getProtocols()).andReturn(protocols).atLeastOnce();
expect(context.getService(ref)).andReturn(factory).atLeastOnce();
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -47,7 +47,7 @@ public class AggregationStrategyClause<T> implements AggregationStrategy {
// *******************************
/**
* Define an aggregation strategy which targets the exchnage.
* Define an aggregation strategy which targets the exchange.
*/
public T exchange(final BiFunction<Exchange, Exchange, Exchange> function) {
strategy = function::apply;

View File

@ -328,7 +328,7 @@ public class RecipientListDefinition<Type extends ProcessorDefinition<Type>> ext
/**
* Sets the {@link Processor} when preparing the
* {@link org.apache.camel.Exchange} to be used send using a fluent buidler.
* {@link org.apache.camel.Exchange} to be used send using a fluent builder.
*/
public ProcessClause<RecipientListDefinition<Type>> onPrepare() {
ProcessClause<RecipientListDefinition<Type>> clause = new ProcessClause<>(this);

View File

@ -542,7 +542,7 @@ public class RedeliveryPolicyDefinition {
}
/**
* Turn on exponential backk off
* Turn on exponential back off
*
* @return the builder
*/

View File

@ -82,6 +82,7 @@
</properties>
<!-- Comment out the snapshot repositories as we don't need them now -->
<!--
<repositories>
<repository>
<id>central</id>
@ -94,7 +95,9 @@
<enabled>true</enabled>
</releases>
</repository>
-->
<!-- camel-jira -->
<!--
<repository>
<id>atlassian-public</id>
<url>https://packages.atlassian.com/maven-external</url>
@ -106,7 +109,9 @@
<enabled>true</enabled>
</releases>
</repository>
-->
<!-- camel-ipfs and camel-weka -->
<!--
<repository>
<id>jboss.thirdparty</id>
<name>JBoss Thirdparty Repository</name>
@ -142,6 +147,7 @@
</releases>
</pluginRepository>
</pluginRepositories>
-->
<build>
<defaultGoal>install</defaultGoal>
@ -876,4 +882,69 @@
</profile>
</profiles>
<repositories>
<!--
contains all used components together with sources and javadocs, proxies Maven Central and Apache
also contains public Entaxy releases & snapshots (snapshots are disabled here)
-->
<repository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--
contains all Entaxy snaphots and releases, authorized access only
use Maven settins.xml to provide access credentials
-->
<repository>
<id>entaxy-private</id>
<name>entaxy-private</name>
<!-- url>http://localhost:8981/repository/entaxy-private/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-private/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -745,4 +745,68 @@
</profile>
</profiles>
<repositories>
<!--
contains all used components together with sources and javadocs, proxies Maven Central and Apache
also contains public Entaxy releases & snapshots (snapshots are disabled here)
-->
<repository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--
contains all Entaxy snaphots and releases, authorized access only
use Maven settins.xml to provide access credentials
-->
<repository>
<id>entaxy-private</id>
<name>entaxy-private</name>
<!-- url>http://localhost:8981/repository/entaxy-private/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-private/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ru.entaxy.esb</groupId>
<artifactId>underlying</artifactId>
<version>1.8.1</version>
<version>1.8.2</version>
</parent>
<groupId>ru.entaxy.esb.underlying</groupId>
<artifactId>entaxy-underlying-configuration</artifactId>

View File

@ -216,6 +216,8 @@ felix.fileinstall.noInitialDelay = true
felix.fileinstall.log.level = 3
felix.fileinstall.log.default = jul
felix.fileinstall.subdir.mode = recurse
# Use cached urls for bundle CodeSource to avoid
# problems with JCE cached informations, see KARAF-3974
felix.bundlecodesource.usecachedurls = true

View File

@ -1,2 +1,4 @@
find = { bundle:list -t 0 -l | grep $args }
entaxy:list = { find entaxy }
inactive = { bundle:list -t 0 -l | grep -v Active }
entaxy:list = { find entaxy }
entaxy:inactive = { entaxy:list | grep -v Active }

View File

@ -60,6 +60,11 @@
</blacklistedRepositories>
<bundleReplacements>
<bundle originalUri="mvn:org.apache.felix/org.apache.felix.configadmin/[1.9, 1.9.16]" replacement="mvn:org.apache.felix/org.apache.felix.configadmin/1.9.16-ENTAXY" mode="maven" />
<bundle originalUri="mvn:org.apache.aries.blueprint/org.apache.aries.blueprint.cm/[1.3, 1.3.2]" replacement="mvn:org.apache.aries.blueprint/org.apache.aries.blueprint.cm/1.3.2-ENTAXY" mode="maven" />
<bundle originalUri="mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.bundle/${cellar.version}" replacement="mvn:org.apache.karaf.cellar/org.apache.karaf.cellar.bundle/${cellar.version}-ENTAXY" mode="maven" />
<bundle originalUri="mvn:com.fasterxml.jackson.core/jackson-annotations/(0,2.10.5)" replacement="mvn:com.fasterxml.jackson.core/jackson-annotations/2.10.5" mode="maven" />
@ -92,6 +97,10 @@
<bundle originalUri="mvn:org.apache.camel/camel-base/${camel.version}" replacement="mvn:org.apache.camel/camel-base/${camel.version}-ENTAXY" mode="maven" />
<bundle originalUri="mvn:org.apache.camel/camel-core-engine/${camel.version}" replacement="mvn:org.apache.camel/camel-core-engine/${camel.version}-ENTAXY" mode="maven" />
<bundle originalUri="mvn:org.codehaus.woodstox/woodstox-core-asl/4.4.1"
replacement="mvn:org.codehaus.woodstox/woodstox-core-asl/4.4.1-ENTAXY" mode="maven" />
<bundle originalUri="mvn:org.apache.activemq/artemis-server-osgi/2.19.0"
replacement="mvn:org.apache.activemq/artemis-server-osgi/2.19.0-ENTAXY" mode="maven" />
</bundleReplacements>
<featureReplacements>

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ru.entaxy.esb</groupId>
<artifactId>underlying</artifactId>
<version>1.8.1</version>
<version>1.8.2</version>
</parent>
<groupId>ru.entaxy.esb.underlying</groupId>
<artifactId>entaxy-underlying-features</artifactId>

View File

@ -76,6 +76,7 @@
<feature name="entaxy-karaf-commons-support" version="${project.version}">
<bundle>mvn:commons-io/commons-io/${commons-io.version}</bundle>
<bundle>mvn:commons-codec/commons-codec/${commons-codec.version}</bundle>
<bundle>mvn:org.apache.commons/commons-collections4/${commons-collections4.version}</bundle>
</feature>
<feature name="entaxy-karaf-liquibase-support" version="${project.version}">

View File

@ -3,7 +3,7 @@
<parent>
<groupId>ru.entaxy.esb</groupId>
<artifactId>root</artifactId>
<version>1.8.1</version>
<version>1.8.2</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<groupId>com.h2database</groupId>

View File

@ -0,0 +1,21 @@
// ------------------------------------------------------------------
// Transitive dependencies of this project determined from the
// maven pom organized by organization.
// ------------------------------------------------------------------
Apache Aries Blueprint CM
From: 'The Apache Software Foundation' (http://www.apache.org)
- Apache Aries Blueprint API (http://aries.apache.org/org.apache.aries.blueprint.api) org.apache.aries.blueprint:org.apache.aries.blueprint.api:bundle:1.0.1
License: The Apache Software License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
- Apache Aries Blueprint Core (http://aries.apache.org/blueprint-parent/org.apache.aries.blueprint.core) org.apache.aries.blueprint:org.apache.aries.blueprint.core:bundle:1.10.0
License: The Apache Software License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
- Apache Aries Proxy API (http://aries.apache.org/org.apache.aries.proxy.api) org.apache.aries.proxy:org.apache.aries.proxy.api:bundle:1.1.0
License: The Apache Software License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)
- Apache Aries Quiesce API (http://aries.apache.org/default-parent/java5-parent/org.apache.aries.quiesce.api) org.apache.aries.quiesce:org.apache.aries.quiesce.api:bundle:1.0.0
License: The Apache Software License, Version 2.0 (http://www.apache.org/licenses/LICENSE-2.0.txt)

View File

@ -0,0 +1,203 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -0,0 +1,8 @@
Apache Aries
Copyright 2009-2011 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@ -0,0 +1,267 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.aries.blueprint</groupId>
<artifactId>blueprint-parent</artifactId>
<version>1.0.0</version>
<relativePath>../blueprint-parent/pom.xml</relativePath>
</parent>
<groupId>org.apache.aries.blueprint</groupId>
<artifactId>org.apache.aries.blueprint.cm</artifactId>
<packaging>bundle</packaging>
<version>1.3.2-ENTAXY</version>
<name>Apache Aries Blueprint CM</name>
<description>
This bundle contains the ConfigAdmin namespace for blueprint with patch for ARIES-2076.
</description>
<scm>
<connection>scm:git:https://gitbox.apache.org/repos/asf/aries.git</connection>
<developerConnection>scm:git:https://gitbox.apache.org/repos/asf/aries.git</developerConnection>
<url>https://gitbox.apache.org/repos/asf?p=aries.git;a=summary</url>
<tag>org.apache.aries.blueprint.cm-1.3.2</tag>
</scm>
<properties>
<!-- Export package versions are maintained in packageinfo files -->
<aries.osgi.export.pkg />
<aries.osgi.import.pkg>
org.apache.aries.blueprint;provide:=true;version="[1.0,2.0)",
org.apache.aries.blueprint.ext;provide:=true;version="[1.0,2.0)",
*
</aries.osgi.import.pkg>
<aries.osgi.private.pkg>
org.apache.aries.blueprint.compendium.cm
</aries.osgi.private.pkg>
<blueprint.api.version>1.0.1</blueprint.api.version>
<blueprint.core.version>1.10.3</blueprint.core.version>
<blueprint.parser.version>1.6.0</blueprint.parser.version>
<lastReleaseVersion>1.1.0</lastReleaseVersion>
</properties>
<profiles>
<profile>
<id>dev</id>
<properties>
<blueprint.api.version>${blueprint.api.dev.version}</blueprint.api.version>
<blueprint.core.version>${blueprint.core.dev.version}</blueprint.core.version>
<blueprint.cm.version>${blueprint.cm.dev.version}</blueprint.cm.version>
<blueprint.parser.version>${blueprint.parser.dev.version}</blueprint.parser.version>
<blueprint.authz.version>${blueprint.authz.dev.version}</blueprint.authz.version>
<blueprint.spring.version>${blueprint.spring.dev.version}</blueprint.spring.version>
<blueprint.spring.extender.version>${blueprint.spring.extender.dev.version}</blueprint.spring.extender.version>
<blueprint.jexl.evaluator.version>${blueprint.jexl.evaluator.dev.version}</blueprint.jexl.evaluator.version>
<blueprint.sample.version>${blueprint.sample.dev.version}</blueprint.sample.version>
<blueprint.sample.fragment.version>${blueprint.sample.fragment.dev.version}</blueprint.sample.fragment.version>
</properties>
</profile>
<profile>
<id>public-deploy</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<distributionManagement>
<repository>
<id>entaxy-public-entaxy</id>
<name>entaxy-public-entaxy</name>
<uniqueVersion>false</uniqueVersion>
<layout>default</layout>
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public-entaxy/</url>
</repository>
</distributionManagement>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>org.apache.aries.blueprint</groupId>
<artifactId>org.apache.aries.blueprint.api</artifactId>
<version>${blueprint.api.version}</version>
</dependency>
<dependency>
<groupId>org.apache.aries.blueprint</groupId>
<artifactId>blueprint-parser</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.aries.blueprint</groupId>
<artifactId>org.apache.aries.blueprint.core</artifactId>
<version>1.10.0</version>
<exclusions>
<exclusion>
<groupId>org.apache.aries</groupId>
<artifactId>org.apache.aries.util</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.compendium</artifactId>
<scope>provided</scope>
<version>4.1.0</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.googlecode.pojosr</groupId>
<artifactId>de.kalpatec.pojosr.framework</artifactId>
<version>0.1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.swissbox</groupId>
<artifactId>pax-swissbox-tinybundles</artifactId>
<version>1.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>1.7.5</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.aries.proxy</groupId>
<artifactId>org.apache.aries.proxy</artifactId>
<version>1.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.configadmin</artifactId>
<version>1.2.8</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.aries.versioning</groupId>
<artifactId>org.apache.aries.versioning.plugin</artifactId>
<executions>
<execution>
<id>default-verify</id>
<phase>verify</phase>
<goals>
<goal>version-check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-release-plugin</artifactId>
<version>2.5.2</version>
</plugin>
</plugins>
</build>
<repositories>
<!--
contains all used components together with sources and javadocs, proxies Maven Central and Apache
also contains public Entaxy releases & snapshots (snapshots are disabled here)
-->
<repository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--
contains all Entaxy snaphots and releases, authorized access only
use Maven settins.xml to provide access credentials
-->
<repository>
<id>entaxy-private</id>
<name>entaxy-private</name>
<!-- url>http://localhost:8981/repository/entaxy-private/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-private/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -0,0 +1,2 @@
This product includes software developed at
the OSGi Alliance (http://www.osgi.org/).

View File

@ -0,0 +1,234 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.util.Dictionary;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.aries.blueprint.utils.JavaUtils;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@SuppressWarnings("rawtypes")
public abstract class BaseManagedServiceFactory<T> implements ManagedServiceFactory {
public static final long DEFAULT_TIMEOUT_BEFORE_INTERRUPT = 30000;
public static final int CONFIGURATION_ADMIN_OBJECT_DELETED = 1;
public static final int BUNDLE_STOPPING = 2;
public static final int INTERNAL_ERROR = 4;
protected final Logger LOGGER = LoggerFactory.getLogger(getClass());
private final BundleContext context;
private final String name;
private final long timeoutBeforeInterrupt;
private final AtomicBoolean destroyed;
private final ExecutorService executor;
private final Map<String, Pair<T, ServiceRegistration>> services;
private final Map<ServiceRegistration, T> registrations;
public BaseManagedServiceFactory(BundleContext context, String name) {
this(context, name, DEFAULT_TIMEOUT_BEFORE_INTERRUPT);
}
public BaseManagedServiceFactory(BundleContext context, String name, long timeoutBeforeInterrupt) {
this.context = context;
this.name = name;
this.timeoutBeforeInterrupt = timeoutBeforeInterrupt;
this.destroyed = new AtomicBoolean(false);
this.executor = Executors.newSingleThreadExecutor();
this.services = new ConcurrentHashMap<String, Pair<T, ServiceRegistration>>();
this.registrations = new ConcurrentHashMap<ServiceRegistration, T>();
}
public String getName() {
return name;
}
public Map<ServiceRegistration, T> getServices() {
return registrations;
}
public void updated(final String pid, final Dictionary properties) throws ConfigurationException {
if (destroyed.get()) {
return;
}
checkConfiguration(pid, properties);
executor.submit(new Runnable() {
public void run() {
try {
internalUpdate(pid, properties);
} catch (Throwable t) {
LOGGER.warn("Error destroying service for ManagedServiceFactory " + getName(), t);
}
}
});
}
public void deleted(final String pid) {
if (destroyed.get()) {
return;
}
executor.submit(new Runnable() {
public void run() {
try {
internalDelete(pid, CONFIGURATION_ADMIN_OBJECT_DELETED);
} catch (Throwable throwable) {
LOGGER.warn("Error destroying service for ManagedServiceFactory " + getName(), throwable);
}
}
});
}
protected void checkConfiguration(String pid, Dictionary properties) throws ConfigurationException {
// Do nothing
}
protected abstract T doCreate(Dictionary properties) throws Exception;
protected abstract T doUpdate(T t, Dictionary properties) throws Exception;
protected abstract void doDestroy(T t, Dictionary properties, int code) throws Exception;
protected abstract String[] getExposedClasses(T t);
private void internalUpdate(String pid, Dictionary properties) {
Pair<T, ServiceRegistration> pair = services.get(pid);
if (pair != null) {
try {
T t = doUpdate(pair.getFirst(), properties);
pair.setFirst(t);
pair.getSecond().setProperties(properties);
} catch (Throwable throwable) {
internalDelete(pid, INTERNAL_ERROR);
LOGGER.warn("Error updating service for ManagedServiceFactory " + getName(), throwable);
}
} else {
if (destroyed.get()) {
return;
}
try {
T t = doCreate(properties);
try {
if (destroyed.get()) {
throw new IllegalStateException("ManagedServiceFactory has been destroyed");
}
ServiceRegistration registration = context.registerService(getExposedClasses(t), t, properties);
services.put(pid, new Pair<T, ServiceRegistration>(t, registration));
registrations.put(registration, t);
postRegister(t, properties, registration);
} catch (Throwable throwable1) {
try {
doDestroy(t, properties, INTERNAL_ERROR);
} catch (Throwable throwable2) {
// Ignore
}
throw throwable1;
}
} catch (Throwable throwable) {
LOGGER.warn("Error creating service for ManagedServiceFactory " + getName(), throwable);
}
}
}
protected void postRegister(T t, Dictionary properties, ServiceRegistration registration) {
// Place holder
}
protected void preUnregister(T t, Dictionary properties, ServiceRegistration registration) {
// Place holder
}
private void internalDelete(String pid, int code) {
Pair<T, ServiceRegistration> pair = services.remove(pid);
if (pair != null) {
registrations.remove(pair.getSecond());
Dictionary properties = JavaUtils.getProperties(pair.getSecond().getReference());
try {
preUnregister(pair.getFirst(), properties, pair.getSecond());
pair.getSecond().unregister();
} catch (Throwable t) {
LOGGER.info("Error unregistering service", t);
}
try {
doDestroy(pair.getFirst(), properties, code);
} catch (Throwable t) {
LOGGER.info("Error destroying service", t);
}
}
}
public void destroy() {
if (destroyed.compareAndSet(false, true)) {
executor.shutdown();
try {
executor.awaitTermination(timeoutBeforeInterrupt, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("Shutdown interrupted");
}
if (!executor.isTerminated()) {
executor.shutdownNow();
try {
executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
} catch (InterruptedException e) {
throw new RuntimeException("Shutdown interrupted");
}
}
while (!services.isEmpty()) {
String pid = services.keySet().iterator().next();
internalDelete(pid, BUNDLE_STOPPING);
}
}
}
static class Pair<U, V> {
private U first;
private V second;
public Pair(U first, V second) {
this.first = first;
this.second = second;
}
public U getFirst() {
return first;
}
public V getSecond() {
return second;
}
public void setFirst(U first) {
this.first = first;
}
public void setSecond(V second) {
this.second = second;
}
}
}

View File

@ -0,0 +1,296 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.aries.blueprint.BeanProcessor;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
import org.apache.aries.blueprint.utils.ReflectionUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.service.blueprint.container.ReifiedType;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TODO
*
* @version $Rev$, $Date$
*/
public class CmManagedProperties implements ManagedObject, BeanProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(CmManagedProperties.class);
private ExtendedBlueprintContainer blueprintContainer;
private ConfigurationAdmin configAdmin;
private ManagedObjectManager managedObjectManager;
private String persistentId;
private String updateStrategy;
private String updateMethod;
private String beanName;
private final Object lock = new Object();
private final Set<Object> beans = new HashSet<Object>();
private Dictionary<String, Object> properties;
private boolean initialized;
public ExtendedBlueprintContainer getBlueprintContainer() {
return blueprintContainer;
}
public void setBlueprintContainer(ExtendedBlueprintContainer blueprintContainer) {
this.blueprintContainer = blueprintContainer;
}
public ConfigurationAdmin getConfigAdmin() {
return configAdmin;
}
public void setConfigAdmin(ConfigurationAdmin configAdmin) {
this.configAdmin = configAdmin;
}
public void setManagedObjectManager(ManagedObjectManager managedObjectManager) {
this.managedObjectManager = managedObjectManager;
}
public ManagedObjectManager getManagedObjectManager() {
return managedObjectManager;
}
public Bundle getBundle() {
return blueprintContainer.getBundleContext().getBundle();
}
public String getPersistentId() {
return persistentId;
}
public void setPersistentId(String persistentId) {
this.persistentId = persistentId;
}
public String getUpdateStrategy() {
return updateStrategy;
}
public void setUpdateStrategy(String updateStrategy) {
this.updateStrategy = updateStrategy;
}
public String getUpdateMethod() {
return updateMethod;
}
public void setUpdateMethod(String updateMethod) {
this.updateMethod = updateMethod;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public void init() throws Exception {
LOGGER.debug("Initializing CmManagedProperties for bean={} / pid={}", beanName, persistentId);
Properties props = new Properties();
props.put(Constants.SERVICE_PID, persistentId);
Bundle bundle = blueprintContainer.getBundleContext().getBundle();
props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION));
synchronized (lock) {
managedObjectManager.register(this, props);
}
}
public void destroy() {
managedObjectManager.unregister(this);
}
public void updated(final Dictionary props) {
if (!initialized) {
properties = props;
initialized = true;
return;
}
LOGGER.debug("Configuration updated for bean={} / pid={}", beanName, persistentId);
synchronized (lock) {
properties = props;
for (Object bean : beans) {
updated(bean, properties);
}
}
}
public void updated(Object bean, final Dictionary props) {
LOGGER.debug("Configuration updated for bean={} / pid={}", beanName, persistentId);
synchronized (lock) {
properties = props;
if (bean != null) {
inject(bean, false);
}
}
}
public Object beforeInit(Object bean, String beanName, BeanCreator beanCreator, BeanMetadata beanData) {
if (beanName != null && beanName.equals(this.beanName)) {
LOGGER.debug("Adding bean for bean={} / pid={}", beanName, persistentId);
synchronized (lock) {
beans.add(bean);
inject(bean, true);
}
}
return bean;
}
public Object afterInit(Object bean, String beanName, BeanCreator beanCreator, BeanMetadata beanData) {
return bean;
}
public void beforeDestroy(Object bean, String beanName) {
if (beanName.equals(this.beanName)) {
LOGGER.debug("Removing bean for bean={} / pid={}", beanName, persistentId);
synchronized (lock) {
beans.remove(bean);
}
}
}
public void afterDestroy(Object bean, String beanName) {
}
private void inject(Object bean, boolean initial) {
LOGGER.debug("Injecting bean for bean={} / pid={}", beanName, persistentId);
LOGGER.debug("Configuration: {}", properties);
if (initial || "container-managed".equals(updateStrategy)) {
if (properties != null) {
for (Enumeration<String> e = properties.keys(); e.hasMoreElements();) {
String key = e.nextElement();
Object val = properties.get(key);
String setterName = "set" + Character.toUpperCase(key.charAt(0));
if (key.length() > 0) {
setterName += key.substring(1);
}
Set<Method> validSetters = new LinkedHashSet<Method>();
List<Method> methods = new ArrayList<Method>(Arrays.asList(bean.getClass().getMethods()));
methods.addAll(Arrays.asList(bean.getClass().getDeclaredMethods()));
for (Method method : methods) {
if (method.getName().equals(setterName)) {
if (shouldSkip(method)) {
continue;
}
Class methodParameterType = method.getParameterTypes()[0];
Object propertyValue;
try {
propertyValue = blueprintContainer.getConverter().convert(val, new ReifiedType(methodParameterType));
} catch (Throwable t) {
LOGGER.debug("Unable to convert value for setter: " + method, t);
continue;
}
if (methodParameterType.isPrimitive() && propertyValue == null) {
LOGGER.debug("Null can not be assigned to {}: {}", methodParameterType.getName(), method);
continue;
}
if (validSetters.add(method)) {
try {
method.invoke(bean, propertyValue);
} catch (Exception t) {
LOGGER.debug("Setter can not be invoked: " + method, getRealCause(t));
}
}
}
}
if (validSetters.isEmpty()) {
LOGGER.debug("Unable to find a valid setter method for property {} and value {}", key, val);
}
}
}
} else if ("component-managed".equals(updateStrategy) && updateMethod != null) {
List<Method> methods = ReflectionUtils.findCompatibleMethods(bean.getClass(), updateMethod, new Class[] { Map.class });
Map map = null;
if (properties != null) {
map = new HashMap();
for (Enumeration<String> e = properties.keys(); e.hasMoreElements();) {
String key = e.nextElement();
Object val = properties.get(key);
map.put(key, val);
}
}
for (Method method : methods) {
try {
method.invoke(bean, map);
} catch (Throwable t) {
LOGGER.warn("Unable to call method " + method + " on bean " + beanName, getRealCause(t));
}
}
}
}
private boolean shouldSkip(Method method) {
String msg = null;
if (method.getParameterTypes().length == 0) {
msg = "takes no parameters";
} else if (method.getParameterTypes().length > 1) {
msg = "takes more than one parameter";
} else if (method.getReturnType() != Void.TYPE) {
msg = "returns a value";
} else if (Modifier.isAbstract(method.getModifiers())) {
msg = "is abstract";
} else if (!Modifier.isPublic(method.getModifiers())) {
msg = "is not public";
} else if (Modifier.isStatic(method.getModifiers())) {
msg = "is static";
}
if (msg != null) {
LOGGER.debug("Skipping setter {} because it " + msg, method);
return true;
} else {
return false;
}
}
private static Throwable getRealCause(Throwable t) {
if (t instanceof InvocationTargetException && t.getCause() != null) {
return t.getCause();
}
return t;
}
}

View File

@ -0,0 +1,281 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.aries.blueprint.BeanProcessor;
import org.apache.aries.blueprint.ServiceProcessor;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
import org.apache.aries.blueprint.utils.JavaUtils;
import org.apache.aries.blueprint.utils.ReflectionUtils;
import org.apache.aries.blueprint.utils.ServiceListener;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.blueprint.reflect.ServiceMetadata;
import org.osgi.service.cm.ManagedServiceFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TODO: if we need to make those exported services tied to their references as for other &lt;service/&gt; elements
* TODO: it becomes a problem as currently we would have to create a specific recipe or something like that
*
* @version $Rev$, $Date$
*/
public class CmManagedServiceFactory extends BaseManagedServiceFactory<Object> {
private static final Logger LOGGER = LoggerFactory.getLogger(CmManagedServiceFactory.class);
private ExtendedBlueprintContainer blueprintContainer;
private String id;
private String factoryPid;
private List<String> interfaces;
private int autoExport;
private int ranking;
private Map<Object, Object> serviceProperties;
private String managedComponentName;
private String componentDestroyMethod;
private List<ServiceListener> listeners;
private ServiceRegistration registration;
public CmManagedServiceFactory(ExtendedBlueprintContainer blueprintContainer) {
super(blueprintContainer.getBundleContext(), null);
this.blueprintContainer = blueprintContainer;
}
public void init() throws Exception {
LOGGER.debug("Initializing CmManagedServiceFactory for factoryPid={}", factoryPid);
Properties props = new Properties();
props.put(Constants.SERVICE_PID, factoryPid);
Bundle bundle = blueprintContainer.getBundleContext().getBundle();
props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION));
registration = blueprintContainer.getBundleContext().registerService(ManagedServiceFactory.class.getName(), this, (Dictionary) props);
}
public void destroy() {
ServiceUtil.safeUnregister(registration);
super.destroy();
}
public Map<ServiceRegistration, Object> getServiceMap() {
return Collections.unmodifiableMap(getServices());
}
public void setListeners(List<ServiceListener> listeners) {
this.listeners = listeners;
}
public void setId(String id) {
this.id = id;
}
public void setFactoryPid(String factoryPid) {
this.factoryPid = factoryPid;
}
public void setInterfaces(List<String> interfaces) {
this.interfaces = interfaces;
}
public void setAutoExport(int autoExport) {
this.autoExport = autoExport;
}
public void setRanking(int ranking) {
this.ranking = ranking;
}
public void setServiceProperties(Map serviceProperties) {
this.serviceProperties = serviceProperties;
}
public void setManagedComponentName(String managedComponentName) {
this.managedComponentName = managedComponentName;
}
public void setComponentDestroyMethod(String componentDestroyMethod) {
this.componentDestroyMethod = componentDestroyMethod;
}
private void getRegistrationProperties(Dictionary properties, boolean update) {
String pid = (String) properties.get(Constants.SERVICE_PID);
CmProperties cm = findServiceProcessor();
if (cm == null) {
while (!properties.isEmpty()) {
properties.remove(properties.keys().nextElement());
}
} else {
if (!cm.getUpdate()) {
if (update) {
while (!properties.isEmpty()) {
properties.remove(properties.keys().nextElement());
}
for (Map.Entry entry : cm.getProperties().entrySet()) {
properties.put(entry.getKey(), entry.getValue());
}
} else {
cm.updated(properties);
}
}
}
if (serviceProperties != null) {
for (Map.Entry entry : serviceProperties.entrySet()) {
properties.put(entry.getKey(), entry.getValue());
}
}
properties.put(Constants.SERVICE_RANKING, ranking);
properties.put(Constants.SERVICE_PID, pid);
}
private void updateComponentProperties(Object bean, Dictionary props) {
CmManagedProperties cm = findBeanProcessor();
if (cm != null) {
cm.updated(bean, props);
}
}
private CmManagedProperties findBeanProcessor() {
for (BeanProcessor beanProcessor : blueprintContainer.getProcessors(BeanProcessor.class)) {
if (beanProcessor instanceof CmManagedProperties) {
CmManagedProperties cm = (CmManagedProperties) beanProcessor;
if (managedComponentName.equals(cm.getBeanName()) && "".equals(cm.getPersistentId())) {
return cm;
}
}
}
return null;
}
private CmProperties findServiceProcessor() {
for (ServiceProcessor processor : blueprintContainer.getProcessors(ServiceProcessor.class)) {
if (processor instanceof CmProperties) {
CmProperties cm = (CmProperties) processor;
if (id.equals(cm.getServiceId())) {
return cm;
}
}
}
return null;
}
private Method findDestroyMethod(Class clazz, Class... args) {
Method method = null;
if (componentDestroyMethod != null && componentDestroyMethod.length() > 0) {
List<Method> methods = ReflectionUtils.findCompatibleMethods(clazz, componentDestroyMethod, args);
if (methods != null && !methods.isEmpty()) {
method = methods.get(0);
}
}
return method;
}
protected Object doCreate(Dictionary properties) throws Exception {
updateComponentProperties(null, copy(properties));
Object component = blueprintContainer.getComponentInstance(managedComponentName);
getRegistrationProperties(properties, false);
return component;
}
protected Object doUpdate(Object service, Dictionary properties) throws Exception {
updateComponentProperties(service, copy(properties));
getRegistrationProperties(properties, true);
return service;
}
protected void doDestroy(Object service, Dictionary properties, int code) throws Exception {
Method method = findDestroyMethod(service.getClass(), int.class);
if (method != null) {
try {
method.invoke(service, code);
} catch (Exception e) {
LOGGER.info("Error destroying component", e);
}
} else {
method = findDestroyMethod(service.getClass());
if (method != null) {
try {
method.invoke(service);
} catch (Exception e) {
LOGGER.info("Error destroying component", e);
}
}
}
}
protected void postRegister(Object service, Dictionary properties, ServiceRegistration registration) {
if (listeners != null && !listeners.isEmpty()) {
Hashtable props = new Hashtable();
JavaUtils.copy(properties, props);
for (ServiceListener listener : listeners) {
listener.register(service, props);
}
}
}
protected void preUnregister(Object service, Dictionary properties, ServiceRegistration registration) {
if (listeners != null && !listeners.isEmpty()) {
Hashtable props = new Hashtable();
JavaUtils.copy(properties, props);
for (ServiceListener listener : listeners) {
listener.unregister(service, props);
}
}
}
protected String[] getExposedClasses(Object service) {
Class serviceClass = service.getClass();
Set<String> classes;
switch (autoExport) {
case ServiceMetadata.AUTO_EXPORT_INTERFACES:
classes = ReflectionUtils.getImplementedInterfaces(new HashSet<String>(), serviceClass);
break;
case ServiceMetadata.AUTO_EXPORT_CLASS_HIERARCHY:
classes = ReflectionUtils.getSuperClasses(new HashSet<String>(), serviceClass);
break;
case ServiceMetadata.AUTO_EXPORT_ALL_CLASSES:
classes = ReflectionUtils.getSuperClasses(new HashSet<String>(), serviceClass);
classes = ReflectionUtils.getImplementedInterfaces(classes, serviceClass);
break;
default:
classes = new HashSet<String>(interfaces);
break;
}
return classes.toArray(new String[classes.size()]);
}
private Hashtable copy(Dictionary source) {
Hashtable ht = new Hashtable();
JavaUtils.copy(ht, source);
return ht;
}
}

View File

@ -0,0 +1,669 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.net.URL;
import java.util.*;
import org.apache.aries.blueprint.ComponentDefinitionRegistry;
import org.apache.aries.blueprint.NamespaceHandler;
import org.apache.aries.blueprint.ParserContext;
import org.apache.aries.blueprint.ext.PlaceholdersUtils;
import org.apache.aries.blueprint.mutable.MutableBeanMetadata;
import org.apache.aries.blueprint.mutable.MutableCollectionMetadata;
import org.apache.aries.blueprint.mutable.MutableComponentMetadata;
import org.apache.aries.blueprint.mutable.MutableIdRefMetadata;
import org.apache.aries.blueprint.mutable.MutableMapMetadata;
import org.apache.aries.blueprint.mutable.MutableRefMetadata;
import org.apache.aries.blueprint.mutable.MutableReferenceMetadata;
import org.apache.aries.blueprint.mutable.MutableValueMetadata;
import org.apache.aries.blueprint.utils.ServiceListener;
import org.osgi.framework.Bundle;
import org.osgi.framework.FrameworkUtil;
import org.osgi.service.blueprint.container.ComponentDefinitionException;
import org.osgi.service.blueprint.reflect.BeanMetadata;
import org.osgi.service.blueprint.reflect.BeanProperty;
import org.osgi.service.blueprint.reflect.CollectionMetadata;
import org.osgi.service.blueprint.reflect.ComponentMetadata;
import org.osgi.service.blueprint.reflect.IdRefMetadata;
import org.osgi.service.blueprint.reflect.MapMetadata;
import org.osgi.service.blueprint.reflect.Metadata;
import org.osgi.service.blueprint.reflect.RefMetadata;
import org.osgi.service.blueprint.reflect.ReferenceMetadata;
import org.osgi.service.blueprint.reflect.RegistrationListener;
import org.osgi.service.blueprint.reflect.ServiceMetadata;
import org.osgi.service.blueprint.reflect.ValueMetadata;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Comment;
import org.w3c.dom.Element;
import org.w3c.dom.EntityReference;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
/**
* Namespace handler for the Config Admin service.
* This handler will parse the various elements defined and populate / modify the registry
* accordingly.
*
* @see CmManagedProperties
* @see CmManagedServiceFactory
* @see CmProperties
* @see CmPropertyPlaceholder
*
* @version $Rev$, $Date$
*/
public class CmNamespaceHandler implements NamespaceHandler {
public static final String BLUEPRINT_NAMESPACE = "http://www.osgi.org/xmlns/blueprint/v1.0.0";
public static final String BLUEPRINT_CM_NAMESPACE_1_0 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0";
public static final String BLUEPRINT_CM_NAMESPACE_1_1 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0";
public static final String BLUEPRINT_CM_NAMESPACE_1_2 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.2.0";
public static final String BLUEPRINT_CM_NAMESPACE_1_3 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.3.0";
public static final String BLUEPRINT_CM_NAMESPACE_1_4 = "http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.4.0";
public static final String BLUEPRINT_EXT_NAMESPACE_V1_0 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0";
public static final String BLUEPRINT_EXT_NAMESPACE_V1_1 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0";
public static final String BLUEPRINT_EXT_NAMESPACE_V1_2 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0";
public static final String BLUEPRINT_EXT_NAMESPACE_V1_3 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0";
public static final String BLUEPRINT_EXT_NAMESPACE_V1_4 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0";
public static final String BLUEPRINT_EXT_NAMESPACE_V1_5 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.5.0";
public static final String BLUEPRINT_EXT_NAMESPACE_V1_6 = "http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.6.0";
public static final String PROPERTY_PLACEHOLDER_ELEMENT = "property-placeholder";
public static final String MANAGED_PROPERTIES_ELEMENT = "managed-properties";
public static final String MANAGED_SERVICE_FACTORY_ELEMENT = "managed-service-factory";
public static final String CM_PROPERTIES_ELEMENT = "cm-properties";
public static final String DEFAULT_PROPERTIES_ELEMENT = "default-properties";
public static final String PROPERTY_ELEMENT = "property";
public static final String INTERFACES_ELEMENT = "interfaces";
public static final String VALUE_ELEMENT = "value";
public static final String MANAGED_COMPONENT_ELEMENT = "managed-component";
public static final String LOCATION_ELEMENT = "location";
public static final String SERVICE_PROPERTIES_ELEMENT = "service-properties";
public static final String REGISTRATION_LISTENER_ELEMENT = "registration-listener";
public static final String ID_ATTRIBUTE = "id";
public static final String SYSTEM_PROPERTIES_NEVER = "never";
public static final String PERSISTENT_ID_ATTRIBUTE = "persistent-id";
public static final String PLACEHOLDER_PREFIX_ATTRIBUTE = "placeholder-prefix";
public static final String PLACEHOLDER_SUFFIX_ATTRIBUTE = "placeholder-suffix";
public static final String PLACEHOLDER_NULL_VALUE_ATTRIBUTE = "null-value";
public static final String DEFAULTS_REF_ATTRIBUTE = "defaults-ref";
public static final String UPDATE_STRATEGY_ATTRIBUTE = "update-strategy";
public static final String UPDATE_METHOD_ATTRIBUTE = "update-method";
public static final String FACTORY_PID_ATTRIBUTE = "factory-pid";
public static final String AUTO_EXPORT_ATTRIBUTE = "auto-export";
public static final String RANKING_ATTRIBUTE = "ranking";
public static final String INTERFACE_ATTRIBUTE = "interface";
public static final String UPDATE_ATTRIBUTE = "update";
public static final String SYSTEM_PROPERTIES_ATTRIBUTE = "system-properties";
public static final String IGNORE_MISSING_LOCATIONS_ATTRIBUTE = "ignore-missing-locations";
public static final String AUTO_EXPORT_DISABLED = "disabled";
public static final String AUTO_EXPORT_INTERFACES = "interfaces";
public static final String AUTO_EXPORT_CLASS_HIERARCHY = "class-hierarchy";
public static final String AUTO_EXPORT_ALL = "all-classes";
public static final String AUTO_EXPORT_DEFAULT = AUTO_EXPORT_DISABLED;
public static final String RANKING_DEFAULT = "0";
private static final String MANAGED_OBJECT_MANAGER_NAME = "org.apache.aries.managedObjectManager";
private static final Logger LOGGER = LoggerFactory.getLogger(CmNamespaceHandler.class);
private static final Set<String> EXT_URIS = Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(
BLUEPRINT_EXT_NAMESPACE_V1_0,
BLUEPRINT_EXT_NAMESPACE_V1_1,
BLUEPRINT_EXT_NAMESPACE_V1_2,
BLUEPRINT_EXT_NAMESPACE_V1_3,
BLUEPRINT_EXT_NAMESPACE_V1_4,
BLUEPRINT_EXT_NAMESPACE_V1_5,
BLUEPRINT_EXT_NAMESPACE_V1_6)));
private static final Set<String> CM_URIS = Collections.unmodifiableSet(new LinkedHashSet<String>(Arrays.asList(
BLUEPRINT_CM_NAMESPACE_1_0,
BLUEPRINT_CM_NAMESPACE_1_1,
BLUEPRINT_CM_NAMESPACE_1_2,
BLUEPRINT_CM_NAMESPACE_1_3,
BLUEPRINT_CM_NAMESPACE_1_4)));
// This property is static but it should be ok since there will be only a single instance
// of this class for the bundle
private static ConfigurationAdmin configAdmin;
private int idCounter;
public int getIdCounter() {
return idCounter;
}
public void setIdCounter(int idCounter) {
this.idCounter = idCounter;
}
public static ConfigurationAdmin getConfigAdmin() {
return configAdmin;
}
public void setConfigAdmin(ConfigurationAdmin configAdmin) {
CmNamespaceHandler.configAdmin = configAdmin;
}
public URL getSchemaLocation(String namespace) {
if (isCmNamespace(namespace)) {
String v = namespace.substring("http://aries.apache.org/blueprint/xmlns/blueprint-cm/v".length());
return getClass().getResource("blueprint-cm-" + v + ".xsd");
} else if (isExtNamespace(namespace)) {
try {
Class<?> extNsHandlerClazz;
Bundle extBundle = FrameworkUtil.getBundle(PlaceholdersUtils.class);
if (extBundle == null) {
// we may not be in OSGi environment
extNsHandlerClazz = getClass().getClassLoader().loadClass("org.apache.aries.blueprint.ext.impl.ExtNamespaceHandler");
} else {
extNsHandlerClazz = extBundle.loadClass("org.apache.aries.blueprint.ext.impl.ExtNamespaceHandler");
}
return ((NamespaceHandler) extNsHandlerClazz.newInstance()).getSchemaLocation(namespace);
} catch (Throwable t) {
LOGGER.warn("Could not locate ext namespace schema", t);
return null;
}
} else {
return null;
}
}
public Set<Class> getManagedClasses() {
return new HashSet<Class>(Arrays.asList(
CmPropertyPlaceholder.class,
CmManagedServiceFactory.class,
CmManagedProperties.class,
CmProperties.class
));
}
public Metadata parse(Element element, ParserContext context) {
LOGGER.debug("Parsing element {{}}{}", element.getNamespaceURI(), element.getLocalName());
ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
registerManagedObjectManager(context, registry);
if (nodeNameEquals(element, PROPERTY_PLACEHOLDER_ELEMENT)) {
return parsePropertyPlaceholder(context, element);
} else if (nodeNameEquals(element, MANAGED_SERVICE_FACTORY_ELEMENT)) {
return parseManagedServiceFactory(context, element);
} else if (nodeNameEquals(element, CM_PROPERTIES_ELEMENT)) {
return parseCmProperties(context, element);
} else {
throw new ComponentDefinitionException("Unsupported element: " + element.getNodeName());
}
}
public ComponentMetadata decorate(Node node, ComponentMetadata component, ParserContext context) {
LOGGER.debug("Decorating node {{}}{}", node.getNamespaceURI(), node.getLocalName());
ComponentDefinitionRegistry registry = context.getComponentDefinitionRegistry();
registerManagedObjectManager(context, registry);
if (node instanceof Element) {
if (nodeNameEquals(node, MANAGED_PROPERTIES_ELEMENT)) {
return decorateManagedProperties(context, (Element) node, component);
} else if (nodeNameEquals(node, CM_PROPERTIES_ELEMENT)) {
return decorateCmProperties(context, (Element) node, component);
} else {
throw new ComponentDefinitionException("Unsupported element: " + node.getNodeName());
}
} else {
throw new ComponentDefinitionException("Illegal use of blueprint cm namespace");
}
}
private ComponentMetadata parseCmProperties(ParserContext context, Element element) {
String id = getId(context, element);
MutableBeanMetadata factoryMetadata = context.createMetadata(MutableBeanMetadata.class);
generateIdIfNeeded(context, factoryMetadata);
factoryMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
factoryMetadata.setRuntimeClass(CmProperties.class);
factoryMetadata.setInitMethod("init");
factoryMetadata.setDestroyMethod("destroy");
factoryMetadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
factoryMetadata.addProperty("configAdmin", createConfigurationAdminRef(context));
factoryMetadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
String persistentId = element.getAttribute(PERSISTENT_ID_ATTRIBUTE);
factoryMetadata.addProperty("persistentId", createValue(context, persistentId));
context.getComponentDefinitionRegistry().registerComponentDefinition(factoryMetadata);
MutableBeanMetadata propertiesMetadata = context.createMetadata(MutableBeanMetadata.class);
propertiesMetadata.setId(id);
propertiesMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
propertiesMetadata.setRuntimeClass(Properties.class);
propertiesMetadata.setFactoryComponent(createRef(context, factoryMetadata.getId()));
propertiesMetadata.setFactoryComponent(factoryMetadata);
propertiesMetadata.setFactoryMethod("getProperties");
// Work around ARIES-877
propertiesMetadata.setDependsOn(Arrays.asList(factoryMetadata.getId()));
return propertiesMetadata;
}
private ComponentMetadata parsePropertyPlaceholder(ParserContext context, Element element) {
MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
metadata.setProcessor(true);
metadata.setId(getId(context, element));
metadata.setScope(BeanMetadata.SCOPE_SINGLETON);
metadata.setRuntimeClass(CmPropertyPlaceholder.class);
metadata.setInitMethod("init");
metadata.setDestroyMethod("destroy");
metadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
metadata.addProperty("configAdmin", createConfigurationAdminRef(context));
metadata.addProperty("persistentId", createValue(context, element.getAttribute(PERSISTENT_ID_ATTRIBUTE)));
String prefix = element.hasAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE)
? element.getAttribute(PLACEHOLDER_PREFIX_ATTRIBUTE)
: "${";
metadata.addProperty("placeholderPrefix", createValue(context, prefix));
String suffix = element.hasAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE)
? element.getAttribute(PLACEHOLDER_SUFFIX_ATTRIBUTE)
: "}";
metadata.addProperty("placeholderSuffix", createValue(context, suffix));
String nullValue = element.hasAttribute(PLACEHOLDER_NULL_VALUE_ATTRIBUTE)
? element.getAttribute(PLACEHOLDER_NULL_VALUE_ATTRIBUTE)
: null;
if (nullValue != null) {
metadata.addProperty("nullValue", createValue(context, nullValue));
}
String defaultsRef = element.hasAttribute(DEFAULTS_REF_ATTRIBUTE) ? element.getAttribute(DEFAULTS_REF_ATTRIBUTE) : null;
if (defaultsRef != null) {
metadata.addProperty("defaultProperties", createRef(context, defaultsRef));
}
String ignoreMissingLocations = extractIgnoreMissingLocations(element);
if (ignoreMissingLocations != null) {
metadata.addProperty("ignoreMissingLocations", createValue(context, ignoreMissingLocations));
}
String systemProperties = extractSystemPropertiesAttribute(element);
if (systemProperties == null) {
systemProperties = SYSTEM_PROPERTIES_NEVER;
}
metadata.addProperty("systemProperties", createValue(context, systemProperties));
String updateStrategy = element.getAttribute(UPDATE_STRATEGY_ATTRIBUTE);
if (updateStrategy != null) {
metadata.addProperty("updateStrategy", createValue(context, updateStrategy));
}
metadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
// Parse elements
List<String> locations = new ArrayList<String>();
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element e = (Element) node;
if (isCmNamespace(e.getNamespaceURI())) {
if (nodeNameEquals(e, DEFAULT_PROPERTIES_ELEMENT)) {
if (defaultsRef != null) {
throw new ComponentDefinitionException("Only one of " + DEFAULTS_REF_ATTRIBUTE + " attribute or " + DEFAULT_PROPERTIES_ELEMENT + " element is allowed");
}
Metadata props = parseDefaultProperties(context, metadata, e);
metadata.addProperty("defaultProperties", props);
}
} else if (isExtNamespace(e.getNamespaceURI())) {
if (nodeNameEquals(e, LOCATION_ELEMENT)) {
locations.add(getTextValue(e));
}
}
}
}
if (!locations.isEmpty()) {
metadata.addProperty("locations", createList(context, locations));
}
PlaceholdersUtils.validatePlaceholder(metadata, context.getComponentDefinitionRegistry());
return metadata;
}
private String extractSystemPropertiesAttribute(Element element) {
for (String uri : EXT_URIS) {
if (element.hasAttributeNS(uri, SYSTEM_PROPERTIES_ATTRIBUTE)) {
return element.getAttributeNS(uri, SYSTEM_PROPERTIES_ATTRIBUTE);
}
}
return null;
}
private String extractIgnoreMissingLocations(Element element) {
for (String uri : EXT_URIS) {
if (element.hasAttributeNS(uri, IGNORE_MISSING_LOCATIONS_ATTRIBUTE)) {
return element.getAttributeNS(uri, IGNORE_MISSING_LOCATIONS_ATTRIBUTE);
}
}
return null;
}
private Metadata parseDefaultProperties(ParserContext context, MutableBeanMetadata enclosingComponent, Element element) {
MutableMapMetadata props = context.createMetadata(MutableMapMetadata.class);
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element e = (Element) node;
if (isCmNamespace(e.getNamespaceURI())) {
if (nodeNameEquals(e, PROPERTY_ELEMENT)) {
BeanProperty prop = context.parseElement(BeanProperty.class, enclosingComponent, e);
props.addEntry(createValue(context, prop.getName(), String.class.getName()), prop.getValue());
}
}
}
}
return props;
}
private ComponentMetadata parseManagedServiceFactory(ParserContext context, Element element) {
String id = getId(context, element);
MutableBeanMetadata factoryMetadata = context.createMetadata(MutableBeanMetadata.class);
generateIdIfNeeded(context, factoryMetadata);
factoryMetadata.addProperty("id", createValue(context, factoryMetadata.getId()));
factoryMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
factoryMetadata.setRuntimeClass(CmManagedServiceFactory.class);
factoryMetadata.setInitMethod("init");
factoryMetadata.setDestroyMethod("destroy");
factoryMetadata.addArgument(createRef(context, "blueprintContainer"), null, 0);
factoryMetadata.addProperty("factoryPid", createValue(context, element.getAttribute(FACTORY_PID_ATTRIBUTE)));
String autoExport = element.hasAttribute(AUTO_EXPORT_ATTRIBUTE) ? element.getAttribute(AUTO_EXPORT_ATTRIBUTE) : AUTO_EXPORT_DEFAULT;
if (AUTO_EXPORT_DISABLED.equals(autoExport)) {
autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_DISABLED);
} else if (AUTO_EXPORT_INTERFACES.equals(autoExport)) {
autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_INTERFACES);
} else if (AUTO_EXPORT_CLASS_HIERARCHY.equals(autoExport)) {
autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_CLASS_HIERARCHY);
} else if (AUTO_EXPORT_ALL.equals(autoExport)) {
autoExport = Integer.toString(ServiceMetadata.AUTO_EXPORT_ALL_CLASSES);
} else {
throw new ComponentDefinitionException("Illegal value (" + autoExport + ") for " + AUTO_EXPORT_ATTRIBUTE + " attribute");
}
factoryMetadata.addProperty("autoExport", createValue(context, autoExport));
String ranking = element.hasAttribute(RANKING_ATTRIBUTE) ? element.getAttribute(RANKING_ATTRIBUTE) : RANKING_DEFAULT;
factoryMetadata.addProperty("ranking", createValue(context, ranking));
List<String> interfaces = null;
if (element.hasAttribute(INTERFACE_ATTRIBUTE)) {
interfaces = Collections.singletonList(element.getAttribute(INTERFACE_ATTRIBUTE));
factoryMetadata.addProperty("interfaces", createList(context, interfaces));
}
// Parse elements
List<RegistrationListener> listeners = new ArrayList<RegistrationListener>();
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element e = (Element) node;
if (isBlueprintNamespace(e.getNamespaceURI())) {
if (nodeNameEquals(e, INTERFACES_ELEMENT)) {
if (interfaces != null) {
throw new ComponentDefinitionException("Only one of " + INTERFACE_ATTRIBUTE + " attribute or " + INTERFACES_ELEMENT + " element must be used");
}
interfaces = parseInterfaceNames(e);
factoryMetadata.addProperty("interfaces", createList(context, interfaces));
} else if (nodeNameEquals(e, SERVICE_PROPERTIES_ELEMENT)) {
MapMetadata map = context.parseElement(MapMetadata.class,
factoryMetadata, e);
factoryMetadata.addProperty("serviceProperties", map);
NodeList enl = e.getChildNodes();
for (int j = 0; j < enl.getLength(); j++) {
Node enode = enl.item(j);
if (enode instanceof Element) {
if (isCmNamespace(enode.getNamespaceURI()) && nodeNameEquals(enode, CM_PROPERTIES_ELEMENT)) {
decorateCmProperties(context, (Element) enode, factoryMetadata);
}
}
}
} else if (nodeNameEquals(e, REGISTRATION_LISTENER_ELEMENT)) {
listeners.add(context.parseElement(RegistrationListener.class,
factoryMetadata, e));
}
} else if (isCmNamespace(e.getNamespaceURI())) {
if (nodeNameEquals(e, MANAGED_COMPONENT_ELEMENT)) {
MutableBeanMetadata managedComponent = context.parseElement(MutableBeanMetadata.class, null, e);
generateIdIfNeeded(context, managedComponent);
managedComponent.setScope(BeanMetadata.SCOPE_PROTOTYPE);
// destroy-method on managed-component has different signature than on regular beans
// so we'll handle it differently
String destroyMethod = managedComponent.getDestroyMethod();
if (destroyMethod != null) {
factoryMetadata.addProperty("componentDestroyMethod", createValue(context, destroyMethod));
managedComponent.setDestroyMethod(null);
}
context.getComponentDefinitionRegistry().registerComponentDefinition(managedComponent);
factoryMetadata.addProperty("managedComponentName", createIdRef(context, managedComponent.getId()));
}
}
}
}
MutableCollectionMetadata listenerCollection = context.createMetadata(MutableCollectionMetadata.class);
listenerCollection.setCollectionClass(List.class);
for (RegistrationListener listener : listeners) {
MutableBeanMetadata bean = context.createMetadata(MutableBeanMetadata.class);
bean.setRuntimeClass(ServiceListener.class);
bean.addProperty("listener", listener.getListenerComponent());
bean.addProperty("registerMethod", createValue(context, listener.getRegistrationMethod()));
bean.addProperty("unregisterMethod", createValue(context, listener.getUnregistrationMethod()));
listenerCollection.addValue(bean);
}
factoryMetadata.addProperty("listeners", listenerCollection);
context.getComponentDefinitionRegistry().registerComponentDefinition(factoryMetadata);
MutableBeanMetadata mapMetadata = context.createMetadata(MutableBeanMetadata.class);
mapMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
mapMetadata.setId(id);
mapMetadata.setFactoryComponent(createRef(context, factoryMetadata.getId()));
mapMetadata.setFactoryMethod("getServiceMap");
return mapMetadata;
}
private ComponentMetadata decorateCmProperties(ParserContext context, Element element, ComponentMetadata component) {
generateIdIfNeeded(context, ((MutableComponentMetadata) component));
MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
metadata.setProcessor(true);
metadata.setId(getId(context, element));
metadata.setRuntimeClass(CmProperties.class);
String persistentId = element.getAttribute(PERSISTENT_ID_ATTRIBUTE);
// if persistentId is "" the cm-properties element in nested in managed-service-factory
// and the configuration object will come from the factory. So we only really need to register
// ManagedService if the persistentId is not an empty string.
if (persistentId.length() > 0) {
metadata.setInitMethod("init");
metadata.setDestroyMethod("destroy");
}
metadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
metadata.addProperty("configAdmin", createConfigurationAdminRef(context));
metadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
metadata.addProperty("persistentId", createValue(context, persistentId));
if (element.hasAttribute(UPDATE_ATTRIBUTE)) {
metadata.addProperty("update", createValue(context, element.getAttribute(UPDATE_ATTRIBUTE)));
}
metadata.addProperty("serviceId", createIdRef(context, component.getId()));
context.getComponentDefinitionRegistry().registerComponentDefinition(metadata);
return component;
}
private ComponentMetadata decorateManagedProperties(ParserContext context, Element element, ComponentMetadata component) {
if (!(component instanceof MutableBeanMetadata)) {
throw new ComponentDefinitionException("Element " + MANAGED_PROPERTIES_ELEMENT + " must be used inside a <bp:bean> element");
}
generateIdIfNeeded(context, ((MutableBeanMetadata) component));
MutableBeanMetadata metadata = context.createMetadata(MutableBeanMetadata.class);
metadata.setProcessor(true);
metadata.setId(getId(context, element));
metadata.setRuntimeClass(CmManagedProperties.class);
String persistentId = element.getAttribute(PERSISTENT_ID_ATTRIBUTE);
// if persistentId is "" the managed properties element in nested in managed-service-factory
// and the configuration object will come from the factory. So we only really need to register
// ManagedService if the persistentId is not an empty string.
if (persistentId.length() > 0) {
metadata.setInitMethod("init");
metadata.setDestroyMethod("destroy");
}
metadata.addProperty("blueprintContainer", createRef(context, "blueprintContainer"));
metadata.addProperty("configAdmin", createConfigurationAdminRef(context));
metadata.addProperty("managedObjectManager", createRef(context, MANAGED_OBJECT_MANAGER_NAME));
metadata.addProperty("persistentId", createValue(context, persistentId));
String updateStrategy = element.getAttribute(UPDATE_STRATEGY_ATTRIBUTE);
if (updateStrategy != null) {
metadata.addProperty("updateStrategy", createValue(context, updateStrategy));
}
if (element.hasAttribute(UPDATE_METHOD_ATTRIBUTE)) {
metadata.addProperty("updateMethod", createValue(context, element.getAttribute(UPDATE_METHOD_ATTRIBUTE)));
} else if ("component-managed".equals(updateStrategy)) {
throw new ComponentDefinitionException(UPDATE_METHOD_ATTRIBUTE + " attribute must be set when " + UPDATE_STRATEGY_ATTRIBUTE + " is set to 'component-managed'");
}
metadata.addProperty("beanName", createIdRef(context, component.getId()));
context.getComponentDefinitionRegistry().registerComponentDefinition(metadata);
return component;
}
private void registerManagedObjectManager(ParserContext context, ComponentDefinitionRegistry registry) {
if (registry.getComponentDefinition(MANAGED_OBJECT_MANAGER_NAME) == null) {
MutableBeanMetadata beanMetadata = context.createMetadata(MutableBeanMetadata.class);
beanMetadata.setScope(BeanMetadata.SCOPE_SINGLETON);
beanMetadata.setId(MANAGED_OBJECT_MANAGER_NAME);
beanMetadata.setRuntimeClass(ManagedObjectManager.class);
registry.registerComponentDefinition(beanMetadata);
}
}
private MutableReferenceMetadata createConfigurationAdminRef(ParserContext context) {
return createServiceRef(context, ConfigurationAdmin.class, null);
}
private static ValueMetadata createValue(ParserContext context, String value) {
return createValue(context, value, null);
}
private static ValueMetadata createValue(ParserContext context, String value, String type) {
MutableValueMetadata m = context.createMetadata(MutableValueMetadata.class);
m.setStringValue(value);
m.setType(type);
return m;
}
private static RefMetadata createRef(ParserContext context, String value) {
MutableRefMetadata m = context.createMetadata(MutableRefMetadata.class);
m.setComponentId(value);
return m;
}
private MutableReferenceMetadata createServiceRef(ParserContext context, Class<?> cls, String filter) {
MutableReferenceMetadata m = context.createMetadata(MutableReferenceMetadata.class);
m.setRuntimeInterface(cls);
m.setInterface(cls.getName());
m.setActivation(ReferenceMetadata.ACTIVATION_EAGER);
m.setAvailability(ReferenceMetadata.AVAILABILITY_MANDATORY);
if (filter != null) {
m.setFilter(filter);
}
return m;
}
private static IdRefMetadata createIdRef(ParserContext context, String value) {
MutableIdRefMetadata m = context.createMetadata(MutableIdRefMetadata.class);
m.setComponentId(value);
return m;
}
private static CollectionMetadata createList(ParserContext context, List<String> list) {
MutableCollectionMetadata m = context.createMetadata(MutableCollectionMetadata.class);
m.setCollectionClass(List.class);
m.setValueType(String.class.getName());
for (String v : list) {
m.addValue(createValue(context, v, String.class.getName()));
}
return m;
}
private static String getTextValue(Element element) {
StringBuffer value = new StringBuffer();
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node item = nl.item(i);
if ((item instanceof CharacterData && !(item instanceof Comment)) || item instanceof EntityReference) {
value.append(item.getNodeValue());
}
}
return value.toString();
}
private static boolean nodeNameEquals(Node node, String name) {
return (name.equals(node.getNodeName()) || name.equals(node.getLocalName()));
}
public static boolean isBlueprintNamespace(String ns) {
return BLUEPRINT_NAMESPACE.equals(ns);
}
public static boolean isCmNamespace(String uri) {
return CM_URIS.contains(uri);
}
public static boolean isExtNamespace(String uri) {
return EXT_URIS.contains(uri);
}
public String getId(ParserContext context, Element element) {
if (element.hasAttribute(ID_ATTRIBUTE)) {
return element.getAttribute(ID_ATTRIBUTE);
} else {
return generateId(context);
}
}
public void generateIdIfNeeded(ParserContext context, MutableComponentMetadata metadata) {
if (metadata.getId() == null) {
metadata.setId(generateId(context));
}
}
private String generateId(ParserContext context) {
String id;
do {
id = ".cm-" + ++idCounter;
} while (context.getComponentDefinitionRegistry().containsComponentDefinition(id));
return id;
}
public List<String> parseInterfaceNames(Element element) {
List<String> interfaceNames = new ArrayList<String>();
NodeList nl = element.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element e = (Element) node;
if (nodeNameEquals(e, VALUE_ELEMENT)) {
String v = getTextValue(e).trim();
if (interfaceNames.contains(v)) {
throw new ComponentDefinitionException("The element " + INTERFACES_ELEMENT + " should not contain the same interface twice");
}
interfaceNames.add(getTextValue(e));
} else {
throw new ComponentDefinitionException("Unsupported element " + e.getNodeName() + " inside an " + INTERFACES_ELEMENT + " element");
}
}
}
return interfaceNames;
}
}

View File

@ -0,0 +1,169 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.util.Dictionary;
import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import org.apache.aries.blueprint.ServiceProcessor;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
import org.apache.aries.blueprint.utils.JavaUtils;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* @version $Rev$, $Date$
*/
public class CmProperties implements ManagedObject, ServiceProcessor {
private static final Logger LOGGER = LoggerFactory.getLogger(CmProperties.class);
private ExtendedBlueprintContainer blueprintContainer;
private ConfigurationAdmin configAdmin;
private ManagedObjectManager managedObjectManager;
private String persistentId;
private boolean update;
private String serviceId;
private final Object lock = new Object();
private final Set<ServicePropertiesUpdater> services = new HashSet<ServicePropertiesUpdater>();
private final Properties properties = new Properties();
private boolean initialized;
public ExtendedBlueprintContainer getBlueprintContainer() {
return blueprintContainer;
}
public void setBlueprintContainer(ExtendedBlueprintContainer blueprintContainer) {
this.blueprintContainer = blueprintContainer;
}
public ConfigurationAdmin getConfigAdmin() {
return configAdmin;
}
public void setConfigAdmin(ConfigurationAdmin configAdmin) {
this.configAdmin = configAdmin;
}
public void setManagedObjectManager(ManagedObjectManager managedObjectManager) {
this.managedObjectManager = managedObjectManager;
}
public ManagedObjectManager getManagedObjectManager() {
return managedObjectManager;
}
public Bundle getBundle() {
return blueprintContainer.getBundleContext().getBundle();
}
public String getPersistentId() {
return persistentId;
}
public void setPersistentId(String persistentId) {
this.persistentId = persistentId;
}
public boolean getUpdate() {
return update;
}
public void setUpdate(boolean update) {
this.update = update;
}
public String getServiceId() {
return serviceId;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public void init() throws Exception {
if (serviceId != null) {
LOGGER.debug("Initializing CmProperties for service={} / pid={}", serviceId, persistentId);
} else {
LOGGER.debug("Initializing CmProperties for pid={}", persistentId);
}
Properties props = new Properties();
props.put(Constants.SERVICE_PID, persistentId);
Bundle bundle = blueprintContainer.getBundleContext().getBundle();
props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION));
synchronized (lock) {
managedObjectManager.register(this, props);
}
}
public void destroy() {
managedObjectManager.unregister(this);
}
public Properties getProperties() {
return properties;
}
public void updated(Dictionary props) {
if (initialized) {
if (serviceId != null) {
LOGGER.debug("Service properties updated for service={} / pid={}, {}", new Object[]{serviceId, persistentId, props});
} else {
LOGGER.debug("Service properties updated for pid={}, {}", new Object[]{persistentId, props});
}
}
synchronized (lock) {
properties.clear();
if (props != null) {
JavaUtils.copy(properties, props);
}
if (!initialized) {
initialized = true;
} else if (update) {
for (ServicePropertiesUpdater service : services) {
service.updateProperties(props);
}
}
}
}
public void updateProperties(ServicePropertiesUpdater service, Dictionary props) {
if (this.serviceId == null || !this.serviceId.equals(service.getId())) {
return;
}
LOGGER.debug("Service properties initialized for service={} / pid={}, {}", new Object[] {serviceId, persistentId, props});
synchronized (lock) {
services.add(service);
JavaUtils.copy(props, properties);
}
}
}

View File

@ -0,0 +1,170 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Properties;
import org.apache.aries.blueprint.ext.PropertyPlaceholderExt;
import org.apache.aries.blueprint.services.ExtendedBlueprintContainer;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* TODO: javadoc
*
* @version $Rev$, $Date$
*/
public class CmPropertyPlaceholder extends PropertyPlaceholderExt implements ManagedObject {
private static final Logger LOGGER = LoggerFactory.getLogger(CmPropertyPlaceholder.class);
private ExtendedBlueprintContainer blueprintContainer;
private ConfigurationAdmin configAdmin;
private String persistentId;
private String updateStrategy;
private ManagedObjectManager managedObjectManager;
private Dictionary<String, Object> properties;
private boolean initialized;
public ExtendedBlueprintContainer getBlueprintContainer() {
return blueprintContainer;
}
public void setBlueprintContainer(ExtendedBlueprintContainer blueprintContainer) {
this.blueprintContainer = blueprintContainer;
}
public ConfigurationAdmin getConfigAdmin() {
return configAdmin;
}
public void setConfigAdmin(ConfigurationAdmin configAdmin) {
this.configAdmin = configAdmin;
}
public String getPersistentId() {
return persistentId;
}
public void setPersistentId(String persistentId) {
this.persistentId = persistentId;
}
public String getUpdateStrategy() {
return updateStrategy;
}
public void setUpdateStrategy(String updateStrategy) {
this.updateStrategy = updateStrategy;
}
public ManagedObjectManager getManagedObjectManager() {
return managedObjectManager;
}
public void setManagedObjectManager(ManagedObjectManager managedObjectManager) {
this.managedObjectManager = managedObjectManager;
}
public void init() throws Exception {
LOGGER.debug("Initializing CmPropertyPlaceholder");
Properties props = new Properties();
props.put(Constants.SERVICE_PID, persistentId);
Bundle bundle = blueprintContainer.getBundleContext().getBundle();
props.put(Constants.BUNDLE_SYMBOLICNAME, bundle.getSymbolicName());
props.put(Constants.BUNDLE_VERSION, bundle.getHeaders().get(Constants.BUNDLE_VERSION));
managedObjectManager.register(this, props);
}
public void destroy() {
LOGGER.debug("Destroying CmPropertyPlaceholder");
managedObjectManager.unregister(this);
}
protected Object getProperty(String val) {
LOGGER.debug("Retrieving property value {} from configuration with pid {}", val, persistentId);
Object v = null;
if (properties != null) {
v = properties.get(val);
if (v != null) {
LOGGER.debug("Found property value {}", v);
} else {
LOGGER.debug("Property not found in configuration");
}
}
if (v == null) {
v = super.getProperty(val);
}
return v;
}
public Bundle getBundle() {
return blueprintContainer.getBundleContext().getBundle();
}
public void updated(Dictionary props) {
if (!initialized) {
properties = props;
initialized = true;
return;
}
if ("reload".equalsIgnoreCase(updateStrategy) && !equals(properties, props)) {
LOGGER.debug("Configuration updated for pid={}", persistentId);
// Run in a separate thread to avoid re-entrance
new Thread() {
public void run() {
blueprintContainer.reload();
}
}.start();
}
}
private <T, U> boolean equals(Dictionary<T, U> d1, Dictionary<T, U> d2) {
if (d1 == null || d1.isEmpty()) {
return d2 == null || d2.isEmpty();
} else if (d2 == null || d1.size() != d2.size()) {
return false;
} else {
for (Enumeration<T> e = d1.keys(); e.hasMoreElements();) {
T k = e.nextElement();
U v1 = d1.get(k);
U v2 = d2.get(k);
if (v1 == null) {
if (v2 != null) {
return false;
}
} else if (v1 instanceof Object[] && v2 instanceof Object[]) {
if (!Arrays.deepEquals((Object[]) v1, (Object[]) v2)) {
return false;
}
} else if (!v1.equals(v2)) {
return false;
}
}
return true;
}
}
}

View File

@ -0,0 +1,246 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationPlugin;
public class CmUtils {
private CmUtils() {
}
public static Configuration getConfiguration(ConfigurationAdmin configAdmin, String persistentId) throws IOException {
String filter = '(' + Constants.SERVICE_PID + '=' + persistentId + ')';
Configuration[] configs;
try {
configs = configAdmin.listConfigurations(filter);
} catch (InvalidSyntaxException e) {
// this should not happen
throw new RuntimeException("Invalid filter: " + filter);
}
if (configs != null && configs.length > 0) {
return configs[0];
} else {
// TODO: what should we do?
// throw new RuntimeException("No configuration object for pid=" + persistentId);
return null;
}
}
public static Dictionary<String, Object> getProperties(ServiceReference service, String persistentId) throws IOException {
BundleContext bc = service.getBundle().getBundleContext();
ServiceReference<ConfigurationAdmin> caRef = bc.getServiceReference(ConfigurationAdmin.class);
try {
ConfigurationAdmin ca = bc.getService(caRef);
Configuration config = getConfiguration(ca, persistentId);
if (config != null) {
Dictionary<String, Object> props = new CaseInsensitiveDictionary(config.getProperties());
Bundle bundle = caRef.getBundle();
if (bundle != null) {
BundleContext caBc = bundle.getBundleContext();
if (caBc != null) {
try {
callPlugins(caBc, props, service, persistentId, null);
} catch (IllegalStateException ise) {
// we don't care it doesn't exist so, shrug.
}
}
}
return props;
} else {
return null;
}
} finally {
bc.ungetService(caRef);
}
}
private static void callPlugins(final BundleContext bundleContext,
final Dictionary<String, Object> props,
final ServiceReference sr,
final String configPid,
final String factoryPid) {
ServiceReference[] plugins = null;
try {
final String targetPid = (factoryPid == null) ? configPid : factoryPid;
// String filter = "(|(!(cm.target=*))(cm.target=" + targetPid + "))";
// patch for https://issues.apache.org/jira/browse/ARIES-2076
String filter = "(|(cm.target=*)(cm.target=" + targetPid + "))";
plugins = bundleContext.getServiceReferences(ConfigurationPlugin.class.getName(), filter);
} catch (InvalidSyntaxException ise) {
// no filter, no exception ...
}
// abort early if there are no plugins
if (plugins == null || plugins.length == 0) {
return;
}
// sort the plugins by their service.cmRanking
if (plugins.length > 1) {
Arrays.sort(plugins, CM_RANKING);
}
// call the plugins in order
for (ServiceReference pluginRef : plugins) {
ConfigurationPlugin plugin = (ConfigurationPlugin) bundleContext.getService(pluginRef);
if (plugin != null) {
try {
plugin.modifyConfiguration(sr, props);
} catch (Throwable t) {
// Ignore
} finally {
// ensure ungetting the plugin
bundleContext.ungetService(pluginRef);
}
setAutoProperties(props, configPid, factoryPid);
}
}
}
private static void setAutoProperties( Dictionary<String, Object> properties, String pid, String factoryPid )
{
replaceProperty(properties, Constants.SERVICE_PID, pid);
replaceProperty(properties, ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid);
properties.remove(ConfigurationAdmin.SERVICE_BUNDLELOCATION);
}
private static void replaceProperty(Dictionary<String, Object> properties, String key, String value) {
if (value == null) {
properties.remove(key);
} else {
properties.put(key, value);
}
}
private static Comparator<ServiceReference> CM_RANKING = new Comparator<ServiceReference>() {
@Override
public int compare(ServiceReference sr1, ServiceReference sr2) {
final long rank1 = getLong(sr1, ConfigurationPlugin.CM_RANKING);
final long rank2 = getLong(sr2, ConfigurationPlugin.CM_RANKING);
if (rank1 == rank2) {
return 0;
}
return (rank1 < rank2) ? -1 : 1;
}
protected long getLong(ServiceReference sr, String property) {
Object rankObj = sr.getProperty(property);
if (rankObj instanceof Number) {
return ((Number) rankObj).longValue();
}
return 0;
}
};
private static class CaseInsensitiveDictionary extends Dictionary<String, Object> {
private final Hashtable<String, Object> internalMap = new Hashtable<String, Object>();
private final Hashtable<String, String> originalKeys = new Hashtable<String, String>();
public CaseInsensitiveDictionary(Dictionary<String, Object> props) {
if (props != null) {
Enumeration<String> keys = props.keys();
while (keys.hasMoreElements()) {
// check the correct syntax of the key
String key = checkKey(keys.nextElement());
// check uniqueness of key
String lowerCase = key.toLowerCase();
if (internalMap.containsKey(lowerCase)) {
throw new IllegalArgumentException("Key [" + key + "] already present in different case");
}
// check the value
Object value = props.get(key);
checkValue(value);
// add the key/value pair
internalMap.put(lowerCase, value);
originalKeys.put(lowerCase, key);
}
}
}
public Enumeration<Object> elements() {
return Collections.enumeration(internalMap.values());
}
public Object get(Object keyObj) {
String lowerCase = checkKey(keyObj == null ? null : keyObj.toString()).toLowerCase();
return internalMap.get(lowerCase);
}
public boolean isEmpty() {
return internalMap.isEmpty();
}
public Enumeration<String> keys() {
return Collections.enumeration(originalKeys.values());
}
public Object put(String key, Object value) {
String lowerCase = checkKey(key).toLowerCase();
checkValue(value);
originalKeys.put(lowerCase, key);
return internalMap.put(lowerCase, value);
}
public Object remove(Object keyObj) {
String lowerCase = checkKey(keyObj == null ? null : keyObj.toString()).toLowerCase();
originalKeys.remove(lowerCase);
return internalMap.remove(lowerCase);
}
public int size() {
return internalMap.size();
}
static String checkKey(String key) {
if (key == null || key.length() == 0) {
throw new IllegalArgumentException("Key must not be null nor an empty string");
}
return key;
}
static Object checkValue(Object value) {
if (value == null) {
throw new IllegalArgumentException("Value must not be null");
}
return value;
}
public String toString() {
return internalMap.toString();
}
}
}

View File

@ -0,0 +1,32 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.util.Dictionary;
import org.osgi.framework.Bundle;
public interface ManagedObject {
Bundle getBundle();
String getPersistentId();
void updated(Dictionary props);
}

View File

@ -0,0 +1,114 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.List;
import java.util.Properties;
import java.util.concurrent.CopyOnWriteArrayList;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Since persistence id can only be associated with one ManagedService in a bundle
* this class ensures only one ManagedService is registered per persistence id.
*/
public class ManagedObjectManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ManagedObjectManager.class);
private HashMap<String, ConfigurationWatcher> map = new HashMap<String, ConfigurationWatcher>();
public synchronized void register(ManagedObject cm, Properties props) {
String key = cm.getPersistentId();
ConfigurationWatcher reg = map.get(key);
if (reg == null) {
reg = new ConfigurationWatcher();
ServiceRegistration registration = cm.getBundle().getBundleContext().registerService(ManagedService.class.getName(), reg, (Dictionary) props);
reg.setRegistration(registration);
map.put(key, reg);
}
reg.add(cm);
try {
Dictionary<String, Object> config = CmUtils.getProperties(reg.getRegistration().getReference(), key);
cm.updated(config);
} catch (Throwable t) {
// Ignore
}
}
public synchronized void unregister(ManagedObject cm) {
String key = cm.getPersistentId();
ConfigurationWatcher reg = map.get(key);
if (reg != null) {
reg.remove(cm);
if (reg.isEmpty()) {
map.remove(key);
ServiceUtil.safeUnregister(reg.getRegistration());
}
}
}
private static class ConfigurationWatcher implements ManagedService {
private ServiceRegistration registration;
private List<ManagedObject> list = new CopyOnWriteArrayList<ManagedObject>();
public ConfigurationWatcher() {
}
public void updated(final Dictionary props) throws ConfigurationException {
// Run in a separate thread to avoid re-entrance
new Thread() {
public void run() {
for (ManagedObject cm : list) {
cm.updated(props);
}
}
}.start();
}
private void setRegistration(ServiceRegistration registration) {
this.registration = registration;
}
private ServiceRegistration getRegistration() {
return registration;
}
private void add(ManagedObject cm) {
list.add(cm);
}
private void remove(ManagedObject cm) {
list.remove(cm);
}
private boolean isEmpty() {
return list.isEmpty();
}
}
}

View File

@ -0,0 +1,38 @@
/**
* 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.aries.blueprint.compendium.cm;
import org.osgi.framework.ServiceRegistration;
public final class ServiceUtil {
private ServiceUtil() {
}
public static void safeUnregister(ServiceRegistration<?> sreg) {
if (sreg != null) {
try {
sreg.unregister();
} catch (Exception e) {
// Ignore
}
}
}
}

View File

@ -0,0 +1,19 @@
#
# 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.
#
version 1.0.0

View File

@ -0,0 +1,65 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
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.
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0" default-activation="lazy">
<bean id="CmNamespaceHandler" class="org.apache.aries.blueprint.compendium.cm.CmNamespaceHandler"/>
<service ref="CmNamespaceHandler">
<interfaces>
<value>org.apache.aries.blueprint.NamespaceHandler</value>
</interfaces>
<service-properties>
<entry key="osgi.service.blueprint.namespace" value="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"/>
</service-properties>
</service>
<service ref="CmNamespaceHandler">
<interfaces>
<value>org.apache.aries.blueprint.NamespaceHandler</value>
</interfaces>
<service-properties>
<entry key="osgi.service.blueprint.namespace" value="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"/>
</service-properties>
</service>
<service ref="CmNamespaceHandler">
<interfaces>
<value>org.apache.aries.blueprint.NamespaceHandler</value>
</interfaces>
<service-properties>
<entry key="osgi.service.blueprint.namespace" value="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.2.0"/>
</service-properties>
</service>
<service ref="CmNamespaceHandler">
<interfaces>
<value>org.apache.aries.blueprint.NamespaceHandler</value>
</interfaces>
<service-properties>
<entry key="osgi.service.blueprint.namespace" value="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.3.0"/>
</service-properties>
</service>
<service ref="CmNamespaceHandler">
<interfaces>
<value>org.apache.aries.blueprint.NamespaceHandler</value>
</interfaces>
<service-properties>
<entry key="osgi.service.blueprint.namespace" value="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.4.0"/>
</service-properties>
</service>
</blueprint>

View File

@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
/*
* $Revision$
*
* Copyright (c) OSGi Alliance (2008, 2009). All Rights Reserved.
*
* 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.
*/
-->
<xsd:schema xmlns="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
xmlns:ext100="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
xmlns:ext110="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0"
xmlns:ext120="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0"
xmlns:ext130="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0"
xmlns:ext140="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
targetNamespace="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.0.0"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0.0">
<xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0" />
<!-- property placeholder -->
<xsd:element name="property-placeholder" type="TpropertyPlaceholder"/>
<xsd:complexType name="TpropertyPlaceholder">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<!-- nested properties declaration -->
<xsd:element name="default-properties" type="TdefaultProperties" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="ext100:location" />
<xsd:element ref="ext110:location" />
<xsd:element ref="ext120:location" />
<xsd:element ref="ext130:location" />
<xsd:element ref="ext140:location" />
</xsd:choice>
</xsd:sequence>
<!-- #### What should be the type for a persistent id? I think we need to define one like class and method -->
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="placeholder-prefix" type="xsd:string" use="optional" default="${"/>
<xsd:attribute name="placeholder-suffix" type="xsd:string" use="optional" default="}"/>
<xsd:attribute name="defaults-ref" type="bp:Tidref" use="optional"/>
<xsd:attributeGroup ref="extAttributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<!-- #### is this the correct type here? This is defining placeholder properties,
so should this be a restricted set of value types or should this be expanded to
all of the elements you can inject into a bean property? -->
<xsd:complexType name="TdefaultProperties">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="property" type="bp:Tproperty"/>
</xsd:sequence>
</xsd:complexType>
<xsd:attributeGroup name="extAttributes">
<xsd:attribute ref="ext100:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext110:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext120:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext130:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext140:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext100:system-properties" use="optional" />
<xsd:attribute ref="ext110:system-properties" use="optional" />
<xsd:attribute ref="ext120:system-properties" use="optional" />
<xsd:attribute ref="ext130:system-properties" use="optional" />
<xsd:attribute ref="ext140:system-properties" use="optional" />
</xsd:attributeGroup>
<!-- managed-properties -->
<xsd:element name="managed-properties" type="TmanagedProperties"/>
<xsd:complexType name="TmanagedProperties">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update-strategy" type="TupdateStrategyType" use="optional"/>
<xsd:attribute name="update-method" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="TupdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="component-managed"/>
<xsd:enumeration value="container-managed"/>
</xsd:restriction>
</xsd:simpleType>
<!-- managed-service-factory -->
<xsd:element name="managed-service-factory" type="TmanagedServiceFactory"/>
<xsd:complexType name="TmanagedServiceFactory">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<xsd:group ref="bp:GbaseServiceElements"/>
<xsd:element name="managed-component" type="TmanagedComponent" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="interface" type="bp:Tclass" use="optional" />
<xsd:attribute name="ref" type="bp:Tidref" use="optional" />
<xsd:attribute name="auto-export" type="bp:TautoExportModes" default="disabled" />
<xsd:attribute name="ranking" type="xsd:int" default="0"/>
<xsd:attribute name="factory-pid" type="xsd:string" use="required"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="TmanagedComponent">
<xsd:group ref="bp:GbeanElements"/>
<xsd:attribute name="class" type="bp:Tclass"/>
<xsd:attribute name="init-method" type="bp:Tmethod"/>
<xsd:attribute name="destroy-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-component" type="bp:Tidref"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- cm-properties -->
<xsd:element name="cm-properties" type="TcmProperties"/>
<xsd:complexType name="TcmProperties">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,159 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
/*
* $Revision$
*
* Copyright (c) OSGi Alliance (2008, 2009). All Rights Reserved.
*
* 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.
*/
-->
<xsd:schema xmlns="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
xmlns:ext100="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
xmlns:ext110="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0"
xmlns:ext120="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0"
xmlns:ext130="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0"
xmlns:ext140="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
targetNamespace="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.1.0"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0.0">
<xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0" />
<!-- property placeholder -->
<xsd:element name="property-placeholder" type="TpropertyPlaceholder"/>
<xsd:complexType name="TpropertyPlaceholder">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<!-- nested properties declaration -->
<xsd:element name="default-properties" type="TdefaultProperties" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="ext100:location" />
<xsd:element ref="ext110:location" />
<xsd:element ref="ext120:location" />
<xsd:element ref="ext130:location" />
<xsd:element ref="ext140:location" />
</xsd:choice>
</xsd:sequence>
<!-- #### What should be the type for a persistent id? I think we need to define one like class and method -->
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="placeholder-prefix" type="xsd:string" use="optional" default="${"/>
<xsd:attribute name="placeholder-suffix" type="xsd:string" use="optional" default="}"/>
<xsd:attribute name="defaults-ref" type="bp:Tidref" use="optional"/>
<xsd:attribute name="update-strategy" type="TplaceholderUpdateStrategyType" use="optional" default="none"/>
<xsd:attributeGroup ref="extAttributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:simpleType name="TplaceholderUpdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="reload"/>
</xsd:restriction>
</xsd:simpleType>
<!-- #### is this the correct type here? This is defining placeholder properties,
so should this be a restricted set of value types or should this be expanded to
all of the elements you can inject into a bean property? -->
<xsd:complexType name="TdefaultProperties">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="property" type="bp:Tproperty"/>
</xsd:sequence>
</xsd:complexType>
<xsd:attributeGroup name="extAttributes">
<xsd:attribute ref="ext100:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext110:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext120:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext130:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext140:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext100:system-properties" use="optional" />
<xsd:attribute ref="ext110:system-properties" use="optional" />
<xsd:attribute ref="ext120:system-properties" use="optional" />
<xsd:attribute ref="ext130:system-properties" use="optional" />
<xsd:attribute ref="ext140:system-properties" use="optional" />
</xsd:attributeGroup>
<!-- managed-properties -->
<xsd:element name="managed-properties" type="TmanagedProperties"/>
<xsd:complexType name="TmanagedProperties">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update-strategy" type="TupdateStrategyType" use="optional"/>
<xsd:attribute name="update-method" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="TupdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="component-managed"/>
<xsd:enumeration value="container-managed"/>
</xsd:restriction>
</xsd:simpleType>
<!-- managed-service-factory -->
<xsd:element name="managed-service-factory" type="TmanagedServiceFactory"/>
<xsd:complexType name="TmanagedServiceFactory">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<xsd:group ref="bp:GbaseServiceElements"/>
<xsd:element name="managed-component" type="TmanagedComponent" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="interface" type="bp:Tclass" use="optional" />
<xsd:attribute name="ref" type="bp:Tidref" use="optional" />
<xsd:attribute name="auto-export" type="bp:TautoExportModes" default="disabled" />
<xsd:attribute name="ranking" type="xsd:int" default="0"/>
<xsd:attribute name="factory-pid" type="xsd:string" use="required"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="TmanagedComponent">
<xsd:group ref="bp:GbeanElements"/>
<xsd:attribute name="class" type="bp:Tclass"/>
<xsd:attribute name="init-method" type="bp:Tmethod"/>
<xsd:attribute name="destroy-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-component" type="bp:Tidref"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- cm-properties -->
<xsd:element name="cm-properties" type="TcmProperties"/>
<xsd:complexType name="TcmProperties">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update" type="xsd:boolean" use="optional" default="false"/>
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,163 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
/*
* $Revision$
*
* Copyright (c) OSGi Alliance (2008, 2009). All Rights Reserved.
*
* 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.
*/
-->
<xsd:schema xmlns="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.2.0"
xmlns:ext100="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
xmlns:ext110="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0"
xmlns:ext120="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0"
xmlns:ext130="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0"
xmlns:ext140="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
targetNamespace="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.2.0"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0.0">
<xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0" />
<!-- property placeholder -->
<xsd:element name="property-placeholder" type="TpropertyPlaceholder"/>
<xsd:complexType name="TpropertyPlaceholder">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<!-- nested properties declaration -->
<xsd:element name="default-properties" type="TdefaultProperties" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="ext100:location" />
<xsd:element ref="ext110:location" />
<xsd:element ref="ext120:location" />
<xsd:element ref="ext130:location" />
<xsd:element ref="ext140:location" />
</xsd:choice>
</xsd:sequence>
<!-- #### What should be the type for a persistent id? I think we need to define one like class and method -->
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="placeholder-prefix" type="xsd:string" use="optional" default="${"/>
<xsd:attribute name="placeholder-suffix" type="xsd:string" use="optional" default="}"/>
<xsd:attribute name="defaults-ref" type="bp:Tidref" use="optional"/>
<xsd:attribute name="update-strategy" type="TplaceholderUpdateStrategyType" use="optional" default="none"/>
<xsd:attributeGroup ref="extAttributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:simpleType name="TplaceholderUpdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="reload"/>
</xsd:restriction>
</xsd:simpleType>
<!-- #### is this the correct type here? This is defining placeholder properties,
so should this be a restricted set of value types or should this be expanded to
all of the elements you can inject into a bean property? -->
<xsd:complexType name="TdefaultProperties">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="property" type="bp:Tproperty"/>
</xsd:sequence>
</xsd:complexType>
<xsd:attributeGroup name="extAttributes">
<xsd:attribute ref="ext100:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext110:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext120:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext130:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext140:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext100:system-properties" use="optional" />
<xsd:attribute ref="ext110:system-properties" use="optional" />
<xsd:attribute ref="ext120:system-properties" use="optional" />
<xsd:attribute ref="ext130:system-properties" use="optional" />
<xsd:attribute ref="ext140:system-properties" use="optional" />
</xsd:attributeGroup>
<!-- managed-properties -->
<xsd:element name="managed-properties" type="TmanagedProperties"/>
<xsd:complexType name="TmanagedProperties">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update-strategy" type="TupdateStrategyType" use="optional"/>
<xsd:attribute name="update-method" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="TupdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="component-managed"/>
<xsd:enumeration value="container-managed"/>
</xsd:restriction>
</xsd:simpleType>
<!-- managed-service-factory -->
<xsd:element name="managed-service-factory" type="TmanagedServiceFactory"/>
<xsd:complexType name="TmanagedServiceFactory">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<xsd:group ref="bp:GbaseServiceElements"/>
<xsd:element name="managed-component" type="TmanagedComponent" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="interface" type="bp:Tclass" use="optional" />
<xsd:attribute name="ref" type="bp:Tidref" use="optional" />
<xsd:attribute name="auto-export" type="bp:TautoExportModes" default="disabled" />
<xsd:attribute name="ranking" type="xsd:int" default="0"/>
<xsd:attribute name="factory-pid" type="xsd:string" use="required"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="TmanagedComponent">
<xsd:group ref="bp:GbeanElements"/>
<xsd:attribute name="class" type="bp:Tclass"/>
<xsd:attribute name="init-method" type="bp:Tmethod"/>
<xsd:attribute name="destroy-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-component" type="bp:Tidref"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- cm-properties -->
<xsd:element name="cm-properties" type="TcmProperties"/>
<xsd:complexType name="TcmProperties">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update" type="xsd:boolean" use="optional" default="false"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,168 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
/*
* $Revision$
*
* Copyright (c) OSGi Alliance (2008, 2009). All Rights Reserved.
*
* 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.
*/
-->
<xsd:schema xmlns="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.3.0"
xmlns:ext100="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
xmlns:ext110="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0"
xmlns:ext120="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0"
xmlns:ext130="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0"
xmlns:ext140="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0"
xmlns:ext150="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.5.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
targetNamespace="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.3.0"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0.0">
<xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.5.0" />
<!-- property placeholder -->
<xsd:element name="property-placeholder" type="TpropertyPlaceholder"/>
<xsd:complexType name="TpropertyPlaceholder">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<!-- nested properties declaration -->
<xsd:element name="default-properties" type="TdefaultProperties" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="ext100:location" />
<xsd:element ref="ext110:location" />
<xsd:element ref="ext120:location" />
<xsd:element ref="ext130:location" />
<xsd:element ref="ext140:location" />
<xsd:element ref="ext150:location" />
</xsd:choice>
</xsd:sequence>
<!-- #### What should be the type for a persistent id? I think we need to define one like class and method -->
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="placeholder-prefix" type="xsd:string" use="optional" default="${"/>
<xsd:attribute name="placeholder-suffix" type="xsd:string" use="optional" default="}"/>
<xsd:attribute name="defaults-ref" type="bp:Tidref" use="optional"/>
<xsd:attribute name="update-strategy" type="TplaceholderUpdateStrategyType" use="optional" default="none"/>
<xsd:attributeGroup ref="extAttributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:simpleType name="TplaceholderUpdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="reload"/>
</xsd:restriction>
</xsd:simpleType>
<!-- #### is this the correct type here? This is defining placeholder properties,
so should this be a restricted set of value types or should this be expanded to
all of the elements you can inject into a bean property? -->
<xsd:complexType name="TdefaultProperties">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="property" type="bp:Tproperty"/>
</xsd:sequence>
</xsd:complexType>
<xsd:attributeGroup name="extAttributes">
<xsd:attribute ref="ext100:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext110:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext120:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext130:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext140:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext150:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext100:system-properties" use="optional" />
<xsd:attribute ref="ext110:system-properties" use="optional" />
<xsd:attribute ref="ext120:system-properties" use="optional" />
<xsd:attribute ref="ext130:system-properties" use="optional" />
<xsd:attribute ref="ext140:system-properties" use="optional" />
<xsd:attribute ref="ext150:system-properties" use="optional" />
</xsd:attributeGroup>
<!-- managed-properties -->
<xsd:element name="managed-properties" type="TmanagedProperties"/>
<xsd:complexType name="TmanagedProperties">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update-strategy" type="TupdateStrategyType" use="optional"/>
<xsd:attribute name="update-method" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="TupdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="component-managed"/>
<xsd:enumeration value="container-managed"/>
</xsd:restriction>
</xsd:simpleType>
<!-- managed-service-factory -->
<xsd:element name="managed-service-factory" type="TmanagedServiceFactory"/>
<xsd:complexType name="TmanagedServiceFactory">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<xsd:group ref="bp:GbaseServiceElements"/>
<xsd:element name="managed-component" type="TmanagedComponent" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="interface" type="bp:Tclass" use="optional" />
<xsd:attribute name="ref" type="bp:Tidref" use="optional" />
<xsd:attribute name="auto-export" type="bp:TautoExportModes" default="disabled" />
<xsd:attribute name="ranking" type="xsd:int" default="0"/>
<xsd:attribute name="factory-pid" type="xsd:string" use="required"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="TmanagedComponent">
<xsd:group ref="bp:GbeanElements"/>
<xsd:attribute name="class" type="bp:Tclass"/>
<xsd:attribute name="init-method" type="bp:Tmethod"/>
<xsd:attribute name="destroy-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-ref" type="bp:Tidref"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- cm-properties -->
<xsd:element name="cm-properties" type="TcmProperties"/>
<xsd:complexType name="TcmProperties">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update" type="xsd:boolean" use="optional" default="false"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,174 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
/*
* $Revision$
*
* Copyright (c) OSGi Alliance (2008, 2009). All Rights Reserved.
*
* 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.
*/
-->
<xsd:schema xmlns="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.4.0"
xmlns:ext100="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0"
xmlns:ext110="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0"
xmlns:ext120="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0"
xmlns:ext130="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0"
xmlns:ext140="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0"
xmlns:ext150="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.5.0"
xmlns:ext160="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.6.0"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:bp="http://www.osgi.org/xmlns/blueprint/v1.0.0"
targetNamespace="http://aries.apache.org/blueprint/xmlns/blueprint-cm/v1.4.0"
elementFormDefault="qualified"
attributeFormDefault="unqualified"
version="1.0.0">
<xsd:import namespace="http://www.osgi.org/xmlns/blueprint/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.0.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.1.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.2.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.3.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.4.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.5.0" />
<xsd:import namespace="http://aries.apache.org/blueprint/xmlns/blueprint-ext/v1.6.0" />
<!-- property placeholder -->
<xsd:element name="property-placeholder" type="TpropertyPlaceholder"/>
<xsd:complexType name="TpropertyPlaceholder">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<!-- nested properties declaration -->
<xsd:element name="default-properties" type="TdefaultProperties" minOccurs="0" maxOccurs="1"/>
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:element ref="ext100:location" />
<xsd:element ref="ext110:location" />
<xsd:element ref="ext120:location" />
<xsd:element ref="ext130:location" />
<xsd:element ref="ext140:location" />
<xsd:element ref="ext150:location" />
<xsd:element ref="ext160:location" />
</xsd:choice>
</xsd:sequence>
<!-- #### What should be the type for a persistent id? I think we need to define one like class and method -->
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="placeholder-prefix" type="xsd:string" use="optional" default="${"/>
<xsd:attribute name="placeholder-suffix" type="xsd:string" use="optional" default="}"/>
<xsd:attribute name="null-value" type="xsd:string" use="optional"/>
<xsd:attribute name="defaults-ref" type="bp:Tidref" use="optional"/>
<xsd:attribute name="update-strategy" type="TplaceholderUpdateStrategyType" use="optional" default="none"/>
<xsd:attributeGroup ref="extAttributes" />
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:simpleType name="TplaceholderUpdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="reload"/>
</xsd:restriction>
</xsd:simpleType>
<!-- #### is this the correct type here? This is defining placeholder properties,
so should this be a restricted set of value types or should this be expanded to
all of the elements you can inject into a bean property? -->
<xsd:complexType name="TdefaultProperties">
<xsd:sequence minOccurs="0" maxOccurs="unbounded">
<xsd:element name="property" type="bp:Tproperty"/>
</xsd:sequence>
</xsd:complexType>
<xsd:attributeGroup name="extAttributes">
<xsd:attribute ref="ext100:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext110:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext120:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext130:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext140:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext150:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext160:ignore-missing-locations" use="optional" />
<xsd:attribute ref="ext100:system-properties" use="optional" />
<xsd:attribute ref="ext110:system-properties" use="optional" />
<xsd:attribute ref="ext120:system-properties" use="optional" />
<xsd:attribute ref="ext130:system-properties" use="optional" />
<xsd:attribute ref="ext140:system-properties" use="optional" />
<xsd:attribute ref="ext150:system-properties" use="optional" />
<xsd:attribute ref="ext160:system-properties" use="optional" />
</xsd:attributeGroup>
<!-- managed-properties -->
<xsd:element name="managed-properties" type="TmanagedProperties"/>
<xsd:complexType name="TmanagedProperties">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update-strategy" type="TupdateStrategyType" use="optional"/>
<xsd:attribute name="update-method" type="xsd:string" use="optional"/>
</xsd:complexType>
<xsd:simpleType name="TupdateStrategyType">
<xsd:restriction base="xsd:NMTOKEN">
<xsd:enumeration value="none"/>
<xsd:enumeration value="component-managed"/>
<xsd:enumeration value="container-managed"/>
</xsd:restriction>
</xsd:simpleType>
<!-- managed-service-factory -->
<xsd:element name="managed-service-factory" type="TmanagedServiceFactory"/>
<xsd:complexType name="TmanagedServiceFactory">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:sequence>
<xsd:group ref="bp:GbaseServiceElements"/>
<xsd:element name="managed-component" type="TmanagedComponent" minOccurs="1" maxOccurs="1"/>
</xsd:sequence>
<xsd:attribute name="interface" type="bp:Tclass" use="optional" />
<xsd:attribute name="ref" type="bp:Tidref" use="optional" />
<xsd:attribute name="auto-export" type="bp:TautoExportModes" default="disabled" />
<xsd:attribute name="ranking" type="xsd:int" default="0"/>
<xsd:attribute name="factory-pid" type="xsd:string" use="required"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
<xsd:complexType name="TmanagedComponent">
<xsd:group ref="bp:GbeanElements"/>
<xsd:attribute name="class" type="bp:Tclass"/>
<xsd:attribute name="init-method" type="bp:Tmethod"/>
<xsd:attribute name="destroy-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-method" type="bp:Tmethod"/>
<xsd:attribute name="factory-ref" type="bp:Tidref"/>
<xsd:anyAttribute namespace="##other" processContents="strict"/>
</xsd:complexType>
<!-- cm-properties -->
<xsd:element name="cm-properties" type="TcmProperties"/>
<xsd:complexType name="TcmProperties">
<xsd:complexContent>
<xsd:extension base="bp:Tcomponent">
<xsd:attribute name="persistent-id" type="xsd:string" use="required"/>
<xsd:attribute name="update" type="xsd:boolean" use="optional" default="false"/>
</xsd:extension>
</xsd:complexContent>
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,784 @@
/**
* 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.aries.blueprint.compendium.cm;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
import java.util.jar.JarInputStream;
import de.kalpatec.pojosr.framework.PojoServiceRegistryFactoryImpl;
import de.kalpatec.pojosr.framework.launch.BundleDescriptor;
import de.kalpatec.pojosr.framework.launch.ClasspathScanner;
import de.kalpatec.pojosr.framework.launch.PojoServiceRegistry;
import de.kalpatec.pojosr.framework.launch.PojoServiceRegistryFactory;
import org.ops4j.pax.swissbox.tinybundles.core.TinyBundle;
import org.ops4j.pax.swissbox.tinybundles.core.TinyBundles;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
public final class Helper {
public static final long DEFAULT_TIMEOUT = 30000;
public static final String BUNDLE_FILTER = "(Bundle-SymbolicName=*)";
public static final String BUNDLE_VERSION = "1.0.0";
private static final transient Logger LOG = LoggerFactory.getLogger(Helper.class);
private Helper() {
}
public static BundleContext createBundleContext(String name, String descriptors, boolean includeTestBundle) throws Exception {
return createBundleContext(name, descriptors, includeTestBundle, BUNDLE_FILTER, BUNDLE_VERSION);
}
public static BundleContext createBundleContext(String name, String descriptors, boolean includeTestBundle,
String bundleFilter, String testBundleVersion) throws Exception {
TinyBundle bundle = null;
if (includeTestBundle) {
// add ourselves as a bundle
bundle = createTestBundle(name, testBundleVersion, descriptors);
}
return createBundleContext(bundleFilter, new TinyBundle[] { bundle });
}
public static BundleContext createBundleContext(String bundleFilter, TinyBundle[] testBundles) throws Exception {
deleteDirectory("target/bundles");
createDirectory("target/bundles");
// ensure pojosr stores bundles in an unique target directory
System.setProperty("org.osgi.framework.storage", "target/bundles/" + System.currentTimeMillis());
// get the bundles
List<BundleDescriptor> bundles = getBundleDescriptors(bundleFilter);
// Add the test bundles at the beginning of the list so that they get started first.
// The reason is that the bundle tracker used by blueprint does not work well
// with pojosr because it does not support bundle hooks, so events are lost.
if (testBundles != null) {
for (TinyBundle bundle : testBundles) {
File tmp = File.createTempFile("test-", ".jar", new File("target/bundles/"));
tmp.delete();
bundles.add(0, getBundleDescriptor(tmp.getPath(), bundle));
}
}
if (LOG.isDebugEnabled()) {
for (int i = 0; i < bundles.size(); i++) {
BundleDescriptor desc = bundles.get(i);
LOG.debug("Bundle #{} -> {}", i, desc);
}
}
// setup pojosr to use our bundles
Map<String, List<BundleDescriptor>> config = new HashMap<String, List<BundleDescriptor>>();
config.put(PojoServiceRegistryFactory.BUNDLE_DESCRIPTORS, bundles);
// create pojorsr osgi service registry
PojoServiceRegistry reg = new PojoServiceRegistryFactoryImpl().newPojoServiceRegistry(config);
return reg.getBundleContext();
}
public static void disposeBundleContext(BundleContext bundleContext) throws BundleException {
try {
if (bundleContext != null) {
bundleContext.getBundle().stop();
}
} finally {
System.clearProperty("org.osgi.framework.storage");
}
}
public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, long timeout) {
return getOsgiService(bundleContext, type, null, timeout);
}
public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type) {
return getOsgiService(bundleContext, type, null, DEFAULT_TIMEOUT);
}
public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, String filter) {
return getOsgiService(bundleContext, type, filter, DEFAULT_TIMEOUT);
}
public static <T> ServiceReference getOsgiServiceReference(BundleContext bundleContext, Class<T> type, String filter, long timeout) {
ServiceTracker tracker = null;
try {
String flt;
if (filter != null) {
if (filter.startsWith("(")) {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")";
} else {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))";
}
} else {
flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")";
}
Filter osgiFilter = FrameworkUtil.createFilter(flt);
tracker = new ServiceTracker(bundleContext, osgiFilter, null);
tracker.open(true);
// Note that the tracker is not closed to keep the reference
// This is buggy, as the service reference may change i think
Object svc = tracker.waitForService(timeout);
if (svc == null) {
Dictionary<?, ?> dic = bundleContext.getBundle().getHeaders();
System.err.println("Test bundle headers: " + explode(dic));
for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, null))) {
System.err.println("ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
}
for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) {
System.err.println("Filtered ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
}
throw new RuntimeException("Gave up waiting for service " + flt);
}
return tracker.getServiceReference();
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Invalid filter", e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
public static <T> T getOsgiService(BundleContext bundleContext, Class<T> type, String filter, long timeout) {
ServiceTracker tracker = null;
try {
String flt;
if (filter != null) {
if (filter.startsWith("(")) {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")" + filter + ")";
} else {
flt = "(&(" + Constants.OBJECTCLASS + "=" + type.getName() + ")(" + filter + "))";
}
} else {
flt = "(" + Constants.OBJECTCLASS + "=" + type.getName() + ")";
}
Filter osgiFilter = FrameworkUtil.createFilter(flt);
tracker = new ServiceTracker(bundleContext, osgiFilter, null);
tracker.open(true);
// Note that the tracker is not closed to keep the reference
// This is buggy, as the service reference may change i think
Object svc = tracker.waitForService(timeout);
if (svc == null) {
Dictionary<?, ?> dic = bundleContext.getBundle().getHeaders();
System.err.println("Test bundle headers: " + explode(dic));
for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, null))) {
System.err.println("ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
}
for (ServiceReference ref : asCollection(bundleContext.getAllServiceReferences(null, flt))) {
System.err.println("Filtered ServiceReference: " + ref + ", bundle: " + ref.getBundle() + ", symbolicName: " + ref.getBundle().getSymbolicName());
}
throw new RuntimeException("Gave up waiting for service " + flt);
}
return type.cast(svc);
} catch (InvalidSyntaxException e) {
throw new IllegalArgumentException("Invalid filter", e);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
protected static TinyBundle createTestBundle(String name, String version, String descriptors) throws FileNotFoundException, MalformedURLException {
TinyBundle bundle = TinyBundles.newBundle();
for (URL url : getBlueprintDescriptors(descriptors)) {
LOG.info("Using Blueprint XML file: " + url.getFile());
bundle.add("OSGI-INF/blueprint/blueprint-" + url.getFile().replace("/", "-"), url);
}
bundle.set("Manifest-Version", "2")
.set("Bundle-ManifestVersion", "2")
.set("Bundle-SymbolicName", name)
.set("Bundle-Version", version);
return bundle;
}
/**
* Explode the dictionary into a <code>,</code> delimited list of <code>key=value</code> pairs.
*/
private static String explode(Dictionary<?, ?> dictionary) {
Enumeration<?> keys = dictionary.keys();
StringBuffer result = new StringBuffer();
while (keys.hasMoreElements()) {
Object key = keys.nextElement();
result.append(String.format("%s=%s", key, dictionary.get(key)));
if (keys.hasMoreElements()) {
result.append(", ");
}
}
return result.toString();
}
/**
* Provides an iterable collection of references, even if the original array is <code>null</code>.
*/
private static Collection<ServiceReference> asCollection(ServiceReference[] references) {
return references == null ? new ArrayList<ServiceReference>(0) : Arrays.asList(references);
}
/**
* Gets list of bundle descriptors.
* @param bundleFilter Filter expression for OSGI bundles.
*
* @return List pointers to OSGi bundles.
* @throws Exception If looking up the bundles fails.
*/
private static List<BundleDescriptor> getBundleDescriptors(final String bundleFilter) throws Exception {
return new ClasspathScanner().scanForBundles(bundleFilter);
}
/**
* Gets the bundle descriptors as {@link URL} resources.
*
* @param descriptors the bundle descriptors, can be separated by comma
* @return the bundle descriptors.
* @throws FileNotFoundException is thrown if a bundle descriptor cannot be found
*/
private static Collection<URL> getBlueprintDescriptors(String descriptors) throws FileNotFoundException, MalformedURLException {
List<URL> answer = new ArrayList<URL>();
String descriptor = descriptors;
if (descriptor != null) {
// there may be more resources separated by comma
Iterator<Object> it = createIterator(descriptor);
while (it.hasNext()) {
String s = (String) it.next();
LOG.trace("Resource descriptor: {}", s);
// remove leading / to be able to load resource from the classpath
s = stripLeadingSeparator(s);
// if there is wildcards for *.xml then we need to find the urls from the package
if (s.endsWith("*.xml")) {
String packageName = s.substring(0, s.length() - 5);
// remove trailing / to be able to load resource from the classpath
Enumeration<URL> urls = loadResourcesAsURL(packageName);
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
File dir = new File(url.getFile());
if (dir.isDirectory()) {
File[] files = dir.listFiles();
if (files != null) {
for (File file : files) {
if (file.isFile() && file.exists() && file.getName().endsWith(".xml")) {
String name = packageName + file.getName();
LOG.debug("Resolving resource: {}", name);
URL xmlUrl = loadResourceAsURL(name);
if (xmlUrl != null) {
answer.add(xmlUrl);
}
}
}
}
}
}
} else {
LOG.debug("Resolving resource: {}", s);
URL url = resolveMandatoryResourceAsUrl(s);
if (url == null) {
throw new FileNotFoundException("Resource " + s + " not found");
}
answer.add(url);
}
}
} else {
throw new IllegalArgumentException("No bundle descriptor configured. Override getBlueprintDescriptor() or getBlueprintDescriptors() method");
}
if (answer.isEmpty()) {
throw new IllegalArgumentException("Cannot find any resources in classpath from descriptor " + descriptors);
}
return answer;
}
private static BundleDescriptor getBundleDescriptor(String path, TinyBundle bundle) throws Exception {
File file = new File(path);
FileOutputStream fos = new FileOutputStream(file, true);
try {
copy(bundle.build(), fos);
} finally {
close(fos);
}
FileInputStream fis = null;
JarInputStream jis = null;
try {
fis = new FileInputStream(file);
jis = new JarInputStream(fis);
Map<String, String> headers = new HashMap<String, String>();
for (Map.Entry<Object, Object> entry : jis.getManifest().getMainAttributes().entrySet()) {
headers.put(entry.getKey().toString(), entry.getValue().toString());
}
return new BundleDescriptor(
bundle.getClass().getClassLoader(),
new URL("jar:" + file.toURI().toString() + "!/"),
headers);
} finally {
close(fis, jis);
}
}
/**
* Closes the given resource if it is available, logging any closing exceptions to the given log.
*
* @param closeable the object to close
* @param name the name of the resource
* @param log the log to use when reporting closure warnings, will use this class's own {@link Logger} if <tt>log == null</tt>
*/
public static void close(Closeable closeable, String name, Logger log) {
if (closeable != null) {
try {
closeable.close();
} catch (IOException e) {
if (log == null) {
// then fallback to use the own Logger
log = LOG;
}
if (name != null) {
log.warn("Cannot close: " + name + ". Reason: " + e.getMessage(), e);
} else {
log.warn("Cannot close. Reason: " + e.getMessage(), e);
}
}
}
}
/**
* Closes the given resource if it is available.
*
* @param closeable the object to close
* @param name the name of the resource
*/
public static void close(Closeable closeable, String name) {
close(closeable, name, LOG);
}
/**
* Closes the given resource if it is available.
*
* @param closeable the object to close
*/
public static void close(Closeable closeable) {
close(closeable, null, LOG);
}
/**
* Closes the given resources if they are available.
*
* @param closeables the objects to close
*/
public static void close(Closeable... closeables) {
for (Closeable closeable : closeables) {
close(closeable);
}
}
private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
private static final String DEFAULT_DELIMITER = ",";
public static int copy(InputStream input, OutputStream output) throws IOException {
return copy(input, output, DEFAULT_BUFFER_SIZE);
}
public static int copy(final InputStream input, final OutputStream output, int bufferSize) throws IOException {
int avail = input.available();
if (avail > 262144) {
avail = 262144;
}
if (avail > bufferSize) {
bufferSize = avail;
}
final byte[] buffer = new byte[bufferSize];
int n = input.read(buffer);
int total = 0;
while (-1 != n) {
output.write(buffer, 0, n);
total += n;
n = input.read(buffer);
}
output.flush();
return total;
}
/**
* Creates an iterator over the value if the value is a collection, an
* Object[], a String with values separated by comma,
* or a primitive type array; otherwise to simplify the caller's code,
* we just create a singleton collection iterator over a single value
* <p/>
* Will default use comma for String separating String values.
* This method does <b>not</b> allow empty values
*
* @param value the value
* @return the iterator
*/
public static Iterator<Object> createIterator(Object value) {
return createIterator(value, DEFAULT_DELIMITER);
}
/**
* Creates an iterator over the value if the value is a collection, an
* Object[], a String with values separated by the given delimiter,
* or a primitive type array; otherwise to simplify the caller's
* code, we just create a singleton collection iterator over a single value
* <p/>
* This method does <b>not</b> allow empty values
*
* @param value the value
* @param delimiter delimiter for separating String values
* @return the iterator
*/
public static Iterator<Object> createIterator(Object value, String delimiter) {
return createIterator(value, delimiter, false);
}
/**
* Creates an iterator over the value if the value is a collection, an
* Object[], a String with values separated by the given delimiter,
* or a primitive type array; otherwise to simplify the caller's
* code, we just create a singleton collection iterator over a single value
*
* @param value the value
* @param delimiter delimiter for separating String values
* @param allowEmptyValues whether to allow empty values
* @return the iterator
*/
@SuppressWarnings("unchecked")
public static Iterator<Object> createIterator(Object value, String delimiter, final boolean allowEmptyValues) {
if (value == null) {
return Collections.emptyList().iterator();
} else if (value instanceof Iterator) {
return (Iterator<Object>) value;
} else if (value instanceof Iterable) {
return ((Iterable<Object>) value).iterator();
} else if (value.getClass().isArray()) {
// TODO we should handle primitive array types?
List<Object> list = Arrays.asList((Object[]) value);
return list.iterator();
} else if (value instanceof NodeList) {
// lets iterate through DOM results after performing XPaths
final NodeList nodeList = (NodeList) value;
return cast(new Iterator<Node>() {
int idx = -1;
public boolean hasNext() {
return (idx + 1) < nodeList.getLength();
}
public Node next() {
idx++;
return nodeList.item(idx);
}
public void remove() {
throw new UnsupportedOperationException();
}
});
} else if (value instanceof String) {
final String s = (String) value;
// this code is optimized to only use a Scanner if needed, eg there is a delimiter
if (delimiter != null && s.contains(delimiter)) {
// use a scanner if it contains the delimiter
Scanner scanner = new Scanner((String) value);
if (DEFAULT_DELIMITER.equals(delimiter)) {
// we use the default delimiter which is a comma, then cater for bean expressions with OGNL
// which may have balanced parentheses pairs as well.
// if the value contains parentheses we need to balance those, to avoid iterating
// in the middle of parentheses pair, so use this regular expression (a bit hard to read)
// the regexp will split by comma, but honor parentheses pair that may include commas
// as well, eg if value = "bean=foo?method=killer(a,b),bean=bar?method=great(a,b)"
// then the regexp will split that into two:
// -> bean=foo?method=killer(a,b)
// -> bean=bar?method=great(a,b)
// http://stackoverflow.com/questions/1516090/splitting-a-title-into-separate-parts
delimiter = ",(?!(?:[^\\(,]|[^\\)],[^\\)])+\\))";
}
scanner.useDelimiter(delimiter);
return cast(scanner);
} else {
// use a plain iterator that returns the value as is as there are only a single value
return cast(new Iterator<String>() {
int idx = -1;
public boolean hasNext() {
return idx + 1 == 0 && (allowEmptyValues || isNotEmpty(s));
}
public String next() {
idx++;
return s;
}
public void remove() {
throw new UnsupportedOperationException();
}
});
}
} else {
return Collections.singletonList(value).iterator();
}
}
/**
* Tests whether the value is <b>not</b> <tt>null</tt> or an empty string.
*
* @param value the value, if its a String it will be tested for text length as well
* @return true if <b>not</b> empty
*/
public static boolean isNotEmpty(Object value) {
if (value == null) {
return false;
} else if (value instanceof String) {
String text = (String) value;
return text.trim().length() > 0;
} else {
return true;
}
}
public static <T> Iterator<T> cast(Iterator<?> p) {
return (Iterator<T>) p;
}
/**
* Strip any leading separators
*/
public static String stripLeadingSeparator(String name) {
if (name == null) {
return null;
}
while (name.startsWith("/") || name.startsWith(File.separator)) {
name = name.substring(1);
}
return name;
}
/**
* Attempts to load the given resources from the given package name using the thread context
* class loader or the class loader used to load this class
*
* @param packageName the name of the package to load its resources
* @return the URLs for the resources or null if it could not be loaded
*/
public static Enumeration<URL> loadResourcesAsURL(String packageName) {
Enumeration<URL> url = null;
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
try {
url = contextClassLoader.getResources(packageName);
} catch (IOException e) {
// ignore
}
}
if (url == null) {
try {
url = Helper.class.getClassLoader().getResources(packageName);
} catch (IOException e) {
// ignore
}
}
return url;
}
/**
* Attempts to load the given resource as a stream using the thread context
* class loader or the class loader used to load this class
*
* @param name the name of the resource to load
* @return the stream or null if it could not be loaded
*/
public static URL loadResourceAsURL(String name) {
URL url = null;
String resolvedName = resolveUriPath(name);
ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
if (contextClassLoader != null) {
url = contextClassLoader.getResource(resolvedName);
}
if (url == null) {
url = Helper.class.getClassLoader().getResource(resolvedName);
}
return url;
}
/**
* Helper operation used to remove relative path notation from
* resources. Most critical for resources on the Classpath
* as resource loaders will not resolve the relative paths correctly.
*
* @param name the name of the resource to load
* @return the modified or unmodified string if there were no changes
*/
private static String resolveUriPath(String name) {
String answer = name;
if (answer.indexOf("//") > -1) {
answer = answer.replaceAll("//", "/");
}
if (answer.indexOf("../") > -1) {
answer = answer.replaceAll("[A-Za-z0-9]*/\\.\\./", "");
}
if (answer.indexOf("./") > -1) {
answer = answer.replaceAll("\\./", "");
}
return answer;
}
/**
* Resolves the mandatory resource.
*
* @param uri uri of the resource
* @return the resource as an {@link InputStream}. Remember to close this stream after usage.
* @throws java.io.FileNotFoundException is thrown if the resource file could not be found
* @throws java.net.MalformedURLException if the URI is malformed
*/
public static URL resolveMandatoryResourceAsUrl(String uri) throws FileNotFoundException, MalformedURLException {
if (uri.startsWith("file:")) {
// check if file exists first
String name = after(uri, "file:");
File file = new File(name);
if (!file.exists()) {
throw new FileNotFoundException("File " + file + " not found");
}
return new URL(uri);
} else if (uri.startsWith("http:")) {
return new URL(uri);
} else if (uri.startsWith("classpath:")) {
uri = after(uri, "classpath:");
}
// load from classpath by default
URL url = loadResourceAsURL(uri);
if (url == null) {
throw new FileNotFoundException("Cannot find resource in classpath for URI: " + uri);
} else {
return url;
}
}
public static String after(String text, String after) {
if (!text.contains(after)) {
return null;
}
return text.substring(text.indexOf(after) + after.length());
}
/**
* Recursively delete a directory, useful to zapping test data
*
* @param file the directory to be deleted
* @return <tt>false</tt> if error deleting directory
*/
public static boolean deleteDirectory(String file) {
return deleteDirectory(new File(file));
}
/**
* Recursively delete a directory, useful to zapping test data
*
* @param file the directory to be deleted
* @return <tt>false</tt> if error deleting directory
*/
public static boolean deleteDirectory(File file) {
int tries = 0;
int maxTries = 5;
boolean exists = true;
while (exists && (tries < maxTries)) {
recursivelyDeleteDirectory(file);
tries++;
exists = file.exists();
if (exists) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// Ignore
}
}
}
return !exists;
}
private static void recursivelyDeleteDirectory(File file) {
if (!file.exists()) {
return;
}
if (file.isDirectory()) {
File[] files = file.listFiles();
for (File child : files) {
recursivelyDeleteDirectory(child);
}
}
boolean success = file.delete();
if (!success) {
LOG.warn("Deletion of file: " + file.getAbsolutePath() + " failed");
}
}
/**
* create the directory
*
* @param file the directory to be created
*/
public static void createDirectory(String file) {
File dir = new File(file);
dir.mkdirs();
}
}

View File

@ -0,0 +1,396 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.felix</groupId>
<artifactId>felix-parent</artifactId>
<version>6</version>
<relativePath />
</parent>
<artifactId>org.apache.felix.configadmin</artifactId>
<version>1.9.16-ENTAXY</version>
<packaging>bundle</packaging>
<name>Apache Felix Configuration Admin Service</name>
<description>
Implementation of the OSGi Configuration Admin Service Specification 1.6
</description>
<scm>
<connection>scm:svn:http://svn.apache.org/repos/asf/felix/releases/org.apache.felix.configadmin-1.9.16</connection>
<developerConnection>scm:svn:https://svn.apache.org/repos/asf/felix/releases/org.apache.felix.configadmin-1.9.16</developerConnection>
<url>scm:svn:https://svn.apache.org/repos/asf/felix/releases/org.apache.felix.configadmin-1.9.16</url>
</scm>
<!--
A Note on Testing
=================
This project contains two kinds of tests: regular unit tests running
in the test phase and integration tests based on PAX Exam running
in the integration-test phase.
Basically the complete project is build using Java 7 source and target
compatibility as inherited by the parent pom.
For running the integration tests from the console using Maven nothing
special has to be done as the tests run automatically. To run the tests
in your IDE, the project has to be built to the "package" phase with
the profile "ide" enabled:
$ mvn -Pide clean package
This creates the configadmin.jar file in the target folder, which is used by
the integration tests when run from the IDE. Alternatively the
"project.bundle.file" system property may be set to the bundle JAR
in the IDE launcher.
-->
<properties>
<bundle.build.name>
${basedir}/target
</bundle.build.name>
<bundle.file.name>
${bundle.build.name}/${project.build.finalName}.jar
</bundle.file.name>
</properties>
<dependencies>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>osgi.annotation</artifactId>
<version>6.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.core</artifactId>
<version>6.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.cm</artifactId>
<version>1.6.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.log</artifactId>
<version>1.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.osgi</groupId>
<artifactId>org.osgi.service.coordinator</artifactId>
<version>1.0.2</version>
<scope>provided</scope>
</dependency>
<!-- Integration Testing with Pax Exam -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>2.17.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-junit4</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-native</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<!-- The forked container is needed so that we can use Java security in the tests -->
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-container-forked</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.exam</groupId>
<artifactId>pax-exam-link-mvn</artifactId>
<version>2.6.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.url</groupId>
<artifactId>pax-url-aether</artifactId>
<version>1.5.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.ops4j.pax.tinybundles</groupId>
<artifactId>tinybundles</artifactId>
<version>1.0.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-atinject_1.0_spec</artifactId>
<version>1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework</artifactId>
<version>5.6.1</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.felix</groupId>
<artifactId>org.apache.felix.framework.security</artifactId>
<version>2.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.felix</groupId>
<artifactId>maven-bundle-plugin</artifactId>
<version>3.5.0</version>
<extensions>true</extensions>
<configuration>
<instructions>
<Bundle-Category>osgi</Bundle-Category>
<Bundle-SymbolicName>
${project.artifactId}
</Bundle-SymbolicName>
<Bundle-Vendor>The Apache Software Foundation</Bundle-Vendor>
<Bundle-DocURL>
http://felix.apache.org/site/apache-felix-config-admin.html
</Bundle-DocURL>
<Bundle-Activator>
org.apache.felix.cm.impl.Activator
</Bundle-Activator>
<Export-Package>
<!-- just list, version from package-info classes -->
<!-- when the spec version changes, update the service property that includes the spec version in ConfigurationManager -->
org.apache.felix.cm;
org.apache.felix.cm.file,
org.osgi.service.cm;provide:=true
</Export-Package>
<Import-Package>
org.osgi.service.cm,
org.osgi.service.coordinator;resolution:=optional,
org.osgi.service.log;resolution:=optional,
*
</Import-Package>
<DynamicImport-Package>
org.osgi.service.coordinator;version="[1.0,2)",
org.osgi.service.log;version="[1.3,2)"
</DynamicImport-Package>
<Provide-Capability><![CDATA[
osgi.service;objectClass:List<String>="org.osgi.service.cm.ConfigurationAdmin";uses:="org.osgi.service.cm,org.apache.felix.cm",
osgi.service;objectClass:List<String>="org.apache.felix.cm.PersistenceManager";uses:="org.osgi.service.cm,org.apache.felix.cm",
osgi.implementation;osgi.implementation="osgi.cm";uses:="org.osgi.service.cm,org.apache.felix.cm";version:Version="1.6"
]]></Provide-Capability>
<Require-Capability><![CDATA[
osgi.service;filter:="(objectClass=org.osgi.service.log.LogService)";effective:=active;resolution:=optional
]]></Require-Capability>
</instructions>
</configuration>
<executions>
<execution>
<id>baseline</id>
<goals>
<goal>baseline</goal>
</goals>
</execution>
</executions>
</plugin>
<!--
Exclude Integration tests in (default) unit tests and
conversely enable integration tests for integration testing
only. Helper classes are completely excluded from testing.
-->
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<executions>
<execution>
<id>surefire-it</id>
<phase>integration-test</phase>
<goals>
<goal>test</goal>
</goals>
<configuration>
<systemProperties>
<property>
<name>project.bundle.file</name>
<value>${bundle.file.name}</value>
</property>
</systemProperties>
<excludes>
<exclude>**/cm/*</exclude>
<exclude>**/cm/file/*</exclude>
<exclude>**/cm/impl/**</exclude>
</excludes>
<includes>
<include>**/integration/*</include>
</includes>
</configuration>
</execution>
</executions>
<configuration>
<!-- @ENTAXY -->
<skip>true</skip>
<excludes>
<exclude>**/integration/**</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
<profiles>
<!--
copy the package such that IDEs may easily use it without
setting the system property
-->
<profile>
<id>ide</id>
<build>
<plugins>
<plugin>
<artifactId>maven-antrun-plugin</artifactId>
<version>1.3</version>
<executions>
<execution>
<id>cm-file-create</id>
<phase>package</phase>
<goals>
<goal>run</goal>
</goals>
<configuration>
<tasks>
<copy file="${project.build.directory}/${project.build.finalName}.jar" tofile="${project.build.directory}/configadmin.jar" />
</tasks>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>public-deploy</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
<distributionManagement>
<repository>
<id>entaxy-public-entaxy</id>
<name>entaxy-public-entaxy</name>
<uniqueVersion>false</uniqueVersion>
<layout>default</layout>
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public-entaxy/</url>
</repository>
</distributionManagement>
</profile>
</profiles>
<repositories>
<!--
contains all used components together with sources and javadocs, proxies Maven Central and Apache
also contains public Entaxy releases & snapshots (snapshots are disabled here)
-->
<repository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--
contains all Entaxy snaphots and releases, authorized access only
use Maven settins.xml to provide access credentials
-->
<repository>
<id>entaxy-private</id>
<name>entaxy-private</name>
<!-- url>http://localhost:8981/repository/entaxy-private/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-private/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</snapshots>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>entaxy-public</id>
<name>entaxy-public</name>
<!-- url>http://localhost:8981/repository/entaxy-public/</url -->
<url>https://nexus.entaxy.ru/nexus/repository/entaxy-public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
<checksumPolicy>warn</checksumPolicy>
<updatePolicy>never</updatePolicy>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>

View File

@ -0,0 +1,25 @@
I. Included Third-Party Software
This product includes software developed at
The OSGi Alliance (http://www.osgi.org/).
Copyright (c) OSGi Alliance (2000, 2012).
Licensed under the Apache License 2.0.
II. Used Third-Party Software
This product uses software developed at
The OSGi Alliance (http://www.osgi.org/).
Copyright (c) OSGi Alliance (2000, 2012).
Licensed under the Apache License 2.0.
This product uses software developed at
The Codehaus (http://www.codehaus.org)
Licensed under the Apache License 2.0.
This product uses software developed at
Open Participation Software for Java (http://www.ops4j.org)
Licensed under the Apache License 2.0.
III. License Summary
- Apache License 2.0

View File

@ -0,0 +1,4 @@
This product includes software developed at
The OSGi Alliance (http://www.osgi.org/).
Copyright (c) OSGi Alliance (2000, 2012).
Licensed under the Apache License 2.0.

View File

@ -0,0 +1,40 @@
/*
* 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.cm;
import org.osgi.annotation.versioning.ConsumerType;
/**
* <code>NotCachablePersistenceManager</code> is a marker interface which
* extends {@link PersistenceManager} to inform that no cache should be applied
* around this persistence manager. This gives the opportunity for the
* persistence manager to implement it's own caching heuristics.
* <p>
* To make implementations of this interface available to the Configuration
* Admin Service they must be registered as service for interface
* {@link PersistenceManager}.
* <p>
* See also {@link PersistenceManager}
*
* @since 1.1
*/
@ConsumerType
public interface NotCachablePersistenceManager extends PersistenceManager
{
}

View File

@ -0,0 +1,146 @@
/*
* 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.cm;
import java.io.IOException;
import java.util.Dictionary;
import java.util.Enumeration;
import org.osgi.annotation.versioning.ConsumerType;
/**
* The <code>PersistenceManager</code> interface defines the API to be
* implemented to support persisting configuration data. This interface may
* be implemented by bundles, which support storing configuration data in
* different locations.
* <p>
* The Apache Felix Configuration Admin Service bundles provides an
* implementation of this interface using the platform file system to store
* configuration data.
* <p>
* Implementations of this interface must support loading and storing
* <code>java.util.Dictionary</code> objects as defined in section 104.4.2,
* Configuration Properties, of the Configuration Admin Service Specification
* Version 1.2.
* <p>
* To make implementations of this interface available to the Configuration
* Admin Service they must be registered as service for this interface. The
* Configuration Admin Service will consider all registered services plus the
* default platform file system based implementation to load configuration data.
* To store new configuration data, the persistence manager service with the
* highest rank value - the <code>service.ranking</code> service property - is
* used. If no persistence manager service has been registered, the platform
* file system based implementation is used.
*/
@ConsumerType
public interface PersistenceManager
{
/**
* Service registration property to define a unique name for the persistence manager.
* @since 1.2
*/
String PROPERTY_NAME = "name";
/**
* Returns <code>true</code> if a persisted <code>Dictionary</code> exists for
* the given <code>pid</code>.
*
* @param pid The identifier for the dictionary to test.
* @return {@code true} if a persisted dictionary exists for the pid.
*/
boolean exists( String pid );
/**
* Returns the <code>Dictionary</code> for the given <code>pid</code>.
* <p>
* Implementations are expected to return dictionary instances which may be
* modified by the caller without affecting any underlying data or affecting
* future calls to this method with the same PID. In other words the
* reference equation <code>load(pid) != load(pid)</code> must hold
* <code>true</code>.
*
* @param pid The identifier for the dictionary to load.
*
* @return The dictionary for the identifier. This must not be
* <code>null</code> but may be empty.
*
* @throws IOException If an error occurs loading the dictionary. An
* <code>IOException</code> must also be thrown if no dictionary
* exists for the given identifier.
*/
Dictionary load( String pid ) throws IOException;
/**
* Returns an enumeration of all <code>Dictionary</code> objects known to
* this persistence manager.
* <p>
* Implementations of this method are allowed to return lazy enumerations.
* That is, it is allowable for the enumeration to not return a dictionary
* if loading it results in an error.
* <p>
* Implementations are expected to return dictionary instances which may be
* modified by the caller without affecting any underlying data or affecting
* future calls to this method.
* <p>
* The <code>Enumeration</code> returned from this method must be stable
* against concurrent calls to either of the {@link #load(String)},
* {@link #store(String, Dictionary)}, and {@link #delete(String)} methods.
*
* @return A possibly empty Enumeration of all dictionaries.
*
* @throws IOException If an error occurs getting the dictionaries.
*/
Enumeration getDictionaries() throws IOException;
/**
* Stores the <code>Dictionary</code> under the given <code>pid</code>.
* <p>
* The dictionary provided to this method must be considered private to the
* caller. Any modification by the caller after this method finishes must
* not influence any internal storage of the provided data. Implementations
* must not modify the dictionary.
*
* @param pid The identifier of the dictionary.
* @param properties The <code>Dictionary</code> to store.
*
* @throws IOException If an error occurs storing the dictionary. If this
* exception is thrown, it is expected, that
* {@link #exists(String) exists(pid} returns <code>false</code>.
*/
void store( String pid, Dictionary properties ) throws IOException;
/**
* Removes the <code>Dictionary</code> for the given <code>pid</code>. If
* such a dictionary does not exist, this method has no effect.
*
* @param pid The identifier of the dictionary to delete.
*
* @throws IOException If an error occurs deleting the dictionary. This
* exception must not be thrown if no dictionary with the given
* identifier exists.
*/
void delete( String pid ) throws IOException;
}

View File

@ -0,0 +1,859 @@
/*
* 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.cm.file;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PushbackReader;
import java.io.Writer;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* The <code>ConfigurationHandler</code> class implements configuration reading
* form a <code>java.io.InputStream</code> and writing to a
* <code>java.io.OutputStream</code> on behalf of the
* {@link FilePersistenceManager} class.
*
* <pre>
* cfg = prop &quot;=&quot; value .
* prop = symbolic-name . // 1.4.2 of OSGi Core Specification
* symbolic-name = token { &quot;.&quot; token } .
* token = { [ 0..9 ] | [ a..z ] | [ A..Z ] | '_' | '-' } .
* value = [ type ] ( &quot;[&quot; values &quot;]&quot; | &quot;(&quot; values &quot;)&quot; | simple ) .
* values = simple { &quot;,&quot; simple } .
* simple = &quot;&quot;&quot; stringsimple &quot;&quot;&quot; .
* type = // 1-char type code .
* stringsimple = // quoted string representation of the value .
* </pre>
*/
public class ConfigurationHandler
{
protected static final String ENCODING = "UTF-8";
protected static final int TOKEN_NAME = 'N';
protected static final int TOKEN_EQ = '=';
protected static final int TOKEN_ARR_OPEN = '[';
protected static final int TOKEN_ARR_CLOS = ']';
protected static final int TOKEN_VEC_OPEN = '(';
protected static final int TOKEN_VEC_CLOS = ')';
protected static final int TOKEN_COMMA = ',';
protected static final int TOKEN_VAL_OPEN = '"'; // '{';
protected static final int TOKEN_VAL_CLOS = '"'; // '}';
protected static final int TOKEN_COMMENT = '#';
// simple types (string & primitive wrappers)
protected static final int TOKEN_SIMPLE_STRING = 'T';
protected static final int TOKEN_SIMPLE_INTEGER = 'I';
protected static final int TOKEN_SIMPLE_LONG = 'L';
protected static final int TOKEN_SIMPLE_FLOAT = 'F';
protected static final int TOKEN_SIMPLE_DOUBLE = 'D';
protected static final int TOKEN_SIMPLE_BYTE = 'X';
protected static final int TOKEN_SIMPLE_SHORT = 'S';
protected static final int TOKEN_SIMPLE_CHARACTER = 'C';
protected static final int TOKEN_SIMPLE_BOOLEAN = 'B';
// primitives
protected static final int TOKEN_PRIMITIVE_INT = 'i';
protected static final int TOKEN_PRIMITIVE_LONG = 'l';
protected static final int TOKEN_PRIMITIVE_FLOAT = 'f';
protected static final int TOKEN_PRIMITIVE_DOUBLE = 'd';
protected static final int TOKEN_PRIMITIVE_BYTE = 'x';
protected static final int TOKEN_PRIMITIVE_SHORT = 's';
protected static final int TOKEN_PRIMITIVE_CHAR = 'c';
protected static final int TOKEN_PRIMITIVE_BOOLEAN = 'b';
protected static final String CRLF = "\r\n";
protected static final String INDENT = " ";
protected static final String COLLECTION_LINE_BREAK = " \\\r\n";
protected static final Map<Integer, Class<?>> code2Type;
protected static final Map<Class<?>, Integer> type2Code;
// set of valid characters for "symblic-name"
private static final BitSet NAME_CHARS;
private static final BitSet TOKEN_CHARS;
static
{
type2Code = new HashMap<Class<?>, Integer>();
// simple (exclusive String whose type code is not written)
type2Code.put( Integer.class, new Integer( TOKEN_SIMPLE_INTEGER ) );
type2Code.put( Long.class, new Integer( TOKEN_SIMPLE_LONG ) );
type2Code.put( Float.class, new Integer( TOKEN_SIMPLE_FLOAT ) );
type2Code.put( Double.class, new Integer( TOKEN_SIMPLE_DOUBLE ) );
type2Code.put( Byte.class, new Integer( TOKEN_SIMPLE_BYTE ) );
type2Code.put( Short.class, new Integer( TOKEN_SIMPLE_SHORT ) );
type2Code.put( Character.class, new Integer( TOKEN_SIMPLE_CHARACTER ) );
type2Code.put( Boolean.class, new Integer( TOKEN_SIMPLE_BOOLEAN ) );
// primitives
type2Code.put( Integer.TYPE, new Integer( TOKEN_PRIMITIVE_INT ) );
type2Code.put( Long.TYPE, new Integer( TOKEN_PRIMITIVE_LONG ) );
type2Code.put( Float.TYPE, new Integer( TOKEN_PRIMITIVE_FLOAT ) );
type2Code.put( Double.TYPE, new Integer( TOKEN_PRIMITIVE_DOUBLE ) );
type2Code.put( Byte.TYPE, new Integer( TOKEN_PRIMITIVE_BYTE ) );
type2Code.put( Short.TYPE, new Integer( TOKEN_PRIMITIVE_SHORT ) );
type2Code.put( Character.TYPE, new Integer( TOKEN_PRIMITIVE_CHAR ) );
type2Code.put( Boolean.TYPE, new Integer( TOKEN_PRIMITIVE_BOOLEAN ) );
// reverse map to map type codes to classes, string class mapping
// to be added manually, as the string type code is not written and
// hence not included in the type2Code map
code2Type = new HashMap<Integer, Class<?>>();
for(final Map.Entry<Class<?>, Integer> entry : type2Code.entrySet())
{
code2Type.put( entry.getValue(), entry.getKey() );
}
code2Type.put( new Integer( TOKEN_SIMPLE_STRING ), String.class );
NAME_CHARS = new BitSet();
for ( int i = '0'; i <= '9'; i++ )
NAME_CHARS.set( i );
for ( int i = 'a'; i <= 'z'; i++ )
NAME_CHARS.set( i );
for ( int i = 'A'; i <= 'Z'; i++ )
NAME_CHARS.set( i );
NAME_CHARS.set( '_' );
NAME_CHARS.set( '-' );
NAME_CHARS.set( '.' );
NAME_CHARS.set( '\\' );
TOKEN_CHARS = new BitSet();
TOKEN_CHARS.set( TOKEN_EQ );
TOKEN_CHARS.set( TOKEN_ARR_OPEN );
TOKEN_CHARS.set( TOKEN_ARR_CLOS );
TOKEN_CHARS.set( TOKEN_VEC_OPEN );
TOKEN_CHARS.set( TOKEN_VEC_CLOS );
TOKEN_CHARS.set( TOKEN_COMMA );
TOKEN_CHARS.set( TOKEN_VAL_OPEN );
TOKEN_CHARS.set( TOKEN_VAL_CLOS );
TOKEN_CHARS.set( TOKEN_SIMPLE_STRING );
TOKEN_CHARS.set( TOKEN_SIMPLE_INTEGER );
TOKEN_CHARS.set( TOKEN_SIMPLE_LONG );
TOKEN_CHARS.set( TOKEN_SIMPLE_FLOAT );
TOKEN_CHARS.set( TOKEN_SIMPLE_DOUBLE );
TOKEN_CHARS.set( TOKEN_SIMPLE_BYTE );
TOKEN_CHARS.set( TOKEN_SIMPLE_SHORT );
TOKEN_CHARS.set( TOKEN_SIMPLE_CHARACTER );
TOKEN_CHARS.set( TOKEN_SIMPLE_BOOLEAN );
// primitives
TOKEN_CHARS.set( TOKEN_PRIMITIVE_INT );
TOKEN_CHARS.set( TOKEN_PRIMITIVE_LONG );
TOKEN_CHARS.set( TOKEN_PRIMITIVE_FLOAT );
TOKEN_CHARS.set( TOKEN_PRIMITIVE_DOUBLE );
TOKEN_CHARS.set( TOKEN_PRIMITIVE_BYTE );
TOKEN_CHARS.set( TOKEN_PRIMITIVE_SHORT );
TOKEN_CHARS.set( TOKEN_PRIMITIVE_CHAR );
TOKEN_CHARS.set( TOKEN_PRIMITIVE_BOOLEAN );
}
/**
* Writes the configuration data from the <code>Dictionary</code> to the
* given <code>OutputStream</code>.
* <p>
* This method writes at the current location in the stream and does not
* close the output stream.
*
* @param out
* The <code>OutputStream</code> to write the configuration data
* to.
* @param properties
* The <code>Dictionary</code> to write.
* @throws IOException
* If an error occurs writing to the output stream.
*/
@SuppressWarnings("rawtypes")
public static void write( OutputStream out, Dictionary properties ) throws IOException
{
BufferedWriter bw = new BufferedWriter( new OutputStreamWriter( out, ENCODING ) );
for ( Enumeration ce = orderedKeys(properties); ce.hasMoreElements(); )
{
String key = ( String ) ce.nextElement();
// cfg = prop "=" value "." .
writeQuoted( bw, key );
bw.write( TOKEN_EQ );
writeValue( bw, properties.get( key ) );
bw.write( CRLF );
}
bw.flush();
}
/**
* Generates an <code>Enumeration</code> for the given
* <code>Dictionary</code> where the keys of the <code>Dictionary</code>
* are provided in sorted order.
*
* @param properties
* The <code>Dictionary</code> that keys are sorted.
* @return An <code>Enumeration</code> that provides the keys of
* properties in an ordered manner.
*/
@SuppressWarnings("rawtypes")
private static Enumeration orderedKeys(Dictionary properties) {
String[] keyArray = new String[properties.size()];
int i = 0;
for ( Enumeration ce = properties.keys(); ce.hasMoreElements(); )
{
keyArray[i] = ( String ) ce.nextElement();
i++;
}
Arrays.sort(keyArray);
return Collections.enumeration( Arrays.asList( keyArray ) );
}
/**
* Reads configuration data from the given <code>InputStream</code> and
* returns a new <code>Dictionary</code> object containing the data.
* <p>
* This method reads from the current location in the stream up to the end of
* the stream but does not close the stream at the end.
*
* @param ins
* The <code>InputStream</code> from which to read the
* configuration data.
* @return A <code>Dictionary</code> object containing the configuration
* data. This object may be empty if the stream contains no
* configuration data.
* @throws IOException
* If an error occurs reading from the stream. This exception
* is also thrown if a syntax error is encountered.
*/
@SuppressWarnings("rawtypes")
public static Dictionary read( InputStream ins ) throws IOException
{
return new ConfigurationHandler().readInternal( ins );
}
// private constructor, this class is not to be instantiated from the
// outside
private ConfigurationHandler()
{
}
// ---------- Configuration Input Implementation ---------------------------
private int token;
private String tokenValue;
private int line;
private int pos;
private Dictionary<String, ?> readInternal( InputStream ins ) throws IOException
{
BufferedReader br = new BufferedReader( new InputStreamReader( ins, ENCODING ) );
PushbackReader pr = new PushbackReader( br, 1 );
token = 0;
tokenValue = null;
line = 0;
pos = 0;
Dictionary<String, Object> configuration = new Hashtable<String, Object>();
token = 0;
while ( nextToken( pr, true ) == TOKEN_NAME )
{
String key = tokenValue;
// expect equal sign
if ( nextToken( pr, false ) != TOKEN_EQ )
{
throw readFailure( token, TOKEN_EQ );
}
// expect the token value
Object value = readValue( pr );
if ( value != null )
{
configuration.put( key, value );
}
}
return configuration;
}
/**
* value = type ( "[" values "]" | "(" values ")" | simple ) . values =
* value { "," value } . simple = "{" stringsimple "}" . type = // 1-char
* type code . stringsimple = // quoted string representation of the value .
*
* @param pr
* @return
* @throws IOException
*/
private Object readValue( PushbackReader pr ) throws IOException
{
// read (optional) type code
int type = read( pr );
// read value kind code if type code is not a value kinde code
int code;
if ( code2Type.containsKey( new Integer( type ) ) )
{
code = read( pr );
}
else
{
code = type;
type = TOKEN_SIMPLE_STRING;
}
switch ( code )
{
case TOKEN_ARR_OPEN:
return readArray( type, pr );
case TOKEN_VEC_OPEN:
return readCollection( type, pr );
case TOKEN_VAL_OPEN:
Object value = readSimple( type, pr );
ensureNext( pr, TOKEN_VAL_CLOS );
return value;
default:
return null;
}
}
private Object readArray( int typeCode, PushbackReader pr ) throws IOException
{
List<Object> list = new ArrayList<Object>();
for ( ;; )
{
int c = ignorablePageBreakAndWhiteSpace( pr );
if ( c == TOKEN_VAL_OPEN )
{
Object value = readSimple( typeCode, pr );
if ( value == null )
{
// abort due to error
return null;
}
ensureNext( pr, TOKEN_VAL_CLOS );
list.add( value );
c = ignorablePageBreakAndWhiteSpace( pr );
}
if ( c == TOKEN_ARR_CLOS )
{
Class<?> type = code2Type.get( new Integer( typeCode ) );
Object array = Array.newInstance( type, list.size() );
for ( int i = 0; i < list.size(); i++ )
{
Array.set( array, i, list.get( i ) );
}
return array;
}
else if ( c < 0 )
{
return null;
}
else if ( c != TOKEN_COMMA )
{
return null;
}
}
}
private Collection<Object> readCollection( int typeCode, PushbackReader pr ) throws IOException
{
Collection<Object> collection = new ArrayList<Object>();
for ( ;; )
{
int c = ignorablePageBreakAndWhiteSpace( pr );
if ( c == TOKEN_VAL_OPEN )
{
Object value = readSimple( typeCode, pr );
if ( value == null )
{
// abort due to error
return null;
}
ensureNext( pr, TOKEN_VAL_CLOS );
collection.add( value );
c = ignorablePageBreakAndWhiteSpace( pr );
}
if ( c == TOKEN_VEC_CLOS )
{
return collection;
}
else if ( c < 0 )
{
return null;
}
else if ( c != TOKEN_COMMA )
{
return null;
}
}
}
private Object readSimple( int code, PushbackReader pr ) throws IOException
{
switch ( code )
{
case -1:
return null;
case TOKEN_SIMPLE_STRING:
return readQuoted( pr );
// Simple/Primitive, only use wrapper classes
case TOKEN_SIMPLE_INTEGER:
case TOKEN_PRIMITIVE_INT:
return Integer.valueOf( readQuoted( pr ) );
case TOKEN_SIMPLE_LONG:
case TOKEN_PRIMITIVE_LONG:
return Long.valueOf( readQuoted( pr ) );
case TOKEN_SIMPLE_FLOAT:
case TOKEN_PRIMITIVE_FLOAT:
int fBits = Integer.parseInt( readQuoted( pr ) );
return new Float( Float.intBitsToFloat( fBits ) );
case TOKEN_SIMPLE_DOUBLE:
case TOKEN_PRIMITIVE_DOUBLE:
long dBits = Long.parseLong( readQuoted( pr ) );
return new Double( Double.longBitsToDouble( dBits ) );
case TOKEN_SIMPLE_BYTE:
case TOKEN_PRIMITIVE_BYTE:
return Byte.valueOf( readQuoted( pr ) );
case TOKEN_SIMPLE_SHORT:
case TOKEN_PRIMITIVE_SHORT:
return Short.valueOf( readQuoted( pr ) );
case TOKEN_SIMPLE_CHARACTER:
case TOKEN_PRIMITIVE_CHAR:
String cString = readQuoted( pr );
if ( cString != null && cString.length() > 0 )
{
return new Character( cString.charAt( 0 ) );
}
return null;
case TOKEN_SIMPLE_BOOLEAN:
case TOKEN_PRIMITIVE_BOOLEAN:
return Boolean.valueOf( readQuoted( pr ) );
// unknown type code
default:
return null;
}
}
private void ensureNext( PushbackReader pr, int expected ) throws IOException
{
int next = read( pr );
if ( next != expected )
{
readFailure( next, expected );
}
}
private String readQuoted( PushbackReader pr ) throws IOException
{
StringBuilder buf = new StringBuilder();
for ( ;; )
{
int c = read( pr );
switch ( c )
{
// escaped character
case '\\':
c = read( pr );
switch ( c )
{
// well known escapes
case 'b':
buf.append( '\b' );
break;
case 't':
buf.append( '\t' );
break;
case 'n':
buf.append( '\n' );
break;
case 'f':
buf.append( '\f' );
break;
case 'r':
buf.append( '\r' );
break;
case 'u':// need 4 characters !
char[] cbuf = new char[4];
if ( read( pr, cbuf ) == 4 )
{
c = Integer.parseInt( new String( cbuf ), 16 );
buf.append( ( char ) c );
}
break;
// just an escaped character, unescape
default:
buf.append( ( char ) c );
}
break;
// eof
case -1: // fall through
// separator token
case TOKEN_EQ:
case TOKEN_VAL_CLOS:
pr.unread( c );
return buf.toString();
// no escaping
default:
buf.append( ( char ) c );
}
}
}
private int nextToken( PushbackReader pr, final boolean newLine ) throws IOException
{
int c = ignorableWhiteSpace( pr );
// immediately return EOF
if ( c < 0 )
{
return ( token = c );
}
// check for comment
if ( newLine && c == TOKEN_COMMENT )
{
// skip everything until end of line
do
{
c = read( pr );
} while ( c != -1 && c != '\n' );
if ( c == -1 )
{
return ( token = c);
}
// and start over
return nextToken( pr, true );
}
// check whether there is a name
if ( NAME_CHARS.get( c ) || !TOKEN_CHARS.get( c ) )
{
// read the property name
pr.unread( c );
tokenValue = readQuoted( pr );
return ( token = TOKEN_NAME );
}
// check another token
if ( TOKEN_CHARS.get( c ) )
{
return ( token = c );
}
// unexpected character -> so what ??
return ( token = -1 );
}
private int ignorableWhiteSpace( PushbackReader pr ) throws IOException
{
int c = read( pr );
while ( c >= 0 && Character.isWhitespace( ( char ) c ) )
{
c = read( pr );
}
return c;
}
private int ignorablePageBreakAndWhiteSpace( PushbackReader pr ) throws IOException
{
int c = ignorableWhiteSpace( pr );
for ( ;; )
{
if ( c != '\\' )
{
break;
}
int c1 = pr.read();
if ( c1 == '\r' || c1 == '\n' )
{
c = ignorableWhiteSpace( pr );
} else {
pr.unread(c1);
break;
}
}
return c;
}
private int read( PushbackReader pr ) throws IOException
{
int c = pr.read();
if ( c == '\r' )
{
int c1 = pr.read();
if ( c1 != '\n' )
{
pr.unread( c1 );
}
c = '\n';
}
if ( c == '\n' )
{
line++;
pos = 0;
}
else
{
pos++;
}
return c;
}
private int read( PushbackReader pr, char[] buf ) throws IOException
{
for ( int i = 0; i < buf.length; i++ )
{
int c = read( pr );
if ( c >= 0 )
{
buf[i] = ( char ) c;
}
else
{
return i;
}
}
return buf.length;
}
private IOException readFailure( int current, int expected )
{
return new IOException( "Unexpected token " + current + "; expected: " + expected + " (line=" + line + ", pos="
+ pos + ")" );
}
// ---------- Configuration Output Implementation --------------------------
private static void writeValue( Writer out, Object value ) throws IOException
{
Class<?> clazz = value.getClass();
if ( clazz.isArray() )
{
writeArray( out, value );
}
else if ( value instanceof Collection )
{
writeCollection( out, ( Collection<?> ) value );
}
else
{
writeType( out, clazz );
writeSimple( out, value );
}
}
private static void writeArray( Writer out, Object arrayValue ) throws IOException
{
int size = Array.getLength( arrayValue );
writeType( out, arrayValue.getClass().getComponentType() );
out.write( TOKEN_ARR_OPEN );
out.write( COLLECTION_LINE_BREAK );
for ( int i = 0; i < size; i++ )
{
writeCollectionElement(out, Array.get( arrayValue, i ));
}
out.write( INDENT );
out.write( TOKEN_ARR_CLOS );
}
private static void writeCollection( Writer out, Collection<?> collection ) throws IOException
{
if ( collection.isEmpty() )
{
out.write( TOKEN_VEC_OPEN );
out.write( COLLECTION_LINE_BREAK );
out.write( TOKEN_VEC_CLOS );
}
else
{
Iterator<?> ci = collection.iterator();
Object firstElement = ci.next();
writeType( out, firstElement.getClass() );
out.write( TOKEN_VEC_OPEN );
out.write( COLLECTION_LINE_BREAK );
writeCollectionElement( out, firstElement );
while ( ci.hasNext() )
{
writeCollectionElement( out, ci.next() );
}
out.write( TOKEN_VEC_CLOS );
}
}
private static void writeCollectionElement(Writer out, Object element) throws IOException {
out.write( INDENT );
writeSimple( out, element );
out.write( TOKEN_COMMA );
out.write(COLLECTION_LINE_BREAK);
}
private static void writeType( Writer out, Class<?> valueType ) throws IOException
{
Integer code = type2Code.get( valueType );
if ( code != null )
{
out.write( ( char ) code.intValue() );
}
}
private static void writeSimple( Writer out, Object value ) throws IOException
{
if ( value instanceof Double )
{
double dVal = ( ( Double ) value ).doubleValue();
value = new Long( Double.doubleToRawLongBits( dVal ) );
}
else if ( value instanceof Float )
{
float fVal = ( ( Float ) value ).floatValue();
value = new Integer( Float.floatToRawIntBits( fVal ) );
}
out.write( TOKEN_VAL_OPEN );
writeQuoted( out, String.valueOf( value ) );
out.write( TOKEN_VAL_CLOS );
}
private static void writeQuoted( Writer out, String simple ) throws IOException
{
if ( simple == null || simple.length() == 0 )
{
return;
}
char c = 0;
int len = simple.length();
for ( int i = 0; i < len; i++ )
{
c = simple.charAt( i );
switch ( c )
{
case '\\':
case TOKEN_VAL_CLOS:
case ' ':
case TOKEN_EQ:
out.write( '\\' );
out.write( c );
break;
// well known escapes
case '\b':
out.write( "\\b" );
break;
case '\t':
out.write( "\\t" );
break;
case '\n':
out.write( "\\n" );
break;
case '\f':
out.write( "\\f" );
break;
case '\r':
out.write( "\\r" );
break;
// other escaping
default:
if ( c < ' ' )
{
String t = "000" + Integer.toHexString( c );
out.write( "\\u" + t.substring( t.length() - 4 ) );
}
else
{
out.write( c );
}
}
}
}
}

View File

@ -0,0 +1,927 @@
/*
* 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.cm.file;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.BitSet;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.NoSuchElementException;
import java.util.Stack;
import java.util.StringTokenizer;
import org.apache.felix.cm.PersistenceManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
/**
* The <code>FilePersistenceManager</code> class stores configuration data in
* properties-like files inside a given directory. All configuration files are
* located in the same directory.
* <p>
* The configuration directory is set by either the
* {@link #FilePersistenceManager(String)} constructor or the
* {@link #FilePersistenceManager(BundleContext, String)} constructor. Refer to
* the respective JavaDocs for more information.
* <p>
* When this persistence manager is used by the Configuration Admin Service, the
* location may be configured using the
* {@link org.apache.felix.cm.impl.ConfigurationManager} bundle context
* property.
* <p>
* If the location is not set, the <code>config</code> directory in the current
* working directory (as set in the <code>user.dir</code> system property) is
* used. If the the location is set but, no such directory exists, the directory
* and any missing parent directories are created. If a file exists at the given
* location, the constructor fails.
* <p>
* Configuration files are created in the configuration directory by appending
* the extension <code>.config</code> to the PID of the configuration. The PID
* is converted into a relative path name by replacing enclosed dots to slashes.
* Non-<code>symbolic-name</code> characters in the PID are encoded with their
* Unicode character code in hexadecimal.
*
* <table border="0" cellspacing="3" cellpadding="0" summary="Examples">
* <tr>
* <td colspan="2"><b>Examples of PID to name conversion:</b></td>
* </tr>
* <tr>
* <th>PID</th>
* <th>Configuration File Name</th>
* </tr>
* <tr>
* <td><code>sample</code>
* <td><code>sample.config</code>
* </tr>
* <tr>
* <td><code>org.apache.felix.log.LogService</code>
* <td><code>org/apache/felix/log/LogService.config</code>
* </tr>
* <tr>
* <td><code>sample.fl&auml;che</code>
* <td><code>sample/fl%00e8che.config</code>
* </tr>
* </table>
* <p>
* <b>Mulithreading Issues</b>
* <p>
* In a multithreaded environment the {@link #store(String, Dictionary)} and
* {@link #load(String)} methods may be called at the the quasi-same time for
* the same configuration PID. It may no happen, that the store method starts
* writing the file and the load method might at the same time read from the
* file currently being written and thus loading corrupt data (if data is
* available at all).
* <p>
* To prevent this situation from happening, the methods use synchronization and
* temporary files as follows:
* <ul>
* <li>The {@link #store(String, Dictionary)} method writes a temporary file
* with file extension <code>.tmp</code>. When done, the file is renamed to
* actual configuration file name as implied by the PID. This last step of
* renaming the file is synchronized on the FilePersistenceManager
* instance.</li>
* <li>The {@link #load(String)} method is completeley synchronized on the
* FilePersistenceManager instance such that the {@link #store} method might
* inadvertantly try to replace the file while it is being read.</li>
* <li>Finally the <code>Iterator</code> returned by {@link #getDictionaries()}
* is implemented such that any temporary configuration file is just
* ignored.</li>
* </ul>
*/
public class FilePersistenceManager implements PersistenceManager
{
/**
* The default configuration data directory if no location is configured
* (value is "config").
*/
public static final String DEFAULT_CONFIG_DIR = "config";
/**
* The name of this persistence manager when registered in the service registry.
* (value is "file").
*/
public static final String DEFAULT_PERSISTENCE_MANAGER_NAME = "file";
/**
* The extension of the configuration files.
*/
private static final String FILE_EXT = ".config";
/**
* The extension of the configuration files, while they are being written
* (value is ".tmp").
*
* @see #store(String, Dictionary)
*/
private static final String TMP_EXT = ".tmp";
private static final BitSet VALID_PATH_CHARS;
/**
* The access control context we use in the presence of a security manager.
*/
private final AccessControlContext acc;
/**
* The abstract path name of the configuration files.
*/
private final File location;
/**
* Flag indicating whether this instance is running on a Windows
* platform or not.
*/
private final boolean isWin;
// sets up this class defining the set of valid characters in path
// set getFile(String) for details.
static
{
VALID_PATH_CHARS = new BitSet();
for ( int i = 'a'; i <= 'z'; i++ )
{
VALID_PATH_CHARS.set( i );
}
for ( int i = 'A'; i <= 'Z'; i++ )
{
VALID_PATH_CHARS.set( i );
}
for ( int i = '0'; i <= '9'; i++ )
{
VALID_PATH_CHARS.set( i );
}
VALID_PATH_CHARS.set( File.separatorChar );
VALID_PATH_CHARS.set( ' ' );
VALID_PATH_CHARS.set( '-' );
VALID_PATH_CHARS.set( '_' );
}
{
// Windows Specific Preparation (FELIX-4302)
// According to the GetJavaProperties method in
// jdk/src/windows/native/java/lang/java_props_md.c the os.name
// system property on all Windows platforms start with the string
// "Windows" hence we assume a Windows platform in thus case.
final String osName = System.getProperty( "os.name" );
isWin = osName != null && osName.startsWith( "Windows" );
}
private static boolean equalsNameWithPrefixPlusOneDigit( String name, String prefix) {
if ( name.length() != prefix.length() + 1 ) {
return false;
}
if ( !name.startsWith(prefix) ) {
return false;
}
char charAfterPrefix = name.charAt( prefix.length() );
return charAfterPrefix > '0' && charAfterPrefix < '9';
}
private static boolean isWinReservedName(String name) {
String upperCaseName = name.toUpperCase();
if ( "CON".equals( upperCaseName ) ) {
return true;
} else if ( "PRN".equals( upperCaseName ) ){
return true;
} else if ( "AUX".equals( upperCaseName ) ){
return true;
} else if ( "CLOCK$".equals( upperCaseName ) ){
return true;
} else if ( "NUL".equals( upperCaseName ) ){
return true;
} else if ( equalsNameWithPrefixPlusOneDigit( upperCaseName, "COM") ) {
return true;
} else if ( equalsNameWithPrefixPlusOneDigit( upperCaseName, "LPT") ){
return true;
}
return false;
}
/**
* Creates an instance of this persistence manager using the given location
* as the directory to store and retrieve the configuration files.
* <p>
* This constructor resolves the configuration file location as follows:
* <ul>
* <li>If <code>location</code> is <code>null</code>, the <code>config</code>
* directory in the current working directory as specified in the
* <code>user.dir</code> system property is assumed.</li>
* <li>Otherwise the named directory is used.</li>
* <li>If the directory name resolved in the first or second step is not an
* absolute path, it is resolved to an absolute path calling the
* <code>File.getAbsoluteFile()</code> method.</li>
* <li>If a non-directory file exists as the location found in the previous
* step or the named directory (including any parent directories) cannot be
* created, an <code>IllegalArgumentException</code> is thrown.</li>
* </ul>
* <p>
* This constructor is equivalent to calling
* {@link #FilePersistenceManager(BundleContext, String)} with a
* <code>null</code> <code>BundleContext</code>.
*
* @param location The configuration file location. If this is
* <code>null</code> the <code>config</code> directory below the current
* working directory is used.
*
* @throws IllegalArgumentException If the <code>location</code> exists but
* is not a directory or does not exist and cannot be created.
*/
public FilePersistenceManager( String location )
{
this( null, location );
}
/**
* Creates an instance of this persistence manager using the given location
* as the directory to store and retrieve the configuration files.
* <p>
* This constructor resolves the configuration file location as follows:
* <ul>
* <li>If <code>location</code> is <code>null</code>, the <code>config</code>
* directory in the persistent storage area of the bundle identified by
* <code>bundleContext</code> is used.</li>
* <li>If the framework does not support persistent storage area for bundles
* in the filesystem or if <code>bundleContext</code> is <code>null</code>,
* the <code>config</code> directory in the current working directory as
* specified in the <code>user.dir</code> system property is assumed.</li>
* <li>Otherwise the named directory is used.</li>
* <li>If the directory name resolved in the first, second or third step is
* not an absolute path and a <code>bundleContext</code> is provided which
* provides access to persistent storage area, the directory name is
* resolved as being inside the persistent storage area. Otherwise the
* directory name is resolved to an absolute path calling the
* <code>File.getAbsoluteFile()</code> method.</li>
* <li>If a non-directory file exists as the location found in the previous
* step or the named directory (including any parent directories) cannot be
* created, an <code>IllegalArgumentException</code> is thrown.</li>
* </ul>
*
* @param bundleContext The <code>BundleContext</code> to optionally get
* the data location for the configuration files. This may be
* <code>null</code>, in which case this constructor acts exactly the
* same as calling {@link #FilePersistenceManager(String)}.
* @param location The configuration file location. If this is
* <code>null</code> the <code>config</code> directory below the current
* working directory is used.
*
* @throws IllegalArgumentException If the location exists but is not a
* directory or does not exist and cannot be created.
* @throws IllegalStateException If the <code>bundleContext</code> is not
* valid.
*/
public FilePersistenceManager( BundleContext bundleContext, String location )
{
// setup the access control context from the calling setup
if ( System.getSecurityManager() != null )
{
acc = AccessController.getContext();
}
else
{
acc = null;
}
// no configured location, use the config dir in the bundle persistent
// area
if ( location == null && bundleContext != null )
{
File locationFile = bundleContext.getDataFile( DEFAULT_CONFIG_DIR );
if ( locationFile != null )
{
location = locationFile.getAbsolutePath();
}
}
// fall back to the current working directory if the platform does
// not support filesystem based data area
if ( location == null )
{
location = System.getProperty( "user.dir" ) + "/config";
}
// ensure the file is absolute
File locationFile = new File( location );
if ( !locationFile.isAbsolute() )
{
if ( bundleContext != null )
{
File bundleLocationFile = bundleContext.getDataFile( locationFile.getPath() );
if ( bundleLocationFile != null )
{
locationFile = bundleLocationFile;
}
}
// ensure the file object is an absolute file object
locationFile = locationFile.getAbsoluteFile();
}
// check the location
if ( !locationFile.isDirectory() )
{
if ( locationFile.exists() )
{
throw new IllegalArgumentException( location + " is not a directory" );
}
if ( !locationFile.mkdirs() )
{
throw new IllegalArgumentException( "Cannot create directory " + location );
}
}
this.location = locationFile;
}
/**
* Encodes a Service PID to a filesystem path as described in the class
* JavaDoc above.
* <p>
* This method is not part of the API of this class and is declared package
* private to enable JUnit testing on it. This method may be removed or
* modified at any time without notice.
*
* @param pid The Service PID to encode into a relative path name.
*
* @return The relative path name corresponding to the Service PID.
*/
String encodePid( String pid )
{
// replace dots by File.separatorChar
pid = pid.replace( '.', File.separatorChar );
// replace slash by File.separatorChar if different
if ( File.separatorChar != '/' )
{
pid = pid.replace( '/', File.separatorChar );
}
// scan for first non-valid character (if any)
int first = 0;
while ( first < pid.length() && VALID_PATH_CHARS.get( pid.charAt( first ) ) )
{
first++;
}
// check whether we exhausted
if ( first < pid.length() )
{
StringBuilder buf = new StringBuilder( pid.substring( 0, first ) );
for ( int i = first; i < pid.length(); i++ )
{
char c = pid.charAt( i );
if ( VALID_PATH_CHARS.get( c ) )
{
buf.append( c );
}
else
{
appendEncoded( buf, c );
}
}
pid = buf.toString();
}
// Prefix special device names on Windows (FELIX-4302)
if ( isWin )
{
final StringTokenizer segments = new StringTokenizer( pid, File.separator, true );
final StringBuilder pidBuffer = new StringBuilder( pid.length() );
while ( segments.hasMoreTokens() )
{
final String segment = segments.nextToken();
if ( isWinReservedName(segment) )
{
appendEncoded( pidBuffer, segment.charAt( 0 ) );
pidBuffer.append( segment.substring( 1 ) );
}
else
{
pidBuffer.append( segment );
}
}
pid = pidBuffer.toString();
}
return pid;
}
private void appendEncoded( StringBuilder buf, final char c )
{
String val = "000" + Integer.toHexString( c );
buf.append( '%' );
buf.append( val.substring( val.length() - 4 ) );
}
/**
* Returns the directory in which the configuration files are written as
* a <code>File</code> object.
*
* @return The configuration file location.
*/
public File getLocation()
{
return location;
}
/**
* Loads configuration data from the configuration location and returns
* it as <code>Dictionary</code> objects.
* <p>
* This method is a lazy implementation, which is just one configuration
* file ahead of the current enumeration location.
*
* @return an enumeration of configuration data returned as instances of
* the <code>Dictionary</code> class.
*/
@SuppressWarnings("rawtypes")
@Override
public Enumeration getDictionaries()
{
return new DictionaryEnumeration();
}
/**
* Deletes the file for the given identifier.
*
* @param pid The identifier of the configuration file to delete.
*/
@Override
public void delete( final String pid )
{
if ( System.getSecurityManager() != null )
{
_privilegedDelete( pid );
}
else
{
_delete( pid );
}
}
private void _privilegedDelete( final String pid )
{
AccessController.doPrivileged( new PrivilegedAction<Object>()
{
@Override
public Object run()
{
_delete( pid );
return null;
}
}, acc );
}
private void _delete( final String pid )
{
synchronized ( this )
{
getFile( pid ).delete();
}
}
/**
* Returns <code>true</code> if a (configuration) file exists for the given
* identifier.
*
* @param pid The identifier of the configuration file to check.
*
* @return <code>true</code> if the file exists
*/
@Override
public boolean exists( final String pid )
{
if ( System.getSecurityManager() != null )
{
return _privilegedExists( pid );
}
return _exists( pid );
}
private boolean _privilegedExists( final String pid )
{
final Object result = AccessController.doPrivileged( new PrivilegedAction<Boolean>()
{
@Override
public Boolean run()
{
// FELIX-2771: Boolean.valueOf(boolean) is not in Foundation
return _exists( pid ) ? Boolean.TRUE : Boolean.FALSE;
}
} );
return ( ( Boolean ) result ).booleanValue();
}
private boolean _exists( final String pid )
{
synchronized ( this )
{
return getFile( pid ).isFile();
}
}
/**
* Reads the (configuration) for the given identifier into a
* <code>Dictionary</code> object.
*
* @param pid The identifier of the configuration file to delete.
*
* @return The configuration read from the file. This <code>Dictionary</code>
* may be empty if the file contains no configuration information
* or is not properly formatted.
*/
@SuppressWarnings("rawtypes")
@Override
public Dictionary load( String pid ) throws IOException
{
final File cfgFile = getFile( pid );
if ( System.getSecurityManager() != null )
{
return _privilegedLoad( cfgFile );
}
return _load( cfgFile );
}
@SuppressWarnings("rawtypes")
private Dictionary _privilegedLoad( final File cfgFile ) throws IOException
{
try
{
Dictionary result = AccessController.doPrivileged( new PrivilegedExceptionAction<Dictionary>()
{
@Override
public Dictionary run() throws IOException
{
return _load( cfgFile );
}
} );
return result;
}
catch ( PrivilegedActionException pae )
{
// FELIX-2771: getCause() is not available in Foundation
throw ( IOException ) pae.getException();
}
}
/**
* Loads the contents of the <code>cfgFile</code> into a new
* <code>Dictionary</code> object.
*
* @param cfgFile
* The file from which to load the data.
* @return A new <code>Dictionary</code> object providing the file contents.
* @throws java.io.FileNotFoundException
* If the given file does not exist.
* @throws IOException
* If an error occurrs reading the configuration file.
*/
@SuppressWarnings("rawtypes")
Dictionary _load( File cfgFile ) throws IOException
{
// this method is not part of the API of this class but is made
// package private to prevent the creation of a synthetic method
// for use by the DictionaryEnumeration._seek method
// synchronize this instance to make at least sure, the file is
// not at the same time accessed by another thread (see store())
// we have to synchronize the complete load time as the store
// method might want to replace the file while we are reading and
// still have the file open. This might be a problem e.g. in Windows
// environments, where files may not be removed which are still open
synchronized ( this )
{
InputStream ins = null;
try
{
ins = new FileInputStream( cfgFile );
return ConfigurationHandler.read( ins );
}
finally
{
if ( ins != null )
{
try
{
ins.close();
}
catch ( IOException ioe )
{
// ignore
}
}
}
}
}
/**
* Stores the contents of the <code>Dictionary</code> in a file denoted
* by the given identifier.
*
* @param pid The identifier of the configuration file to which to write
* the configuration contents.
* @param props The configuration data to write.
*
* @throws IOException If an error occurrs writing the configuration data.
*/
@SuppressWarnings("rawtypes")
@Override
public void store( final String pid, final Dictionary props ) throws IOException
{
if ( System.getSecurityManager() != null )
{
_privilegedStore( pid, props );
}
else
{
_store( pid, props );
}
}
@SuppressWarnings("rawtypes")
private void _privilegedStore( final String pid, final Dictionary props ) throws IOException
{
try
{
AccessController.doPrivileged( new PrivilegedExceptionAction<Object>()
{
@Override
public Object run() throws IOException
{
_store( pid, props );
return null;
}
} );
}
catch ( PrivilegedActionException pae )
{
// FELIX-2771: getCause() is not available in Foundation
throw ( IOException ) pae.getException();
}
}
@SuppressWarnings("rawtypes")
private void _store( final String pid, final Dictionary props ) throws IOException
{
OutputStream out = null;
File tmpFile = null;
try
{
File cfgFile = getFile( pid );
// ensure parent path
File cfgDir = cfgFile.getParentFile();
cfgDir.mkdirs();
// write the configuration to a temporary file
tmpFile = File.createTempFile( cfgFile.getName(), TMP_EXT, cfgDir );
out = new FileOutputStream( tmpFile );
ConfigurationHandler.write( out, props );
out.close();
// after writing the file, rename it but ensure, that no other
// might at the same time open the new file
// see load(File)
synchronized ( this )
{
// make sure the cfg file does not exists (just for sanity)
if ( cfgFile.exists() )
{
// FELIX-4165: detect failure to delete old file
if ( !cfgFile.delete() )
{
throw new IOException( "Cannot remove old file '" + cfgFile + "'; changes in '" + tmpFile
+ "' cannot be persisted at this time" );
}
}
// rename the temporary file to the new file
if ( !tmpFile.renameTo( cfgFile ) )
{
throw new IOException( "Failed to rename configuration file from '" + tmpFile + "' to '" + cfgFile );
}
}
}
finally
{
if ( out != null )
{
try
{
out.close();
}
catch ( IOException ioe )
{
// ignore
}
}
if (tmpFile != null && tmpFile.exists())
{
tmpFile.delete();
}
}
}
/**
* Creates an abstract path name for the <code>pid</code> encoding it as
* follows:
* <ul>
* <li>Dots (<code>.</code>) are replaced by <code>File.separatorChar</code>
* <li>Characters not matching [a-zA-Z0-9 _-] are encoded with a percent
* character (<code>%</code>) and a 4-place hexadecimal unicode value.
* </ul>
* Before returning the path name, the parent directory and any ancestors
* are created.
*
* @param pid The identifier for which to create the abstract file name.
*
* @return The abstract path name, which the parent directory path created.
*/
File getFile( String pid )
{
// this method is not part of the API of this class but is made
// package private to prevent the creation of a synthetic method
// for use by the DictionaryEnumeration._seek method
return new File( location, encodePid( pid ) + FILE_EXT );
}
/**
* The <code>DictionaryEnumeration</code> class implements the
* <code>Enumeration</code> returning configuration <code>Dictionary</code>
* objects on behalf of the {@link FilePersistenceManager#getDictionaries()}
* method.
* <p>
* This enumeration loads configuration lazily with a look ahead of one
* dictionary.
*/
@SuppressWarnings("rawtypes")
class DictionaryEnumeration implements Enumeration
{
private Stack<File> dirStack;
private File[] fileList;
private int idx;
private Dictionary next;
DictionaryEnumeration()
{
dirStack = new Stack<>();
fileList = null;
idx = 0;
dirStack.push( getLocation() );
next = seek();
}
@Override
public boolean hasMoreElements()
{
return next != null;
}
@Override
public Object nextElement()
{
if ( next == null )
{
throw new NoSuchElementException();
}
Dictionary toReturn = next;
next = seek();
return toReturn;
}
private Dictionary seek()
{
if ( System.getSecurityManager() != null )
{
return _privilegedSeek();
}
return _seek();
}
protected Dictionary _privilegedSeek()
{
Dictionary result = AccessController.doPrivileged( new PrivilegedAction<Dictionary>()
{
@Override
public Dictionary run()
{
return _seek();
}
} );
return result;
}
protected Dictionary _seek()
{
while ( ( fileList != null && idx < fileList.length ) || !dirStack.isEmpty() )
{
if ( fileList == null || idx >= fileList.length )
{
File dir = dirStack.pop();
fileList = dir.listFiles();
idx = 0;
}
else
{
File cfgFile = fileList[idx++];
if ( cfgFile.isFile() && !cfgFile.getName().endsWith( TMP_EXT ))
{
try
{
Dictionary dict = _load( cfgFile );
// use the dictionary if it has no PID or the PID
// derived file name matches the source file name
if ( dict.get( Constants.SERVICE_PID ) == null
|| cfgFile.equals( getFile( ( String ) dict.get( Constants.SERVICE_PID ) ) ) )
{
return dict;
}
}
catch ( IOException ioe )
{
// ignore, check next file
}
}
else if ( cfgFile.isDirectory() )
{
dirStack.push( cfgFile );
}
}
}
// exhausted
return null;
}
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.
*/
@org.osgi.annotation.versioning.Version("1.1.0")
package org.apache.felix.cm.file;

View File

@ -0,0 +1,247 @@
/*
* 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.cm.impl;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.file.FilePersistenceManager;
import org.apache.felix.cm.impl.persistence.MemoryPersistenceManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleActivator;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.log.LogService;
/**
* Activator for the configuration admin implementation.
* When the bundle is started this activator:
* <ul>
* <li>Sets up the logger {@link Log}.
* <li>A {@link FilePersistenceManager} instance is registered as a default
* {@link PersistenceManager}.
* <li>Creates and sets up the {@link ConfigurationManager}.
* </ul>
* <p>
* The default {@link FilePersistenceManager} is configured with a configuration
* location taken from the <code>felix.cm.dir</code> framework property. If
* this property is not set the <code>config</code> directory in the current
* working directory as specified in the <code>user.dir</code> system property
* is used.
*/
public class Activator implements BundleActivator
{
/**
* The name of the framework context property defining the location for the
* configuration files (value is "felix.cm.dir").
*
* @see #start(BundleContext)
*/
private static final String CM_CONFIG_DIR = "felix.cm.dir";
/**
* The name of the framework context property defining the persistence
* manager to be used. If this property is not set or empty, the built-in
* persistence manager (named file) is used. If it is specified it refers
* to the name property of a persistence manager and that persistence manager
* needs to be registered.
*
* @see #start(BundleContext)
*/
private static final String CM_CONFIG_PM = "felix.cm.pm";
/**
* The name of the framework context property defining the required
* configuration plugins. If this property is specified it refers to the
* {@link RequiredConfigurationPluginTracker#PROPERTY_NAME} property of a
* configuration plugin and that configuration plugin must be registered and
* available.
*
* @see #start(BundleContext)
*/
public static final String CM_CONFIG_PLUGINS = "felix.cm.config.plugins";
private volatile DependencyTracker tracker;
// the service registration of the default file persistence manager
private volatile ServiceRegistration<PersistenceManager> filepmRegistration;
// the service registration of the memory persistence manager
private volatile ServiceRegistration<PersistenceManager> memorypmRegistration;
@Override
public void start( final BundleContext bundleContext ) throws BundleException
{
// setup log
Log.logger.start(bundleContext);
// register default file persistence manager
final ServiceFactory<PersistenceManager> defaultFactory = this.registerFilePersistenceManager(bundleContext);
// register memory persistence manager
registerMemoryPersistenceManager(bundleContext);
try
{
this.tracker = new DependencyTracker(bundleContext, defaultFactory,
getConfiguredPersistenceManager(bundleContext),
getConfiguredConfigurationPlugins(bundleContext));
}
catch ( InvalidSyntaxException iae )
{
Log.logger.log( LogService.LOG_ERROR, "Cannot create the persistence manager tracker", iae );
throw new BundleException(iae.getMessage(), iae);
}
}
private String getConfiguredPersistenceManager(final BundleContext bundleContext) {
String configuredPM = bundleContext.getProperty(CM_CONFIG_PM);
if (configuredPM != null && (configuredPM.isEmpty()
|| FilePersistenceManager.DEFAULT_PERSISTENCE_MANAGER_NAME.equals(configuredPM))) {
configuredPM = null;
}
return configuredPM;
}
private String[] getConfiguredConfigurationPlugins(final BundleContext bundleContext) {
String[] configuredPlugins = null;
String configuredPls = bundleContext.getProperty(CM_CONFIG_PLUGINS);
if (configuredPls != null) {
final List<String> values = new ArrayList<>();
configuredPlugins = configuredPls.split(",");
for (int i = 0; i < configuredPlugins.length; i++) {
final String v = configuredPlugins[i].trim();
if (!v.isEmpty()) {
values.add(v);
}
}
if (!values.isEmpty()) {
configuredPlugins = values.toArray(new String[values.size()]);
}
}
return configuredPlugins;
}
@Override
public void stop( final BundleContext bundleContext )
{
// stop logger
Log.logger.stop();
// stop tracker and configuration manager implementation
if ( this.tracker != null )
{
this.tracker.stop();
this.tracker = null;
}
// shutdown the file and memory persistence manager and unregister
this.unregisterFilePersistenceManager();
this.unregisterMemoryPersistenceManager();
}
private ServiceFactory<PersistenceManager> registerFilePersistenceManager(final BundleContext bundleContext)
{
final Dictionary<String, Object> props = new Hashtable<>();
props.put(Constants.SERVICE_DESCRIPTION, "Platform Filesystem Persistence Manager");
props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
props.put(Constants.SERVICE_RANKING, new Integer(Integer.MIN_VALUE));
props.put(PersistenceManager.PROPERTY_NAME, FilePersistenceManager.DEFAULT_PERSISTENCE_MANAGER_NAME);
final ServiceFactory<PersistenceManager> factory = new ServiceFactory<PersistenceManager>()
{
private volatile FilePersistenceManager fpm;
@Override
public PersistenceManager getService(Bundle bundle, ServiceRegistration<PersistenceManager> registration) {
if (fpm == null) {
fpm = new FilePersistenceManager(bundleContext, bundleContext.getProperty(CM_CONFIG_DIR));
}
return fpm;
}
@Override
public void ungetService(Bundle bundle, ServiceRegistration<PersistenceManager> registration,
PersistenceManager service) {
// nothing to do
}
};
filepmRegistration = bundleContext.registerService(PersistenceManager.class, factory, props);
return factory;
}
private void registerMemoryPersistenceManager(final BundleContext bundleContext) {
final MemoryPersistenceManager mpm = new MemoryPersistenceManager();
final Dictionary<String, Object> props = new Hashtable<>();
props.put(Constants.SERVICE_DESCRIPTION, "Platform Memory Persistence Manager");
props.put(Constants.SERVICE_VENDOR, "The Apache Software Foundation");
props.put(PersistenceManager.PROPERTY_NAME, "memory");
memorypmRegistration = bundleContext.registerService(PersistenceManager.class, mpm, props);
}
private void unregisterFilePersistenceManager()
{
if ( this.filepmRegistration != null )
{
this.filepmRegistration.unregister();
this.filepmRegistration = null;
}
}
private void unregisterMemoryPersistenceManager() {
if (this.memorypmRegistration != null) {
this.memorypmRegistration.unregister();
this.memorypmRegistration = null;
}
}
public static String getLocation(final Bundle bundle)
{
if (System.getSecurityManager() != null)
{
return AccessController.doPrivileged(new PrivilegedAction<String>()
{
@Override
public String run()
{
return bundle.getLocation();
}
});
}
else
{
return bundle.getLocation();
}
}
}

View File

@ -0,0 +1,90 @@
/*
* 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.cm.impl;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import org.osgi.service.log.LogService;
/**
* The activator worker queue is used to asynchronously activate the
* configuration admin if it's activation depends on the existence of other
* services like a configured persistence manager or required configuration
* plugins.
*/
public class ActivatorWorkerQueue implements Runnable {
private final ThreadFactory threadFactory;
private final List<Runnable> tasks = new ArrayList<>();
private volatile Thread backgroundThread;
private volatile boolean stopped = false;
public ActivatorWorkerQueue() {
this.threadFactory = Executors.defaultThreadFactory();
}
public void stop() {
synchronized ( this.tasks ) {
this.stopped = true;
}
}
public void enqueue(final Runnable r) {
synchronized ( this.tasks ) {
if ( !this.stopped ) {
this.tasks.add(r);
if ( this.backgroundThread == null ) {
this.backgroundThread = this.threadFactory.newThread(this);
this.backgroundThread.setDaemon(true);
this.backgroundThread.setName("Apache Felix Configuration Admin Activator Thread");
this.backgroundThread.start();
}
}
}
}
@Override
public void run() {
Runnable r;
do {
r = null;
synchronized ( this.tasks ) {
if ( !this.stopped && !this.tasks.isEmpty() ) {
r = this.tasks.remove(0);
} else {
this.backgroundThread = null;
}
}
if ( r != null ) {
try {
r.run();
} catch ( final Throwable t) {
// just to be sure our loop never dies
Log.logger.log(LogService.LOG_ERROR, "Error processing task", t);
}
}
} while ( r != null );
}
}

View File

@ -0,0 +1,548 @@
/*
* 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.cm.impl;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.Vector;
/**
* The <code>CaseInsensitiveDictionary</code> is a
* <code>java.util.Dictionary</code> which conforms to the requirements laid
* out by the Configuration Admin Service Specification requiring the property
* names to keep case but to ignore case when accessing the properties.
*/
public class CaseInsensitiveDictionary extends Dictionary<String, Object>
{
/**
* The backend dictionary with lower case keys.
*/
private SortedMap<String, Object> internalMap;
public CaseInsensitiveDictionary()
{
internalMap = new TreeMap<>( CASE_INSENSITIVE_ORDER );
}
public CaseInsensitiveDictionary( Dictionary props )
{
if ( props instanceof CaseInsensitiveDictionary)
{
internalMap = new TreeMap<>( ((CaseInsensitiveDictionary) props).internalMap );
}
else if ( props != null )
{
internalMap = new TreeMap<>( CASE_INSENSITIVE_ORDER );
Enumeration keys = props.keys();
while ( keys.hasMoreElements() )
{
Object key = keys.nextElement();
// check the correct syntax of the key
String k = checkKey( key );
// check uniqueness of key
if ( internalMap.containsKey( k ) )
{
throw new IllegalArgumentException( "Key [" + key + "] already present in different case" );
}
// check the value
Object value = props.get( key );
value = checkValue( value );
// add the key/value pair
internalMap.put( k, value );
}
}
else
{
internalMap = new TreeMap<>( CASE_INSENSITIVE_ORDER );
}
}
CaseInsensitiveDictionary( CaseInsensitiveDictionary props, boolean deepCopy )
{
if ( deepCopy )
{
internalMap = new TreeMap<>( CASE_INSENSITIVE_ORDER );
for( Map.Entry<String, Object> entry : props.internalMap.entrySet() )
{
Object value = entry.getValue();
if ( value.getClass().isArray() )
{
// copy array
int length = Array.getLength( value );
Object newValue = Array.newInstance( value.getClass().getComponentType(), length );
System.arraycopy( value, 0, newValue, 0, length );
value = newValue;
}
else if ( value instanceof Collection )
{
// copy collection, create Vector
// a Vector is created because the R4 and R4.1 specs
// state that the values must be simple, array or
// Vector. And even though we accept Collection nowadays
// there might be clients out there still written against
// R4 and R4.1 spec expecting Vector
value = new Vector<>( ( Collection ) value );
}
internalMap.put( entry.getKey(), value );
}
}
else
{
internalMap = new TreeMap<>( props.internalMap );
}
}
/*
* (non-Javadoc)
*
* @see java.util.Dictionary#elements()
*/
@Override
public Enumeration<Object> elements()
{
return Collections.enumeration( internalMap.values() );
}
/*
* (non-Javadoc)
*
* @see java.util.Dictionary#get(java.lang.Object)
*/
@Override
public Object get( Object key )
{
if ( key == null )
{
throw new NullPointerException( "key" );
}
return internalMap.get( key );
}
/*
* (non-Javadoc)
*
* @see java.util.Dictionary#isEmpty()
*/
@Override
public boolean isEmpty()
{
return internalMap.isEmpty();
}
/*
* (non-Javadoc)
*
* @see java.util.Dictionary#keys()
*/
@Override
public Enumeration<String> keys()
{
return Collections.enumeration( internalMap.keySet() );
}
/*
* (non-Javadoc)
*
* @see java.util.Dictionary#put(java.lang.String, java.lang.Object)
*/
@Override
public Object put( String key, Object value )
{
if ( key == null || value == null )
{
throw new NullPointerException( "key or value" );
}
checkKey( key );
value = checkValue( value );
return internalMap.put( key, value );
}
/*
* (non-Javadoc)
*
* @see java.util.Dictionary#remove(java.lang.Object)
*/
@Override
public Object remove( Object key )
{
if ( key == null )
{
throw new NullPointerException( "key" );
}
return internalMap.remove( key );
}
/*
* (non-Javadoc)
*
* @see java.util.Dictionary#size()
*/
@Override
public int size()
{
return internalMap.size();
}
// ---------- internal -----------------------------------------------------
/**
* Ensures the <code>key</code> complies with the <em>symbolic-name</em>
* production of the OSGi core specification (1.3.2):
*
* <pre>
* symbolic-name :: = token('.'token)*
* digit ::= [0..9]
* alpha ::= [a..zA..Z]
* alphanum ::= alpha | digit
* token ::= ( alphanum | _ | - )+
* </pre>
*
* If the key does not comply an <code>IllegalArgumentException</code> is
* thrown.
*
* @param keyObject
* The configuration property key to check.
* @throws IllegalArgumentException
* if the key does not comply with the symbolic-name production.
*/
static String checkKey( Object keyObject )
{
// check for wrong type or null key
if ( !( keyObject instanceof String ) )
{
throw new IllegalArgumentException( "Key [" + keyObject + "] must be a String" );
}
String key = ( String ) keyObject;
// check for empty string
if ( key.length() == 0 )
{
throw new IllegalArgumentException( "Key [" + key + "] must not be an empty string" );
}
return key;
}
private static final Set<Class> KNOWN = new HashSet<>(Arrays.<Class>asList(
String.class, Integer.class, Long.class, Float.class,
Double.class, Byte.class, Short.class, Character.class,
Boolean.class));
static Object checkValue( Object value )
{
if ( value == null )
{
// null is illegal
throw new IllegalArgumentException( "Value must not be null" );
}
Class type = value.getClass();
// Fast check for simple types
if ( KNOWN.contains( type ) )
{
return value;
}
else if ( type.isArray() )
{
// check simple or primitive
type = value.getClass().getComponentType();
// check for primitive type (simple types are checked below)
// note: void[] cannot be created, so we ignore this here
if ( type.isPrimitive() )
{
return value;
}
}
else if ( value instanceof Collection )
{
// check simple
Collection collection = ( Collection ) value;
if ( collection.isEmpty() )
{
return Collections.EMPTY_LIST;
}
else
{
// ensure all elements have the same type and to internal list
Collection<Object> internalValue = new ArrayList<>( collection.size() );
type = null;
for ( Object el : collection )
{
if ( el == null )
{
throw new IllegalArgumentException( "Collection must not contain null elements" );
}
if ( type == null )
{
type = el.getClass();
}
else if ( type != el.getClass() )
{
throw new IllegalArgumentException( "Collection element types must not be mixed" );
}
internalValue.add( el );
}
value = internalValue;
}
}
else
{
// get the type to check (must be simple)
type = value.getClass();
}
// check for simple type
if ( KNOWN.contains( type ) )
{
return value;
}
// not a valid type
throw new IllegalArgumentException( "Value [" + value + "] has unsupported (base-) type " + type );
}
// ---------- Object Overwrites --------------------------------------------
@Override
public String toString()
{
return internalMap.toString();
}
@Override
public int hashCode()
{
return internalMap.hashCode();
}
@Override
public synchronized boolean equals(final Object o)
{
if (o == this)
{
return true;
}
if (!(o instanceof Dictionary))
{
return false;
}
@SuppressWarnings("unchecked")
final Dictionary<String,Object> t = (Dictionary<String,Object>) o;
if (t.size() != size())
{
return false;
}
try
{
final Enumeration<String> keys = keys();
while ( keys.hasMoreElements() )
{
final String key = keys.nextElement();
final Object value = get(key);
if (!value.equals(t.get(key)))
{
return false;
}
}
}
catch (ClassCastException unused)
{
return false;
}
catch (NullPointerException unused)
{
return false;
}
return true;
}
public static Dictionary<String, Object> unmodifiable(Dictionary<String, Object> dict) {
return new UnmodifiableDictionary(dict);
}
public static final class UnmodifiableDictionary extends Dictionary<String, Object>
{
private final Dictionary<String, Object> delegatee;
public UnmodifiableDictionary(final Dictionary<String, Object> delegatee)
{
this.delegatee = delegatee;
}
@Override
public Object put(String key, Object value)
{
// prevent put
return null;
}
@Override
public Object remove(Object key)
{
// prevent remove
return null;
}
@Override
public int hashCode()
{
return delegatee.hashCode();
}
@Override
public boolean equals(Object obj)
{
return delegatee.equals(obj);
}
@Override
public String toString()
{
return delegatee.toString();
}
@Override
public int size()
{
return delegatee.size();
}
@Override
public boolean isEmpty()
{
return delegatee.isEmpty();
}
@Override
public Enumeration<String> keys()
{
return delegatee.keys();
}
@Override
public Enumeration<Object> elements()
{
return delegatee.elements();
}
@Override
public Object get(Object key)
{
return delegatee.get(key);
}
}
public static final Comparator<String> CASE_INSENSITIVE_ORDER = new CaseInsensitiveComparator();
private static class CaseInsensitiveComparator implements Comparator<String>
{
@Override
public int compare(String s1, String s2)
{
int n1 = s1.length();
int n2 = s2.length();
int min = n1 < n2 ? n1 : n2;
for ( int i = 0; i < min; i++ )
{
char c1 = s1.charAt( i );
char c2 = s2.charAt( i );
if ( c1 != c2 )
{
// Fast check for simple ascii codes
if ( c1 <= 128 && c2 <= 128 )
{
c1 = toLowerCaseFast(c1);
c2 = toLowerCaseFast(c2);
if ( c1 != c2 )
{
return c1 - c2;
}
}
else
{
c1 = Character.toUpperCase( c1 );
c2 = Character.toUpperCase( c2 );
if ( c1 != c2 )
{
c1 = Character.toLowerCase( c1 );
c2 = Character.toLowerCase( c2 );
if ( c1 != c2 )
{
// No overflow because of numeric promotion
return c1 - c2;
}
}
}
}
}
return n1 - n2;
}
}
private static char toLowerCaseFast( char ch )
{
return ( ch >= 'A' && ch <= 'Z' ) ? ( char ) ( ch + 'a' - 'A' ) : ch;
}
}

View File

@ -0,0 +1,372 @@
/*
* 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.cm.impl;
import java.io.IOException;
import java.util.Dictionary;
import java.util.EnumSet;
import java.util.Set;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationPermission;
import org.osgi.service.cm.ReadOnlyConfigurationException;
import org.osgi.service.log.LogService;
/**
* The <code>ConfigurationAdapter</code> is just an adapter to the internal
* configuration object. Instances of this class are returned as Configuration
* objects to the client, where each caller gets a fresh instance of this
* class while internal Configuration objects are shared.
*/
public class ConfigurationAdapter implements Configuration
{
private final ConfigurationAdminImpl configurationAdmin;
private final ConfigurationImpl delegatee;
ConfigurationAdapter( ConfigurationAdminImpl configurationAdmin, ConfigurationImpl delegatee )
{
this.configurationAdmin = configurationAdmin;
this.delegatee = delegatee;
}
/**
* @see org.apache.felix.cm.impl.ConfigurationImpl#getPid()
*/
@Override
public String getPid()
{
checkDeleted();
return delegatee.getPidString();
}
/**
* @see org.apache.felix.cm.impl.ConfigurationImpl#getFactoryPid()
*/
@Override
public String getFactoryPid()
{
checkDeleted();
return delegatee.getFactoryPidString();
}
/**
* @see org.apache.felix.cm.impl.ConfigurationImpl#getBundleLocation()
*/
@Override
public String getBundleLocation()
{
// CM 1.4 / 104.13.2.4
final String bundleLocation = delegatee.getBundleLocation();
//delegatee.getConfigurationManager().log( LogService.LOG_DEBUG, "getBundleLocation() ==> {0}", new Object[]
// { bundleLocation } );
checkActive();
configurationAdmin.checkPermission( delegatee.getConfigurationManager(), ( bundleLocation == null ) ? "*" : bundleLocation, true );
checkDeleted();
return bundleLocation;
}
/**
* @param bundleLocation
* @see org.apache.felix.cm.impl.ConfigurationImpl#setStaticBundleLocation(String)
*/
@Override
public void setBundleLocation( String bundleLocation )
{
Log.logger.log( LogService.LOG_DEBUG, "setBundleLocation(bundleLocation={0})",
new Object[]
{ bundleLocation } );
// CM 1.4 / 104.13.2.4
checkActive();
final String configLocation = delegatee.getBundleLocation();
configurationAdmin.checkPermission( delegatee.getConfigurationManager(), ( configLocation == null ) ? "*" : configLocation, true );
configurationAdmin.checkPermission( delegatee.getConfigurationManager(), ( bundleLocation == null ) ? "*" : bundleLocation, true );
checkDeleted();
delegatee.setStaticBundleLocation( bundleLocation );
}
/**
* @throws IOException
* @see org.apache.felix.cm.impl.ConfigurationImpl#update()
*/
@Override
public void update() throws IOException
{
Log.logger.log( LogService.LOG_DEBUG, "update()", ( Throwable ) null );
checkActive();
checkDeleted();
delegatee.update();
}
/**
* @param properties
* @throws IOException
* @see org.apache.felix.cm.impl.ConfigurationImpl#update(java.util.Dictionary)
*/
@Override
public void update( Dictionary<String, ?> properties ) throws IOException
{
Log.logger.log( LogService.LOG_DEBUG, "update(properties={0})", new Object[]
{ properties } );
checkActive();
checkDeleted();
checkLocked();
delegatee.update( properties );
}
@Override
public Dictionary<String, Object> getProperties()
{
//delegatee.getConfigurationManager().log( LogService.LOG_DEBUG, "getProperties()", ( Throwable ) null );
checkDeleted();
// return a deep copy since the spec says, that modification of
// any value should not modify the internal, stored value
return delegatee.getProperties( true );
}
@Override
public long getChangeCount()
{
//delegatee.getConfigurationManager().log( LogService.LOG_DEBUG, "getChangeCount()", ( Throwable ) null );
checkDeleted();
return delegatee.getRevision();
}
/**
* @throws IOException
* @see org.apache.felix.cm.impl.ConfigurationImpl#delete()
*/
@Override
public void delete() throws IOException
{
Log.logger.log( LogService.LOG_DEBUG, "delete()", ( Throwable ) null );
checkActive();
checkDeleted();
delegatee.delete();
}
/**
* @see org.osgi.service.cm.Configuration#updateIfDifferent(java.util.Dictionary)
*/
@SuppressWarnings("unchecked")
@Override
public boolean updateIfDifferent(final Dictionary<String, ?> properties) throws IOException
{
Log.logger.log( LogService.LOG_DEBUG, "updateIfDifferent(properties={0})", new Object[]
{ properties } );
checkActive();
checkDeleted();
checkLocked();
if ( !ConfigurationImpl.equals((Dictionary<String, Object>)properties, delegatee.getProperties(false)) )
{
delegatee.update( properties );
return true;
}
return false;
}
/**
* @see org.osgi.service.cm.Configuration#addAttributes(org.osgi.service.cm.Configuration.ConfigurationAttribute[])
*/
@Override
public void addAttributes(final ConfigurationAttribute... attrs) throws IOException
{
checkDeleted();
final String bundleLocation = delegatee.getBundleLocation();
this.configurationAdmin.checkPermission(this.delegatee.getConfigurationManager(),
( bundleLocation == null ) ? "*" : bundleLocation,
ConfigurationPermission.ATTRIBUTE,
false);
Log.logger.log( LogService.LOG_DEBUG, "addAttributes({0})", attrs );
if ( attrs != null )
{
for(ConfigurationAttribute ca : attrs)
{
// locked is the only attribute at the moment
if ( ca == ConfigurationAttribute.READ_ONLY ) {
delegatee.setLocked( true );
}
}
}
}
/**
* @see org.osgi.service.cm.Configuration#getAttributes()
*/
@Override
public Set<ConfigurationAttribute> getAttributes()
{
checkDeleted();
if ( delegatee.isLocked() )
{
return EnumSet.of(ConfigurationAttribute.READ_ONLY);
}
return EnumSet.noneOf(ConfigurationAttribute.class);
}
/**
* @see org.osgi.service.cm.Configuration#removeAttributes(org.osgi.service.cm.Configuration.ConfigurationAttribute[])
*/
@Override
public void removeAttributes(final ConfigurationAttribute... attrs) throws IOException
{
checkDeleted();
final String bundleLocation = delegatee.getBundleLocation();
this.configurationAdmin.checkPermission(this.delegatee.getConfigurationManager(),
( bundleLocation == null ) ? "*" : bundleLocation,
ConfigurationPermission.ATTRIBUTE,
false);
Log.logger.log( LogService.LOG_DEBUG, "removeAttributes({0})", attrs );
if ( attrs != null )
{
for(ConfigurationAttribute ca : attrs)
{
// locked is the only attribute at the moment
if ( ca == ConfigurationAttribute.READ_ONLY ) {
delegatee.setLocked( false );
}
}
}
}
/**
* @see org.osgi.service.cm.Configuration#getProcessedProperties(ServiceReference)
*/
@Override
public Dictionary<String, Object> getProcessedProperties(ServiceReference<?> sr)
{
final Dictionary<String, Object> props = this.getProperties();
this.delegatee.getConfigurationManager().callPlugins(props, sr,
(String)props.get(Constants.SERVICE_PID),
(String)props.get(ConfigurationAdmin.SERVICE_FACTORYPID));
return props;
}
/**
* @see org.apache.felix.cm.impl.ConfigurationImpl#hashCode()
*/
@Override
public int hashCode()
{
return delegatee.hashCode();
}
/**
* @param obj
* @see org.apache.felix.cm.impl.ConfigurationImpl#equals(java.lang.Object)
*/
@Override
public boolean equals( Object obj )
{
return delegatee.equals( obj );
}
/**
* @see org.apache.felix.cm.impl.ConfigurationImpl#toString()
*/
@Override
public String toString()
{
return delegatee.toString();
}
/**
* Checks whether this configuration object is backed by an active
* Configuration Admin Service (ConfigurationManager here).
*
* @throws IllegalStateException If this configuration object is not
* backed by an active ConfigurationManager
*/
private void checkActive()
{
if ( !delegatee.isActive() )
{
throw new IllegalStateException( "Configuration " + delegatee.getPid()
+ " not backed by an active Configuration Admin Service" );
}
}
/**
* Checks whether this configuration object has already been deleted.
*
* @throws IllegalStateException If this configuration object has been
* deleted.
*/
private void checkDeleted()
{
if ( delegatee.isDeleted() )
{
throw new IllegalStateException( "Configuration " + delegatee.getPid() + " deleted" );
}
}
/**
* Checks whether this configuration object is locked.
*
* @throws ReadOnlyConfigurationException If this configuration object is locked.
*/
private void checkLocked() throws IOException
{
if ( delegatee.isLocked() )
{
throw new ReadOnlyConfigurationException( "Configuration " + delegatee.getPid() + " is read-only" );
}
}
}

View File

@ -0,0 +1,69 @@
/*
* 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.cm.impl;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* The <code>ConfigurationAdminFactory</code> is the <code>ServiceFactory</code>
* registered as the <code>ConfigurationAdmin</code> service responsible to
* create the real <code>ConfiguratAdmin</code> instances returend to client
* bundles. Each bundle gets a separate instance.
*/
class ConfigurationAdminFactory implements ServiceFactory<ConfigurationAdmin>
{
// The configuration manager to which the configuration admin instances
// delegate most of their work
private final ConfigurationManager configurationManager;
ConfigurationAdminFactory( final ConfigurationManager configurationManager )
{
this.configurationManager = configurationManager;
}
/**
* Returns a new instance of the {@link ConfigurationAdminImpl} class for
* the given bundle.
*/
@Override
public ConfigurationAdmin getService( final Bundle bundle, final ServiceRegistration<ConfigurationAdmin> registration )
{
return new ConfigurationAdminImpl( configurationManager, bundle );
}
/**
* Disposes off the given {@link ConfigurationAdminImpl} instance as the
* given bundle has no use of it any more.
*/
@Override
public void ungetService( final Bundle bundle, final ServiceRegistration<ConfigurationAdmin> registration, final ConfigurationAdmin service )
{
( ( ConfigurationAdminImpl ) service ).dispose();
}
}

View File

@ -0,0 +1,405 @@
/*
* 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.cm.impl;
import java.io.IOException;
import org.osgi.framework.Bundle;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationPermission;
import org.osgi.service.log.LogService;
/**
* The <code>ConfigurationAdminImpl</code> is the per-bundle frontend to the
* configuration manager. Instances of this class are created on-demand for
* each bundle trying to get hold of the <code>ConfigurationAdmin</code>
* service.
*/
public class ConfigurationAdminImpl implements ConfigurationAdmin
{
// The configuration manager to which most of the tasks are delegated
private volatile ConfigurationManager configurationManager;
// The bundle for which this instance has been created
private volatile Bundle bundle;
ConfigurationAdminImpl( ConfigurationManager configurationManager, Bundle bundle )
{
this.configurationManager = configurationManager;
this.bundle = bundle;
}
void dispose()
{
bundle = null;
configurationManager = null;
}
Bundle getBundle()
{
return bundle;
}
//---------- ConfigurationAdmin interface ---------------------------------
/* (non-Javadoc)
* @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String)
*/
@Override
public Configuration createFactoryConfiguration( String factoryPid ) throws IOException
{
final ConfigurationManager configurationManager = getConfigurationManager();
Log.logger.log( LogService.LOG_DEBUG, "createFactoryConfiguration(factoryPid={0})", new Object[]
{ factoryPid } );
// FELIX-3360: new factory configuration with implicit binding is dynamic
ConfigurationImpl config = configurationManager.createFactoryConfiguration( factoryPid, null );
config.setDynamicBundleLocation( Activator.getLocation(this.getBundle()), false );
return this.wrap( config );
}
/* (non-Javadoc)
* @see org.osgi.service.cm.ConfigurationAdmin#createFactoryConfiguration(java.lang.String, java.lang.String)
*/
@Override
public Configuration createFactoryConfiguration( String factoryPid, String location ) throws IOException
{
final ConfigurationManager configurationManager = getConfigurationManager();
Log.logger.log( LogService.LOG_DEBUG, "createFactoryConfiguration(factoryPid={0}, location={1})",
new Object[]
{ factoryPid, location } );
// CM 1.4 / 104.13.2.3
this.checkPermission( configurationManager, ( location == null ) ? "*" : location, false );
ConfigurationImpl config = configurationManager.createFactoryConfiguration( factoryPid, location );
return this.wrap( config );
}
/* (non-Javadoc)
* @see org.osgi.service.cm.ConfigurationAdmin#getConfiguration(java.lang.String)
*/
@Override
public Configuration getConfiguration( String pid ) throws IOException
{
final ConfigurationManager configurationManager = getConfigurationManager();
Log.logger.log( LogService.LOG_DEBUG, "getConfiguration(pid={0})", new Object[]
{ pid } );
ConfigurationImpl config = configurationManager.getConfiguration( pid );
if ( config == null )
{
config = configurationManager.createConfiguration( pid, null );
// FELIX-3360: configuration creation with implicit binding is dynamic
config.setDynamicBundleLocation( Activator.getLocation(getBundle()), false );
}
else
{
if ( config.getBundleLocation() == null )
{
Log.logger.log( LogService.LOG_DEBUG, "Binding configuration {0} (isNew: {1}) to bundle {2}",
new Object[]
{ config.getPid(), config.isNew() ? Boolean.TRUE : Boolean.FALSE,
Activator.getLocation(this.getBundle()) } );
// FELIX-3360: first implicit binding is dynamic
config.setDynamicBundleLocation( Activator.getLocation(getBundle()), true );
}
else
{
// CM 1.4 / 104.13.2.3
this.checkPermission( configurationManager, config.getBundleLocation(), false );
}
}
return this.wrap( config );
}
/* (non-Javadoc)
* @see org.osgi.service.cm.ConfigurationAdmin#getConfiguration(java.lang.String, java.lang.String)
*/
@Override
public Configuration getConfiguration( String pid, String location ) throws IOException
{
final ConfigurationManager configurationManager = getConfigurationManager();
Log.logger.log( LogService.LOG_DEBUG, "getConfiguration(pid={0}, location={1})", new Object[]
{ pid, location } );
// CM 1.4 / 104.13.2.3
this.checkPermission( configurationManager, ( location == null ) ? "*" : location, false );
ConfigurationImpl config = configurationManager.getConfiguration( pid );
if ( config == null )
{
config = configurationManager.createConfiguration( pid, location );
}
else
{
final String configLocation = config.getBundleLocation();
this.checkPermission( configurationManager, ( configLocation == null ) ? "*" : configLocation, false );
}
return this.wrap( config );
}
/* (non-Javadoc)
* @see org.osgi.service.cm.ConfigurationAdmin#listConfigurations(java.lang.String)
*/
@Override
public Configuration[] listConfigurations( String filter ) throws IOException, InvalidSyntaxException
{
final ConfigurationManager configurationManager = getConfigurationManager();
Log.logger.log( LogService.LOG_DEBUG, "listConfigurations(filter={0})", new Object[]
{ filter } );
ConfigurationImpl ci[] = configurationManager.listConfigurations( this, filter );
if ( ci == null || ci.length == 0 )
{
return null;
}
Configuration[] cfgs = new Configuration[ci.length];
for ( int i = 0; i < cfgs.length; i++ )
{
cfgs[i] = this.wrap( ci[i] );
}
return cfgs;
}
//---------- Security checks ----------------------------------------------
private Configuration wrap( ConfigurationImpl configuration )
{
return new ConfigurationAdapter( this, configuration );
}
/**
* Returns <code>true</code> if the current access control context (call
* stack) has the CONFIGURE permission.
*/
boolean hasPermission( final ConfigurationManager configurationManager, String name )
{
try
{
checkPermission(configurationManager, name, false);
return true;
}
catch ( SecurityException se )
{
return false;
}
}
/**
* Checks whether the current access control context (call stack) has
* the given permission for the given bundle location and throws a
* <code>SecurityException</code> if this is not the case.
*
* @param name The bundle location to check for permission. If this
* is <code>null</code> permission is always granted.
* @param checkOwn If {@code false} permission is always granted if
* {@code name} is the same the using bundle's location.
*
* @throws SecurityException if the access control context does not
* have the appropriate permission
*/
void checkPermission( final ConfigurationManager configurationManager, String name, boolean checkOwn )
{
checkPermission(configurationManager, name, ConfigurationPermission.CONFIGURE, checkOwn);
}
/**
* Checks whether the current access control context (call stack) has
* the given permission for the given bundle location and throws a
* <code>SecurityException</code> if this is not the case.
*
* @param name The bundle location to check for permission. If this
* is <code>null</code> permission is always granted.
* @param action The action to check.
* @param checkOwn If {@code false} permission is always granted if
* {@code name} is the same as the using bundle's location.
*
* @throws SecurityException if the access control context does not
* have the appropriate permission
*/
void checkPermission( final ConfigurationManager configurationManager, String name, String action, boolean checkOwn )
{
// the caller's permission must be checked
final SecurityManager sm = System.getSecurityManager();
if ( sm != null )
{
// CM 1.4 / 104.11.1 Implicit permission
if ( name != null && ( checkOwn || !name.equals( Activator.getLocation(getBundle()) ) ) )
{
try
{
sm.checkPermission( new ConfigurationPermission( name, action ) );
Log.logger.log( LogService.LOG_DEBUG,
"Explicit Permission; grant {0} permission on configuration bound to {1} to bundle {2}",
new Object[]
{ action, name, Activator.getLocation(getBundle()) } );
}
catch ( SecurityException se )
{
Log.logger
.log(
LogService.LOG_DEBUG,
"No Permission; denied {0} permission on configuration bound to {1} to bundle {2}; reason: {3}",
new Object[]
{ action, name, Activator.getLocation(getBundle()), se.getMessage() } );
throw se;
}
}
else if ( Log.logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
Log.logger.log( LogService.LOG_DEBUG,
"Implicit Permission; grant {0} permission on configuration bound to {1} to bundle {2}",
new Object[]
{ action, name, Activator.getLocation(getBundle()) } );
}
}
else if ( Log.logger.isLogEnabled( LogService.LOG_DEBUG ) )
{
Log.logger.log( LogService.LOG_DEBUG,
"No SecurityManager installed; grant {0} permission on configuration bound to {1} to bundle {2}",
new Object[]
{ action, name, Activator.getLocation(getBundle()) } );
}
}
/**
* Returns the {@link ConfigurationManager} backing this configuration
* admin instance or throws {@code IllegalStateException} if already
* disposed off.
*
* @return The {@link ConfigurationManager} instance if still active
* @throws IllegalStateException if this instance has been
* {@linkplain #dispose() disposed off} already.
*/
private ConfigurationManager getConfigurationManager()
{
if ( this.configurationManager == null )
{
throw new IllegalStateException( "Configuration Admin service has been unregistered" );
}
return this.configurationManager;
}
/**
* @see org.osgi.service.cm.ConfigurationAdmin#getFactoryConfiguration(java.lang.String, java.lang.String, java.lang.String)
*/
@Override
public Configuration getFactoryConfiguration(String factoryPid, String name, String location) throws IOException
{
final ConfigurationManager configurationManager = getConfigurationManager();
Log.logger.log( LogService.LOG_DEBUG, "getFactoryConfiguration(factoryPid={0}, name={1}, location={2})", new Object[]
{ factoryPid, name, location } );
final String pid = factoryPid + '~' + name;
// CM 1.4 / 104.13.2.3
this.checkPermission( configurationManager, ( location == null ) ? "*" : location, false );
ConfigurationImpl config = configurationManager.getConfiguration( pid );
if ( config == null )
{
config = configurationManager.createFactoryConfiguration( pid, factoryPid, location );
}
else
{
final String configLocation = config.getBundleLocation();
this.checkPermission( configurationManager, ( configLocation == null ) ? "*" : configLocation, false );
}
return this.wrap( config );
}
/**
* @see org.osgi.service.cm.ConfigurationAdmin#getFactoryConfiguration(java.lang.String, java.lang.String)
*/
@Override
public Configuration getFactoryConfiguration(String factoryPid, String name) throws IOException {
final ConfigurationManager configurationManager = getConfigurationManager();
Log.logger.log( LogService.LOG_DEBUG, "getFactoryConfiguration(factoryPid={0}, name={1})", new Object[]
{ factoryPid, name } );
final String pid = factoryPid + '~' + name;
ConfigurationImpl config = configurationManager.getConfiguration( pid );
if ( config == null )
{
config = configurationManager.createFactoryConfiguration( pid, factoryPid, null );
// FELIX-3360: configuration creation with implicit binding is dynamic
config.setDynamicBundleLocation( Activator.getLocation(getBundle()), false );
}
else
{
if ( config.getBundleLocation() == null )
{
Log.logger.log( LogService.LOG_DEBUG, "Binding configuration {0} (isNew: {1}) to bundle {2}",
new Object[]
{ config.getPid(), config.isNew() ? Boolean.TRUE : Boolean.FALSE,
Activator.getLocation(this.getBundle()) } );
// FELIX-3360: first implicit binding is dynamic
config.setDynamicBundleLocation( Activator.getLocation(getBundle()), true );
}
else
{
// CM 1.4 / 104.13.2.3
this.checkPermission( configurationManager, config.getBundleLocation(), false );
}
}
return this.wrap( config );
}
}

View File

@ -0,0 +1,198 @@
/*
* 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.cm.impl;
import java.io.IOException;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.felix.cm.impl.persistence.ExtPersistenceManager;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* The configuration admin starter starts and stops the configuration admin. It
* also keeps track of an optional coordinator service.
*/
public class ConfigurationAdminStarter {
private final BundleContext bundleContext;
private volatile ConfigurationManager configurationManager;
private volatile ExtPersistenceManager persistenceManager;
private final AtomicBoolean pluginsAvailable = new AtomicBoolean(false);
private volatile String registeredConfigurationPlugins = "";
// service tracker for optional coordinator
private volatile ServiceTracker<Object, Object> coordinatorTracker;
public ConfigurationAdminStarter(final BundleContext bundleContext)
throws BundleException, InvalidSyntaxException
{
this.bundleContext = bundleContext;
}
/**
* Stop configuration admin
*/
public void stop( )
{
this.deactivate();
}
public void activate(final ExtPersistenceManager pm)
{
try
{
configurationManager = new ConfigurationManager(pm, bundleContext);
// start coordinator tracker
this.startCoordinatorTracker();
final ServiceReference<ConfigurationAdmin> ref = configurationManager.start();
configurationManager.updateRegisteredConfigurationPlugins(this.registeredConfigurationPlugins);
// update log
Log.logger.set(ref);
}
catch (final IOException ioe )
{
Log.logger.log( LogService.LOG_ERROR, "Failure setting up dynamic configuration bindings", ioe );
}
}
public void deactivate()
{
this.stopCoordinatorTracker();
if ( this.configurationManager != null )
{
this.configurationManager.stop();
this.configurationManager = null;
}
// update log
Log.logger.set(null);
}
private void startCoordinatorTracker()
{
this.coordinatorTracker = new ServiceTracker<>(bundleContext, "org.osgi.service.coordinator.Coordinator",
new ServiceTrackerCustomizer<Object, Object>()
{
private final SortedMap<ServiceReference<Object>, Object> sortedServices = new TreeMap<>();
@Override
public Object addingService(final ServiceReference<Object> reference)
{
final Object srv = bundleContext.getService(reference);
if ( srv != null )
{
synchronized ( this.sortedServices )
{
sortedServices.put(reference, srv);
configurationManager.setCoordinator(sortedServices.get(sortedServices.lastKey()));
}
}
return srv;
}
@Override
public void modifiedService(final ServiceReference<Object> reference, final Object srv) {
synchronized ( this.sortedServices )
{
// update the map, service ranking might have changed
sortedServices.remove(reference);
sortedServices.put(reference, srv);
configurationManager.setCoordinator(sortedServices.get(sortedServices.lastKey()));
}
}
@Override
public void removedService(final ServiceReference<Object> reference, final Object service) {
synchronized ( this.sortedServices )
{
sortedServices.remove(reference);
if ( sortedServices.isEmpty() )
{
configurationManager.setCoordinator(null);
}
else
{
configurationManager.setCoordinator(sortedServices.get(sortedServices.lastKey()));
}
}
bundleContext.ungetService(reference);
}
});
coordinatorTracker.open();
}
private void stopCoordinatorTracker()
{
if ( this.coordinatorTracker != null )
{
this.coordinatorTracker.close();
this.coordinatorTracker = null;
}
}
public void unsetPersistenceManager() {
this.persistenceManager = null;
this.deactivate();
}
public void setPersistenceManager(final ExtPersistenceManager persistenceManager) {
this.persistenceManager = persistenceManager;
this.checkStart();
}
public void updatePluginsSet(final boolean value) {
if (this.pluginsAvailable.compareAndSet(!value, value)) {
if (!value) {
this.deactivate();
} else {
this.checkStart();
}
}
}
public void checkStart() {
if (this.pluginsAvailable.get() && this.persistenceManager != null) {
this.activate(this.persistenceManager);
}
}
public void updateRegisteredConfigurationPlugins(final String propValue) {
this.registeredConfigurationPlugins = propValue;
final ConfigurationManager localCM = this.configurationManager;
if (localCM != null) {
localCM.updateRegisteredConfigurationPlugins(propValue);
}
}
}

View File

@ -0,0 +1,910 @@
/*
* 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.cm.impl;
import java.io.IOException;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.helper.TargetedPID;
import org.osgi.framework.Constants;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
/**
* The <code>ConfigurationImpl</code> is the backend implementation of the
* Configuration Admin Service Specification <i>Configuration object</i>
* (section 104.4). Instances of this class are shared by multiple instances of
* the {@link ConfigurationAdapter} class, whose instances are actually returned
* to clients.
*/
public class ConfigurationImpl
{
/*
* Concurrency note: There is a slight (but real) chance of a race condition
* between a configuration update and a ManagedService[Factory] registration.
* Per the specification a ManagedService must be called with configuration
* or null when registered and a ManagedService must be called with currently
* existing configuration when registered. Also the ManagedService[Factory]
* must be updated when the configuration is updated.
*
* Consider now this situation of two threads T1 and T2:
*
* T1. create and update configuration
* ConfigurationImpl.update persists configuration and sets field
* Thread preempted
*
* T2. ManagedServiceUpdate constructor reads configuration
* Uses configuration already persisted by T1 for update
* Schedules task to update service with the configuration
*
* T1. Runs again creating the UpdateConfiguration task with the
* configuration persisted before being preempted
* Schedules task to update service
*
* Update Thread:
* Updates ManagedService with configuration prepared by T2
* Updates ManagedService with configuration prepared by T1
*
* The correct behaviour would be here, that the second call to update
* would not take place. We cannot at this point in time easily fix
* this issue. Also, it seems that changes for this to happen are
* small.
*
* This class provides modification counter (lastModificationTime)
* which is incremented on each change of the configuration. This
* helps the update tasks in the ConfigurationManager to log the
* revision of the configuration supplied.
*/
/**
* The name of a synthetic property stored in the persisted configuration
* data to indicate that the configuration data is new, that is created but
* never updated (value is "_felix_.cm.newConfiguration").
* <p>
* This special property is stored by the
* {@link #ConfigurationImpl(ConfigurationManager, PersistenceManager, String, String, String)}
* constructor, when the configuration is first created and persisted and is
* interpreted by the
* {@link #ConfigurationImpl(ConfigurationManager, PersistenceManager, Dictionary)}
* method when the configuration data is loaded in a new object.
* <p>
* The goal of this property is to keep the information on whether
* configuration data is new (but persisted as per the spec) or has already
* been assigned with possible no data.
*/
private static final String CONFIGURATION_NEW = "_felix_.cm.newConfiguration";
private static final String PROPERTY_LOCKED = ":org.apache.felix.configadmin.locked:";
private static final String PROPERTY_REVISION = ":org.apache.felix.configadmin.revision:";
/**
* The factory PID of this configuration or <code>null</code> if this
* is not a factory configuration.
*/
private final TargetedPID factoryPID;
/**
* The statically bound bundle location, which is set explicitly by calling
* the Configuration.setBundleLocation(String) method or when the
* configuration was created with the two-argument method.
*/
private volatile String staticBundleLocation;
/**
* The bundle location from dynamic binding. This value is set as the
* configuration or factory is assigned to a ManagedService[Factory].
*/
private volatile String dynamicBundleLocation;
/**
* The configuration data of this configuration instance. This is a private
* copy of the properties of which a copy is made when the
* {@link #getProperties()} method is called. This field is
* <code>null</code> if the configuration has been created and never been
* updated with acutal configuration properties.
*/
private volatile CaseInsensitiveDictionary properties;
/**
* Flag indicating that this configuration has been deleted.
*
* @see #isDeleted()
*/
private volatile boolean isDeleted;
/**
* Configuration revision counter incremented each time the
* {@link #properties} is set (in the constructor or the
* {@link #configure(Dictionary)} method. This counter is
* persisted transparently so that {@link NotCachablePersistenceManager}
* can provide a proper change count. The persistence is forward
* compatible such that previously persisted configurations are
* handled gracefully.
*/
private volatile long revision;
private volatile boolean locked;
/**
* The {@link ConfigurationManager configuration manager} instance which
* caused this configuration object to be created.
*/
private final ConfigurationManager configurationManager;
// the persistence manager storing this factory mapping
private final PersistenceManager persistenceManager;
// the basic ID of this instance
private final TargetedPID baseId;
public ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager,
Dictionary<String, Object> properties )
{
if ( configurationManager == null )
{
throw new IllegalArgumentException( "ConfigurationManager must not be null" );
}
if ( persistenceManager == null )
{
throw new IllegalArgumentException( "PersistenceManager must not be null" );
}
this.configurationManager = configurationManager;
this.persistenceManager = persistenceManager;
this.baseId = new TargetedPID( ( String ) properties.remove( Constants.SERVICE_PID ) );
final String factoryPid = ( String ) properties.remove( ConfigurationAdmin.SERVICE_FACTORYPID );
this.factoryPID = ( factoryPid == null ) ? null : new TargetedPID( factoryPid );
this.isDeleted = false;
// set bundle location from persistence and/or check for dynamic binding
this.staticBundleLocation = ( String ) properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION ) ;
this.dynamicBundleLocation = configurationManager.getDynamicBundleLocation( this.baseId.toString() );
// set the properties internally
configureFromPersistence( properties );
}
ConfigurationImpl( ConfigurationManager configurationManager, PersistenceManager persistenceManager, String pid,
String factoryPid, String bundleLocation ) throws IOException
{
if ( configurationManager == null )
{
throw new IllegalArgumentException( "ConfigurationManager must not be null" );
}
if ( persistenceManager == null )
{
throw new IllegalArgumentException( "PersistenceManager must not be null" );
}
this.configurationManager = configurationManager;
this.persistenceManager = persistenceManager;
this.baseId = new TargetedPID( pid );
this.factoryPID = ( factoryPid == null ) ? null : new TargetedPID( factoryPid );
this.isDeleted = false;
// set bundle location from persistence and/or check for dynamic binding
this.staticBundleLocation = bundleLocation;
this.dynamicBundleLocation = configurationManager.getDynamicBundleLocation( this.baseId.toString() );
// first "update"
this.properties = null;
this.revision = 1;
// this is a new configuration object, store immediately unless
// the new configuration object is created from a factory, in which
// case the configuration is only stored when first updated
if ( factoryPid == null )
{
storeNewConfiguration();
}
}
/**
* Returns <code>true</code> if the ConfigurationManager of this
* configuration is still active.
*/
boolean isActive()
{
return configurationManager.isActive();
}
void storeSilently()
{
try
{
this.store();
}
catch ( IOException ioe )
{
Log.logger.log( LogService.LOG_ERROR, "Persisting ID {0} failed", new Object[]
{ this.baseId, ioe } );
}
}
static protected void replaceProperty( Dictionary<String, Object> properties, String key, String value )
{
if ( value == null )
{
properties.remove( key );
}
else
{
properties.put( key, value );
}
}
public void delete() throws IOException
{
this.isDeleted = true;
this.persistenceManager.delete( this.getPidString() );
configurationManager.setDynamicBundleLocation( this.getPidString(), null );
configurationManager.deleted( this );
}
public String getPidString()
{
return this.baseId.toString();
}
public TargetedPID getPid()
{
return this.baseId;
}
public String getFactoryPidString()
{
return (factoryPID == null) ? null : factoryPID.toString();
}
public TargetedPID getFactoryPid()
{
return factoryPID;
}
/**
* Returns the "official" bundle location as visible from the outside
* world of code calling into the Configuration.getBundleLocation() method.
* <p>
* In other words: The {@link #getStaticBundleLocation()} is returned if
* not <code>null</code>. Otherwise the {@link #getDynamicBundleLocation()}
* is returned (which may also be <code>null</code>).
*/
String getBundleLocation()
{
if ( staticBundleLocation != null )
{
return staticBundleLocation;
}
return dynamicBundleLocation;
}
String getDynamicBundleLocation()
{
return dynamicBundleLocation;
}
String getStaticBundleLocation()
{
return staticBundleLocation;
}
void setStaticBundleLocation( final String bundleLocation )
{
// CM 1.4; needed for bundle location change at the end
final String oldBundleLocation = getBundleLocation();
// 104.15.2.8 The bundle location will be set persistently
this.staticBundleLocation = bundleLocation;
storeSilently();
// FELIX-3360: Always clear dynamic binding if a new static
// location is set. The static location is the relevant binding
// for a configuration unless it is not explicitly set.
setDynamicBundleLocation( null, false );
// CM 1.4
this.configurationManager.locationChanged( this, oldBundleLocation );
}
void setDynamicBundleLocation( final String bundleLocation, final boolean dispatchConfiguration )
{
// CM 1.4; needed for bundle location change at the end
final String oldBundleLocation = getBundleLocation();
this.dynamicBundleLocation = bundleLocation;
this.configurationManager.setDynamicBundleLocation( this.getPidString(), bundleLocation );
// CM 1.4
if ( dispatchConfiguration )
{
this.configurationManager.locationChanged( this, oldBundleLocation );
}
}
/**
* Dynamically binds this configuration to the given location unless
* the configuration is already bound (statically or dynamically). In
* the case of this configuration to be dynamically bound a
* <code>CM_LOCATION_CHANGED</code> event is dispatched.
*/
void tryBindLocation( final String bundleLocation )
{
if ( this.getBundleLocation() == null )
{
Log.logger.log( LogService.LOG_DEBUG, "Dynamically binding config {0} to {1}", new Object[]
{ getPidString(), bundleLocation } );
setDynamicBundleLocation( bundleLocation, true );
}
}
/**
* Returns an optionally deep copy of the properties of this configuration
* instance.
* <p>
* This method returns a copy of the internal dictionary. If the
* <code>deepCopy</code> parameter is true array and collection values are
* copied into new arrays or collections. Otherwise just a new dictionary
* referring to the same objects is returned.
*
* @param deepCopy
* <code>true</code> if a deep copy is to be returned.
* @return the configuration properties
*/
public Dictionary<String, Object> getProperties( boolean deepCopy )
{
// no properties yet
if ( properties == null )
{
return null;
}
CaseInsensitiveDictionary props = new CaseInsensitiveDictionary( properties, deepCopy );
// fix special properties (pid, factory PID, bundle location)
setAutoProperties( props, false );
return props;
}
/* (non-Javadoc)
* @see org.osgi.service.cm.Configuration#update()
*/
public void update() throws IOException
{
// read configuration from persistence (again)
if ( persistenceManager.exists( getPidString() ) )
{
@SuppressWarnings("unchecked")
Dictionary<String, Object> properties = persistenceManager.load( getPidString() );
// ensure serviceReference pid
String servicePid = ( String ) properties.get( Constants.SERVICE_PID );
if ( servicePid != null && !getPidString().equals( servicePid ) )
{
throw new IOException( "PID of configuration file does match requested PID; expected " + getPidString()
+ ", got " + servicePid );
}
// we're doing a local update, so override the properties revision
properties.put( PROPERTY_REVISION, Long.valueOf(getRevision()) );
configureFromPersistence( properties );
}
// update the service but do not fire an CM_UPDATED event
configurationManager.updated( this, false );
}
/**
* @see org.osgi.service.cm.Configuration#update(java.util.Dictionary)
*/
public void update( Dictionary<String, ?> properties ) throws IOException
{
CaseInsensitiveDictionary newProperties = new CaseInsensitiveDictionary( properties );
Log.logger.log( LogService.LOG_DEBUG, "Updating config {0} with {1}", new Object[]
{ getPidString(), newProperties } );
setAutoProperties( newProperties, true );
// persist new configuration
newProperties.put( PROPERTY_REVISION, Long.valueOf(getRevision()) );
persistenceManager.store( getPidString(), newProperties );
// finally assign the configuration for use
configure( newProperties );
// update the service and fire an CM_UPDATED event
configurationManager.updated( this, true );
}
//---------- Object overwrites --------------------------------------------
@Override
public boolean equals( Object obj )
{
if ( obj == this )
{
return true;
}
if ( obj instanceof Configuration )
{
return getPidString().equals( ( ( Configuration ) obj ).getPid() );
}
return false;
}
@Override
public int hashCode()
{
return getPidString().hashCode();
}
@Override
public String toString()
{
return "Configuration PID=" + getPidString() + ", factoryPID=" + factoryPID + ", bundleLocation=" + getBundleLocation();
}
// ---------- private helper -----------------------------------------------
/**
* Stores the configuration if it is a newly factory configuration
* which has not been persisted yet.
* <p>
* This is used to ensure a configuration c as in
* <pre>
* Configuration cf = cm.createFactoryConfiguration(factoryPid);
* Configuration c = cm.getConfiguration(cf.getPid());
* </pre>
* is persisted after <code>getConfiguration</code> while
* <code>createConfiguration</code> alone does not persist yet.
*/
void ensureFactoryConfigPersisted() throws IOException
{
if ( this.factoryPID != null && isNew() && !persistenceManager.exists( getPidString() ) )
{
storeNewConfiguration();
}
}
/**
* Persists a new (freshly created) configuration with a marker for
* it to be a new configuration.
*
* @throws IOException If an error occurrs storing the configuraiton
*/
private void storeNewConfiguration() throws IOException
{
Dictionary<String, Object> props = new Hashtable<>();
setAutoProperties( props, true );
props.put( CONFIGURATION_NEW, Boolean.TRUE );
props.put( PROPERTY_REVISION, Long.valueOf(getRevision()) );
persistenceManager.store( getPidString(), props );
}
void store() throws IOException
{
// we don't need a deep copy, since we are not modifying
// any value in the dictionary itself. we are just adding
// properties to it, which are required for storing
Dictionary<String, Object> props = getProperties( false );
// if this is a new configuration, we just use an empty Dictionary
if ( props == null )
{
props = new Hashtable<>();
// add automatic properties including the bundle location (if
// statically bound)
setAutoProperties( props, true );
}
else
{
replaceProperty( props, ConfigurationAdmin.SERVICE_BUNDLELOCATION, getStaticBundleLocation() );
}
if ( this.locked )
{
props.put(PROPERTY_LOCKED, this.locked);
}
else
{
props.remove(PROPERTY_LOCKED);
}
// only store now, if this is not a new configuration
props.put( PROPERTY_REVISION, Long.valueOf(getRevision()) );
persistenceManager.store( getPidString(), props );
}
/**
* Returns the revision of this configuration object.
* <p>
* When getting both the configuration properties and this revision
* counter, the two calls should be synchronized on this instance to
* ensure configuration values and revision counter match.
*/
public long getRevision()
{
return revision;
}
/**
* Returns <code>false</code> if this configuration contains configuration
* properties. Otherwise <code>true</code> is returned and this is a
* newly creted configuration object whose {@link #update(Dictionary)}
* method has never been called.
*/
boolean isNew()
{
return properties == null;
}
/**
* Returns <code>true</code> if this configuration has already been deleted
* on the persistence.
*/
boolean isDeleted()
{
return isDeleted;
}
private void configureFromPersistence( Dictionary<String, Object> properties )
{
// if the this is not an empty/new configuration, accept the properties
// otherwise just set the properties field to null
if ( properties.get( CONFIGURATION_NEW ) == null )
{
configure( properties );
}
else
{
configure( null );
}
}
private void configure( final Dictionary<String, Object> properties )
{
final Object revisionValue = properties == null ? null : properties.get(PROPERTY_REVISION);
final Object lockedValue = properties == null ? null : properties.get(PROPERTY_LOCKED);
if ( lockedValue != null )
{
this.locked = true;
}
final CaseInsensitiveDictionary newProperties;
if ( properties == null )
{
newProperties = null;
}
else
{
// remove predefined properties
clearAutoProperties( properties );
// ensure CaseInsensitiveDictionary
if ( properties instanceof CaseInsensitiveDictionary )
{
newProperties = ( CaseInsensitiveDictionary ) properties;
}
else
{
newProperties = new CaseInsensitiveDictionary( properties );
}
}
synchronized ( this )
{
this.properties = newProperties;
this.revision = (revisionValue != null) ? 1 + ((Long)revisionValue).longValue() : ++revision ;
}
}
void setAutoProperties( Dictionary<String, Object> properties, boolean withBundleLocation )
{
// set pid and factory pid in the properties
replaceProperty( properties, Constants.SERVICE_PID, getPidString() );
replaceProperty( properties, ConfigurationAdmin.SERVICE_FACTORYPID, getFactoryPidString() );
// bundle location is not set here
if ( withBundleLocation )
{
replaceProperty( properties, ConfigurationAdmin.SERVICE_BUNDLELOCATION, getStaticBundleLocation() );
}
else
{
properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
}
properties.remove( PROPERTY_LOCKED );
properties.remove( PROPERTY_REVISION );
}
static void setAutoProperties( Dictionary<String, Object> properties, String pid, String factoryPid )
{
replaceProperty( properties, Constants.SERVICE_PID, pid );
replaceProperty( properties, ConfigurationAdmin.SERVICE_FACTORYPID, factoryPid );
properties.remove( ConfigurationAdmin.SERVICE_BUNDLELOCATION );
properties.remove( PROPERTY_LOCKED );
properties.remove( PROPERTY_REVISION );
}
private static final String[] AUTO_PROPS = new String[] {
Constants.SERVICE_PID,
ConfigurationAdmin.SERVICE_FACTORYPID,
ConfigurationAdmin.SERVICE_BUNDLELOCATION,
PROPERTY_LOCKED, PROPERTY_REVISION
};
static void clearAutoProperties( Dictionary<String, Object> properties )
{
for(final String p : AUTO_PROPS)
{
properties.remove( p );
}
}
public void setLocked(final boolean flag) throws IOException
{
this.locked = flag;
store();
}
/**
* Compare the two properties, ignoring auto properties
* @param props1 Set of properties
* @param props2 Set of properties
* @return {@code true} if the set of properties is equal
*/
static boolean equals( Dictionary<String, Object> props1, Dictionary<String, Object> props2)
{
if (props1 == null) {
if (props2 == null) {
return true;
} else {
return false;
}
} else if (props2 == null) {
return false;
}
final int count1 = getCount(props1);
final int count2 = getCount(props2);
if ( count1 != count2 )
{
return false;
}
final Enumeration<String> keys = props1.keys();
while ( keys.hasMoreElements() )
{
final String key = keys.nextElement();
if ( !isAutoProp(key) )
{
final Object val1 = props1.get(key);
final Object val2 = props2.get(key);
if ( val1 == null )
{
if ( val2 != null )
{
return false;
}
}
else
{
if ( val2 == null )
{
return false;
}
// arrays are compared using Arrays.equals
if ( val1.getClass().isArray() )
{
if ( !val2.getClass().isArray() )
{
return false;
}
final Object[] a1 = convertToObjectArray(val1);
final Object[] a2 = convertToObjectArray(val2);
if ( ! Arrays.equals(a1, a2) )
{
return false;
}
}
else if ( !val1.equals(val2) )
{
return false;
}
}
}
}
return true;
}
/**
* Convert the object to an array
* @param value The array
* @return an object array
*/
private static Object[] convertToObjectArray(final Object value)
{
final Object[] values;
if (value instanceof long[])
{
final long[] a = (long[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
}
else if (value instanceof int[]) {
final int[] a = (int[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
} else if (value instanceof double[])
{
final double[] a = (double[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
}
else if (value instanceof byte[])
{
final byte[] a = (byte[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
}
else if (value instanceof float[])
{
final float[] a = (float[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
}
else if (value instanceof short[])
{
final short[] a = (short[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
}
else if (value instanceof boolean[])
{
final boolean[] a = (boolean[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
}
else if (value instanceof char[])
{
final char[] a = (char[])value;
values = new Object[a.length];
for(int i=0;i<a.length;i++)
{
values[i] = a[i];
}
}
else
{
values = (Object[]) value;
}
return values;
}
static boolean isAutoProp(final String name)
{
for(final String p : AUTO_PROPS)
{
if ( p.equals(name) )
{
return true;
}
}
return false;
}
static int getCount( Dictionary<String, Object> props )
{
int count = (props == null ? 0 : props.size());
if ( props != null )
{
for(final String p : AUTO_PROPS)
{
if ( props.get(p) != null )
{
count--;
}
}
}
return count;
}
public boolean isLocked()
{
return this.locked;
}
final ConfigurationManager getConfigurationManager()
{
return this.configurationManager;
}
}

View File

@ -0,0 +1,97 @@
/*
* 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.cm.impl;
import java.util.ArrayList;
import java.util.List;
import org.osgi.service.coordinator.Coordination;
import org.osgi.service.coordinator.Coordinator;
import org.osgi.service.coordinator.Participant;
/**
* Utility class for coordinations
*/
public class CoordinatorUtil
{
public static final class Notifier implements Participant
{
private final List<Runnable> runnables = new ArrayList<Runnable>();
private final UpdateThread thread;
public Notifier(final UpdateThread t)
{
this.thread = t;
}
private void execute()
{
for(final Runnable r : runnables)
{
this.thread.schedule(r);
}
runnables.clear();
}
@Override
public void ended(Coordination coordination) throws Exception
{
execute();
}
@Override
public void failed(Coordination coordination) throws Exception
{
execute();
}
public void add(final Runnable t)
{
runnables.add(t);
}
}
public static boolean addToCoordination(final Object srv, final UpdateThread thread, final Runnable task)
{
final Coordinator coordinator = (Coordinator) srv;
Coordination c = coordinator.peek();
if ( c != null && !c.isTerminated() )
{
Notifier n = null;
for(final Participant p : c.getParticipants())
{
if ( p instanceof Notifier )
{
n = (Notifier) p;
break;
}
}
if ( n == null )
{
n = new Notifier(thread);
c.addParticipant(n);
}
n.add(task);
return true;
}
return false;
}
}

View File

@ -0,0 +1,123 @@
/*
* 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.cm.impl;
import java.util.Arrays;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.persistence.ExtPersistenceManager;
import org.apache.felix.cm.impl.persistence.PersistenceManagerTracker;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceFactory;
import org.osgi.service.log.LogService;
/**
* The dependency tracker keeps track of all required dependencies (services)
* for the configuration admin and only activates the admin if everything is
* satisfied.
*/
public class DependencyTracker
{
/** The persistence manager tracker (optional) */
private final PersistenceManagerTracker persistenceManagerTracker;
/** The configuration plugin tracker (optional) */
private final RequiredConfigurationPluginTracker configurationPluginTracker;
private final ActivatorWorkerQueue workerQueue;
private final ConfigurationAdminStarter starter;
public DependencyTracker(final BundleContext bundleContext,
final ServiceFactory<PersistenceManager> defaultFactory,
final String pmName, final String[] pluginNames)
throws BundleException, InvalidSyntaxException
{
this.starter = new ConfigurationAdminStarter(bundleContext);
final boolean useQueue = pmName != null || pluginNames != null;
if (useQueue) {
this.workerQueue = new ActivatorWorkerQueue();
} else {
this.workerQueue = null;
}
if (pluginNames != null) {
Log.logger.log(LogService.LOG_DEBUG, "Requiring configuration plugins {0}",
new Object[] { Arrays.toString(pluginNames) });
this.configurationPluginTracker = new RequiredConfigurationPluginTracker(bundleContext, workerQueue,
starter, pluginNames);
} else {
this.configurationPluginTracker = null;
if (useQueue) {
starter.updatePluginsSet(true);
}
}
if ( pmName != null )
{
Log.logger.log(LogService.LOG_DEBUG, "Using persistence manager {0}", new Object[] {pmName});
this.persistenceManagerTracker = new PersistenceManagerTracker(bundleContext, workerQueue, starter, pmName);
}
else
{
this.persistenceManagerTracker = null;
Log.logger.log(LogService.LOG_DEBUG, "Using default persistence manager", (Object[])null);
PersistenceManager defaultPM = null;
try {
defaultPM = defaultFactory.getService(null, null);
} catch (final IllegalArgumentException iae) {
Log.logger.log(LogService.LOG_ERROR, "Cannot create the FilePersistenceManager", iae);
}
if (defaultPM == null) {
throw new BundleException("Unable to register default persistence manager.");
}
final ExtPersistenceManager epm = PersistenceManagerTracker.createPersistenceManagerProxy(defaultPM);
if (useQueue) {
starter.setPersistenceManager(epm);
} else {
this.starter.activate(epm);
}
}
}
/**
* Stop the tracker, stop configuration admin
*/
public void stop( )
{
if (this.workerQueue != null) {
this.workerQueue.stop();
}
this.starter.deactivate();
if ( this.persistenceManagerTracker != null )
{
this.persistenceManagerTracker.stop();
}
if (this.configurationPluginTracker != null)
{
this.configurationPluginTracker.stop();
}
}
}

View File

@ -0,0 +1,125 @@
/*
* 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.cm.impl;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.apache.felix.cm.PersistenceManager;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
class DynamicBindings
{
static final String BINDINGS_FILE_NAME = "org_apache_felix_cm_impl_DynamicBindings";
private final PersistenceManager persistenceManager;
private final Dictionary<String, String> bindings;
@SuppressWarnings("unchecked")
DynamicBindings( BundleContext bundleContext, PersistenceManager persistenceManager ) throws IOException
{
this.persistenceManager = persistenceManager;
if ( persistenceManager.exists( BINDINGS_FILE_NAME ) )
{
this.bindings = persistenceManager.load( BINDINGS_FILE_NAME );
// get locations of installed bundles to validate the bindings
final Set<String> locations = new HashSet<>();
final Bundle[] bundles = bundleContext.getBundles();
for ( int i = 0; i < bundles.length; i++ )
{
locations.add( Activator.getLocation(bundles[i]) );
}
// collect pids whose location is not installed any more
List<String> removedKeys = new ArrayList<>();
for (Enumeration<String> ke = bindings.keys(); ke.hasMoreElements();)
{
final String pid = ke.nextElement();
final String location = bindings.get( pid );
if ( !locations.contains( location ) )
{
removedKeys.add( pid );
}
}
// if some bindings had to be removed, store the mapping again
if ( removedKeys.size() > 0 )
{
// remove invalid mappings
for (Iterator<String> rki = removedKeys.iterator(); rki.hasNext();)
{
bindings.remove( rki.next() );
}
// store the modified map
persistenceManager.store( BINDINGS_FILE_NAME, bindings );
}
}
else
{
this.bindings = new Hashtable<>();
}
}
String getLocation( final String pid )
{
synchronized ( this )
{
return this.bindings.get( pid );
}
}
void putLocation( final String pid, final String location ) throws IOException
{
synchronized ( this )
{
if ( location == null )
{
this.bindings.remove( pid );
}
else
{
this.bindings.put( pid, location );
}
if (this.bindings.isEmpty()) {
this.persistenceManager.delete(BINDINGS_FILE_NAME);
} else {
this.persistenceManager.store(BINDINGS_FILE_NAME, bindings);
}
}
}
}

View File

@ -0,0 +1,254 @@
/*
* 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.cm.impl;
import java.text.MessageFormat;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
/**
* Log implementation either logging to a {@code LogService} or to {@code System.err}.
* The logger can be get using the static {@link #logger} field.
*
* The logger is initialized through {@link #start(BundleContext)} and {@link #set(ServiceReference)}.
* It gets cleaned up through {@link #stop()}.
*/
public class Log
{
/** The shared logger instance. */
public static final Log logger = new Log();
/**
* The name of the bundle context property defining the maximum log level
* (value is "felix.cm.loglevel"). The log level setting is only used if
* there is no OSGi LogService available. Otherwise this setting is ignored.
* <p>
* This value of this property is expected to be an integer number
* corresponding to the log level values of the OSGi LogService. That is 1
* for errors, 2 for warnings, 3 for informational messages and 4 for debug
* messages. The default value is 2, such that only warnings and errors are
* logged in the absence of a LogService.
*/
private static final String CM_LOG_LEVEL = "felix.cm.loglevel";
// The name of the LogService (not using the class, which might be missing)
private static final String LOG_SERVICE_NAME = "org.osgi.service.log.LogService";
private static final int CM_LOG_LEVEL_DEFAULT = 2;
// the ServiceTracker to emit log services (see log(int, String, Throwable))
@SuppressWarnings("rawtypes")
private volatile ServiceTracker logTracker;
// the maximum log level when no LogService is available
private volatile int logLevel = CM_LOG_LEVEL_DEFAULT;
private volatile ServiceReference<ConfigurationAdmin> serviceReference;
/**
* Start the tracker for the logger and set the log level according to the configuration.
* @param bundleContext The bundle context
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public void start( final BundleContext bundleContext)
{
// track the log service using a ServiceTracker
logTracker = new ServiceTracker( bundleContext, LOG_SERVICE_NAME , null );
logTracker.open();
// assign the log level
String logLevelProp = bundleContext.getProperty( CM_LOG_LEVEL );
if ( logLevelProp == null )
{
logLevel = CM_LOG_LEVEL_DEFAULT;
}
else
{
try
{
logLevel = Integer.parseInt( logLevelProp );
}
catch ( NumberFormatException nfe )
{
logLevel = CM_LOG_LEVEL_DEFAULT;
}
}
}
/**
* Set the service reference to the configuration admin in order to include this
* in every log message.
* @param ref The service reference
*/
public void set(final ServiceReference<ConfigurationAdmin> ref)
{
this.serviceReference = ref;
}
/**
* Stop the log service tracker and clear the service reference
*/
public void stop()
{
if ( logTracker != null )
{
logTracker.close();
logTracker = null;
}
serviceReference = null;
}
/**
* Is the log level enabled?
* @param level The level
* @return {@code true} if enabled
*/
public boolean isLogEnabled( final int level )
{
return level <= logLevel;
}
/**
* Log a message in the given level.
* If arguments are provided and contain a {@code ServiceReference} then
* the argument is replaced with the result of {@link #toString(ServiceReference)}.
*
* @param level The log level
* @param format The message text
* @param args The optional arguments
*/
public void log( final int level, final String format, final Object[] args )
{
@SuppressWarnings("rawtypes")
final ServiceTracker tracker = this.logTracker;
final Object log = tracker == null ? null : tracker.getService();
if ( log != null || isLogEnabled( level ) )
{
Throwable throwable = null;
String message = format;
if ( args != null && args.length > 0 )
{
for(int i=0; i<args.length; i++)
{
if ( args[i] instanceof ServiceReference )
{
args[i] = toString((ServiceReference<?>)args[i]);
}
}
if ( args[args.length - 1] instanceof Throwable )
{
throwable = ( Throwable ) args[args.length - 1];
}
message = MessageFormat.format( format, args );
}
log( level, message, throwable );
}
}
/**
* Log the message with the given level and throwable.
* @param level The log level
* @param message The message
* @param t The exception
*/
public void log( final int level, final String message, final Throwable t )
{
// log using the LogService if available
@SuppressWarnings("rawtypes")
final ServiceTracker tracker = this.logTracker;
final Object log = tracker == null ? null : tracker.getService();
if ( log != null )
{
( ( LogService ) log ).log( serviceReference, level, message, t );
return;
}
// Otherwise only log if more serious than the configured level
if ( isLogEnabled( level ) )
{
String code;
switch ( level )
{
case LogService.LOG_INFO:
code = "*INFO *";
break;
case LogService.LOG_WARNING:
code = "*WARN *";
break;
case LogService.LOG_ERROR:
code = "*ERROR*";
break;
case LogService.LOG_DEBUG:
default:
code = "*DEBUG*";
}
System.err.println( code + " " + message );
if ( t != null )
{
t.printStackTrace( System.err );
}
}
}
/**
* Create a string representation of the service reference
* @param ref The service reference
* @return The string representation
*/
private static String toString( final ServiceReference<?> ref )
{
String[] ocs = ( String[] ) ref.getProperty( "objectClass" );
StringBuilder buf = new StringBuilder( "[" );
for ( int i = 0; i < ocs.length; i++ )
{
buf.append( ocs[i] );
if ( i < ocs.length - 1 )
buf.append( ", " );
}
buf.append( ", id=" ).append( ref.getProperty( Constants.SERVICE_ID ) );
Bundle provider = ref.getBundle();
if ( provider != null )
{
buf.append( ", bundle=" ).append( provider.getBundleId() );
buf.append( '/' ).append( Activator.getLocation(provider) );
}
else
{
buf.append( ", unregistered" );
}
buf.append( "]" );
return buf.toString();
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.cm.impl;
import java.util.Comparator;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationPlugin;
/**
* The <code>RankingComparator</code> may be used to maintain sorted
* sets or to sort arrays such that the first element in the set or
* array is the one to use first and the last elements the one to
* use last.
*/
public abstract class RankingComparator implements Comparator<ServiceReference<?>>
{
/**
* Implements a comparator to sort arrays and sets according to the
* specification of the <code>service.ranking</code> property. This
* results in collections whose first element has the highest ranking
* and the last element has the lowest ranking. Thus the results of
* this comparator are as follows:
* <ul>
* <li><code>&lt; 0</code> if obj1 has higher ranking than obj2</li>
* <li><code>== 0</code> if obj1 and obj2 reference the same service</li>
* <li><code>&gt; 0</code> if obj1 has lower ranking than obj2</li>
* </ul>
*/
public static Comparator<ServiceReference<?>> SRV_RANKING = new RankingComparator()
{
public int compare( ServiceReference<?> obj1, ServiceReference<?> obj2 )
{
final long id1 = this.getLong( obj1, Constants.SERVICE_ID );
final long id2 = this.getLong( obj2, Constants.SERVICE_ID );
if ( id1 == id2 )
{
return 0;
}
final int rank1 = this.getInteger( obj1, Constants.SERVICE_RANKING );
final int rank2 = this.getInteger( obj2, Constants.SERVICE_RANKING );
if ( rank1 == rank2 )
{
return ( id1 < id2 ) ? -1 : 1;
}
return ( rank1 > rank2 ) ? -1 : 1;
}
};
/**
* Implements a comparator to sort arrays and sets according to the
* specification of the <code>service.cmRanking</code> property in
* the Configuration Admin specification. This results in collections
* where the first element has the lowest ranking value and the last
* element has the highest ranking value. Order amongst elements with
* the same ranking value is left undefined, however we order it
* by service id, lowest last. Thus the results of this
* comparator are as follows:
* <ul>
* <li><code>&lt; 0</code> if obj1 has lower ranking than obj2</li>
* <li><code>== 0</code> if obj1 and obj2 have the same ranking</li>
* <li><code>&gt; 0</code> if obj1 has higher ranking than obj2</li>
* </ul>
*/
public static Comparator<ServiceReference<?>> CM_RANKING = new RankingComparator()
{
public int compare( ServiceReference<?> obj1, ServiceReference<?> obj2 )
{
final long id1 = this.getLong( obj1, Constants.SERVICE_ID );
final long id2 = this.getLong( obj2, Constants.SERVICE_ID );
if ( id1 == id2 )
{
return 0;
}
final int rank1 = this.getInteger( obj1, ConfigurationPlugin.CM_RANKING );
final int rank2 = this.getInteger( obj2, ConfigurationPlugin.CM_RANKING );
if ( rank1 == rank2 )
{
return ( id1 > id2 ) ? -1 : 1;
}
return ( rank1 < rank2 ) ? -1 : 1;
}
};
protected int getInteger( ServiceReference<?> sr, String property )
{
Object rankObj = sr.getProperty( property );
if ( rankObj instanceof Integer )
{
return ( ( Integer ) rankObj ).intValue();
}
// null or not an integer
return 0;
}
protected long getLong( ServiceReference<?> sr, String property )
{
Object rankObj = sr.getProperty( property );
if ( rankObj instanceof Long )
{
return ( ( Long ) rankObj ).longValue();
}
// null or not a long
return 0;
}
}

View File

@ -0,0 +1,185 @@
/*
* 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.cm.impl;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationPlugin;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* This tracker tracks existence of configuration plugins which have a
* {@link #PROPERTY_NAME} property. The tracker checks just for existence of a
* plugin and tries to get it. However, this is not a guarantee that the plugin
* can and actually is used by the configuration admin as the admin has a
* separate tracker.
*/
public class RequiredConfigurationPluginTracker
implements ServiceTrackerCustomizer<ConfigurationPlugin, ConfigurationPlugin> {
public static final String PROPERTY_NAME = "config.plugin.id";
/** Tracker for the configuration plugins. */
private final ServiceTracker<ConfigurationPlugin, ConfigurationPlugin> pluginTracker;
private final BundleContext bundleContext;
private final ConcurrentHashMap<String, AtomicInteger> serviceMap = new ConcurrentHashMap<>();
private final Map<Long, String> idToNameMap = new ConcurrentHashMap<>();
private final ConfigurationAdminStarter starter;
private final Set<String> requiredNames = new HashSet<>();
private final Set<String> registeredPluginNames = new TreeSet<>();
private final ActivatorWorkerQueue workerQueue;
public RequiredConfigurationPluginTracker(final BundleContext bundleContext,
final ActivatorWorkerQueue workerQueue,
final ConfigurationAdminStarter starter,
final String[] pluginNames) throws BundleException, InvalidSyntaxException {
this.workerQueue = workerQueue;
this.starter = starter;
for (final String name : pluginNames) {
requiredNames.add(name);
}
this.bundleContext = bundleContext;
pluginTracker = new ServiceTracker<>(bundleContext, ConfigurationPlugin.class, this);
pluginTracker.open();
}
/**
* Stop the tracker
*/
public void stop() {
if (this.pluginTracker != null) {
this.pluginTracker.close();
}
}
private boolean hasRequiredPlugins() {
for (final String name : this.requiredNames) {
final AtomicInteger v = this.serviceMap.get(name);
if (v == null || v.get() == 0) {
return false;
}
}
return true;
}
@Override
public ConfigurationPlugin addingService(final ServiceReference<ConfigurationPlugin> reference) {
ConfigurationPlugin plugin = null;
final Object nameObj = reference.getProperty(PROPERTY_NAME);
if (nameObj != null) {
final String name = nameObj.toString();
idToNameMap.put((Long) reference.getProperty(Constants.SERVICE_ID), name);
this.serviceMap.putIfAbsent(name, new AtomicInteger());
final AtomicInteger counter = this.serviceMap.get(name);
final boolean checkActivate = counter.getAndIncrement() == 0;
boolean activate = false;
if (this.requiredNames.contains(name)) {
plugin = bundleContext.getService(reference);
if (plugin != null) {
activate = checkActivate && hasRequiredPlugins();
}
}
final boolean activateCA = activate;
this.workerQueue.enqueue(new Runnable() {
@Override
public void run() {
if (activateCA) {
starter.updatePluginsSet(true);
}
registeredPluginNames.add(name);
updateRegisteredConfigurationPlugins();
}
});
}
return plugin;
}
@Override
public void modifiedService(final ServiceReference<ConfigurationPlugin> reference,
ConfigurationPlugin service) {
removedService(reference, service);
addingService(reference);
}
@Override
public void removedService(final ServiceReference<ConfigurationPlugin> reference,
ConfigurationPlugin service) {
final String name = idToNameMap.remove(reference.getProperty(Constants.SERVICE_ID));
if (name != null) {
final AtomicInteger counter = this.serviceMap.get(name);
final boolean deactivate = counter.decrementAndGet() == 0;
if (this.requiredNames.contains(name)) {
bundleContext.ungetService(reference);
}
if (deactivate) {
this.workerQueue.enqueue(new Runnable() {
@Override
public void run() {
if (!hasRequiredPlugins()) {
starter.updatePluginsSet(false);
}
registeredPluginNames.remove(name);
updateRegisteredConfigurationPlugins();
}
});
}
}
}
private void updateRegisteredConfigurationPlugins() {
final String propValue;
if (this.registeredPluginNames.isEmpty()) {
propValue = "";
} else {
final StringBuilder sb = new StringBuilder();
boolean first = true;
for (final String name : this.registeredPluginNames) {
if (first) {
first = false;
} else {
sb.append(",");
}
sb.append(name);
}
propValue = sb.toString();
}
starter.updateRegisteredConfigurationPlugins(propValue);
}
}

View File

@ -0,0 +1,866 @@
/*
* 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.cm.impl;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Iterator;
import java.util.List;
import org.osgi.framework.InvalidSyntaxException;
public class SimpleFilter
{
public static final int MATCH_ALL = 0;
public static final int AND = 1;
public static final int OR = 2;
public static final int NOT = 3;
public static final int EQ = 4;
public static final int LTE = 5;
public static final int GTE = 6;
public static final int SUBSTRING = 7;
public static final int PRESENT = 8;
public static final int APPROX = 9;
private final String m_name;
private final Object m_value;
private final int m_op;
public SimpleFilter(String attr, Object value, int op)
{
m_name = attr;
m_value = value;
m_op = op;
}
public String getName()
{
return m_name;
}
public Object getValue()
{
return m_value;
}
public int getOperation()
{
return m_op;
}
@Override
public String toString()
{
StringBuilder sb = new StringBuilder();
toString(sb);
return sb.toString();
}
private void toString(StringBuilder sb)
{
switch (m_op)
{
case AND:
sb.append("(&");
toString(sb, (List) m_value);
sb.append(")");
break;
case OR:
sb.append("(|");
toString(sb, (List) m_value);
sb.append(")");
break;
case NOT:
sb.append("(!");
toString(sb, (List) m_value);
sb.append(")");
break;
case EQ:
sb.append("(")
.append(m_name)
.append("=");
toEncodedString(sb, m_value);
sb.append(")");
break;
case LTE:
sb.append("(")
.append(m_name)
.append("<=");
toEncodedString(sb, m_value);
sb.append(")");
break;
case GTE:
sb.append("(")
.append(m_name)
.append(">=");
toEncodedString(sb, m_value);
sb.append(")");
break;
case SUBSTRING:
sb.append("(").append(m_name).append("=");
unparseSubstring(sb, (List) m_value);
sb.append(")");
break;
case PRESENT:
sb.append("(").append(m_name).append("=*)");
break;
case APPROX:
sb.append("(").append(m_name).append("~=");
toEncodedString(sb, m_value);
sb.append(")");
break;
case MATCH_ALL:
sb.append("(*)");
break;
}
}
private static void toString(StringBuilder sb, List list)
{
for (Object o : list)
{
SimpleFilter sf = (SimpleFilter) o;
sf.toString(sb);
}
}
private static String toDecodedString(String s, int startIdx, int endIdx)
{
StringBuilder sb = new StringBuilder(endIdx - startIdx);
boolean escaped = false;
for (int i = 0; i < (endIdx - startIdx); i++)
{
char c = s.charAt(startIdx + i);
if (!escaped && (c == '\\'))
{
escaped = true;
}
else
{
escaped = false;
sb.append(c);
}
}
return sb.toString();
}
private static void toEncodedString(StringBuilder sb, Object o)
{
if (o instanceof String)
{
String s = (String) o;
for (int i = 0; i < s.length(); i++)
{
char c = s.charAt(i);
if ((c == '\\') || (c == '(') || (c == ')') || (c == '*'))
{
sb.append('\\');
}
sb.append(c);
}
}
else
{
sb.append(o);
}
}
public static SimpleFilter parse(final String filter) throws InvalidSyntaxException
{
int idx = skipWhitespace(filter, 0);
if ((filter == null) || (filter.length() == 0) || (idx >= filter.length()))
{
throw new InvalidSyntaxException("Null or empty filter.", filter);
}
else if (filter.charAt(idx) != '(')
{
throw new InvalidSyntaxException("Missing opening parenthesis", filter);
}
SimpleFilter sf = null;
List<Object> stack = new ArrayList<Object>();
boolean isEscaped = false;
while (idx < filter.length())
{
if (sf != null)
{
throw new InvalidSyntaxException(
"Only one top-level operation allowed", filter);
}
if (!isEscaped && (filter.charAt(idx) == '('))
{
// Skip paren and following whitespace.
idx = skipWhitespace(filter, idx + 1);
if (filter.charAt(idx) == '&')
{
int peek = skipWhitespace(filter, idx + 1);
if (filter.charAt(peek) == '(')
{
idx = peek - 1;
stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.AND));
}
else
{
stack.add(0, idx);
}
}
else if (filter.charAt(idx) == '|')
{
int peek = skipWhitespace(filter, idx + 1);
if (filter.charAt(peek) == '(')
{
idx = peek - 1;
stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.OR));
}
else
{
stack.add(0, idx);
}
}
else if (filter.charAt(idx) == '!')
{
int peek = skipWhitespace(filter, idx + 1);
if (filter.charAt(peek) == '(')
{
idx = peek - 1;
stack.add(0, new SimpleFilter(null, new ArrayList(), SimpleFilter.NOT));
}
else
{
stack.add(0, idx);
}
}
else
{
stack.add(0, idx);
}
}
else if (!isEscaped && (filter.charAt(idx) == ')'))
{
Object top = stack.remove(0);
if (top instanceof SimpleFilter)
{
if (!stack.isEmpty() && (stack.get(0) instanceof SimpleFilter))
{
((List) ((SimpleFilter) stack.get(0)).m_value).add(top);
}
else
{
sf = (SimpleFilter) top;
}
}
else if (!stack.isEmpty() && (stack.get(0) instanceof SimpleFilter))
{
((List) ((SimpleFilter) stack.get(0)).m_value).add(
SimpleFilter.subfilter(filter, (Integer) top, idx));
}
else
{
sf = SimpleFilter.subfilter(filter, (Integer) top, idx);
}
}
else if (!isEscaped && (filter.charAt(idx) == '\\'))
{
isEscaped = true;
}
else
{
isEscaped = false;
}
idx = skipWhitespace(filter, idx + 1);
}
if (sf == null)
{
throw new InvalidSyntaxException("Missing closing parenthesis", filter);
}
return sf;
}
private static SimpleFilter subfilter(String filter, int startIdx, int endIdx) throws InvalidSyntaxException
{
final String opChars = "=<>~";
// Determine the ending index of the attribute name.
int attrEndIdx = startIdx;
for (int i = 0; i < (endIdx - startIdx); i++)
{
char c = filter.charAt(startIdx + i);
if (opChars.indexOf(c) >= 0)
{
break;
}
else if (!Character.isWhitespace(c))
{
attrEndIdx = startIdx + i + 1;
}
}
if (attrEndIdx == startIdx)
{
throw new InvalidSyntaxException(
"Missing attribute name: " + filter.substring(startIdx, endIdx), filter);
}
String attr = filter.substring(startIdx, attrEndIdx);
// Skip the attribute name and any following whitespace.
startIdx = skipWhitespace(filter, attrEndIdx);
// Determine the operator type.
int op;
switch (filter.charAt(startIdx))
{
case '=':
op = EQ;
startIdx++;
break;
case '<':
if (filter.charAt(startIdx + 1) != '=')
{
throw new InvalidSyntaxException(
"Unknown operator: " + filter.substring(startIdx, endIdx), filter);
}
op = LTE;
startIdx += 2;
break;
case '>':
if (filter.charAt(startIdx + 1) != '=')
{
throw new InvalidSyntaxException(
"Unknown operator: " + filter.substring(startIdx, endIdx), filter);
}
op = GTE;
startIdx += 2;
break;
case '~':
if (filter.charAt(startIdx + 1) != '=')
{
throw new InvalidSyntaxException(
"Unknown operator: " + filter.substring(startIdx, endIdx), filter);
}
op = APPROX;
startIdx += 2;
break;
default:
throw new InvalidSyntaxException(
"Unknown operator: " + filter.substring(startIdx, endIdx), filter);
}
// Parse value.
Object value = toDecodedString(filter, startIdx, endIdx);
// Check if the equality comparison is actually a substring
// or present operation.
if (op == EQ)
{
String valueStr = filter.substring(startIdx, endIdx);
List<String> values = parseSubstring(valueStr);
if ((values.size() == 2)
&& (values.get(0).length() == 0)
&& (values.get(1).length() == 0))
{
op = PRESENT;
}
else if (values.size() > 1)
{
op = SUBSTRING;
value = values;
}
}
return new SimpleFilter(attr, value, op);
}
public static List<String> parseSubstring(String value)
{
List<String> pieces = new ArrayList<String>();
StringBuilder ss = new StringBuilder();
// int kind = SIMPLE; // assume until proven otherwise
boolean wasStar = false; // indicates last piece was a star
boolean leftstar = false; // track if the initial piece is a star
boolean rightstar = false; // track if the final piece is a star
int idx = 0;
// We assume (sub)strings can contain leading and trailing blanks
boolean escaped = false;
loop: for (;;)
{
if (idx >= value.length())
{
if (wasStar)
{
// insert last piece as "" to handle trailing star
rightstar = true;
}
else
{
pieces.add(ss.toString());
// accumulate the last piece
// note that in the case of
// (cn=); this might be
// the string "" (!=null)
}
ss.setLength(0);
break loop;
}
// Read the next character and account for escapes.
char c = value.charAt(idx++);
if (!escaped && (c == '*'))
{
// If we have successive '*' characters, then we can
// effectively collapse them by ignoring succeeding ones.
if (!wasStar)
{
if (ss.length() > 0)
{
pieces.add(ss.toString()); // accumulate the pieces
// between '*' occurrences
}
ss.setLength(0);
// if this is a leading star, then track it
if (pieces.isEmpty())
{
leftstar = true;
}
wasStar = true;
}
}
else if (!escaped && (c == '\\'))
{
escaped = true;
}
else
{
escaped = false;
wasStar = false;
ss.append(c);
}
}
if (leftstar || rightstar || pieces.size() > 1)
{
// insert leading and/or trailing "" to anchor ends
if (rightstar)
{
pieces.add("");
}
if (leftstar)
{
pieces.add(0, "");
}
}
return pieces;
}
public static void unparseSubstring(StringBuilder sb, List<String> pieces)
{
for (int i = 0; i < pieces.size(); i++)
{
if (i > 0)
{
sb.append("*");
}
toEncodedString(sb, pieces.get(i));
}
}
public static boolean compareSubstring(List<String> pieces, String s)
{
// Walk the pieces to match the string
// There are implicit stars between each piece,
// and the first and last pieces might be "" to anchor the match.
// assert (pieces.length > 1)
// minimal case is <string>*<string>
boolean result = true;
int len = pieces.size();
// Special case, if there is only one piece, then
// we must perform an equality test.
if (len == 1)
{
return s.equals(pieces.get(0));
}
// Otherwise, check whether the pieces match
// the specified string.
int index = 0;
loop: for (int i = 0; i < len; i++)
{
String piece = pieces.get(i);
// If this is the first piece, then make sure the
// string starts with it.
if (i == 0)
{
if (!s.startsWith(piece))
{
result = false;
break loop;
}
}
// If this is the last piece, then make sure the
// string ends with it.
if (i == (len - 1))
{
if (s.endsWith(piece) && (s.length() >= (index + piece.length())))
{
result = true;
}
else
{
result = false;
}
break loop;
}
// If this is neither the first or last piece, then
// make sure the string contains it.
if ((i > 0) && (i < (len - 1)))
{
index = s.indexOf(piece, index);
if (index < 0)
{
result = false;
break loop;
}
}
// Move string index beyond the matching piece.
index += piece.length();
}
return result;
}
private static int skipWhitespace(String s, int startIdx)
{
int len = s.length();
while ((startIdx < len) && Character.isWhitespace(s.charAt(startIdx)))
{
startIdx++;
}
return startIdx;
}
public boolean matches(Dictionary dict)
{
boolean matched = true;
if (getOperation() == SimpleFilter.MATCH_ALL)
{
matched = true;
}
else if (getOperation() == SimpleFilter.AND)
{
// Evaluate each subfilter against the remaining capabilities.
// For AND we calculate the intersection of each subfilter.
// We can short-circuit the AND operation if there are no
// remaining capabilities.
List<SimpleFilter> sfs = (List<SimpleFilter>) getValue();
for (int i = 0; matched && (i < sfs.size()); i++)
{
matched = sfs.get(i).matches(dict);
}
}
else if (getOperation() == SimpleFilter.OR)
{
// Evaluate each subfilter against the remaining capabilities.
// For OR we calculate the union of each subfilter.
matched = false;
List<SimpleFilter> sfs = (List<SimpleFilter>) getValue();
for (int i = 0; !matched && (i < sfs.size()); i++)
{
matched = sfs.get(i).matches(dict);
}
}
else if (getOperation() == SimpleFilter.NOT)
{
// Evaluate each subfilter against the remaining capabilities.
// For OR we calculate the union of each subfilter.
List<SimpleFilter> sfs = (List<SimpleFilter>) getValue();
for (int i = 0; i < sfs.size(); i++)
{
matched = !sfs.get(i).matches(dict);
}
}
else
{
matched = false;
Object lhs = dict.get( getName() );
if (lhs != null)
{
matched = compare(lhs, getValue(), getOperation());
}
}
return matched;
}
private static final Class<?>[] STRING_CLASS = new Class[] { String.class };
private static boolean compare(Object lhs, Object rhsUnknown, int op)
{
if (lhs == null)
{
return false;
}
// If this is a PRESENT operation, then just return true immediately
// since we wouldn't be here if the attribute wasn't present.
if (op == SimpleFilter.PRESENT)
{
return true;
}
// If the type is comparable, then we can just return the
// result immediately.
if (lhs instanceof Comparable)
{
// Spec says SUBSTRING is false for all types other than string.
if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
{
return false;
}
Object rhs;
if (op == SimpleFilter.SUBSTRING)
{
rhs = rhsUnknown;
}
else
{
try
{
rhs = coerceType(lhs, (String) rhsUnknown);
}
catch (Exception ex)
{
return false;
}
}
switch (op)
{
case SimpleFilter.EQ :
try
{
return (((Comparable) lhs).compareTo(rhs) == 0);
}
catch (Exception ex)
{
return false;
}
case SimpleFilter.GTE :
try
{
return (((Comparable) lhs).compareTo(rhs) >= 0);
}
catch (Exception ex)
{
return false;
}
case SimpleFilter.LTE :
try
{
return (((Comparable) lhs).compareTo(rhs) <= 0);
}
catch (Exception ex)
{
return false;
}
case SimpleFilter.APPROX :
return compareApproximate((lhs), rhs);
case SimpleFilter.SUBSTRING :
return SimpleFilter.compareSubstring((List<String>) rhs, (String) lhs);
default:
throw new RuntimeException(
"Unknown comparison operator: " + op);
}
}
// Booleans do not implement comparable, so special case them.
else if (lhs instanceof Boolean)
{
Object rhs;
try
{
rhs = coerceType(lhs, (String) rhsUnknown);
}
catch (Exception ex)
{
return false;
}
switch (op)
{
case SimpleFilter.EQ :
case SimpleFilter.GTE :
case SimpleFilter.LTE :
case SimpleFilter.APPROX :
return (lhs.equals(rhs));
default:
throw new RuntimeException(
"Unknown comparison operator: " + op);
}
}
// If the LHS is not a comparable or boolean, check if it is an
// array. If so, convert it to a list so we can treat it as a
// collection.
if (lhs.getClass().isArray())
{
lhs = convertArrayToList(lhs);
}
// If LHS is a collection, then call compare() on each element
// of the collection until a match is found.
if (lhs instanceof Collection)
{
for (Iterator iter = ((Collection) lhs).iterator(); iter.hasNext(); )
{
if (compare(iter.next(), rhsUnknown, op))
{
return true;
}
}
return false;
}
// Spec says SUBSTRING is false for all types other than string.
if ((op == SimpleFilter.SUBSTRING) && !(lhs instanceof String))
{
return false;
}
// Since we cannot identify the LHS type, then we can only perform
// equality comparison.
try
{
return lhs.equals(coerceType(lhs, (String) rhsUnknown));
}
catch (Exception ex)
{
return false;
}
}
private static boolean compareApproximate(Object lhs, Object rhs)
{
if (rhs instanceof String)
{
return removeWhitespace((String) lhs)
.equalsIgnoreCase(removeWhitespace((String) rhs));
}
else if (rhs instanceof Character)
{
return Character.toLowerCase(((Character) lhs))
== Character.toLowerCase(((Character) rhs));
}
return lhs.equals(rhs);
}
private static String removeWhitespace(String s)
{
StringBuilder sb = new StringBuilder(s.length());
for (int i = 0; i < s.length(); i++)
{
if (!Character.isWhitespace(s.charAt(i)))
{
sb.append(s.charAt(i));
}
}
return sb.toString();
}
private static Object coerceType(Object lhs, String rhsString) throws Exception
{
// If the LHS expects a string, then we can just return
// the RHS since it is a string.
if (lhs.getClass() == rhsString.getClass())
{
return rhsString;
}
// Try to convert the RHS type to the LHS type by using
// the string constructor of the LHS class, if it has one.
Object rhs = null;
try
{
// The Character class is a special case, since its constructor
// does not take a string, so handle it separately.
if (lhs instanceof Character)
{
rhs = rhsString.charAt(0);
}
else
{
// Spec says we should trim number types.
if ((lhs instanceof Number) || (lhs instanceof Boolean))
{
rhsString = rhsString.trim();
}
Constructor ctor = lhs.getClass().getConstructor(STRING_CLASS);
ctor.setAccessible(true);
rhs = ctor.newInstance(rhsString);
}
}
catch (Exception ex)
{
throw new Exception(
"Could not instantiate class "
+ lhs.getClass().getName()
+ " from string constructor with argument '"
+ rhsString + "' because " + ex);
}
return rhs;
}
/**
* This is an ugly utility method to convert an array of primitives
* to an array of primitive wrapper objects. This method simplifies
* processing LDAP filters since the special case of primitive arrays
* can be ignored.
* @param array An array of primitive types.
* @return An corresponding array using pritive wrapper objects.
**/
private static List convertArrayToList(Object array)
{
int len = Array.getLength(array);
List<Object> list = new ArrayList<Object>(len);
for (int i = 0; i < len; i++)
{
list.add(Array.get(array, i));
}
return list;
}
}

View File

@ -0,0 +1,218 @@
/*
* 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.cm.impl;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.LinkedList;
import org.osgi.service.log.LogService;
/**
* The <code>UpdateThread</code> is the thread used to update managed services
* and managed service factories as well as to send configuration events.
*/
public class UpdateThread implements Runnable
{
// the thread group into which the worker thread will be placed
private final ThreadGroup workerThreadGroup;
// the thread's base name
private final String workerBaseName;
// the queue of Runnable instances to be run
private final LinkedList<Runnable> updateTasks;
// the actual thread
private Thread worker;
// the access control context
private final AccessControlContext acc;
public UpdateThread( final ThreadGroup tg, final String name )
{
this.workerThreadGroup = tg;
this.workerBaseName = name;
this.acc = AccessController.getContext();
this.updateTasks = new LinkedList<>();
}
// waits on Runnable instances coming into the queue. As instances come
// in, this method calls the Runnable.run method, logs any exception
// happening and keeps on waiting for the next Runnable. If the Runnable
// taken from the queue is this thread instance itself, the thread
// terminates.
@Override
public void run()
{
for ( ;; )
{
Runnable task;
synchronized ( updateTasks )
{
while ( updateTasks.isEmpty() )
{
try
{
updateTasks.wait();
}
catch ( InterruptedException ie )
{
// don't care
}
}
task = updateTasks.removeFirst();
}
// return if the task is this thread itself
if ( task == this )
{
return;
}
// otherwise execute the task, log any issues
try
{
// set the thread name indicating the current task
Thread.currentThread().setName( workerBaseName + " (" + task + ")" );
Log.logger.log( LogService.LOG_DEBUG, "Running task {0}", new Object[]
{ task } );
run0(task);
}
catch ( Throwable t )
{
Log.logger.log( LogService.LOG_ERROR, "Unexpected problem executing task", t );
}
finally
{
// reset the thread name to "idle"
Thread.currentThread().setName( workerBaseName );
}
}
}
void run0(final Runnable task) throws Throwable {
if (System.getSecurityManager() != null) {
try {
AccessController.doPrivileged(
new PrivilegedExceptionAction<Void>() {
@Override
public Void run() throws Exception {
task.run();
return null;
}
},
acc
);
}
catch (PrivilegedActionException pae) {
throw pae.getException();
}
}
else {
task.run();
}
}
/**
* Starts processing the queued tasks. This method does nothing if the
* worker has already been started.
*/
synchronized void start()
{
if ( this.worker == null )
{
Thread workerThread = new Thread( workerThreadGroup, this, workerBaseName );
workerThread.setDaemon( true );
workerThread.start();
this.worker = workerThread;
}
}
/**
* Terminates the worker thread and waits for the thread to have processed
* all outstanding events up to and including the termination job. All
* jobs {@link #schedule(Runnable) scheduled} after termination has been
* initiated will not be processed any more. This method does nothing if
* the worker thread is not currently active.
* <p>
* If the worker thread does not terminate within 5 seconds it is killed
* by calling the (deprecated) <code>Thread.stop()</code> method. It may
* be that the worker thread may be blocked by a deadlock (it should not,
* though). In this case hope is that <code>Thread.stop()</code> will be
* able to released that deadlock at the expense of one or more tasks to
* not be executed any longer.... In any case an ERROR message is logged
* with the LogService in this situation.
*/
synchronized void terminate()
{
if ( this.worker != null )
{
Thread workerThread = this.worker;
this.worker = null;
schedule( this );
// wait for all updates to terminate (<= 10 seconds !)
try
{
workerThread.join( 5000 );
}
catch ( InterruptedException ie )
{
// don't really care
}
if ( workerThread.isAlive() )
{
Log.logger.log( LogService.LOG_ERROR,
"Worker thread {0} did not terminate within 5 seconds; trying to kill", new Object[]
{ workerBaseName } );
workerThread.stop();
}
}
}
// queue the given runnable to be run as soon as possible
void schedule( Runnable update )
{
synchronized ( updateTasks )
{
Log.logger.log( LogService.LOG_DEBUG, "Scheduling task {0}", new Object[]
{ update } );
// append to the task queue
updateTasks.add( update );
// notify the waiting thread
updateTasks.notifyAll();
}
}
}

View File

@ -0,0 +1,337 @@
/*
* 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.cm.impl.helper;
import java.security.AccessControlContext;
import java.security.AccessController;
import java.security.DomainCombiner;
import java.security.Permission;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.List;
import org.apache.felix.cm.impl.CaseInsensitiveDictionary;
import org.apache.felix.cm.impl.ConfigurationManager;
import org.apache.felix.cm.impl.Log;
import org.apache.felix.cm.impl.RankingComparator;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
import org.osgi.service.cm.ManagedServiceFactory;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
/**
* The <code>BaseTracker</code> is the base class for tracking
* <code>ManagedService</code> and <code>ManagedServiceFactory</code>
* services. It maps their <code>ServiceRegistration</code> to the
* {@link ConfigurationMap} mapping their service PIDs to provided
* configuration.
*/
public abstract class BaseTracker<S> extends ServiceTracker<S, ConfigurationMap<?>>
{
protected final ConfigurationManager cm;
private final boolean managedServiceFactory;
protected BaseTracker( final ConfigurationManager cm, final boolean managedServiceFactory )
{
super( cm.getBundleContext(), ( managedServiceFactory ? ManagedServiceFactory.class.getName()
: ManagedService.class.getName() ), null );
this.cm = cm;
this.managedServiceFactory = managedServiceFactory;
open();
}
@Override
public ConfigurationMap<?> addingService( ServiceReference<S> reference )
{
Log.logger.log( LogService.LOG_DEBUG, "Registering service {0}", new Object[]
{ reference } );
final String[] pids = getServicePid( reference );
final ConfigurationMap<?> configurations = createConfigurationMap( pids );
configure( reference, pids, configurations );
return configurations;
}
@Override
public void modifiedService( ServiceReference<S> reference, ConfigurationMap<?> service )
{
Log.logger.log( LogService.LOG_DEBUG, "Modified service {0}", new Object[]
{ reference} );
String[] pids = getServicePid( reference );
if ( service.isDifferentPids( pids ) )
{
service.setConfiguredPids( pids );
configure( reference, pids, service );
}
}
@Override
public void removedService( ServiceReference<S> reference, ConfigurationMap<?> service )
{
// just log
Log.logger.log( LogService.LOG_DEBUG, "Unregistering service {0}", new Object[]
{ reference } );
}
private void configure( ServiceReference<S> reference, String[] pids, ConfigurationMap<?> configurations )
{
if ( pids != null )
{
this.cm.configure( pids, reference, managedServiceFactory, configurations );
}
}
public final List<ServiceReference<S>> getServices( final TargetedPID pid )
{
ServiceReference<S>[] refs = this.getServiceReferences();
if ( refs != null )
{
ArrayList<ServiceReference<S>> result = new ArrayList<ServiceReference<S>>( refs.length );
for ( ServiceReference<S> ref : refs )
{
ConfigurationMap map = this.getService( ref );
if ( map != null
&& ( map.accepts( pid.getRawPid() ) || ( map.accepts( pid.getServicePid() ) && pid
.matchesTarget( ref ) ) ) )
{
result.add( ref );
}
}
if ( result.size() > 1 )
{
Collections.sort( result, RankingComparator.SRV_RANKING );
}
return result;
}
return Collections.emptyList();
}
protected abstract ConfigurationMap<?> createConfigurationMap( String[] pids );
/**
* Returns the String to be used as the PID of the service PID for the
* {@link TargetedPID pid} retrieved from the configuration.
* <p>
* This method will return {@link TargetedPID#getServicePid()} most of
* the time except if the service PID used for the consumer's service
* registration contains one or more pipe symbols (|). In this case
* {@link TargetedPID#getRawPid()} might be returned.
*
* @param service The reference ot the service for which the service
* PID is to be returned.
* @param pid The {@link TargetedPID} for which to return the service
* PID.
* @return The service PID or <code>null</code> if the service does not
* respond to the targeted PID at all.
*/
public abstract String getServicePid( ServiceReference<S> service, TargetedPID pid );
/**
* Updates the given service with the provided configuration.
* <p>
* See the implementations of this method for more information.
*
* @param service The reference to the service to update
* @param configPid The targeted configuration PID
* @param factoryPid The targeted factory PID or <code>null</code> for
* a non-factory configuration
* @param properties The configuration properties, which may be
* <code>null</code> if this is the provisioning call upon
* service registration of a ManagedService
* @param revision The configuration revision or -1 if there is no
* configuration actually to provide.
* @param configurationMap The PID to configuration map for PIDs
* used by the service to update
*
* @see ManagedServiceTracker#provideConfiguration(ServiceReference, TargetedPID, TargetedPID, Dictionary, long, ConfigurationMap)
* @see ManagedServiceFactoryTracker#provideConfiguration(ServiceReference, TargetedPID, TargetedPID, Dictionary, long, ConfigurationMap)
*/
public abstract void provideConfiguration( ServiceReference<S> service, TargetedPID configPid,
TargetedPID factoryPid, Dictionary<String, ?> properties, long revision,
ConfigurationMap<?> configurationMap);
/**
* Remove the configuration indicated by the {@code configPid} from
* the service.
*
* @param service The reference to the service from which the
* configuration is to be removed.
* @param configPid The {@link TargetedPID} of the configuration
* @param factoryPid The {@link TargetedPID factory PID} of the
* configuration. This may be {@code null} for a non-factory
* configuration.
*/
public abstract void removeConfiguration( ServiceReference<S> service, TargetedPID configPid, TargetedPID factoryPid);
protected final S getRealService( ServiceReference<S> reference )
{
return this.context.getService( reference );
}
protected final void ungetRealService( ServiceReference<S> reference )
{
this.context.ungetService( reference );
}
protected final Dictionary<String, Object> getProperties( Dictionary<String, ?> rawProperties, ServiceReference<?> service,
String configPid, String factoryPid )
{
Dictionary<String, Object> props = new CaseInsensitiveDictionary( rawProperties );
this.cm.callPlugins( props, service, configPid, factoryPid );
return props;
}
protected final void handleCallBackError( final Throwable error, final ServiceReference target, final TargetedPID pid )
{
if ( error instanceof ConfigurationException )
{
final ConfigurationException ce = ( ConfigurationException ) error;
if ( ce.getProperty() != null )
{
Log.logger.log( LogService.LOG_ERROR,
"{0}: Updating property {1} of configuration {2} caused a problem: {3}", new Object[]
{ target , ce.getProperty(), pid, ce.getReason(), ce } );
}
else
{
Log.logger.log( LogService.LOG_ERROR, "{0}: Updating configuration {1} caused a problem: {2}",
new Object[]
{ target, pid, ce.getReason(), ce } );
}
}
else
{
{
Log.logger.log( LogService.LOG_ERROR, "{0}: Unexpected problem updating configuration {1}", new Object[]
{ target, pid, error } );
}
}
}
/**
* Returns the <code>service.pid</code> property of the service reference as
* an array of strings or <code>null</code> if the service reference does
* not have a service PID property.
* <p>
* The service.pid property may be a single string, in which case a single
* element array is returned. If the property is an array of string, this
* array is returned. If the property is a collection it is assumed to be a
* collection of strings and the collection is converted to an array to be
* returned. Otherwise (also if the property is not set) <code>null</code>
* is returned.
*
* @throws NullPointerException
* if reference is <code>null</code>
* @throws ArrayStoreException
* if the service pid is a collection and not all elements are
* strings.
*/
private static String[] getServicePid( ServiceReference reference )
{
Object pidObj = reference.getProperty( Constants.SERVICE_PID );
if ( pidObj instanceof String )
{
return new String[]
{ ( String ) pidObj };
}
else if ( pidObj instanceof String[] )
{
return ( String[] ) pidObj;
}
else if ( pidObj instanceof Collection )
{
Collection pidCollection = ( Collection ) pidObj;
return ( String[] ) pidCollection.toArray( new String[pidCollection.size()] );
}
return null;
}
public static AccessControlContext getAccessControlContext( final Bundle bundle )
{
return new AccessControlContext(AccessController.getContext(), new CMDomainCombiner(bundle));
}
private static class CMDomainCombiner implements DomainCombiner {
private final CMProtectionDomain domain;
CMDomainCombiner(Bundle bundle) {
// FELIX-5908 - Eagerly instantiate this class
// to avoid a potential NoClassDefFoundError
this.domain = new CMProtectionDomain(bundle);
}
@Override
public ProtectionDomain[] combine(ProtectionDomain[] arg0,
ProtectionDomain[] arg1) {
return new ProtectionDomain[] { domain };
}
}
private static class CMProtectionDomain extends ProtectionDomain {
private final Bundle bundle;
CMProtectionDomain(Bundle bundle) {
super(null, null);
this.bundle = bundle;
}
@Override
public boolean implies(Permission permission) {
try {
return bundle.hasPermission(permission);
} catch (IllegalStateException e) {
return false;
}
}
}
}

View File

@ -0,0 +1,167 @@
/*
* 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.cm.impl.helper;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public abstract class ConfigurationMap<T>
{
private Map<String, T> configurations;
protected ConfigurationMap( final String[] configuredPids )
{
this.configurations = Collections.emptyMap();
setConfiguredPids( configuredPids );
}
protected abstract Map<String, T> createMap( int size );
protected abstract boolean shallTake( TargetedPID configPid, TargetedPID factoryPid, long revision );
protected abstract void record( TargetedPID configPid, TargetedPID factoryPid, long revision );
protected abstract boolean removeConfiguration( TargetedPID configPid, TargetedPID factoryPid );
protected T get( final TargetedPID key )
{
final String servicePid = getKeyPid( key );
if ( servicePid != null )
{
return this.configurations.get( servicePid );
}
// the targeted PID does not match here
return null;
}
protected void put( final TargetedPID key, final T value )
{
final String servicePid = getKeyPid( key );
if ( servicePid != null )
{
this.configurations.put( servicePid, value );
}
}
protected String getKeyPid( final TargetedPID targetedPid )
{
// regular use case: service PID is the key
if ( this.accepts( targetedPid.getServicePid() ) )
{
return targetedPid.getServicePid();
}
// the raw PID is the key (if the service PID contains pipes)
if ( this.accepts( targetedPid.getRawPid() ) )
{
return targetedPid.getRawPid();
}
// this is not really expected here
return null;
}
/**
* Returns <code>true</code> if this map is foreseen to take a
* configuration with the given service PID.
*
* @param servicePid The service PID of the configuration which is
* the part of the targeted PID without the bundle's symbolic
* name, version, and location; i.e. {@link TargetedPID#getServicePid()}
*
* @return <code>true</code> if this map is configured to take
* configurations for the service PID.
*/
public boolean accepts( final String servicePid )
{
return configurations.containsKey( servicePid );
}
public void setConfiguredPids( String[] configuredPids )
{
final Map<String, T> newConfigs;
if ( configuredPids != null )
{
newConfigs = this.createMap( configuredPids.length );
for ( String pid : configuredPids )
{
newConfigs.put( pid, this.configurations.get( pid ) );
}
}
else
{
newConfigs = Collections.emptyMap();
}
this.configurations = newConfigs;
}
/**
* Returns <code>true</code> if the set of service PIDs given is
* different from the current set of service PIDs.
* <p>
* For comparison a <code>null</code> argument is considered to
* be an empty set of service PIDs.
*
* @param pids The new set of service PIDs to be compared to the
* current set of service PIDs.
* @return <code>true</code> if the set is different
*/
boolean isDifferentPids( final String[] pids )
{
if ( this.configurations.isEmpty() && pids == null )
{
return false;
}
else if ( this.configurations.isEmpty() )
{
return true;
}
else if ( pids == null )
{
return true;
}
else if ( this.configurations.size() != pids.length )
{
return true;
}
else
{
Set<String> thisPids = this.configurations.keySet();
HashSet<String> otherPids = new HashSet<String>( Arrays.asList( pids ) );
return !thisPids.equals( otherPids );
}
}
}

View File

@ -0,0 +1,112 @@
/*
* 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.cm.impl.helper;
import java.util.HashMap;
import java.util.Map;
class ManagedServiceConfigurationMap extends ConfigurationMap<ManagedServiceConfigurationMap.Entry>
{
protected ManagedServiceConfigurationMap( String[] configuredPids )
{
super( configuredPids );
}
@Override
protected Map<String, Entry> createMap( int size )
{
return new HashMap<String, Entry>( size );
}
@Override
protected boolean shallTake( TargetedPID configPid, TargetedPID factoryPid, long revision )
{
Entry entry = this.get( configPid );
// no configuration assigned yet, take it
if ( entry == null )
{
return true;
}
// compare revision numbers if raw PID is the same
if ( configPid.equals( entry.targetedPid ) )
{
return revision > entry.revision;
}
// otherwise only take if targeted PID is more binding
return configPid.bindsStronger( entry.targetedPid );
}
@Override
protected boolean removeConfiguration( TargetedPID configPid, TargetedPID factoryPid )
{
Entry entry = this.get( configPid );
// nothing to remove because the service does not know it anyway
if ( entry == null )
{
return false;
}
// update if the used targeted PID matches
if ( configPid.equals( entry.targetedPid ) )
{
return true;
}
// the config is not assigned and so there must not be a removal
return false;
}
@Override
protected void record( TargetedPID configPid, TargetedPID factoryPid, long revision )
{
final Entry entry = ( revision < 0 ) ? null : new Entry( configPid, revision );
this.put( configPid, entry );
}
static class Entry
{
final TargetedPID targetedPid;
final long revision;
Entry( final TargetedPID targetedPid, final long revision )
{
this.targetedPid = targetedPid;
this.revision = revision;
}
@Override
public String toString()
{
return "Entry(pid=" + targetedPid + ",rev=" + revision + ")";
}
}
}

View File

@ -0,0 +1,93 @@
/*
* 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.cm.impl.helper;
import java.util.HashMap;
import java.util.Map;
public class ManagedServiceFactoryConfigurationMap extends ConfigurationMap<Map<TargetedPID, Long>>
{
protected ManagedServiceFactoryConfigurationMap( String[] configuredPids )
{
super( configuredPids );
}
@Override
protected Map<String, Map<TargetedPID, Long>> createMap( int size )
{
return new HashMap<String, Map<TargetedPID, Long>>( size );
}
@Override
protected boolean shallTake( TargetedPID configPid, TargetedPID factoryPid, long revision )
{
Map<TargetedPID, Long> configs = this.get( factoryPid );
// no configuration yet, yes we can
if (configs == null) {
return true;
}
Long rev = configs.get( configPid );
// this config is missing, yes we can
if (rev == null) {
return true;
}
// finally take if newer
return rev < revision;
}
@Override
protected boolean removeConfiguration( TargetedPID configPid, TargetedPID factoryPid )
{
Map<TargetedPID, Long> configs = this.get( factoryPid );
return configs != null && configs.containsKey( configPid );
}
@Override
protected void record( TargetedPID configPid, TargetedPID factoryPid, long revision )
{
Map<TargetedPID, Long> configs = this.get( factoryPid );
if (configs == null) {
configs = new HashMap<TargetedPID, Long>( 4 );
}
if (revision < 0) {
configs.remove( configPid );
} else {
configs.put(configPid, revision);
}
if (configs.size() == 0) {
configs = null;
}
this.put( factoryPid, configs );
}
}

View File

@ -0,0 +1,182 @@
/*
* 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.cm.impl.helper;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Dictionary;
import org.apache.felix.cm.impl.ConfigurationManager;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedServiceFactory;
public class ManagedServiceFactoryTracker extends BaseTracker<ManagedServiceFactory>
{
public ManagedServiceFactoryTracker( ConfigurationManager cm )
{
super( cm, true );
}
@Override
protected ConfigurationMap<?> createConfigurationMap( String[] pids )
{
return new ManagedServiceFactoryConfigurationMap( pids );
}
/**
* Always returns the raw PID because for a ManagedServiceFactory
* the configuration's PID is automatically generated and is not a
* real targeted PID.
*/
@Override
public String getServicePid( ServiceReference<ManagedServiceFactory> service, TargetedPID pid )
{
return pid.getRawPid();
}
@Override
public void provideConfiguration( ServiceReference<ManagedServiceFactory> reference, TargetedPID configPid,
TargetedPID factoryPid, Dictionary<String, ?> properties, long revision, ConfigurationMap<?> configs )
{
// Get the ManagedServiceFactory and terminate here if already
// unregistered from the framework concurrently
ManagedServiceFactory service = getRealService( reference );
if (service == null) {
return;
}
// Get the Configuration-to-PID map from the parameter or from
// the service tracker. If not available, the service tracker
// already unregistered this service concurrently
if ( configs == null )
{
configs = this.getService( reference );
if ( configs == null )
{
return;
}
}
// Both the ManagedService to update and the Configuration-to-PID
// are available, so the service can be updated with the
// configuration (which may be null)
if ( configs.shallTake( configPid, factoryPid, revision ) )
{
try
{
Dictionary props = getProperties( properties, reference, configPid.toString(),
factoryPid.toString() );
updated( reference, service, configPid.toString(), props );
configs.record( configPid, factoryPid, revision );
}
catch ( Throwable t )
{
this.handleCallBackError( t, reference, configPid );
}
finally
{
this.ungetRealService( reference );
}
}
}
@Override
public void removeConfiguration( ServiceReference<ManagedServiceFactory> reference, TargetedPID configPid,
TargetedPID factoryPid )
{
final ManagedServiceFactory service = this.getRealService( reference );
final ConfigurationMap configs = this.getService( reference );
if ( service != null && configs != null)
{
if ( configs.removeConfiguration( configPid, factoryPid ) )
{
try
{
deleted( reference, service, configPid.toString() );
configs.record( configPid, factoryPid, -1 );
}
catch ( Throwable t )
{
this.handleCallBackError( t, reference, configPid );
}
finally
{
this.ungetRealService( reference );
}
}
}
}
private void updated( final ServiceReference<ManagedServiceFactory> reference, final ManagedServiceFactory service, final String pid, final Dictionary properties )
throws ConfigurationException
{
if ( System.getSecurityManager() != null )
{
try
{
AccessController.doPrivileged( new PrivilegedExceptionAction()
{
public Object run() throws ConfigurationException
{
service.updated( pid, properties );
return null;
}
}, getAccessControlContext( reference.getBundle() ) );
}
catch ( PrivilegedActionException e )
{
throw ( ConfigurationException ) e.getException();
}
}
else
{
service.updated( pid, properties );
}
}
private void deleted( final ServiceReference<ManagedServiceFactory> reference, final ManagedServiceFactory service, final String pid )
{
if ( System.getSecurityManager() != null )
{
AccessController.doPrivileged( new PrivilegedAction()
{
public Object run()
{
service.deleted( pid );
return null;
}
}, getAccessControlContext( reference.getBundle() ) );
}
else
{
service.deleted( pid );
}
}
}

View File

@ -0,0 +1,192 @@
/*
* 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.cm.impl.helper;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Dictionary;
import java.util.Hashtable;
import org.apache.felix.cm.impl.ConfigurationManager;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationException;
import org.osgi.service.cm.ManagedService;
public class ManagedServiceTracker extends BaseTracker<ManagedService>
{
private static final Dictionary<String, ?> INITIAL_MARKER = new Hashtable<String, Object>( 0 );
public ManagedServiceTracker( ConfigurationManager cm )
{
super( cm, false );
}
@Override
protected ConfigurationMap<?> createConfigurationMap( String[] pids )
{
return new ManagedServiceConfigurationMap( pids );
}
@Override
public String getServicePid( ServiceReference<ManagedService> service, TargetedPID pid )
{
final ConfigurationMap configs = this.getService( service );
if ( configs != null )
{
return configs.getKeyPid( pid );
}
// this service is not handled...
return null;
}
/**
* Provides the given configuration to the managed service.
* <p>
* Depending on targeted PIDs this configuration may not actually be
* provided if the service already has more strictly binding
* configuration from a targeted configuration bound.
* <p>
* If the revision is a negative value, the provided configuration
* is assigned to the ManagedService in any case without further
* checks. This allows a replacement configuration for a deleted
* or invisible configuration to be assigned without first removing
* the deleted or invisible configuration.
*/
@Override
public void provideConfiguration( ServiceReference<ManagedService> service, TargetedPID configPid,
TargetedPID factoryPid, Dictionary<String, ?> properties, long revision, ConfigurationMap<?> configs )
{
Dictionary<String, ?> supplied = ( properties == null ) ? INITIAL_MARKER : properties;
updateService( service, configPid, supplied, revision, configs );
}
@Override
public void removeConfiguration( ServiceReference<ManagedService> service, TargetedPID configPid,
TargetedPID factoryPid )
{
updateService( service, configPid, null, -1, null );
}
private void updateService( ServiceReference<ManagedService> service, final TargetedPID configPid,
Dictionary<String, ?> properties, long revision, ConfigurationMap<?> configs)
{
// Get the ManagedService and terminate here if already
// unregistered from the framework concurrently
final ManagedService srv = this.getRealService( service );
if (srv == null) {
return;
}
// Get the Configuration-to-PID map from the parameter or from
// the service tracker. If not available, the service tracker
// already unregistered this service concurrently
if ( configs == null )
{
configs = this.getService( service );
if ( configs == null )
{
return;
}
}
// Both the ManagedService to update and the Configuration-to-PID
// are available, so the service can be updated with the
// configuration (which may be null)
boolean doUpdate = false;
if ( properties == null )
{
doUpdate = configs.removeConfiguration( configPid, null );
}
else if ( properties == INITIAL_MARKER )
{
// initial call to ManagedService may supply null properties
properties = null;
revision = -1;
doUpdate = true;
}
else if ( revision < 0 || configs.shallTake( configPid, null, revision ) )
{
// run the plugins and cause the update
properties = getProperties( properties, service, configPid.toString(), null );
doUpdate = true;
revision = Math.abs( revision );
}
else
{
// new configuration is not a better match, don't update
doUpdate = false;
}
if ( doUpdate )
{
try
{
updated( service, srv, properties );
configs.record( configPid, null, revision );
}
catch ( Throwable t )
{
this.handleCallBackError( t, service, configPid );
}
finally
{
this.ungetRealService( service );
}
}
}
private void updated( final ServiceReference<ManagedService> reference, final ManagedService service, final Dictionary properties) throws ConfigurationException
{
if ( System.getSecurityManager() != null )
{
try
{
AccessController.doPrivileged( new PrivilegedExceptionAction()
{
public Object run() throws ConfigurationException
{
service.updated( properties );
return null;
}
}, getAccessControlContext( reference.getBundle() ) );
}
catch ( PrivilegedActionException e )
{
throw ( ConfigurationException ) e.getException();
}
}
else
{
service.updated( properties );
}
}
}

View File

@ -0,0 +1,241 @@
/*
* 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.cm.impl.helper;
import org.apache.felix.cm.impl.Activator;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;
/**
* The <code>TargetedPID</code> class represents a targeted PID as read
* from a configuration object.
* <p>
* For a factory configuration the <code>TargetedPID</code> represents
* the factory PID of the configuration. Otherwise it represents the
* PID itself of the configuration.
*/
public class TargetedPID
{
private final String rawPid;
private final String servicePid;
private final String symbolicName;
private final String version;
private final String location;
/**
* The level of binding of this targeted PID:
* <ul>
* <li><code>0</code> -- this PID is not targeted at all</li>
* <li><code>1</code> -- this PID is targeted by the symbolic name</li>
* <li><code>2</code> -- this PID is targeted by the symbolic name and version</li>
* <li><code>3</code> -- this PID is targeted by the symoblic name, version, and location</li>
* </ul>
*/
private final short bindingLevel;
public TargetedPID( final String rawPid )
{
this.rawPid = rawPid;
if ( rawPid.indexOf( '|' ) < 0 )
{
this.servicePid = rawPid;
this.symbolicName = null;
this.version = null;
this.location = null;
this.bindingLevel = 0;
}
else
{
int start = 0;
int end = rawPid.indexOf( '|' );
this.servicePid = rawPid.substring( start, end );
start = end + 1;
end = rawPid.indexOf( '|', start );
if ( end >= 0 )
{
this.symbolicName = rawPid.substring( start, end );
start = end + 1;
end = rawPid.indexOf( '|', start );
if ( end >= 0 )
{
this.version = rawPid.substring( start, end );
this.location = rawPid.substring( end + 1 );
this.bindingLevel = 3;
}
else
{
this.version = rawPid.substring( start );
this.location = null;
this.bindingLevel = 2;
}
}
else
{
this.symbolicName = rawPid.substring( start );
this.version = null;
this.location = null;
this.bindingLevel = 1;
}
}
}
/**
* Returns true if the target of this PID (bundle symbolic name,
* version, and location) match the bundle registering the referenced
* service.
* <p>
* This method just checks the target not the PID value itself, so
* this method returning <code>true</code> does not indicate whether
* the service actually is registered with a service PID equal to the
* raw PID of this targeted PID.
* <p>
* This method also returns <code>false</code> if the service has
* concurrently been unregistered and the registering bundle is now
* <code>null</code>.
*
* @param reference <code>ServiceReference</code> to the registered
* service
* @return <code>true</code> if the referenced service matches the
* target of this PID.
*/
public boolean matchesTarget( ServiceReference<?> reference )
{
// already unregistered
final Bundle serviceBundle = reference.getBundle();
if ( serviceBundle == null )
{
return false;
}
// This is not really targeted
if ( this.symbolicName == null )
{
return true;
}
// bundle symbolic names don't match
if ( !this.symbolicName.equals( serviceBundle.getSymbolicName() ) )
{
return false;
}
// no more specific target
if ( this.version == null )
{
return true;
}
// bundle version does not match
if ( !this.version.equals( serviceBundle.getVersion().toString() ) )
{
return false;
}
// assert bundle location match
return this.location == null || this.location.equals( Activator.getLocation(serviceBundle) );
}
/**
* Gets the raw PID with which this instance has been created.
* <p>
* If an actual service PID contains pipe symbols that PID might be
* considered being targeted PID without it actually being one. This
* method provides access to the raw PID to allow for such services to
* be configured.
*/
public String getRawPid()
{
return rawPid;
}
/**
* Returns the service PID of this targeted PID which basically is
* the targeted PID without the targeting information.
*/
public String getServicePid()
{
return servicePid;
}
/**
* Returns <code>true</code> if this targeted PID binds stronger than
* the <code>other</code> {@link TargetedPID}.
* <p>
* This method assumes both targeted PIDs have already been checked for
* suitability for the bundle encoded in the targetting.
*
* @param other The targeted PID to check whether it is binding stronger
* or not.
* @return <code>true</code> if the <code>other</code> targeted PID
* is binding strong.
*/
boolean bindsStronger( final TargetedPID other )
{
return this.bindingLevel > other.bindingLevel;
}
@Override
public int hashCode()
{
return this.rawPid.hashCode();
}
@Override
public boolean equals( Object obj )
{
if ( obj == null )
{
return false;
}
else if ( obj == this )
{
return true;
}
// assume equality if same class and raw PID equals
if ( this.getClass() == obj.getClass() )
{
return this.rawPid.equals( ( ( TargetedPID ) obj ).rawPid );
}
// not the same class or different raw PID
return false;
}
@Override
public String toString()
{
return this.rawPid;
}
}

View File

@ -0,0 +1,344 @@
/*
* 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.cm.impl.persistence;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.CaseInsensitiveDictionary;
import org.apache.felix.cm.impl.SimpleFilter;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* The <code>CachingPersistenceManagerProxy</code> adds a caching layer to the
* underlying actual {@link PersistenceManager} implementation. All API calls
* are also (or primarily) routed through a local cache of dictionaries indexed
* by the <code>service.pid</code>.
*/
public class CachingPersistenceManagerProxy implements ExtPersistenceManager
{
/** The actual PersistenceManager */
private final PersistenceManager pm;
/** Cached dictionaries */
private final Map<String, CaseInsensitiveDictionary> cache = new HashMap<>();
/** Protecting lock */
private final ReadWriteLock globalLock = new ReentrantReadWriteLock();
/**
* Indicates whether the getDictionaries method has already been called
* and the cache is complete with respect to the contents of the underlying
* persistence manager.
*/
private volatile boolean fullyLoaded;
/** Factory configuration cache. */
private final Map<String, Set<String>> factoryConfigCache = new HashMap<>();
/**
* Creates a new caching layer for the given actual {@link PersistenceManager}.
* @param pm The actual {@link PersistenceManager}
*/
public CachingPersistenceManagerProxy( final PersistenceManager pm )
{
this.pm = pm;
}
@Override
public PersistenceManager getDelegatee()
{
return pm;
}
/**
* Remove the configuration with the given PID. This implementation removes
* the entry from the cache before calling the underlying persistence
* manager.
*/
@Override
public void delete( final String pid ) throws IOException
{
Lock lock = globalLock.writeLock();
try
{
lock.lock();
final Dictionary props = cache.remove( pid );
if ( props != null )
{
final String factoryPid = (String)props.get(ConfigurationAdmin.SERVICE_FACTORYPID);
if ( factoryPid != null )
{
final Set<String> factoryPids = this.factoryConfigCache.get(factoryPid);
if ( factoryPids != null )
{
factoryPids.remove(pid);
if ( factoryPids.isEmpty() )
{
this.factoryConfigCache.remove(factoryPid);
}
}
}
}
pm.delete(pid);
}
finally
{
lock.unlock();
}
}
/**
* Checks whether a dictionary with the given pid exists. First checks for
* the existence in the cache. If not in the cache the underlying
* persistence manager is asked.
*/
@Override
public boolean exists( final String pid )
{
Lock lock = globalLock.readLock();
try
{
lock.lock();
return cache.containsKey( pid ) || ( !fullyLoaded && pm.exists( pid ) );
}
finally
{
lock.unlock();
}
}
/**
* Returns an <code>Enumeration</code> of <code>Dictionary</code> objects
* representing the configurations stored in the underlying persistence
* managers. The dictionaries returned are guaranteed to contain the
* <code>service.pid</code> property.
* <p>
* Note, that each call to this method will return new dictionary objects.
* That is modifying the contents of a dictionary returned from this method
* has no influence on the dictionaries stored in the cache.
*/
@Override
public Enumeration getDictionaries() throws IOException
{
return Collections.enumeration(getDictionaries( null ));
}
private final CaseInsensitiveDictionary cache(final Dictionary props)
{
final String pid = (String) props.get( Constants.SERVICE_PID );
CaseInsensitiveDictionary dict = null;
if ( pid != null )
{
dict = cache.get(pid);
if ( dict == null )
{
dict = new CaseInsensitiveDictionary(props);
cache.put( pid, dict );
final String factoryPid = (String)props.get(ConfigurationAdmin.SERVICE_FACTORYPID);
if ( factoryPid != null )
{
Set<String> factoryPids = this.factoryConfigCache.get(factoryPid);
if ( factoryPids == null )
{
factoryPids = new HashSet<>();
this.factoryConfigCache.put(factoryPid, factoryPids);
}
factoryPids.add(pid);
}
}
}
return dict;
}
@Override
public Collection<Dictionary> getDictionaries( final SimpleFilter filter ) throws IOException
{
Lock lock = globalLock.readLock();
try
{
lock.lock();
// if not fully loaded, call back to the underlying persistence
// manager and cache all dictionaries whose service.pid is set
if ( !fullyLoaded )
{
lock.unlock();
lock = globalLock.writeLock();
lock.lock();
if ( !fullyLoaded )
{
Enumeration fromPm = pm.getDictionaries();
while ( fromPm.hasMoreElements() )
{
Dictionary next = (Dictionary) fromPm.nextElement();
this.cache(next);
}
this.fullyLoaded = true;
}
}
// Deep copy the configuration to avoid any threading issue
final List<Dictionary> configs = new ArrayList<>();
for (final Dictionary d : cache.values())
{
if ( d.get( Constants.SERVICE_PID ) != null && ( filter == null || filter.matches( d ) ) )
{
configs.add( new CaseInsensitiveDictionary( d ) );
}
}
return configs;
}
finally
{
lock.unlock();
}
}
/**
* Returns the dictionary for the given PID or <code>null</code> if no
* such dictionary is stored by the underlying persistence manager. This
* method caches the returned dictionary for future use after retrieving
* if from the persistence manager.
* <p>
* Note, that each call to this method will return new dictionary instance.
* That is modifying the contents of a dictionary returned from this method
* has no influence on the dictionaries stored in the cache.
*/
@Override
public Dictionary load( final String pid ) throws IOException
{
Lock lock = globalLock.readLock();
try
{
lock.lock();
CaseInsensitiveDictionary loaded = cache.get( pid );
if ( loaded == null && !fullyLoaded )
{
lock.unlock();
lock = globalLock.writeLock();
lock.lock();
loaded = cache.get( pid );
if ( loaded == null )
{
final Dictionary props = pm.load( pid );
if ( props != null )
{
loaded = this.cache(props);
}
}
}
return loaded == null ? null : new CaseInsensitiveDictionary(loaded);
}
finally
{
lock.unlock();
}
}
/**
* Stores the dictionary in the cache and in the underlying persistence
* manager. This method first calls the underlying persistence manager
* before updating the dictionary in the cache.
* <p>
* Note, that actually a copy of the dictionary is stored in the cache. That
* is subsequent modification to the given dictionary has no influence on
* the cached data.
*/
@Override
public void store( final String pid, final Dictionary properties ) throws IOException
{
final Lock lock = globalLock.writeLock();
try
{
lock.lock();
pm.store( pid, properties );
this.cache.remove(pid);
this.cache(properties);
}
finally
{
lock.unlock();
}
}
@Override
public Set<String> getFactoryConfigurationPids(final List<String> targetedFactoryPids )
throws IOException
{
final Set<String> pids = new HashSet<>();
Lock lock = globalLock.readLock();
try
{
lock.lock();
if ( !this.fullyLoaded )
{
lock.unlock();
lock = globalLock.writeLock();
lock.lock();
if ( !this.fullyLoaded )
{
final Enumeration fromPm = pm.getDictionaries();
while ( fromPm.hasMoreElements() )
{
Dictionary next = (Dictionary) fromPm.nextElement();
this.cache(next);
}
this.fullyLoaded = true;
}
lock.unlock();
lock = globalLock.readLock();
lock.lock();
}
for(final String targetFactoryPid : targetedFactoryPids)
{
final Set<String> cachedPids = this.factoryConfigCache.get(targetFactoryPid);
if ( cachedPids != null )
{
pids.addAll(cachedPids);
}
}
}
finally
{
lock.unlock();
}
return pids;
}
}

View File

@ -0,0 +1,41 @@
/*
* 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.cm.impl.persistence;
import java.io.IOException;
import java.util.Collection;
import java.util.Dictionary;
import java.util.List;
import java.util.Set;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.SimpleFilter;
/**
* Extension of the {@link PersistenceManager}.
*/
public interface ExtPersistenceManager extends PersistenceManager
{
Collection<Dictionary> getDictionaries( SimpleFilter filter ) throws IOException;
Set<String> getFactoryConfigurationPids( List<String> targetedFactoryPids )
throws IOException;
PersistenceManager getDelegatee();
}

View File

@ -0,0 +1,132 @@
/*
* 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.cm.impl.persistence;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.felix.cm.NotCachablePersistenceManager;
import org.apache.felix.cm.impl.CaseInsensitiveDictionary;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* The <code>MemoryPersistenceManager</code> holds all configurations in memory.
*/
public class MemoryPersistenceManager implements NotCachablePersistenceManager
{
/** Cached dictionaries */
private final Map<String, CaseInsensitiveDictionary> cache = new HashMap<>();
/** Factory configuration cache. */
private final Map<String, Set<String>> factoryConfigCache = new HashMap<>();
@Override
public void delete( final String pid ) throws IOException
{
final Dictionary props = cache.remove(pid);
if (props != null)
{
final String factoryPid = (String) props.get(ConfigurationAdmin.SERVICE_FACTORYPID);
if (factoryPid != null)
{
final Set<String> factoryPids = this.factoryConfigCache.get(factoryPid);
if (factoryPids != null)
{
factoryPids.remove(pid);
if (factoryPids.isEmpty())
{
this.factoryConfigCache.remove(factoryPid);
}
}
}
}
}
@Override
public boolean exists( final String pid )
{
return cache.containsKey(pid);
}
@Override
public Enumeration getDictionaries() throws IOException
{
// Deep copy the configuration to avoid any threading issue
final List<Dictionary> configs = new ArrayList<>();
for (final Dictionary d : cache.values()) {
if (d.get(Constants.SERVICE_PID) != null) {
configs.add(new CaseInsensitiveDictionary(d));
}
}
return Collections.enumeration(configs);
}
private final CaseInsensitiveDictionary cache(final Dictionary props)
{
final String pid = (String) props.get( Constants.SERVICE_PID );
CaseInsensitiveDictionary dict = null;
if ( pid != null )
{
dict = cache.get(pid);
if ( dict == null )
{
dict = new CaseInsensitiveDictionary(props);
cache.put( pid, dict );
final String factoryPid = (String)props.get(ConfigurationAdmin.SERVICE_FACTORYPID);
if ( factoryPid != null )
{
Set<String> factoryPids = this.factoryConfigCache.get(factoryPid);
if ( factoryPids == null )
{
factoryPids = new HashSet<>();
this.factoryConfigCache.put(factoryPid, factoryPids);
}
factoryPids.add(pid);
}
}
}
return dict;
}
@Override
public Dictionary load( final String pid ) throws IOException
{
CaseInsensitiveDictionary loaded = cache.get(pid);
return loaded == null ? null : new CaseInsensitiveDictionary(loaded);
}
@Override
public void store( final String pid, final Dictionary properties ) throws IOException
{
this.cache.remove(pid);
this.cache(properties);
}
}

View File

@ -0,0 +1,247 @@
/*
* 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.cm.impl.persistence;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.CaseInsensitiveDictionary;
import org.apache.felix.cm.impl.SimpleFilter;
import org.osgi.framework.Constants;
import org.osgi.service.cm.ConfigurationAdmin;
/**
* The <code>PersistenceManagerProxy</code> proxies a persistence
* manager and adds a read/write lock.
*/
public class PersistenceManagerProxy implements ExtPersistenceManager
{
/** the actual PersistenceManager */
private final PersistenceManager pm;
/** protecting lock */
private final ReadWriteLock globalLock = new ReentrantReadWriteLock();
/**
* Creates a new proxy for the given actual {@link PersistenceManager}.
* @param pm The actual {@link PersistenceManager}
*/
public PersistenceManagerProxy( final PersistenceManager pm )
{
this.pm = pm;
}
@Override
public PersistenceManager getDelegatee()
{
return pm;
}
/**
* Remove the configuration with the given PID. This implementation removes
* the entry from the cache before calling the underlying persistence
* manager.
*/
@Override
public void delete( final String pid ) throws IOException
{
Lock lock = globalLock.writeLock();
try
{
lock.lock();
pm.delete(pid);
}
finally
{
lock.unlock();
}
}
/**
* Checks whether a dictionary with the given pid exists. First checks for
* the existence in the cache. If not in the cache the underlying
* persistence manager is asked.
*/
@Override
public boolean exists( String pid )
{
Lock lock = globalLock.readLock();
try
{
lock.lock();
return pm.exists( pid );
}
finally
{
lock.unlock();
}
}
/**
* Returns an <code>Enumeration</code> of <code>Dictionary</code> objects
* representing the configurations stored in the underlying persistence
* managers. The dictionaries returned are garanteed to contain the
* <code>service.pid</code> property.
* <p>
* Note, that each call to this method will return new dictionary objects.
* That is modifying the contents of a dictionary returned from this method
* has no influence on the dictionaries stored in the cache.
*/
@Override
public Enumeration getDictionaries() throws IOException
{
return Collections.enumeration(getDictionaries( null ));
}
@Override
public Collection<Dictionary> getDictionaries( final SimpleFilter filter ) throws IOException
{
Lock lock = globalLock.readLock();
try
{
final Set<String> pids = new HashSet<>();
final List<Dictionary> result = new ArrayList<>();
lock.lock();
Enumeration fromPm = pm.getDictionaries();
while ( fromPm.hasMoreElements() )
{
Dictionary next = (Dictionary) fromPm.nextElement();
String pid = (String) next.get( Constants.SERVICE_PID );
if ( pid != null && !pids.contains(pid) && ( filter == null || filter.matches( next ) ) )
{
pids.add(pid);
result.add( new CaseInsensitiveDictionary( next ) );
}
}
return result;
}
finally
{
lock.unlock();
}
}
/**
* Returns the dictionary for the given PID or <code>null</code> if no
* such dictionary is stored by the underyling persistence manager. This
* method caches the returned dictionary for future use after retrieving
* if from the persistence manager.
* <p>
* Note, that each call to this method will return new dictionary instance.
* That is modifying the contents of a dictionary returned from this method
* has no influence on the dictionaries stored in the cache.
*/
@Override
public Dictionary load( String pid ) throws IOException
{
Lock lock = globalLock.readLock();
try
{
lock.lock();
Dictionary loaded = pm.load( pid );
if ( loaded != null )
{
return new CaseInsensitiveDictionary( loaded );
}
return null;
}
finally
{
lock.unlock();
}
}
/**
* Stores the dictionary in the cache and in the underlying persistence
* manager. This method first calls the underlying persistence manager
* before updating the dictionary in the cache.
* <p>
* Note, that actually a copy of the dictionary is stored in the cache. That
* is subsequent modification to the given dictionary has no influence on
* the cached data.
*/
@Override
public void store( String pid, Dictionary properties ) throws IOException
{
Lock lock = globalLock.writeLock();
try
{
lock.lock();
pm.store( pid, properties );
}
finally
{
lock.unlock();
}
}
@Override
public Set<String> getFactoryConfigurationPids(List<String> targetedFactoryPids) throws IOException {
final Set<String> pids = new HashSet<>();
Lock lock = globalLock.readLock();
try
{
lock.lock();
final Enumeration fromPm = pm.getDictionaries();
while ( fromPm.hasMoreElements() )
{
final Dictionary next = (Dictionary) fromPm.nextElement();
final String pid = (String)next.get(Constants.SERVICE_PID);
if ( pid != null )
{
final String factoryPid = (String)next.get(ConfigurationAdmin.SERVICE_FACTORYPID);
if ( factoryPid != null )
{
for(final String targetedFactoryPid : targetedFactoryPids)
{
if ( targetedFactoryPid.equals(factoryPid) )
{
pids.add(pid);
break;
}
}
}
}
}
}
finally
{
lock.unlock();
}
return pids;
}
}

View File

@ -0,0 +1,255 @@
/*
* 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.cm.impl.persistence;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.felix.cm.NotCachablePersistenceManager;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.ActivatorWorkerQueue;
import org.apache.felix.cm.impl.ConfigurationAdminStarter;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.osgi.util.tracker.ServiceTrackerCustomizer;
/**
* This tracker tracks registered persistence managers and
* if the required PM becomes available, configuration admin
* is registered.
* Service ranking of registered persistence managers
* is respected.
*/
public class PersistenceManagerTracker
implements ServiceTrackerCustomizer<PersistenceManager, PersistenceManagerTracker.Holder>
{
private final List<Holder> holders = new ArrayList<>();
private final ServiceTracker<PersistenceManager, Holder> persistenceManagerTracker;
private final BundleContext bundleContext;
private final ActivatorWorkerQueue workerQueue;
private final ConfigurationAdminStarter starter;
public PersistenceManagerTracker(final BundleContext bundleContext,
final ActivatorWorkerQueue workerQueue,
final ConfigurationAdminStarter starter,
final String pmName)
throws BundleException, InvalidSyntaxException
{
this.workerQueue = workerQueue;
this.starter = starter;
this.bundleContext = bundleContext;
this.persistenceManagerTracker = new ServiceTracker<>(bundleContext,
bundleContext.createFilter("(&(" + Constants.OBJECTCLASS + "=" + PersistenceManager.class.getName() + ")(name=" + pmName + "))"),
this );
this.persistenceManagerTracker.open();
}
/**
* Stop the tracker
*/
public void stop( )
{
this.persistenceManagerTracker.close();
}
public static ExtPersistenceManager createPersistenceManagerProxy(final PersistenceManager pm)
{
final ExtPersistenceManager extPM;
if ( pm instanceof NotCachablePersistenceManager )
{
extPM = new PersistenceManagerProxy( pm );
}
else
{
extPM = new CachingPersistenceManagerProxy( pm );
}
return extPM;
}
@Override
public Holder addingService(final ServiceReference<PersistenceManager> reference)
{
final PersistenceManager pm = this.bundleContext.getService(reference);
if ( pm != null )
{
final ExtPersistenceManager extPM = createPersistenceManagerProxy(pm);
final Holder holder = new Holder(reference, extPM);
synchronized ( this.holders )
{
final Holder oldHolder = this.holders.isEmpty() ? null : this.holders.get(0);
this.holders.add(holder);
Collections.sort(holders);
if ( holders.get(0) == holder )
{
this.workerQueue.enqueue(new Runnable()
{
@Override
public void run()
{
if ( oldHolder != null )
{
starter.unsetPersistenceManager();
}
if (!holder.isActivated()) {
starter.setPersistenceManager(holder.getPersistenceManager());
holder.activate();
}
}
});
}
}
return holder;
}
return null;
}
@Override
public void modifiedService(final ServiceReference<PersistenceManager> reference, final Holder holder)
{
// find the old holder, remove, add new holder, sort
synchronized ( this.holders )
{
final Holder oldHolder = this.holders.isEmpty() ? null : this.holders.get(0);
this.holders.remove(holder);
this.holders.add(new Holder(reference, holder.getPersistenceManager()));
Collections.sort(this.holders);
if ( holders.get(0) == holder && oldHolder != null && oldHolder.compareTo(holder) != 0 )
{
this.workerQueue.enqueue(new Runnable()
{
@Override
public void run()
{
starter.unsetPersistenceManager();
if (!holder.isActivated()) {
starter.setPersistenceManager(holder.getPersistenceManager());
holder.activate();
}
}
});
}
}
}
@Override
public void removedService(final ServiceReference<PersistenceManager> reference,
final Holder holder)
{
synchronized ( this.holders )
{
final boolean deactivate = holders.get(0) == holder;
this.holders.remove(holder);
if ( deactivate )
{
this.workerQueue.enqueue(new Runnable()
{
@Override
public void run()
{
starter.unsetPersistenceManager();
if ( !holders.isEmpty() )
{
Holder h = holders.get(0);
if (!h.isActivated()) {
starter.setPersistenceManager(h.getPersistenceManager());
h.activate();
}
}
}
});
}
}
}
public static final class Holder implements Comparable<Holder>
{
private final ServiceReference<PersistenceManager> reference;
private final ExtPersistenceManager manager;
// no need to synchronize, as it's changed only in WorkQueue tasks
private boolean activated;
public Holder(final ServiceReference<PersistenceManager> ref, final ExtPersistenceManager epm)
{
this.reference = ref;
this.manager = epm;
}
public ExtPersistenceManager getPersistenceManager()
{
return this.manager;
}
@Override
public int compareTo(final Holder o)
{
// sort, highest first
return -reference.compareTo(o.reference);
}
public boolean isActivated() {
return activated;
}
public void activate() {
this.activated = true;
}
@Override
public int hashCode()
{
return this.reference.hashCode();
}
@Override
public boolean equals(final Object obj)
{
if (this == obj)
{
return true;
}
if (obj == null || getClass() != obj.getClass())
{
return false;
}
final Holder other = (Holder) obj;
return this.reference.equals(other.reference);
}
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.
*/
@org.osgi.annotation.versioning.Version("1.2.0")
package org.apache.felix.cm;

View File

@ -0,0 +1,59 @@
# 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.
# Apache Felix Configuration Admin Service
# Bundle permissions
# see FELIX-4039
#
# Imported/Exported packages
# -> MANIFEST.MF
(org.osgi.framework.PackagePermission "org.osgi.service.log" "import")
(org.osgi.framework.PackagePermission "org.osgi.framework" "import")
(org.osgi.framework.PackagePermission "org.osgi.util.tracker" "import")
(org.osgi.framework.PackagePermission "org.osgi.service.cm" "import,exportonly")
(org.osgi.framework.PackagePermission "org.apache.felix.cm" "import,exportonly")
(org.osgi.framework.PackagePermission "org.apache.felix.cm.file" "import,exportonly")
# General bundle permissions
(java.util.PropertyPermission "felix.cm.*" "read")
(org.osgi.framework.ServicePermission "org.apache.felix.cm.*" "get,register")
(org.osgi.framework.ServicePermission "org.osgi.service.cm.*" "get,register")
(org.osgi.framework.ServicePermission "org.osgi.service.log.LogService" "get")
# Manage configurations
# -> ConfigurationAdminImpl
(org.osgi.framework.AdminPermission "*" "metadata")
(org.osgi.service.cm.ConfigurationPermission "*" "configure,target")
# Handle persistent configuration files
# -> FilePersistenceManager
(java.util.PropertyPermission "os.name" "read")
(java.util.PropertyPermission "user.dir" "read")
(java.io.FilePermission "-" "read,write,execute,delete")
# -> ConfigurationManager
(org.osgi.framework.ServicePermission "org.apache.felix.cm.PersistenceManager" "register")
# -> BaseTracker.getAccessControlContext
(java.security.SecurityPermission "createAccessControlContext")
# Coordinator Support
(org.osgi.framework.PackagePermission "org.osgi.service.coordinator" "import")
(org.osgi.framework.ServicePermission "org.osgi.service.coordinator.*" "get")
(org.osgi.service.coordinator.CoordinationPermission "*" "initiate,participate")
# Capability Support
(org.osgi.framework.CapabilityPermission "osgi.implementation" "provide")

View File

@ -0,0 +1,263 @@
/*
* 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.cm;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.X509Certificate;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.Version;
public class MockBundle implements Bundle
{
private final BundleContext context;
private final String location;
public MockBundle( BundleContext context, String location )
{
this.context = context;
this.location = location;
}
@Override
public Enumeration<URL> findEntries( String arg0, String arg1, boolean arg2 )
{
return null;
}
@Override
public BundleContext getBundleContext()
{
return context;
}
@Override
public long getBundleId()
{
return 0;
}
@Override
public URL getEntry( String arg0 )
{
return null;
}
@Override
public Enumeration<String> getEntryPaths( String arg0 )
{
return null;
}
@Override
public Dictionary<String, String> getHeaders()
{
return null;
}
@Override
public Dictionary<String, String> getHeaders( String arg0 )
{
return null;
}
@Override
public long getLastModified()
{
return 0;
}
@Override
public String getLocation()
{
return location;
}
@Override
public ServiceReference<?>[] getRegisteredServices()
{
return null;
}
@Override
public URL getResource( String arg0 )
{
return null;
}
@Override
public Enumeration<URL> getResources( String arg0 )
{
return null;
}
@Override
public ServiceReference<?>[] getServicesInUse()
{
return null;
}
@Override
public int getState()
{
return 0;
}
@Override
public String getSymbolicName()
{
return null;
}
@Override
public boolean hasPermission( Object arg0 )
{
return false;
}
@Override
public Class<?> loadClass( String arg0 ) throws ClassNotFoundException
{
throw new ClassNotFoundException( arg0 );
}
@Override
public void start()
{
}
@Override
public void stop()
{
}
@Override
public void uninstall()
{
}
@Override
public void update()
{
}
@Override
public void update( InputStream arg0 ) throws BundleException
{
if ( arg0 != null )
{
try
{
arg0.close();
}
catch ( IOException ioe )
{
throw new BundleException( ioe.getMessage(), ioe );
}
}
}
@Override
public void start( int options )
{
}
@Override
public void stop( int options )
{
}
@Override
public int compareTo( Bundle o )
{
return 0;
}
// Framework 1.5 additions
@Override
public Map<X509Certificate, List<X509Certificate>> getSignerCertificates( int signersType )
{
throw new AbstractMethodError( "Not supported on Framework API 1.4; added in Framework API 1.5" );
}
@Override
public Version getVersion()
{
return Version.emptyVersion;
}
// Framework 1.6 additions
@Override
public <A> A adapt( Class<A> type )
{
throw new AbstractMethodError( "Not supported on Framework API 1.4; added in Framework API 1.6" );
}
@Override
public File getDataFile( String filename )
{
throw new AbstractMethodError( "Not supported on Framework API 1.4; added in Framework API 1.6" );
}
}

View File

@ -0,0 +1,367 @@
/*
* 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.cm;
import java.io.File;
import java.io.InputStream;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Properties;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.BundleListener;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkListener;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceListener;
import org.osgi.framework.ServiceObjects;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
/**
* The <code>MockBundleContext</code> is a dummy implementation of the
* <code>BundleContext</code> interface. No methods are implemented here, that
* is all methods have no effect and return <code>null</code> if a return value
* is specified.
* <p>
* Extensions may overwrite methods as see fit.
*/
public class MockBundleContext implements BundleContext
{
private final Properties properties = new Properties();
public void setProperty( String name, String value )
{
if ( value == null )
{
properties.remove( name );
}
else
{
properties.setProperty( name, value );
}
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#addBundleListener(org.osgi.framework
* .BundleListener)
*/
@Override
public void addBundleListener( BundleListener arg0 )
{
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#addFrameworkListener(org.osgi.framework
* .FrameworkListener)
*/
@Override
public void addFrameworkListener( FrameworkListener arg0 )
{
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#addServiceListener(org.osgi.framework
* .ServiceListener)
*/
@Override
public void addServiceListener( ServiceListener arg0 )
{
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#addServiceListener(org.osgi.framework
* .ServiceListener, java.lang.String)
*/
@Override
public void addServiceListener( ServiceListener arg0, String arg1 )
{
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#createFilter(java.lang.String)
*/
@Override
public Filter createFilter( String arg0 )
{
return null;
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#getAllServiceReferences(java.lang.String
* , java.lang.String)
*/
@Override
public ServiceReference<?>[] getAllServiceReferences( String arg0, String arg1 )
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#getBundle()
*/
@Override
public Bundle getBundle()
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#getBundle(long)
*/
@Override
public Bundle getBundle( long arg0 )
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#getBundles()
*/
@Override
public Bundle[] getBundles()
{
return new Bundle[0];
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#getDataFile(java.lang.String)
*/
@Override
public File getDataFile( String arg0 )
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#getProperty(java.lang.String)
*/
@Override
public String getProperty( String name )
{
return properties.getProperty( name );
}
/*
* (non-Javadoc)
* @seeorg.osgi.framework.BundleContext#getService(org.osgi.framework.
* ServiceReference)
*/
@Override
public <S> S getService( ServiceReference<S> reference )
{
return null;
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#getServiceReference(java.lang.String)
*/
@Override
public ServiceReference<?> getServiceReference( String arg0 )
{
return null;
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#getServiceReferences(java.lang.String,
* java.lang.String)
*/
@Override
public ServiceReference<?>[] getServiceReferences( String arg0, String arg1 )
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#installBundle(java.lang.String)
*/
@Override
public Bundle installBundle( String arg0 )
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#installBundle(java.lang.String,
* java.io.InputStream)
*/
@Override
public Bundle installBundle( String arg0, InputStream arg1 )
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#registerService(java.lang.String[],
* java.lang.Object, java.util.Dictionary)
*/
@Override
public ServiceRegistration<?> registerService( String[] clazzes, Object service, Dictionary<String, ?> properties )
{
return null;
}
/*
* (non-Javadoc)
* @see org.osgi.framework.BundleContext#registerService(java.lang.String,
* java.lang.Object, java.util.Dictionary)
*/
@Override
public ServiceRegistration<?> registerService( String clazz, Object service, Dictionary<String, ?> properties )
{
return null;
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#removeBundleListener(org.osgi.framework
* .BundleListener)
*/
@Override
public void removeBundleListener( BundleListener arg0 )
{
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#removeFrameworkListener(org.osgi.framework
* .FrameworkListener)
*/
@Override
public void removeFrameworkListener( FrameworkListener arg0 )
{
}
/*
* (non-Javadoc)
* @see
* org.osgi.framework.BundleContext#removeServiceListener(org.osgi.framework
* .ServiceListener)
*/
@Override
public void removeServiceListener( ServiceListener arg0 )
{
}
/*
* (non-Javadoc)
* @seeorg.osgi.framework.BundleContext#ungetService(org.osgi.framework.
* ServiceReference)
*/
@Override
public boolean ungetService( ServiceReference<?> reference )
{
return false;
}
@Override
public <S> ServiceRegistration<S> registerService( Class<S> clazz, S service, Dictionary<String, ?> properties )
{
return null;
}
@Override
public <S> ServiceReference<S> getServiceReference( Class<S> clazz )
{
return null;
}
@Override
public <S> Collection<ServiceReference<S>> getServiceReferences( Class<S> clazz, String filter )
{
return null;
}
@Override
public Bundle getBundle( String location )
{
return null;
}
@Override
public <S> ServiceRegistration<S> registerService(Class<S> clazz, ServiceFactory<S> factory,
Dictionary<String, ?> properties)
{
return null;
}
@Override
public <S> ServiceObjects<S> getServiceObjects(ServiceReference<S> reference)
{
return null;
}
}

View File

@ -0,0 +1,88 @@
/*
* 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.cm;
import org.osgi.framework.ServiceReference;
import org.osgi.service.log.LogService;
/**
* The <code>MockLogService</code> is a very simple log service, which just
* prints the loglevel and message to StdErr.
*/
public class MockLogService implements LogService
{
@Override
public void log( int logLevel, String message )
{
System.err.print( toMessageLine( logLevel, message ) );
}
@Override
public void log( int logLevel, String message, Throwable t )
{
log( logLevel, message );
}
@Override
public void log( @SuppressWarnings("rawtypes") ServiceReference ref, int logLevel, String message )
{
log( logLevel, message );
}
@Override
public void log( @SuppressWarnings("rawtypes") ServiceReference ref, int logLevel, String message, Throwable t )
{
log( logLevel, message );
}
/**
* Helper method to format log level and log message exactly the same as the
* <code>ConfigurationManager.log()</code> does.
*/
public static String toMessageLine( int level, String message )
{
String messageLine;
switch ( level )
{
case LogService.LOG_INFO:
messageLine = "*INFO *";
break;
case LogService.LOG_WARNING:
messageLine = "*WARN *";
break;
case LogService.LOG_ERROR:
messageLine = "*ERROR*";
break;
case LogService.LOG_DEBUG:
default:
messageLine = "*DEBUG*";
}
return messageLine + " " + message + System.getProperty( "line.separator" );
}
}

View File

@ -0,0 +1,85 @@
/*
* 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.cm;
import java.io.IOException;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import org.apache.felix.cm.impl.CaseInsensitiveDictionary;
public class MockNotCachablePersistenceManager implements NotCachablePersistenceManager
{
private final Map<String, Dictionary<String, Object>> configs = new HashMap<>();
public Map<String, Dictionary<String, Object>> getStored()
{
return configs;
}
@Override
public void delete( String pid )
{
configs.remove( pid );
}
@Override
public boolean exists( String pid )
{
return configs.containsKey( pid );
}
@SuppressWarnings("rawtypes")
@Override
public Enumeration getDictionaries()
{
return Collections.enumeration( configs.values() );
}
@SuppressWarnings("rawtypes")
@Override
public Dictionary load( String pid ) throws IOException
{
Dictionary config = configs.get( pid );
if ( config != null )
{
return config;
}
throw new IOException( "No such configuration: " + pid );
}
@SuppressWarnings("unchecked")
@Override
public void store( String pid, @SuppressWarnings("rawtypes") Dictionary properties )
{
configs.put( pid, new CaseInsensitiveDictionary( properties ) );
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.cm;
import java.io.IOException;
import java.util.Collections;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
public class MockPersistenceManager implements PersistenceManager
{
private final Map<String, Dictionary<String, Object>> configs = new HashMap<>();
@Override
public void delete( final String pid )
{
configs.remove( pid );
}
@Override
public boolean exists( final String pid )
{
return configs.containsKey( pid );
}
@SuppressWarnings("rawtypes")
@Override
public Enumeration getDictionaries()
{
return Collections.enumeration( configs.values() );
}
@SuppressWarnings("rawtypes")
@Override
public Dictionary load( final String pid ) throws IOException
{
Dictionary config = configs.get( pid );
if ( config != null )
{
return config;
}
throw new IOException( "No such configuration: " + pid );
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
public void store( String pid, Dictionary properties )
{
configs.put( pid, properties );
}
}

View File

@ -0,0 +1,70 @@
/*
* 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.cm;
import org.osgi.framework.Bundle;
import org.osgi.framework.ServiceReference;
public class MockServiceReference<S> implements ServiceReference<S>
{
@Override
public Object getProperty( String key )
{
return null;
}
@Override
public String[] getPropertyKeys()
{
return null;
}
@Override
public Bundle getBundle()
{
return null;
}
@Override
public Bundle[] getUsingBundles()
{
return null;
}
@Override
public boolean isAssignableTo( Bundle bundle, String className )
{
return false;
}
@Override
public int compareTo( Object reference )
{
return 0;
}
}

View File

@ -0,0 +1,348 @@
/*
* 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.cm.file;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Hashtable;
import java.util.List;
import org.junit.Assert;
import org.junit.Test;
public class ConfigurationHandlerTest {
private static final String SERVICE_PID = "service.pid";
private static final String PAR_1 = "mongouri";
private static final String VAL_1 = "127.0.0.1:27017";
private static final String PAR_2 = "customBlobStore";
private static final String VAL_2 = "true";
private static final String CONFIG =
"#mongodb URI\n" +
PAR_1 + "=\"" + VAL_1 + "\"\n" +
"\n" +
" # custom datastore\n" +
PAR_2 + "=B\"" + VAL_2 + "\"\n";
@Test
public void testComments() throws IOException
{
@SuppressWarnings("unchecked")
final Dictionary<String, Object> dict = ConfigurationHandler.read(new ByteArrayInputStream(CONFIG.getBytes("UTF-8")));
Assert.assertEquals(2, dict.size());
Assert.assertEquals(VAL_1, dict.get(PAR_1));
Assert.assertEquals(VAL_2, dict.get(PAR_2).toString());
}
@Test
public void test_writeArray() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Object> properties = new Hashtable<>();
properties.put(SERVICE_PID , new String [] {"foo", "bar"});
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=[ \\\r\n \"foo\", \\\r\n \"bar\", \\\r\n ]\r\n", entry);
}
@Test
public void test_writeEmptyCollection() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Object> properties = new Hashtable<>();
properties.put(SERVICE_PID , new ArrayList<String>());
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=( \\\r\n)\r\n", entry);
}
@Test
public void test_writeCollection() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Object> properties = new Hashtable<>();
List<String> list = new ArrayList<>();
list.add("foo");
list.add("bar");
properties.put(SERVICE_PID , list);
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=( \\\r\n \"foo\", \\\r\n \"bar\", \\\r\n)\r\n", entry);
}
@Test
public void test_writeSimpleString() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, String> properties = new Hashtable<>();
properties.put(SERVICE_PID, "com.adobe.granite.foo.Bar");
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=\"com.adobe.granite.foo.Bar\"\r\n", entry);
}
@Test
public void test_writeInteger() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Integer> properties = new Hashtable<>();
properties.put(SERVICE_PID, 1000);
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=I\"1000\"\r\n", entry);
}
@Test
public void test_writeLong() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Long> properties = new Hashtable<>();
properties.put(SERVICE_PID, 1000L);
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=L\"1000\"\r\n", entry);
}
@Test
public void test_writeFloat() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Float> properties = new Hashtable<>();
properties.put(SERVICE_PID, 3.6f);
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=F\"1080452710\"\r\n", entry);
}
@Test
public void test_writeDouble() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Double> properties = new Hashtable<>();
properties.put(SERVICE_PID, 3.6d);
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=D\"4615288898129284301\"\r\n", entry);
}
@Test
public void test_writeByte() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Byte> properties = new Hashtable<>();
properties.put(SERVICE_PID, new Byte("10"));
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=X\"10\"\r\n", entry);
}
@Test
public void test_writeShort() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Short> properties = new Hashtable<>();
properties.put(SERVICE_PID, (short)10);
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=S\"10\"\r\n", entry);
}
@Test
public void test_writeChar() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Character> properties = new Hashtable<>();
properties.put(SERVICE_PID, 'c');
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=C\"c\"\r\n", entry);
}
@Test
public void test_writeBoolean() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, Boolean> properties = new Hashtable<>();
properties.put(SERVICE_PID, true);
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("service.pid=B\"true\"\r\n", entry);
}
@Test
public void test_writeSimpleStringWithError() throws IOException {
OutputStream out = new ByteArrayOutputStream();
Dictionary< String, String> properties = new Hashtable<>();
properties.put("foo.bar", "com.adobe.granite.foo.Bar");
ConfigurationHandler.write(out, properties);
String entry = new String(((ByteArrayOutputStream)out).toByteArray(),"UTF-8");
Assert.assertEquals("foo.bar=\"com.adobe.granite.foo.Bar\"\r\n", entry);
}
@Test
public void test_readArray() throws IOException {
String entry = "service.pid=[ \\\r\n \"foo\", \\\r\n \"bar\", \\\r\n ]\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertArrayEquals(new String [] {"foo", "bar"}, (String [])dictionary.get(SERVICE_PID));
}
@Test
public void test_readEmptyCollection() throws IOException {
String entry = "service.pid=( \\\r\n)\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals(new ArrayList<String>(), dictionary.get(SERVICE_PID));
}
@Test
public void test_readCollection() throws IOException {
String entry = "service.pid=( \\\r\n \"foo\", \\\r\n \"bar\", \\\r\n)\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
List<String> list = new ArrayList<>();
list.add("foo");
list.add("bar");
Assert.assertEquals(list, dictionary.get(SERVICE_PID));
}
@Test
public void test_readSimpleString() throws IOException {
String entry = "service.pid=\"com.adobe.granite.foo.Bar\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals( "com.adobe.granite.foo.Bar", dictionary.get(SERVICE_PID));
}
@Test
public void test_readSimpleStrings() throws IOException {
String entry = "service.pid=\"com.adobe.granite.foo.Bar\"\r\nfoo.bar=\"com.adobe.granite.foo.Baz\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(2, dictionary.size());
Assert.assertEquals( "com.adobe.granite.foo.Bar", dictionary.get(SERVICE_PID));
Assert.assertNotNull(dictionary.get("foo.bar"));
}
@Test
public void test_readInteger() throws IOException {
String entry = "service.pid=I\"1000\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals( 1000, dictionary.get(SERVICE_PID));
}
@Test
public void test_readLong() throws IOException {
String entry = "service.pid=L\"1000\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals( 1000L, dictionary.get(SERVICE_PID));
}
@Test
public void test_readFloat() throws IOException {
String entry = "service.pid=F\"1080452710\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals( 3.6f, dictionary.get(SERVICE_PID));
}
@Test
public void test_readDouble() throws IOException {
String entry = "service.pid=D\"4615288898129284301\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals( 3.6d, dictionary.get(SERVICE_PID));
}
@Test
public void test_readByte() throws IOException {
String entry = "service.pid=X\"10\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals((byte)10 , dictionary.get(SERVICE_PID));
}
@Test
public void test_readShort() throws IOException {
String entry = "service.pid=S\"10\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals((short)10 , dictionary.get(SERVICE_PID));
}
@Test
public void test_readChar() throws IOException {
String entry = "service.pid=C\"c\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals('c' , dictionary.get(SERVICE_PID));
}
@Test
public void test_readBoolean() throws IOException {
String entry = "service.pid=B\"true\"\r\n";
InputStream stream = new ByteArrayInputStream(entry.getBytes(StandardCharsets.UTF_8));
@SuppressWarnings("unchecked")
Dictionary<String, Object> dictionary = ConfigurationHandler.read(stream);
Assert.assertEquals(1, dictionary.size());
Assert.assertEquals(true , dictionary.get(SERVICE_PID));
}
@Test
public void test_backslash() throws IOException {
final String VALUE = "val\\ue\\\\";
final Dictionary<String, Object> dict = new Hashtable<>();
dict.put("key", VALUE);
try (final ByteArrayOutputStream out = new ByteArrayOutputStream()) {
ConfigurationHandler.write(out, dict);
try (final ByteArrayInputStream ins = new ByteArrayInputStream(out.toByteArray())) {
@SuppressWarnings("unchecked")
final Dictionary<String, Object> read = ConfigurationHandler.read(ins);
Assert.assertNotNull(read.get("key"));
Assert.assertEquals(VALUE, read.get("key"));
}
}
}
}

View File

@ -0,0 +1,196 @@
/*
* 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.cm.file;
import static org.junit.Assert.assertEquals;
import java.io.File;
import org.apache.felix.cm.MockBundleContext;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.BundleContext;
public class FilePersistenceManagerConstructorTest
{
/** The current working directory for the tests */
private static final String TEST_WORKING_DIRECTORY = "target";
/** The Bundle Data Area directory for testing */
private static final String BUNDLE_DATA = "bundleData";
/** The Configuration location path for testing */
private static final String LOCATION_TEST = "test";
/** the previous working directory to return to on tearDown */
private String oldWorkingDirectory;
@Before
public void setUp() throws Exception
{
String testDir = new File(TEST_WORKING_DIRECTORY).getAbsolutePath();
oldWorkingDirectory = System.getProperty( "user.dir" );
System.setProperty( "user.dir", testDir );
System.out.println("\n\n SET user.dir TO " + testDir);
System.out.println("\n\n config results in " + (new File("config")).getAbsolutePath());
}
@After
public void tearDown() throws Exception
{
System.setProperty( "user.dir", oldWorkingDirectory );
}
/**
* Test method for {@link org.apache.felix.cm.file.FilePersistenceManager#FilePersistenceManager(java.lang.String)}.
*/
@Test
public void testFilePersistenceManagerString()
{
// variables used in these tests
FilePersistenceManager fpm;
String relPath;
String absPath;
// with null
fpm = new FilePersistenceManager(null);
assertFpm(fpm, new File(FilePersistenceManager.DEFAULT_CONFIG_DIR) );
// with a relative path
relPath = LOCATION_TEST;
fpm = new FilePersistenceManager(relPath);
assertFpm(fpm, new File(relPath) );
// with an absolute path
absPath = new File(LOCATION_TEST).getAbsolutePath();
fpm = new FilePersistenceManager(absPath);
assertFpm(fpm, new File(absPath) );
}
/**
* Test method for {@link org.apache.felix.cm.file.FilePersistenceManager#FilePersistenceManager(org.osgi.framework.BundleContext, java.lang.String)}.
*/
@Test
public void testFilePersistenceManagerBundleContextString()
{
// variables used in these tests
BundleContext bundleContext;
FilePersistenceManager fpm;
String relPath;
String absPath;
File dataArea;
// first suite: no BundleContext at all
// with null
fpm = new FilePersistenceManager(null);
assertFpm(fpm, new File(FilePersistenceManager.DEFAULT_CONFIG_DIR) );
// with a relative path
relPath = LOCATION_TEST;
fpm = new FilePersistenceManager(relPath);
assertFpm(fpm, new File(relPath) );
// with an absolute path
absPath = new File(LOCATION_TEST).getAbsolutePath();
fpm = new FilePersistenceManager(absPath);
assertFpm(fpm, new File(absPath) );
// second suite: BundleContext without data file
bundleContext = new FilePersistenceManagerBundleContext(null);
// with null
fpm = new FilePersistenceManager(bundleContext, null);
assertFpm(fpm, new File(FilePersistenceManager.DEFAULT_CONFIG_DIR) );
// with a relative path
relPath = LOCATION_TEST;
fpm = new FilePersistenceManager(bundleContext, relPath);
assertFpm(fpm, new File(relPath) );
// with an absolute path
absPath = new File(LOCATION_TEST).getAbsolutePath();
fpm = new FilePersistenceManager(bundleContext, absPath);
assertFpm(fpm, new File(absPath) );
// third suite: BundleContext with relative data file
dataArea = new File(BUNDLE_DATA);
bundleContext = new FilePersistenceManagerBundleContext(dataArea);
// with null
fpm = new FilePersistenceManager(bundleContext, null);
assertFpm(fpm, new File(dataArea, FilePersistenceManager.DEFAULT_CONFIG_DIR) );
// with a relative path
relPath = LOCATION_TEST;
fpm = new FilePersistenceManager(bundleContext, relPath);
assertFpm(fpm, new File(dataArea, relPath) );
// with an absolute path
absPath = new File(LOCATION_TEST).getAbsolutePath();
fpm = new FilePersistenceManager(bundleContext, absPath);
assertFpm(fpm, new File(absPath) );
// fourth suite: BundleContext with absolute data file
dataArea = new File(BUNDLE_DATA).getAbsoluteFile();
bundleContext = new FilePersistenceManagerBundleContext(dataArea);
// with null
fpm = new FilePersistenceManager(bundleContext, null);
assertFpm(fpm, new File(dataArea, FilePersistenceManager.DEFAULT_CONFIG_DIR) );
// with a relative path
relPath = LOCATION_TEST;
fpm = new FilePersistenceManager(bundleContext, relPath);
assertFpm(fpm, new File(dataArea, relPath) );
// with an absolute path
absPath = new File(LOCATION_TEST).getAbsolutePath();
fpm = new FilePersistenceManager(bundleContext, absPath);
assertFpm(fpm, new File(absPath) );
}
private void assertFpm(FilePersistenceManager fpm, File expected) {
assertEquals( expected.getAbsoluteFile(), fpm.getLocation() );
}
private static final class FilePersistenceManagerBundleContext extends MockBundleContext {
private File dataArea;
private FilePersistenceManagerBundleContext( File dataArea )
{
this.dataArea = dataArea;
}
@Override
public File getDataFile( String path )
{
return (dataArea != null) ? new File(dataArea, path) : null;
}
}
}

View File

@ -0,0 +1,354 @@
/*
* 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.cm.file;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
public class FilePersistenceManagerTest
{
private File file = new File( System.getProperty( "java.io.tmpdir" ), "config" );
private FilePersistenceManager fpm;
@Before
public void setUp() throws Exception
{
fpm = new FilePersistenceManager( file.getAbsolutePath() );
}
@After
public void tearDown() throws Exception
{
File[] children = file.listFiles();
for ( int i = 0; children != null && i < children.length; i++ )
{
children[i].delete();
}
file.delete();
}
@Test
public void testPidPlain()
{
assertEquals( "plain", fpm.encodePid( "plain" ) );
assertEquals( "plain" + File.separatorChar + "path", fpm.encodePid( "plain.path" ) );
assertEquals( "encod%00e8", fpm.encodePid( "encod\u00E8" ) );
assertEquals( "encod%00e8" + File.separatorChar + "path", fpm.encodePid( "encod\u00E8/path" ) );
assertEquals( "encode" + File.separatorChar + "%1234" + File.separatorChar + "path",
fpm.encodePid( "encode/\u1234/path" ) );
assertEquals( "encode" + File.separatorChar + " %0025 " + File.separatorChar + "path",
fpm.encodePid( "encode/ % /path" ) );
}
@Test
public void testPidEncodingCollision() {
// assert a == encode(a) ==> encode(a) == encode(encode(a))
final String plain = "plain";
assertEquals( plain, fpm.encodePid( plain ) );
assertEquals( fpm.encodePid( plain ), fpm.encodePid( fpm.encodePid( plain ) ) );
assertEquals( plain, fpm.encodePid( fpm.encodePid( plain ) ) );
// assert a != encode(a) ==> encode(a) != encode(encode(a))
final String encode = "encod\u00E8";
final String encoded = "encod%00e8";
assertEquals( encoded, fpm.encodePid( encode ) );
assertFalse( encode.equals( fpm.encodePid( encode ) ) );
assertFalse( fpm.encodePid( encode ).equals( fpm.encodePid( fpm.encodePid( encode ) ) ) );
assertFalse( encode.equals( fpm.encodePid( fpm.encodePid( encode ) ) ) );
}
@Test
public void testPidDeviceNameEncodingWindows() {
// assert proper encoding of windows device file names (FELIX-4302)
String oldOsName = System.getProperty( "os.name" );
try {
System.setProperty("os.name", "Windows for testing");
FilePersistenceManager winFpm = new FilePersistenceManager( file.getAbsolutePath() );
assertEquals("%004cPT1", winFpm.encodePid( "LPT1" ));
assertEquals("%006cpt1", winFpm.encodePid( "lpt1" ));
assertEquals("%0043ON", winFpm.encodePid( "CON" ));
assertEquals("%0050RN", winFpm.encodePid( "PRN" ));
assertEquals("%0041UX", winFpm.encodePid( "AUX" ));
assertEquals("CLOCK%0024", winFpm.encodePid( "CLOCK$" ));
assertEquals("%004eUL", winFpm.encodePid( "NUL" ));
assertEquals("%0043OM6", winFpm.encodePid( "COM6" ));
} finally {
System.setProperty( "os.name", oldOsName );
}
}
@Test
public void testPidDeviceNameEncodingNonWindows() {
// assert no encoding of windows device file names (FELIX-4302)
String oldOsName = System.getProperty( "os.name" );
try {
System.setProperty("os.name", "Unix for testing");
FilePersistenceManager winFpm = new FilePersistenceManager( file.getAbsolutePath() );
assertEquals("LPT1", winFpm.encodePid( "LPT1" ));
assertEquals("lpt1", winFpm.encodePid( "lpt1" ));
assertEquals("CON", winFpm.encodePid( "CON" ));
assertEquals("PRN", winFpm.encodePid( "PRN" ));
assertEquals("AUX", winFpm.encodePid( "AUX" ));
assertEquals("CLOCK%0024", winFpm.encodePid( "CLOCK$" ));
assertEquals("NUL", winFpm.encodePid( "NUL" ));
assertEquals("COM6", winFpm.encodePid( "COM6" ));
} finally {
System.setProperty( "os.name", oldOsName );
}
}
@Test
public void testCreateDir()
{
assertTrue( file.isDirectory() );
}
@Test
public void testSimple() throws IOException
{
check( "String", "String Value" );
check( "Integer", new Integer( 2 ) );
check( "Long", new Long( 2 ) );
check( "Float", new Float( 2 ) );
check( "Double", new Double( 2 ) );
check( "Byte", new Byte( ( byte ) 2 ) );
check( "Short", new Short( ( short ) 2 ) );
check( "Character", new Character( 'a' ) );
check( "Boolean", Boolean.TRUE );
}
@Test
public void testQuoting() throws IOException
{
check( "QuotingSeparators", "\\()[]{}.,=\"\"''" );
check( "QuotingWellKnown", "BSP:\b, TAB:\t, LF:\n, FF:\f, CR:\r" );
check( "QuotingControl", new String( new char[]
{ 5, 10, 32, 64 } ) );
}
@Test
public void testArray() throws IOException
{
check( "StringArray", new String[]
{ "one", "two", "three" } );
check( "IntArray", new int[]
{ 0, 1, 2 } );
check( "IntegerArray", new Integer[]
{ new Integer( 0 ), new Integer( 1 ), new Integer( 2 ) } );
}
@Test
public void testEmptyArray() throws IOException
{
check( "StringArray", new String[0] );
check( "IntArray", new int[0] );
check( "CharArray", new char[0] );
check( "ShortArray", new short[0] );
}
@Test
public void testVector() throws IOException
{
check( "StringVector", new Vector<>( Arrays.asList( new String[]
{ "one", "two", "three" } ) ) );
check( "IntegerVector", new Vector<>( Arrays.asList( new Integer[]
{ new Integer( 0 ), new Integer( 1 ), new Integer( 2 ) } ) ) );
}
@Test
public void testEmptyVector() throws IOException
{
check( "StringArray", new Vector<String>() );
check( "IntArray", new Vector<Integer>() );
check( "CharArray", new Vector<Character>() );
check( "ShortArray", new Vector<Short>() );
}
@Test
public void testList() throws IOException
{
check( "StringList", Arrays.asList( new String[]
{ "one", "two", "three" } ) );
check( "IntegerList", Arrays.asList( new Integer[]
{ new Integer( 0 ), new Integer( 1 ), new Integer( 2 ) } ) );
}
@Test
public void testEmptyList() throws IOException
{
check( "StringArray", new ArrayList<String>(0) );
check( "IntArray", new ArrayList<Integer>(0) );
check( "CharArray", new ArrayList<Character>(0) );
check( "ShortArray", new ArrayList<Short>(0) );
}
@Test
public void testMultiValue() throws IOException
{
Dictionary<String, Object> props = new Hashtable<>();
props.put( "String", "String Value" );
props.put( "Integer", new Integer( 2 ) );
props.put( "Long", new Long( 2 ) );
props.put( "Float", new Float( 2 ) );
props.put( "Double", new Double( 2 ) );
props.put( "Byte", new Byte( ( byte ) 2 ) );
props.put( "Short", new Short( ( short ) 2 ) );
props.put( "Character", new Character( 'a' ) );
props.put( "Boolean", Boolean.TRUE );
props.put( "Array", new boolean[]
{ true, false } );
check( "MultiValue", props );
}
// test configuration keys not conforming to the recommended specification
// for configuration keys in OSGi CM 1.3, 104.4.2, Configuration Properties
@Test
public void testNonSpecKeys() throws IOException {
check( "with\ttab", "the value" );
check( "with blank", "the value" );
check( "\\()[]{}.,=\"\"''", "quoted key" );
check( "\"with quotes\"", "key with quotes" );
check( "=leading equals", "leading equals" );
}
// Test expected to always succeed on non-Windows platforms. It may
// break if FilePersistenceManager.encode does not cope properly
// with Windows device names (see FELIX-4302)
@Test
public void testWindowsSpecialNames() throws IOException
{
check( "prefixLPT1", "lpt1" );
check( "prefix.prefix2.LPT1.suffix", "lpt1" );
check( "prefix.LPT1.suffix", "lpt1" );
check( "prefix.LPT1", "lpt1" );
check( "LPT1", "lpt1" );
}
@Test
public void testKeyOrderInFile() throws IOException
{
Dictionary<String, Object> props = new Hashtable<>();
// The following keys are stored as "c, a, b" in HashTable based
// due to their hash code
props.put( "a_first", "a" );
props.put( "b_second", "b" );
props.put( "c_third", "c" );
String pid = "keyOrderInFile";
fpm.store( pid, props );
File configFile = new File( file, fpm.encodePid( pid ) + ".config" );
FileReader reader = new FileReader( configFile );
BufferedReader breader = new BufferedReader(reader);
try
{
String previousLine = breader.readLine();
while ( previousLine != null)
{
String line = breader.readLine();
if (line != null) {
assertTrue( previousLine.compareTo( line ) < 0 );
}
previousLine = line;
}
}
finally
{
breader.close();
}
}
private void check( String name, Object value ) throws IOException
{
Dictionary<String, Object> props = new Hashtable<>();
props.put( name, value );
check( name, props );
}
private void check( String pid, Dictionary<String, Object> props ) throws IOException
{
fpm.store( pid, props );
assertTrue( new File( file, fpm.encodePid( pid ) + ".config" ).exists() );
@SuppressWarnings("unchecked")
Dictionary<String, Object> loaded = fpm.load( pid );
assertNotNull( loaded );
assertEquals( props.size(), loaded.size() );
for ( Enumeration<String> pe = props.keys(); pe.hasMoreElements(); )
{
String key = pe.nextElement();
checkValues( props.get( key ), loaded.get( key ) );
}
}
private void checkValues( Object value1, Object value2 )
{
assertNotNull( value2 );
if ( value1.getClass().isArray() )
{
assertTrue( value2.getClass().isArray() );
assertEquals( value1.getClass().getComponentType(), value2.getClass().getComponentType() );
assertEquals( Array.getLength( value1 ), Array.getLength( value2 ) );
for ( int i = 0; i < Array.getLength( value1 ); i++ )
{
assertEquals( Array.get( value1, i ), Array.get( value2, i ) );
}
}
else
{
assertEquals( value1, value2 );
}
}
}

View File

@ -0,0 +1,293 @@
/*
* 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.cm.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Vector;
import org.junit.Test;
public class CaseInsensitiveDictionaryTest
{
@Test
public void testLocaleIndependence() {
Locale defaultLocal = Locale.getDefault();
CaseInsensitiveDictionary dict = new CaseInsensitiveDictionary();
dict.put("illegal", "value1");
dict.put("ILLEGAL", "value2");
assertEquals(dict.get("illegal"), "value2");
assertEquals(dict.get("ILLEGAL"), "value2");
// validate "i" conversion with Turkish default locale
Locale.setDefault(new Locale("tr", "" ,""));
try {
dict = new CaseInsensitiveDictionary();
dict.put("illegal", "value1");
dict.put("ILLEGAL", "value2");
assertEquals(dict.get("illegal"), "value2");
assertEquals(dict.get("ILLEGAL"), "value2");
} finally {
Locale.setDefault(defaultLocal);
}
}
@Test
public void testCheckValueNull()
{
// null which must throw IllegalArgumentException
try
{
CaseInsensitiveDictionary.checkValue( null );
fail( "Expected IllegalArgumentException for null value" );
}
catch ( IllegalArgumentException iae )
{
}
}
@Test
public void testCheckValueSimple()
{
internalTestCheckValue( "String" );
internalTestCheckValue( new Integer( 1 ) );
internalTestCheckValue( new Long( 1 ) );
internalTestCheckValue( new Float( 1 ) );
internalTestCheckValue( new Double( 1 ) );
internalTestCheckValue( new Byte( ( byte ) 1 ) );
internalTestCheckValue( new Short( ( short ) 1 ) );
internalTestCheckValue( new Character( 'a' ) );
internalTestCheckValue( Boolean.TRUE );
}
@Test
public void testCheckValueSimpleArray()
{
internalTestCheckValue( new String[]
{ "String" } );
internalTestCheckValue( new Integer[]
{ new Integer( 1 ) } );
internalTestCheckValue( new Long[]
{ new Long( 1 ) } );
internalTestCheckValue( new Float[]
{ new Float( 1 ) } );
internalTestCheckValue( new Double[]
{ new Double( 1 ) } );
internalTestCheckValue( new Byte[]
{ new Byte( ( byte ) 1 ) } );
internalTestCheckValue( new Short[]
{ new Short( ( short ) 1 ) } );
internalTestCheckValue( new Character[]
{ new Character( 'a' ) } );
internalTestCheckValue( new Boolean[]
{ Boolean.TRUE } );
}
@Test
public void testCheckValuePrimitiveArray()
{
internalTestCheckValue( new long[]
{ 1 } );
internalTestCheckValue( new int[]
{ 1 } );
internalTestCheckValue( new short[]
{ 1 } );
internalTestCheckValue( new char[]
{ 1 } );
internalTestCheckValue( new byte[]
{ 1 } );
internalTestCheckValue( new double[]
{ 1 } );
internalTestCheckValue( new float[]
{ 1 } );
internalTestCheckValue( new boolean[]
{ true } );
}
@Test
public void testCheckValueSimpleVector()
{
internalTestCheckValueVector( "String", String.class );
internalTestCheckValueVector( new Integer( 1 ), Integer.class );
internalTestCheckValueVector( new Long( 1 ), Long.class );
internalTestCheckValueVector( new Float( 1 ), Float.class );
internalTestCheckValueVector( new Double( 1 ), Double.class );
internalTestCheckValueVector( new Byte( ( byte ) 1 ), Byte.class );
internalTestCheckValueVector( new Short( ( short ) 1 ), Short.class );
internalTestCheckValueVector( new Character( 'a' ), Character.class );
internalTestCheckValueVector( Boolean.TRUE, Boolean.class );
}
@Test
public void testCheckValueSimpleSet()
{
internalTestCheckValueSet( "String", String.class );
internalTestCheckValueSet( new Integer( 1 ), Integer.class );
internalTestCheckValueSet( new Long( 1 ), Long.class );
internalTestCheckValueSet( new Float( 1 ), Float.class );
internalTestCheckValueSet( new Double( 1 ), Double.class );
internalTestCheckValueSet( new Byte( ( byte ) 1 ), Byte.class );
internalTestCheckValueSet( new Short( ( short ) 1 ), Short.class );
internalTestCheckValueSet( new Character( 'a' ), Character.class );
internalTestCheckValueSet( Boolean.TRUE, Boolean.class );
}
@Test
public void testCheckValueSimpleArrayList()
{
internalTestCheckValueList( "String", String.class );
internalTestCheckValueList( new Integer( 1 ), Integer.class );
internalTestCheckValueList( new Long( 1 ), Long.class );
internalTestCheckValueList( new Float( 1 ), Float.class );
internalTestCheckValueList( new Double( 1 ), Double.class );
internalTestCheckValueList( new Byte( ( byte ) 1 ), Byte.class );
internalTestCheckValueList( new Short( ( short ) 1 ), Short.class );
internalTestCheckValueList( new Character( 'a' ), Character.class );
internalTestCheckValueList( Boolean.TRUE, Boolean.class );
}
private <T> void internalTestCheckValueList( T value, Class<T> collectionType )
{
Collection<T> coll = new ArrayList<>();
coll.add( value );
internalTestCheckValue( coll );
}
private <T> void internalTestCheckValueVector( T value, Class<T> collectionType )
{
Collection<T> coll = new Vector<>();
coll.add( value );
internalTestCheckValue( coll );
}
private <T> void internalTestCheckValueSet( T value, Class<T> collectionType )
{
Collection<T> coll = new HashSet<>();
coll.add( value );
internalTestCheckValue( coll );
}
private void internalTestCheckValue( Object value )
{
assertEqualValue( value, CaseInsensitiveDictionary.checkValue( value ) );
}
private void assertEqualValue( Object expected, Object actual )
{
if ( ( expected instanceof Collection ) && ( actual instanceof Collection ) )
{
Collection<?> eColl = ( Collection<?> ) expected;
Collection<?> aColl = ( Collection<?> ) actual;
if ( eColl.size() != aColl.size() )
{
fail( "Unexpected size. expected:" + eColl.size() + ", actual: " + aColl.size() );
}
// create a list from the expected collection and remove
// all values from the actual collection, this should get
// an empty collection
List<?> eList = new ArrayList<>( eColl );
eList.removeAll( aColl );
assertTrue( "Collections do not match. expected:" + eColl + ", actual: " + aColl, eList.isEmpty() );
}
else
{
assertEquals( expected, actual );
}
}
@Test
public void testValidKeys()
{
CaseInsensitiveDictionary.checkKey( "a" );
CaseInsensitiveDictionary.checkKey( "1" );
CaseInsensitiveDictionary.checkKey( "-" );
CaseInsensitiveDictionary.checkKey( "_" );
CaseInsensitiveDictionary.checkKey( "A" );
CaseInsensitiveDictionary.checkKey( "a.b.c" );
CaseInsensitiveDictionary.checkKey( "a.1.c" );
CaseInsensitiveDictionary.checkKey( "a-sample.dotted_key.end" );
}
@Test
public void testKeyDots()
{
// FELIX-2184 these keys are all valid (but not recommended)
CaseInsensitiveDictionary.checkKey( "." );
CaseInsensitiveDictionary.checkKey( "a.b.c." );
CaseInsensitiveDictionary.checkKey( ".a.b.c." );
CaseInsensitiveDictionary.checkKey( "a..b" );
// valid key as of OSGi Compendium R4.2 (CM 1.3)
CaseInsensitiveDictionary.checkKey( ".a.b.c" );
}
@Test
public void testKeyIllegalCharacters()
{
testFailingKey( null );
testFailingKey( "" );
// FELIX-2184 these keys are all valid (but not recommended)
CaseInsensitiveDictionary.checkKey( " " );
CaseInsensitiveDictionary.checkKey( "§" );
CaseInsensitiveDictionary.checkKey( "${yikes}" );
CaseInsensitiveDictionary.checkKey( "a key with spaces" );
CaseInsensitiveDictionary.checkKey( "fail:key" );
}
private void testFailingKey( String key )
{
try
{
CaseInsensitiveDictionary.checkKey( key );
fail( "Expected IllegalArgumentException for key [" + key + "]" );
}
catch ( IllegalArgumentException iae )
{
// expected
}
}
}

View File

@ -0,0 +1,168 @@
/*
* 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.cm.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Dictionary;
import java.util.Hashtable;
import org.apache.felix.cm.MockPersistenceManager;
import org.apache.felix.cm.PersistenceManager;
import org.junit.Test;
import org.mockito.Mockito;
import org.osgi.framework.Constants;
import org.osgi.service.cm.Configuration;
public class ConfigurationAdapterTest
{
private static final String SCALAR = "scalar";
private static final String STRING_VALUE = "String Value";
private static final String STRING_VALUE2 = "Another String Value";
private static final String ARRAY = "array";
private final String[] ARRAY_VALUE;
private static final String COLLECTION = "collection";
private final Collection<String> COLLECTION_VALUE;
private static final String TEST_PID = "test.pid";
private static final String TEST_LOCATION = "test:location";
private final PersistenceManager pm = new MockPersistenceManager();
{
ARRAY_VALUE = new String[]
{ STRING_VALUE };
COLLECTION_VALUE = new ArrayList<>();
COLLECTION_VALUE.add( STRING_VALUE );
}
private Configuration getConfiguration() throws IOException
{
final ConfigurationManager configMgr = Mockito.mock(ConfigurationManager.class);
Mockito.when(configMgr.isActive()).thenReturn(true);
ConfigurationImpl cimpl = new ConfigurationImpl( configMgr, pm, TEST_PID, null, TEST_LOCATION );
return new ConfigurationAdapter( null, cimpl );
}
@Test public void testScalar() throws IOException
{
Configuration cimpl = getConfiguration();
Dictionary<String, Object> props = cimpl.getProperties();
assertNull( "Configuration is fresh", props );
props = new Hashtable<>();
props.put( SCALAR, STRING_VALUE );
cimpl.update( props );
Dictionary<String, Object> newProps = cimpl.getProperties();
assertNotNull( "Configuration is not fresh", newProps );
assertEquals( "Expect 2 elements", 2, newProps.size() );
assertEquals( "Service.pid must match", TEST_PID, newProps.get( Constants.SERVICE_PID ) );
assertEquals( "Scalar value must match", STRING_VALUE, newProps.get( SCALAR ) );
}
@Test public void testArray() throws IOException
{
Configuration cimpl = getConfiguration();
Dictionary<String, Object> props = cimpl.getProperties();
assertNull( "Configuration is fresh", props );
props = new Hashtable<>();
props.put( ARRAY, ARRAY_VALUE );
cimpl.update( props );
Dictionary<String, Object> newProps = cimpl.getProperties();
assertNotNull( "Configuration is not fresh", newProps );
assertEquals( "Expect 2 elements", 2, newProps.size() );
assertEquals( "Service.pid must match", TEST_PID, newProps.get( Constants.SERVICE_PID ) );
Object testProp = newProps.get( ARRAY );
assertNotNull( testProp );
assertTrue( testProp.getClass().isArray() );
assertEquals( 1, Array.getLength( testProp ) );
assertEquals( STRING_VALUE, Array.get( testProp, 0 ) );
// modify the array property
Array.set( testProp, 0, STRING_VALUE2 );
// the array element change must not be reflected in the configuration
Dictionary<String, Object> newProps2 = cimpl.getProperties();
Object testProp2 = newProps2.get( ARRAY );
assertNotNull( testProp2 );
assertTrue( testProp2.getClass().isArray() );
assertEquals( 1, Array.getLength( testProp2 ) );
assertEquals( STRING_VALUE, Array.get( testProp2, 0 ) );
}
@SuppressWarnings("unchecked")
@Test public void testCollection() throws IOException
{
Configuration cimpl = getConfiguration();
Dictionary<String, Object> props = cimpl.getProperties();
assertNull( "Configuration is fresh", props );
props = new Hashtable<>();
props.put( COLLECTION, COLLECTION_VALUE );
cimpl.update( props );
Dictionary<String, Object> newProps = cimpl.getProperties();
assertNotNull( "Configuration is not fresh", newProps );
assertEquals( "Expect 2 elements", 2, newProps.size() );
assertEquals( "Service.pid must match", TEST_PID, newProps.get( Constants.SERVICE_PID ) );
Object testProp = newProps.get( COLLECTION );
assertNotNull( testProp );
assertTrue( testProp instanceof Collection );
Collection<String> coll = ( Collection<String> ) testProp;
assertEquals( 1, coll.size() );
assertEquals( STRING_VALUE, coll.iterator().next() );
// modify the array property
coll.clear();
coll.add( STRING_VALUE2 );
// the array element change must not be reflected in the configuration
Dictionary<String, Object> newProps2 = cimpl.getProperties();
Object testProp2 = newProps2.get( COLLECTION );
assertNotNull( testProp2 );
assertTrue( testProp2 instanceof Collection );
Collection<String> coll2 = ( Collection<String> ) testProp2;
assertEquals( 1, coll2.size() );
assertEquals( STRING_VALUE, coll2.iterator().next() );
}
}

View File

@ -0,0 +1,139 @@
/*
* 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.cm.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.felix.cm.impl.persistence.ExtPersistenceManager;
import org.junit.Test;
import org.mockito.Mockito;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationAdmin;
public class ConfigurationAdminStarterTest {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testWaitingForPlugins() throws Exception {
final BundleContext bundleContext = Mockito.mock(BundleContext.class);
Mockito.when(bundleContext.registerService(Mockito.eq(ConfigurationAdmin.class),
Mockito.any(ServiceFactory.class),
(Dictionary) Mockito.any())).thenReturn(Mockito.mock(ServiceRegistration.class));
final ExtPersistenceManager epm = Mockito.mock(ExtPersistenceManager.class);
Mockito.when(epm.getDelegatee()).thenReturn(epm);
final AtomicInteger deactivateCount = new AtomicInteger();
final List<ExtPersistenceManager> activateList = new ArrayList<ExtPersistenceManager>();
final ConfigurationAdminStarter starter = new ConfigurationAdminStarter(bundleContext) {
@Override
public void activate(ExtPersistenceManager pm) {
activateList.add(pm);
super.activate(pm);
}
@Override
public void deactivate() {
deactivateCount.incrementAndGet();
super.deactivate();
}
};
starter.setPersistenceManager(epm);
assertTrue(activateList.isEmpty());
assertEquals(0, deactivateCount.get());
starter.updatePluginsSet(true);
assertEquals(0, deactivateCount.get());
assertEquals(1, activateList.size());
assertEquals(epm, activateList.get(0));
starter.updatePluginsSet(true);
assertEquals(0, deactivateCount.get());
assertEquals(1, activateList.size());
assertEquals(epm, activateList.get(0));
starter.updatePluginsSet(false);
assertEquals(1, deactivateCount.get());
assertEquals(1, activateList.size());
assertEquals(epm, activateList.get(0));
}
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void testWaitingForPM() throws Exception {
final BundleContext bundleContext = Mockito.mock(BundleContext.class);
Mockito.when(bundleContext.registerService(Mockito.eq(ConfigurationAdmin.class),
Mockito.any(ServiceFactory.class),
(Dictionary) Mockito.any())).thenReturn(Mockito.mock(ServiceRegistration.class));
final ExtPersistenceManager epm = Mockito.mock(ExtPersistenceManager.class);
Mockito.when(epm.getDelegatee()).thenReturn(epm);
final AtomicInteger deactivateCount = new AtomicInteger();
final List<ExtPersistenceManager> activateList = new ArrayList<ExtPersistenceManager>();
final ConfigurationAdminStarter starter = new ConfigurationAdminStarter(bundleContext) {
@Override
public void activate(ExtPersistenceManager pm) {
activateList.add(pm);
super.activate(pm);
}
@Override
public void deactivate() {
deactivateCount.incrementAndGet();
super.deactivate();
}
};
starter.updatePluginsSet(true);
assertTrue(activateList.isEmpty());
assertEquals(0, deactivateCount.get());
starter.setPersistenceManager(epm);
assertEquals(0, deactivateCount.get());
assertEquals(1, activateList.size());
assertEquals(epm, activateList.get(0));
starter.unsetPersistenceManager();
assertEquals(1, deactivateCount.get());
assertEquals(1, activateList.size());
assertEquals(epm, activateList.get(0));
}
}

View File

@ -0,0 +1,59 @@
/*
* 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.cm.impl;
import java.util.Dictionary;
import java.util.Hashtable;
import org.junit.Test;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class ConfigurationImplTest {
@Test public void testEqualsWithArrays() {
final Dictionary<String, Object> props1 = new Hashtable<String, Object>();
props1.put("array", new long[] {1,2});
final Dictionary<String, Object> props2 = new Hashtable<String, Object>();
props2.put("array", new long[] {1,2});
assertTrue(ConfigurationImpl.equals(props1, props2));
props2.put("array", new Long[] {1L,2L});
assertTrue(ConfigurationImpl.equals(props1, props2));
final Dictionary<String, Object> props3 = new Hashtable<String, Object>();
props3.put("array", new long[] {1,2,3});
assertFalse(ConfigurationImpl.equals(props1, props3));
final Dictionary<String, Object> props4 = new Hashtable<String, Object>();
props3.put("array", new long[] {1});
assertFalse(ConfigurationImpl.equals(props1, props4));
}
@Test public void testEqualsForNull() {
final Dictionary<String, Object> props = new Hashtable<String, Object>();
assertFalse(ConfigurationImpl.equals(props, null));
assertFalse(ConfigurationImpl.equals(null, props));
assertTrue(ConfigurationImpl.equals(null, null));
}
}

View File

@ -0,0 +1,644 @@
/*
* 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.cm.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.lang.reflect.Field;
import java.util.Dictionary;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import org.apache.felix.cm.MockBundleContext;
import org.apache.felix.cm.MockLogService;
import org.apache.felix.cm.MockNotCachablePersistenceManager;
import org.apache.felix.cm.MockPersistenceManager;
import org.apache.felix.cm.PersistenceManager;
import org.apache.felix.cm.impl.helper.ManagedServiceFactoryTracker;
import org.apache.felix.cm.impl.persistence.CachingPersistenceManagerProxy;
import org.apache.felix.cm.impl.persistence.PersistenceManagerProxy;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationEvent;
import org.osgi.service.cm.SynchronousConfigurationListener;
import org.osgi.service.log.LogService;
import org.osgi.util.tracker.ServiceTracker;
public class ConfigurationManagerTest
{
private PrintStream replacedStdErr;
private ByteArrayOutputStream output;
@Before
public void setUp() throws Exception
{
replacedStdErr = System.err;
output = new ByteArrayOutputStream();
System.setErr( new PrintStream( output ) );
setLogLevel(LogService.LOG_WARNING);
}
@After
public void tearDown() throws Exception
{
System.setErr( replacedStdErr );
}
@Test public void test_listConfigurations_cached() throws Exception
{
String pid = "testDefaultPersistenceManager";
PersistenceManager pm =new MockPersistenceManager();
Dictionary<String, Object> dictionary = new Hashtable<>();
dictionary.put( "property1", "value1" );
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
ConfigurationImpl[] conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
dictionary = new Hashtable<>();
dictionary.put( "property1", "value2" );
pid = "testDefaultPersistenceManager";
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
// verify that the property in the configurations cache was used
assertEquals("value1", conf[0].getProperties(true).get("property1"));
}
@Test public void test_listConfigurations_notcached() throws Exception
{
String pid = "testDefaultPersistenceManager";
PersistenceManager pm = new MockNotCachablePersistenceManager();
Dictionary<String, Object> dictionary = new Hashtable<>();
dictionary.put( "property1", "value1" );
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
ConfigurationManager configMgr = new ConfigurationManager(new PersistenceManagerProxy(pm), null);
ConfigurationImpl[] conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
dictionary = new Hashtable<>();
dictionary.put("property1", "valueNotCached");
pid = "testDefaultPersistenceManager";
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
conf = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf.length);
assertEquals(2, conf[0].getProperties(true).size());
// verify that the value returned was not the one from the cache
assertEquals("valueNotCached", conf[0].getProperties(true).get("property1"));
}
@Test public void test_listConfigurations_notcached_handlesUpdates() throws Exception
{
String pid = "testDefaultPersistenceManager";
PersistenceManager pm = new MockNotCachablePersistenceManager();
Dictionary<String, Object> dictionary = new Hashtable<>();
dictionary.put( "property1", "value1" );
dictionary.put( Constants.SERVICE_PID, pid );
pm.store( pid, dictionary );
ConfigurationManager configMgr = new ConfigurationManager(new PersistenceManagerProxy(pm), null) {
@Override
void updated(ConfigurationImpl config, boolean fireEvent) {
}
};
ConfigurationImpl[] conf1 = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf1.length);
assertEquals(2, conf1[0].getProperties(true).size());
// internal changecount
long revision = conf1[0].getRevision();
dictionary = new Hashtable<>();
dictionary.put("property1", "valueNotCached");
dictionary.put( Constants.SERVICE_PID, pid );
conf1[0].update(dictionary);
assertEquals(revision + 1, conf1[0].getRevision());
ConfigurationImpl[] conf2 = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf2.length);
assertEquals(2, conf2[0].getProperties(true).size());
assertEquals(revision + 1, conf2[0].getRevision());
dictionary = new Hashtable<>();
dictionary.put("property1", "secondUpdate");
dictionary.put( Constants.SERVICE_PID, pid );
conf2[0].update(dictionary);
assertEquals(revision + 2, conf2[0].getRevision());
ConfigurationImpl[] conf3 = configMgr.listConfigurations(new ConfigurationAdminImpl(configMgr, null), null);
assertEquals(1, conf3.length);
assertEquals(2, conf3[0].getProperties(true).size());
assertEquals(revision + 2, conf3[0].getRevision());
}
@Test public void testLogNoLogService() throws IOException
{
ConfigurationManager configMgr = createConfigurationManagerAndLog( null );
setLogLevel( LogService.LOG_WARNING );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_ERROR );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertNoLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
// lower than error -- no output
setLogLevel( LogService.LOG_ERROR - 1 );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertNoLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertNoLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
// minimal log level -- no output
setLogLevel( Integer.MIN_VALUE );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertNoLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertNoLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertNoLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_INFO );
assertNoLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_DEBUG );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
// maximal log level -- all output
setLogLevel( Integer.MAX_VALUE );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
}
// this test always expects output since when using a LogService, the log
// level property is ignored
@Test public void testLogWithLogService() throws IOException
{
LogService logService = new MockLogService();
ConfigurationManager configMgr = createConfigurationManagerAndLog( logService );
setLogLevel( LogService.LOG_WARNING );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_ERROR );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_ERROR - 1 );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( Integer.MIN_VALUE );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_INFO );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( LogService.LOG_DEBUG );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
setLogLevel( Integer.MAX_VALUE );
assertLog( configMgr, LogService.LOG_DEBUG, "Debug Test Message", null );
assertLog( configMgr, LogService.LOG_INFO, "Info Test Message", null );
assertLog( configMgr, LogService.LOG_WARNING, "Warning Test Message", null );
assertLog( configMgr, LogService.LOG_ERROR, "Error Test Message", null );
}
@Test public void testLogSetup() throws IOException
{
final MockBundleContext bundleContext = new MockBundleContext();
createConfigurationManagerAndLog( null );
// ensure the configuration data goes to target
bundleContext.setProperty( "felix.cm.dir", "target/config" );
// default value is 2
bundleContext.setProperty( "felix.cm.loglevel", null );
Log.logger.start( bundleContext );
assertEquals( 2, getLogLevel( ) );
Log.logger.stop( );
// illegal number yields default value
bundleContext.setProperty( "felix.cm.loglevel", "not-a-number" );
Log.logger.start( bundleContext );
assertEquals( 2, getLogLevel( ) );
Log.logger.stop( );
bundleContext.setProperty( "felix.cm.loglevel", "-100" );
Log.logger.start( bundleContext );
assertEquals( -100, getLogLevel( ) );
Log.logger.stop( );
bundleContext.setProperty( "felix.cm.loglevel", "4" );
Log.logger.start( bundleContext );
assertEquals( 4, getLogLevel( ) );
Log.logger.stop( );
}
@Test public void testEventsStartingBundle() throws Exception
{
final Set<String> result = new HashSet<>();
SynchronousConfigurationListener syncListener1 = new SynchronousConfigurationListener()
{
@Override
public void configurationEvent(ConfigurationEvent event)
{
result.add("L1");
}
};
SynchronousConfigurationListener syncListener2 = new SynchronousConfigurationListener()
{
@Override
public void configurationEvent(ConfigurationEvent event)
{
result.add("L2");
}
};
SynchronousConfigurationListener syncListener3 = new SynchronousConfigurationListener()
{
@Override
public void configurationEvent(ConfigurationEvent event)
{
result.add("L3");
}
};
ServiceReference mockRef = Mockito.mock( ServiceReference.class );
ServiceRegistration mockReg = Mockito.mock( ServiceRegistration.class );
Mockito.when( mockReg.getReference() ).thenReturn( mockRef );
ConfigurationManager configMgr = new ConfigurationManager(new PersistenceManagerProxy(new MockPersistenceManager()), null);
setServiceTrackerField( configMgr, "configurationListenerTracker" );
ServiceReference[] refs =
setServiceTrackerField( configMgr, "syncConfigurationListenerTracker",
syncListener1, syncListener2, syncListener3 );
for ( int i=0; i < refs.length; i++)
{
Bundle mockBundle = Mockito.mock( Bundle.class );
switch (i)
{
case 0:
Mockito.when( mockBundle.getState() ).thenReturn( Bundle.ACTIVE );
break;
case 1:
Mockito.when( mockBundle.getState() ).thenReturn( Bundle.STARTING );
break;
case 2:
Mockito.when( mockBundle.getState() ).thenReturn( Bundle.STOPPING );
break;
}
Mockito.when( refs[i].getBundle() ).thenReturn( mockBundle );
}
Field srField = configMgr.getClass().getDeclaredField( "configurationAdminRegistration" );
srField.setAccessible( true );
srField.set( configMgr, mockReg );
Field utField = configMgr.getClass().getDeclaredField( "updateThread" );
utField.setAccessible( true );
utField.set( configMgr, new UpdateThread( null, "Test updater" ));
Dictionary<String, Object> props = new Hashtable<>();
props.put( Constants.SERVICE_PID, "org.acme.testpid" );
ConfigurationImpl config = new ConfigurationImpl( configMgr, new MockPersistenceManager(), props );
configMgr.updated( config, true );
assertEquals("Both listeners should have been called, both in the STARTING and ACTIVE state, but not in the STOPPING state",
2, result.size());
}
@Test
public void test_factoryConfigurationCleanup() throws Exception
{
MockNotCachablePersistenceManager pm = new MockNotCachablePersistenceManager();
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
final Field bcField = configMgr.getClass().getDeclaredField("bundleContext");
bcField.setAccessible(true);
bcField.set(configMgr, new MockBundleContext());
setServiceTrackerField( configMgr, "configurationListenerTracker" );
setServiceTrackerField( configMgr, "syncConfigurationListenerTracker" );
final Field mstField = configMgr.getClass().getDeclaredField("managedServiceFactoryTracker");
mstField.setAccessible(true);
mstField.set( configMgr, new ManagedServiceFactoryTracker(configMgr) {
@Override
public void open() {
}
});
final Field utField = configMgr.getClass().getDeclaredField( "updateThread" );
utField.setAccessible( true );
utField.set( configMgr, new UpdateThread( null, "Test updater" ) {
@Override
void schedule(Runnable update) {
update.run();
}
});
final String factoryPid = "my.factory";
final Dictionary<String, Object> props = new Hashtable<>();
props.put("hello", "world");
final ConfigurationImpl c1 = configMgr.createFactoryConfiguration(factoryPid, null);
c1.update(props);
final ConfigurationImpl c2 = configMgr.createFactoryConfiguration(factoryPid, null);
c2.update(props);
final ConfigurationImpl c3 = configMgr.createFactoryConfiguration(factoryPid, null);
c3.update(props);
assertEquals(3, pm.getStored().size());
c1.delete();
assertEquals(2, pm.getStored().size());
c2.delete();
assertEquals(1, pm.getStored().size());
c3.delete();
assertEquals(0, pm.getStored().size());
}
@Test
public void test_namedFactoryConfigurationCleanup() throws Exception {
MockNotCachablePersistenceManager pm = new MockNotCachablePersistenceManager();
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
final ConfigurationAdmin admin = new ConfigurationAdminImpl(configMgr, null);
final Field activeField = configMgr.getClass().getDeclaredField("isActive");
activeField.setAccessible(true);
activeField.set(configMgr, Boolean.TRUE);
setServiceTrackerField(configMgr, "configurationListenerTracker");
setServiceTrackerField(configMgr, "syncConfigurationListenerTracker");
final Field bcField = configMgr.getClass().getDeclaredField("bundleContext");
bcField.setAccessible(true);
bcField.set(configMgr, new MockBundleContext());
setServiceTrackerField(configMgr, "configurationListenerTracker");
setServiceTrackerField(configMgr, "syncConfigurationListenerTracker");
final Field mstField = configMgr.getClass().getDeclaredField("managedServiceFactoryTracker");
mstField.setAccessible(true);
mstField.set(configMgr, new ManagedServiceFactoryTracker(configMgr) {
@Override
public void open() {
}
});
final Field utField = configMgr.getClass().getDeclaredField("updateThread");
utField.setAccessible(true);
utField.set(configMgr, new UpdateThread(null, "Test updater") {
@Override
void schedule(Runnable update) {
update.run();
}
});
final String factoryPid = "my.factory";
final Dictionary<String, Object> props = new Hashtable<>();
props.put("hello", "world");
final Configuration c1 = admin.getFactoryConfiguration(factoryPid, "1", null);
c1.update(props);
final Configuration c2 = admin.getFactoryConfiguration(factoryPid, "2", null);
c2.update(props);
final Configuration c3 = admin.getFactoryConfiguration(factoryPid, "3", null);
c3.update(props);
assertEquals(3, pm.getStored().size());
c1.delete();
assertEquals(2, pm.getStored().size());
c2.delete();
assertEquals(1, pm.getStored().size());
c3.delete();
assertEquals(0, pm.getStored().size());
}
private void assertNoLog( ConfigurationManager configMgr, int level, String message, Throwable t )
{
try
{
Log.logger.log( level, message, t );
assertTrue( "Expecting no log output", output.size() == 0 );
}
finally
{
// clear the output for future data
output.reset();
}
}
private void assertLog( ConfigurationManager configMgr, int level, String message, Throwable t )
{
try
{
Log.logger.log( level, message, t );
assertTrue( "Expecting log output", output.size() > 0 );
final String expectedLog = MockLogService.toMessageLine( level, message );
final String actualLog = new String( output.toByteArray() );
assertEquals( "Log Message not correct", expectedLog, actualLog );
}
finally
{
// clear the output for future data
output.reset();
}
}
private static void setLogLevel( int level )
{
final String fieldName = "logLevel";
try
{
Field field = Log.class.getDeclaredField( fieldName );
field.setAccessible( true );
field.setInt( Log.logger, level );
}
catch ( Throwable ignore )
{
throw ( IllegalArgumentException ) new IllegalArgumentException( "Cannot set logLevel field value" )
.initCause( ignore );
}
}
private static int getLogLevel( )
{
final String fieldName = "logLevel";
try
{
Field field = Log.class.getDeclaredField( fieldName );
field.setAccessible( true );
return field.getInt( Log.logger );
}
catch ( Throwable ignore )
{
throw ( IllegalArgumentException ) new IllegalArgumentException( "Cannot get logLevel field value" )
.initCause( ignore );
}
}
private static ServiceReference[] setServiceTrackerField( ConfigurationManager configMgr,
String fieldName, Object ... services ) throws Exception
{
final Map<ServiceReference, Object> refMap = new HashMap<>();
for ( Object svc : services )
{
ServiceReference sref = Mockito.mock( ServiceReference.class );
Mockito.when( sref.getProperty( "objectClass" ) ).thenReturn(new String[] { "TestService" });
refMap.put( sref, svc );
}
Field field = configMgr.getClass().getDeclaredField( fieldName );
field.setAccessible( true );
field.set( configMgr, new ServiceTracker( new MockBundleContext(), "", null )
{
@Override
public ServiceReference[] getServiceReferences()
{
return refMap.keySet().toArray( new ServiceReference[0] );
}
@Override
public Object getService(ServiceReference reference)
{
return refMap.get( reference );
}
} );
return refMap.keySet().toArray(new ServiceReference[0]);
}
private static ConfigurationManager createConfigurationManagerAndLog( final LogService logService )
throws IOException
{
final PersistenceManager pm = Mockito.mock(PersistenceManager.class);
ConfigurationManager configMgr = new ConfigurationManager(new CachingPersistenceManagerProxy(pm), null);
try
{
Field field = Log.class.getDeclaredField( "logTracker" );
field.setAccessible( true );
field.set( Log.logger, new ServiceTracker( new MockBundleContext(), "", null )
{
@Override
public Object getService()
{
return logService;
}
} );
}
catch ( Throwable ignore )
{
throw ( IllegalArgumentException ) new IllegalArgumentException( "Cannot set logTracker field value" )
.initCause( ignore );
}
return configMgr;
}
}

View File

@ -0,0 +1,180 @@
/*
* 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.cm.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.Dictionary;
import org.apache.felix.cm.MockBundle;
import org.apache.felix.cm.MockBundleContext;
import org.apache.felix.cm.file.FilePersistenceManager;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
public class DynamicBindingsTest
{
private File configLocation;
private File bindingsFile;
private FilePersistenceManager persistenceManager;
private static final String PID1 = "test.pid.1";
private static final String LOCATION1 = "test://location.1";
@Before
public void setUp() throws Exception
{
configLocation = new File( "target/config." + System.currentTimeMillis() );
persistenceManager = new FilePersistenceManager( configLocation.getAbsolutePath() );
bindingsFile = new File( configLocation, DynamicBindings.BINDINGS_FILE_NAME + ".config" );
}
@After
public void tearDown() throws Exception
{
bindingsFile.delete();
configLocation.delete();
}
@Test public void test_no_bindings() throws IOException
{
// ensure there is no file
bindingsFile.delete();
final BundleContext ctx = new MockBundleContext();
final DynamicBindings dm = new DynamicBindings( ctx, persistenceManager );
final Dictionary<String, Object> bindings = getBindings( dm );
assertNotNull( bindings );
assertTrue( bindings.isEmpty() );
}
@Test public void test_store_bindings() throws IOException
{
// ensure there is no file
bindingsFile.delete();
final BundleContext ctx = new MockBundleContext();
final DynamicBindings dm = new DynamicBindings( ctx, persistenceManager );
dm.putLocation( PID1, LOCATION1 );
assertEquals( LOCATION1, dm.getLocation( PID1 ) );
assertTrue( bindingsFile.exists() );
@SuppressWarnings("unchecked")
final Dictionary<String, Object> bindings = persistenceManager.load( DynamicBindings.BINDINGS_FILE_NAME );
assertNotNull( bindings );
assertEquals( 1, bindings.size() );
assertEquals( LOCATION1, bindings.get( PID1 ) );
}
@Test public void test_store_and_load_bindings() throws IOException
{
// ensure there is no file
bindingsFile.delete();
// preset bindings
final DynamicBindings dm0 = new DynamicBindings( new MockBundleContext(), persistenceManager );
dm0.putLocation( PID1, LOCATION1 );
// check bindings
final BundleContext ctx = new DMTestMockBundleContext();
final DynamicBindings dm = new DynamicBindings( ctx, persistenceManager );
// API check
assertEquals( LOCATION1, dm.getLocation( PID1 ) );
// low level check
final Dictionary<String, Object> bindings = getBindings( dm );
assertNotNull( bindings );
assertEquals( 1, bindings.size() );
assertEquals( LOCATION1, bindings.get( PID1 ) );
}
@Test public void test_store_and_load_bindings_with_cleanup() throws IOException
{
// ensure there is no file
bindingsFile.delete();
// preset bindings
final DynamicBindings dm0 = new DynamicBindings( new MockBundleContext(), persistenceManager );
dm0.putLocation( PID1, LOCATION1 );
// check bindings
final DynamicBindings dm = new DynamicBindings( new MockBundleContext(), persistenceManager );
// API check
assertNull( dm.getLocation( PID1 ) );
// low level check
final Dictionary<String, Object> bindings = getBindings( dm );
assertNotNull( bindings );
assertTrue( bindings.isEmpty() );
}
@SuppressWarnings("unchecked")
private static Dictionary<String, Object> getBindings( DynamicBindings dm )
{
try
{
final Field bindings = dm.getClass().getDeclaredField( "bindings" );
bindings.setAccessible( true );
return ( Dictionary<String, Object> ) bindings.get( dm );
}
catch ( Throwable t )
{
fail( "Cannot get bindings field: " + t );
return null;
}
}
private static class DMTestMockBundleContext extends MockBundleContext
{
@Override
public Bundle[] getBundles()
{
return new Bundle[]
{ new MockBundle( this, LOCATION1 ) };
}
}
}

View File

@ -0,0 +1,276 @@
/*
* 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.cm.impl;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.junit.Test;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceReference;
import org.osgi.service.cm.ConfigurationPlugin;
public class RankingComparatorTest
{
private final Comparator<ServiceReference<?>> srvRank = RankingComparator.SRV_RANKING;
private final Comparator<ServiceReference<?>> cmRank = RankingComparator.CM_RANKING;
@Test public void test_service_ranking_no_property()
{
ServiceReference<?> r1 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, null );
ServiceReference<?> r2 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, null );
ServiceReference<?> r3 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, null );
assertTrue( srvRank.compare( r1, r1 ) == 0 );
assertTrue( srvRank.compare( r1, r2 ) < 0 );
assertTrue( srvRank.compare( r1, r3 ) < 0 );
assertTrue( srvRank.compare( r2, r1 ) > 0 );
assertTrue( srvRank.compare( r2, r2 ) == 0 );
assertTrue( srvRank.compare( r2, r3 ) < 0 );
assertTrue( srvRank.compare( r3, r1 ) > 0 );
assertTrue( srvRank.compare( r3, r2 ) > 0 );
assertTrue( srvRank.compare( r3, r3 ) == 0 );
assertTrue( cmRank.compare( r1, r1 ) == 0 );
assertTrue( cmRank.compare( r1, r2 ) > 0 );
assertTrue( cmRank.compare( r1, r3 ) > 0 );
assertTrue( cmRank.compare( r2, r1 ) < 0 );
assertTrue( cmRank.compare( r2, r2 ) == 0 );
assertTrue( cmRank.compare( r2, r3 ) > 0 );
assertTrue( cmRank.compare( r3, r1 ) < 0 );
assertTrue( cmRank.compare( r3, r2 ) < 0 );
assertTrue( cmRank.compare( r3, r3 ) == 0 );
}
@Test public void test_service_ranking_property()
{
ServiceReference<?> r1 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, new Integer( 100 ) );
ServiceReference<?> r2 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, new Integer( -100 ) );
ServiceReference<?> r3 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, null );
assertTrue( srvRank.compare( r1, r1 ) == 0 );
assertTrue( srvRank.compare( r1, r2 ) < 0 );
assertTrue( srvRank.compare( r1, r3 ) < 0 );
assertTrue( srvRank.compare( r2, r1 ) > 0 );
assertTrue( srvRank.compare( r2, r2 ) == 0 );
assertTrue( srvRank.compare( r2, r3 ) > 0 );
assertTrue( srvRank.compare( r3, r1 ) > 0 );
assertTrue( srvRank.compare( r3, r2 ) < 0 );
assertTrue( srvRank.compare( r3, r3 ) == 0 );
}
@Test public void test_service_cm_ranking_property()
{
ServiceReference<?> r1 = new MockServiceReference()
.setProperty( ConfigurationPlugin.CM_RANKING, new Integer( 100 ) );
ServiceReference<?> r2 = new MockServiceReference().setProperty( ConfigurationPlugin.CM_RANKING,
new Integer( -100 ) );
ServiceReference<?> r3 = new MockServiceReference().setProperty( ConfigurationPlugin.CM_RANKING, null );
assertTrue( cmRank.compare( r1, r1 ) == 0 );
assertTrue( cmRank.compare( r1, r2 ) > 0 );
assertTrue( cmRank.compare( r1, r3 ) > 0 );
assertTrue( cmRank.compare( r2, r1 ) < 0 );
assertTrue( cmRank.compare( r2, r2 ) == 0 );
assertTrue( cmRank.compare( r2, r3 ) < 0 );
assertTrue( cmRank.compare( r3, r1 ) < 0 );
assertTrue( cmRank.compare( r3, r2 ) > 0 );
assertTrue( cmRank.compare( r3, r3 ) == 0 );
}
@Test public void test_service_ranking_sort()
{
ServiceReference<?> r1 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, new Integer( 100 ) );
ServiceReference<?> r2 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, new Integer( -100 ) );
ServiceReference<?> r3 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, null );
ServiceReference<?>[] refs =
{ r1, r2, r3 };
assertSame( r1, refs[0] );
assertSame( r2, refs[1] );
assertSame( r3, refs[2] );
Arrays.sort( refs, srvRank );
assertSame( r1, refs[0] );
assertSame( r2, refs[2] );
assertSame( r3, refs[1] );
}
@Test public void test_service_ranking_set()
{
ServiceReference<?> r1 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, new Integer( 100 ) );
ServiceReference<?> r2 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, new Integer( -100 ) );
ServiceReference<?> r3 = new MockServiceReference().setProperty( Constants.SERVICE_RANKING, null );
Set<ServiceReference<?>> refSet = new TreeSet<>( srvRank );
refSet.add( r1 );
refSet.add( r2 );
refSet.add( r3 );
Iterator<ServiceReference<?>> refIter = refSet.iterator();
assertSame( r1, refIter.next() );
assertSame( r3, refIter.next() );
assertSame( r2, refIter.next() );
}
@Test public void test_service_cm_ranking_sort()
{
ServiceReference<?> r1 = new MockServiceReference()
.setProperty( ConfigurationPlugin.CM_RANKING, new Integer( 100 ) );
ServiceReference<?> r2 = new MockServiceReference().setProperty( ConfigurationPlugin.CM_RANKING,
new Integer( -100 ) );
ServiceReference<?> r3 = new MockServiceReference().setProperty( ConfigurationPlugin.CM_RANKING, null );
ServiceReference<?>[] refs =
{ r1, r2, r3 };
assertSame( r1, refs[0] );
assertSame( r2, refs[1] );
assertSame( r3, refs[2] );
Arrays.sort( refs, cmRank );
assertSame( r1, refs[2] );
assertSame( r2, refs[0] );
assertSame( r3, refs[1] );
}
@Test public void test_service_cm_ranking_set()
{
ServiceReference<?> r1 = new MockServiceReference()
.setProperty( ConfigurationPlugin.CM_RANKING, new Integer( 100 ) );
ServiceReference<?> r2 = new MockServiceReference().setProperty( ConfigurationPlugin.CM_RANKING,
new Integer( -100 ) );
ServiceReference<?> r3 = new MockServiceReference().setProperty( ConfigurationPlugin.CM_RANKING, null );
Set<ServiceReference<?>> refSet = new TreeSet<>( cmRank );
refSet.add( r1 );
refSet.add( r2 );
refSet.add( r3 );
Iterator<ServiceReference<?>> refIter = refSet.iterator();
assertSame( r2, refIter.next() );
assertSame( r3, refIter.next() );
assertSame( r1, refIter.next() );
}
private static class MockServiceReference implements ServiceReference<Object>
{
static long id = 0;
private final Map<String, Object> props = new HashMap<>();
{
props.put( Constants.SERVICE_ID, new Long( id ) );
id++;
}
MockServiceReference setProperty( final String key, final Object value )
{
if ( value == null )
{
props.remove( key );
}
else
{
props.put( key, value );
}
return this;
}
@Override
public Object getProperty( String key )
{
return props.get( key );
}
@Override
public String[] getPropertyKeys()
{
return props.keySet().toArray( new String[props.size()] );
}
@Override
public Bundle getBundle()
{
return null;
}
@Override
public Bundle[] getUsingBundles()
{
return null;
}
@Override
public boolean isAssignableTo( Bundle bundle, String className )
{
return false;
}
@Override
public int compareTo( Object reference )
{
return 0;
}
@Override
public String toString()
{
return "ServiceReference " + getProperty( Constants.SERVICE_ID );
}
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.cm.impl;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import org.apache.felix.cm.impl.persistence.ExtPersistenceManager;
import org.junit.Test;
import org.mockito.Mockito;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.ServiceFactory;
import org.osgi.framework.ServiceReference;
import org.osgi.framework.ServiceRegistration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.cm.ConfigurationPlugin;
public class RequiredConfigurationPluginTrackerTest {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Test
public void test() throws Exception {
final BundleContext bundleContext = Mockito.mock(BundleContext.class);
Mockito.when(bundleContext.getService(Mockito.any(ServiceReference.class)))
.thenReturn(Mockito.mock(ConfigurationPlugin.class));
Mockito.when(bundleContext.registerService(Mockito.eq(ConfigurationAdmin.class),
Mockito.any(ServiceFactory.class), (Dictionary) Mockito.any()))
.thenReturn(Mockito.mock(ServiceRegistration.class));
final String[] pluginNames = new String[] { "p1", "p3" };
final AtomicInteger deactivateCount = new AtomicInteger();
final List<ExtPersistenceManager> activateList = new ArrayList<ExtPersistenceManager>();
final List<String> propList = new ArrayList<>();
final ExtPersistenceManager epm = Mockito.mock(ExtPersistenceManager.class);
Mockito.when(epm.getDelegatee()).thenReturn(epm);
final ConfigurationAdminStarter starter = new ConfigurationAdminStarter(bundleContext) {
@Override
public void activate(ExtPersistenceManager pm) {
activateList.add(pm);
super.activate(pm);
}
@Override
public void deactivate() {
deactivateCount.incrementAndGet();
super.deactivate();
}
@Override
public void updateRegisteredConfigurationPlugins(final String propValue) {
propList.add(propValue);
super.updateRegisteredConfigurationPlugins(propValue);
}
};
starter.setPersistenceManager(epm);
final ActivatorWorkerQueue queue = new ActivatorWorkerQueue() {
@Override
public void enqueue(Runnable r) {
r.run();
}
};
final RequiredConfigurationPluginTracker tracker = new RequiredConfigurationPluginTracker(bundleContext,
queue, starter, pluginNames);
final ServiceReference<ConfigurationPlugin> r1 = Mockito.mock(ServiceReference.class);
Mockito.when(r1.getProperty(RequiredConfigurationPluginTracker.PROPERTY_NAME)).thenReturn("p1");
Mockito.when(r1.getProperty(Constants.SERVICE_ID)).thenReturn(1L);
tracker.addingService(r1);
final ServiceReference<ConfigurationPlugin> r2 = Mockito.mock(ServiceReference.class);
Mockito.when(r2.getProperty(RequiredConfigurationPluginTracker.PROPERTY_NAME)).thenReturn("p2");
Mockito.when(r2.getProperty(Constants.SERVICE_ID)).thenReturn(2L);
tracker.addingService(r2);
assertTrue(activateList.isEmpty());
assertEquals(0, deactivateCount.get());
assertEquals(2, propList.size());
assertEquals("p1,p2", propList.get(1));
final ServiceReference<ConfigurationPlugin> r3 = Mockito.mock(ServiceReference.class);
Mockito.when(r3.getProperty(RequiredConfigurationPluginTracker.PROPERTY_NAME)).thenReturn("p3");
Mockito.when(r3.getProperty(Constants.SERVICE_ID)).thenReturn(3L);
tracker.addingService(r3);
assertEquals(1, activateList.size());
assertEquals(0, deactivateCount.get());
assertEquals(3, propList.size());
assertEquals("p1,p2,p3", propList.get(2));
final ServiceReference<ConfigurationPlugin> r4 = Mockito.mock(ServiceReference.class);
Mockito.when(r4.getProperty(RequiredConfigurationPluginTracker.PROPERTY_NAME)).thenReturn("p4");
Mockito.when(r4.getProperty(Constants.SERVICE_ID)).thenReturn(4L);
tracker.addingService(r4);
assertEquals(1, activateList.size());
assertEquals(0, deactivateCount.get());
assertEquals(4, propList.size());
assertEquals("p1,p2,p3,p4", propList.get(3));
tracker.removedService(r4, Mockito.mock(ConfigurationPlugin.class));
assertEquals(1, activateList.size());
assertEquals(0, deactivateCount.get());
assertEquals(5, propList.size());
assertEquals("p1,p2,p3", propList.get(4));
tracker.removedService(r1, Mockito.mock(ConfigurationPlugin.class));
assertEquals(1, activateList.size());
assertEquals(1, deactivateCount.get());
assertEquals(6, propList.size());
assertEquals("p2,p3", propList.get(5));
}
}

View File

@ -0,0 +1,140 @@
/*
* 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.cm.impl.helper;
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import junit.framework.TestCase;
public class ConfigurationMapTest
{
@Test
public void test_accepts()
{
ConfigurationMap<String> holder = new TestConfigurationMap( new String[]
{ "a", "b", "c" } );
TestCase.assertTrue( holder.accepts( "a" ) );
TestCase.assertTrue( holder.accepts( "b" ) );
TestCase.assertTrue( holder.accepts( "c" ) );
TestCase.assertFalse( holder.accepts( "x" ) );
}
@Test
public void test_isDifferentPids_null_null()
{
ConfigurationMap<String> holder = new TestConfigurationMap( null );
TestCase.assertFalse( "Expect both pids null to be the same", holder.isDifferentPids( null ) );
}
@Test
public void test_isDifferentPids_null_notNull()
{
ConfigurationMap<String> holder = new TestConfigurationMap( null );
TestCase.assertTrue( "Expect not same for one pid not null", holder.isDifferentPids( new String[]
{ "entry" } ) );
}
@Test
public void test_isDifferentPids_notNull_null()
{
ConfigurationMap<String> holder = new TestConfigurationMap( new String[]
{ "entry" } );
TestCase.assertTrue( "Expect not same for one pid not null", holder.isDifferentPids( null ) );
}
@Test
public void test_isDifferentPids_notNull_notNull()
{
final String[] pids10 =
{ "a", "b" };
final String[] pids11 =
{ "b", "a" };
final String[] pids20 =
{ "a", "c" };
final String[] pids30 =
{ "a", "b", "c" };
final ConfigurationMap<String> holder10 = new TestConfigurationMap( pids10 );
TestCase.assertFalse( holder10.isDifferentPids( pids10 ) );
TestCase.assertFalse( holder10.isDifferentPids( pids11 ) );
TestCase.assertTrue( holder10.isDifferentPids( pids20 ) );
TestCase.assertTrue( holder10.isDifferentPids( pids30 ) );
final ConfigurationMap<String> holder20 = new TestConfigurationMap( pids20 );
TestCase.assertTrue( holder20.isDifferentPids( pids10 ) );
TestCase.assertTrue( holder20.isDifferentPids( pids11 ) );
TestCase.assertFalse( holder20.isDifferentPids( pids20 ) );
TestCase.assertTrue( holder20.isDifferentPids( pids30 ) );
}
/*
* Simple ConfigurationMap implementation sufficing for these tests
* which only test the methods in the abstract base class.
*/
static class TestConfigurationMap extends ConfigurationMap<String>
{
protected TestConfigurationMap( String[] configuredPids )
{
super( configuredPids );
}
@Override
protected Map<String, String> createMap( int size )
{
return new HashMap<>( size );
}
@Override
protected void record( TargetedPID configPid, TargetedPID factoryPid, long revision )
{
TestCase.fail( "<record> is not implemented" );
}
@Override
protected boolean shallTake( TargetedPID configPid, TargetedPID factoryPid, long revision )
{
TestCase.fail( "<shallTake> is not implemented" );
return false;
}
@Override
protected boolean removeConfiguration( TargetedPID configPid, TargetedPID factoryPid )
{
TestCase.fail( "<removeConfiguration> is not implemented" );
return false;
}
}
}

Some files were not shown because too many files have changed in this diff Show More