ENTAXY-374 release 1.8.2
This commit is contained in:
161
underlying/artemis.entaxy/artemis-server-osgi/pom.xml
Normal file
161
underlying/artemis.entaxy/artemis-server-osgi/pom.xml
Normal 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>
|
@ -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);
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
1988
underlying/artemis.entaxy/pom.xml
Normal file
1988
underlying/artemis.entaxy/pom.xml
Normal file
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
|
@ -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);
|
||||
|
@ -542,7 +542,7 @@ public class RedeliveryPolicyDefinition {
|
||||
}
|
||||
|
||||
/**
|
||||
* Turn on exponential backk off
|
||||
* Turn on exponential back off
|
||||
*
|
||||
* @return the builder
|
||||
*/
|
||||
|
@ -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>
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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 }
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -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}">
|
||||
|
@ -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>
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
||||
|
203
underlying/org.apache.aries.blueprint.cm-1.3.2.entaxy/LICENSE
Normal file
203
underlying/org.apache.aries.blueprint.cm-1.3.2.entaxy/LICENSE
Normal 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.
|
||||
|
@ -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/).
|
||||
|
||||
|
267
underlying/org.apache.aries.blueprint.cm-1.3.2.entaxy/pom.xml
Normal file
267
underlying/org.apache.aries.blueprint.cm-1.3.2.entaxy/pom.xml
Normal 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>
|
@ -0,0 +1,2 @@
|
||||
This product includes software developed at
|
||||
the OSGi Alliance (http://www.osgi.org/).
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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 <service/> 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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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>
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
396
underlying/org.apache.felix.configadmin-1.9.16.entaxy/pom.xml
Normal file
396
underlying/org.apache.felix.configadmin-1.9.16.entaxy/pom.xml
Normal 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>
|
@ -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
|
||||
|
@ -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.
|
@ -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
|
||||
{
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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 "=" value .
|
||||
* prop = symbolic-name . // 1.4.2 of OSGi Core Specification
|
||||
* symbolic-name = token { "." token } .
|
||||
* token = { [ 0..9 ] | [ a..z ] | [ A..Z ] | '_' | '-' } .
|
||||
* value = [ type ] ( "[" values "]" | "(" values ")" | simple ) .
|
||||
* values = simple { "," simple } .
|
||||
* simple = """ stringsimple """ .
|
||||
* 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 );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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ä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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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" );
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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 );
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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>< 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>> 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>< 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>> 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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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 + ")";
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
||||
|
||||
|
@ -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")
|
@ -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" );
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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" );
|
||||
}
|
||||
}
|
@ -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 ) );
|
||||
}
|
||||
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
@ -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() );
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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));
|
||||
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 ) };
|
||||
}
|
||||
}
|
||||
}
|
@ -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 );
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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
Reference in New Issue
Block a user