This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.commons.osgi-2.0.2-incubator in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-commons-osgi.git
commit df63e8f03c158a39a9eb78a13e8ba075bff1b3d5 Author: Felix Meschberger <[email protected]> AuthorDate: Fri Jan 4 16:41:06 2008 +0000 First steps towards a Prototype of 'Everything is a Resource' git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/whiteboard/fmeschbe/resource/osgi/commons@608914 13f79535-47bb-0310-9956-ffa450edef68 --- pom.xml | 97 +++++ .../apache/sling/osgi/commons/AdapterFactory.java | 80 ++++ .../apache/sling/osgi/commons/AdapterManager.java | 52 +++ .../org/apache/sling/osgi/commons/OsgiUtil.java | 215 +++++++++++ .../apache/sling/osgi/commons/SlingAdaptable.java | 40 ++ .../commons/internal/AdapterFactoryDescriptor.java | 47 +++ .../internal/AdapterFactoryDescriptorKey.java | 88 +++++ .../internal/AdapterFactoryDescriptorMap.java | 39 ++ .../osgi/commons/internal/AdapterManagerImpl.java | 428 +++++++++++++++++++++ src/main/resources/META-INF/LICENSE | 202 ++++++++++ src/main/resources/META-INF/NOTICE | 5 + .../osgi/commons/internal/AdapterManagerTest.java | 245 ++++++++++++ .../osgi/commons/mock/MockAdapterFactory.java | 53 +++ .../apache/sling/osgi/commons/mock/MockBundle.java | 124 ++++++ .../osgi/commons/mock/MockComponentContext.java | 79 ++++ .../osgi/commons/mock/MockServiceReference.java | 62 +++ 16 files changed, 1856 insertions(+) diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..4cb2d06 --- /dev/null +++ b/pom.xml @@ -0,0 +1,97 @@ +<?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.sling</groupId> + <artifactId>sling</artifactId> + <version>1-incubator-SNAPSHOT</version> + <relativePath>../../parent/pom.xml</relativePath> + </parent> + + <artifactId>org.apache.sling.osgi.commons</artifactId> + <version>2.0.0-incubator-SNAPSHOT</version> + <packaging>bundle</packaging> + + <name>Sling - Commons OSGi support</name> + <description>Commons OSGi</description> + + <scm> + <connection> + scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/osgi/commons + </connection> + <developerConnection> + scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/osgi/commons + </developerConnection> + <url> + http://svn.apache.org/viewvc/incubator/sling/trunk/osgi/commons + </url> + </scm> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Export-Package> + org.apache.sling.osgi.commons;version=${pom.version} + </Export-Package> + <Private-Package> + org.apache.sling.osgi.commons.internal + </Private-Package> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + + <dependencies> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.api</artifactId> + <version>2.0.0-incubator-SNAPSHOT</version> + </dependency> + + <!-- OSGi Libraries --> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi_R4_core</artifactId> + </dependency> + <dependency> + <groupId>org.osgi</groupId> + <artifactId>osgi_R4_compendium</artifactId> + </dependency> + + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + </dependency> + </dependencies> + +</project> diff --git a/src/main/java/org/apache/sling/osgi/commons/AdapterFactory.java b/src/main/java/org/apache/sling/osgi/commons/AdapterFactory.java new file mode 100644 index 0000000..beaabc2 --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/AdapterFactory.java @@ -0,0 +1,80 @@ +/* + * 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.sling.osgi.commons; + +/** + * The <code>AdapterFactory</code> interface defines the API for helpers which + * may be provided to enhance the adaptability of adaptable objects. + * <p> + * Implementations of this interface are registered as OSGi services and are + * used by the {@link AdapterManager} to adapt objects on demand. The + * <code>AdapterFactory</code> services are not really intended to be used by + * clients directly. + */ +public interface AdapterFactory { + + /** + * The service name to use when registering implementations of this + * interface as services (value is + * "org.apache.sling.osgi.commons.AdapterFactory"). + */ + static final String SERVICE_NAME = AdapterFactory.class.getName(); + + /** + * The service registration property listing the fully qualified names of + * classes which can be adapted by this adapter factory (value is + * "adaptables"). The "adaptable" parameters of the + * {@link #getAdapter(Object, Class)} method must be an instance of any of + * these classes for this factory to be able to adapt the object. + */ + static final String ADAPTABLE_CLASSES = "adaptables"; + + /** + * The service registration property listing the fully qualified names of + * classes to which this factory can adapt adaptables (value is "adapters"). + */ + static final String ADAPTER_CLASSES = "adapters"; + + /** + * Adapt the given object to the adaptable type. The adaptable object is + * guaranteed to be an instance of one of the classes listed in the + * {@link #ADAPTABLE_CLASSES} services registration property. The type + * parameter is on of the classes listed in the {@link #ADAPTER_CLASSES} + * service registration properties. + * <p> + * This method may return <code>null</code> if the adaptable object may + * not be adapted to the adapter (target) type for any reason. In this case, + * the implementation should log a message to the log facility noting the + * cause for not being able to adapt. + * <p> + * Note that the <code>adaptable</code> object is not required to + * implement the <code>Adaptable</code> interface, though most of the time + * this method is called by means of calling the + * {@link SlingAdaptable#adaptTo(Class)} method. + * + * @param <AdapterType> The generic type of the adapter (target) type. + * @param adaptable The object to adapt to the adapter type. + * @param type The type to which the object is to be adapted. + * @return The adapted object or <code>null</code> if this factory + * instance cannot adapt the object. + */ + <AdapterType> AdapterType getAdapter(Object adaptable, + Class<AdapterType> type); + +} diff --git a/src/main/java/org/apache/sling/osgi/commons/AdapterManager.java b/src/main/java/org/apache/sling/osgi/commons/AdapterManager.java new file mode 100644 index 0000000..0a142e9 --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/AdapterManager.java @@ -0,0 +1,52 @@ +/* + * 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.sling.osgi.commons; + +/** + * The <code>AdapterManager</code> defines the service interface for a manager + * for object adaption. The adapter manager coordinates the registered + * {@link AdapterFactory} services on behalf of clients wishing to adapt objects + * to other types. One such client is the {@link SlingAdaptable} class, which + * uses the implementation of this bundle to adapt "itself". + * <p> + * This interface is not intended to be implemented by clients. Rather it is + * used to define the service API used by the implementation is this bundle. + */ +public interface AdapterManager { + + /** + * Returns an adapter object of the requested <code>AdapterType</code> for + * the given <code>adaptable</code> object. + * <p> + * The <code>adaptable</code> object may be any non-<code>null</code> + * object and is not required to implement the <code>Adaptable</code> + * interface. + * + * @param <AdapterType> The generic type of the adapter (target) type. + * @param adaptable The object to adapt to the adapter type. + * @param type The type to which the object is to be adapted. + * @return The adapted object or <code>null</code> if no factory exists to + * adapt the <code>adaptable</code> to the + * <code>AdapterType</code> or if the <code>adaptable</code> + * cannot be adapted for any other reason. + */ + <AdapterType> AdapterType getAdapter(Object adaptable, + Class<AdapterType> type); + +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/osgi/commons/OsgiUtil.java b/src/main/java/org/apache/sling/osgi/commons/OsgiUtil.java new file mode 100644 index 0000000..49d7e23 --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/OsgiUtil.java @@ -0,0 +1,215 @@ +/* + * 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.sling.osgi.commons; + +import java.util.ArrayList; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; +import java.util.Vector; + +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceReference; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventAdmin; +import org.osgi.service.event.EventConstants; + +/** + * The <code>OsgiUtil</code> is a utility class providing some usefull utility + * methods. + */ +public class OsgiUtil { + + /** + * Returns the boolean value of the named service reference property or the + * <code>defaultValue</code> if no such service reference property exist. + * If the service property is not a <code>Boolean</code> it is converted + * by calling <code>Boolean.valueOf</code> on the string value of the + * property. + */ + public static boolean getProperty(ServiceReference reference, String name, + boolean defaultValue) { + Object propValue = getPropertyObject(reference, name); + if (propValue instanceof Boolean) { + return (Boolean) propValue; + } else if (propValue != null) { + return Boolean.valueOf(String.valueOf(propValue)); + } + + return defaultValue; + } + + /** + * Returns the named service reference property as a string or the + * <code>defaultValue</code> if no such reference property exists. + */ + public static String getProperty(ServiceReference reference, String name, + String defaultValue) { + Object propValue = getPropertyObject(reference, name); + return (propValue != null) ? propValue.toString() : defaultValue; + } + + /** + * Returns the named service reference property as an integer or the + * <code>defaultValue</code> if no such reference property exists or if + * the property is not an <code>Integer</code> and cannot be converted to + * an <code>Integer</code> from the property's string value. + */ + public static int getProperty(ServiceReference reference, String name, + int defaultValue) { + Object propValue = getPropertyObject(reference, name); + if (propValue instanceof Integer) { + return (Integer) propValue; + } else if (propValue != null) { + try { + return Integer.valueOf(String.valueOf(propValue)); + } catch (NumberFormatException nfe) { + // don't care, fall through to default value + } + } + + return defaultValue; + } + + /** + * Returns the named service reference property as a double or the + * <code>defaultValue</code> if no such reference property exists or if + * the property is not an <code>Double</code> and cannot be converted to + * an <code>Double</code> from the property's string value. + */ + public static double getProperty(ServiceReference reference, String name, + double defaultValue) { + Object propValue = getPropertyObject(reference, name); + if (propValue instanceof Double) { + return (Double) propValue; + } else if (propValue != null) { + try { + return Double.valueOf(String.valueOf(propValue)); + } catch (NumberFormatException nfe) { + // don't care, fall through to default value + } + } + + return defaultValue; + } + + /** + * Returns the named service reference property as a single value. If the + * property is neither an array nor a <code>java.util.Vector</code> the + * property is returned unmodified. If the property is a non-empty array, + * the first array element is returned. If the property is a non-empty + * <code>java.util.Vector</code>, the first vector element is returned. + * Otherwise <code>null</code> is returned. + */ + public static Object getPropertyObject(ServiceReference reference, + String name) { + Object propValue = reference.getProperty(name); + if (propValue == null) { + return null; + } else if (propValue.getClass().isArray()) { + Object[] prop = (Object[]) propValue; + return prop.length > 0 ? prop[0] : null; + } else if (propValue instanceof Vector) { + Vector<?> prop = (Vector<?>) propValue; + return prop.isEmpty() ? null : prop.get(0); + } else { + return propValue; + } + } + + /** + * Returns the named service reference property as an array of Strings. If + * the property is a scalar value its string value is returned as a single + * element array. If the property is an array, the elements are converted to + * String objects and returned as an array. If the property is a vector, the + * vector elements are converted to String objects and returned as an array. + * Otherwise (if the property does not exist) <code>null</code> is + * returned. + */ + public static String[] getProperty(ServiceReference reference, String name) { + Object propValue = reference.getProperty(name); + if (propValue instanceof String) { + // single string + return new String[] { (String) propValue }; + + } else if (propValue instanceof String[]) { + // String[] + return (String[]) propValue; + + } else if (propValue.getClass().isArray()) { + // other array + Object[] valueArray = (Object[]) propValue; + List<String> values = new ArrayList<String>(valueArray.length); + for (Object value : valueArray) { + if (value != null) { + values.add(value.toString()); + } + } + return values.toArray(new String[values.size()]); + + } else if (propValue instanceof Vector) { + // vector + Vector<?> valueVector = (Vector<?>) propValue; + List<String> values = new ArrayList<String>(valueVector.size()); + for (Object value : valueVector) { + if (value != null) { + values.add(value.toString()); + } + } + return values.toArray(new String[values.size()]); + } + + return null; + } + + public static Event createEvent(Bundle sourceBundle, ServiceReference sourceService, String eventName, + Map<String, Object> props) { + + // get a private copy of the properties + Dictionary<String, Object> table = new Hashtable<String, Object>(props); + + // service information of this JcrResourceResolverFactoryImpl service + if (sourceService != null) { + table.put(EventConstants.SERVICE, sourceService); + table.put(EventConstants.SERVICE_ID, + sourceService.getProperty(org.osgi.framework.Constants.SERVICE_ID)); + table.put(EventConstants.SERVICE_OBJECTCLASS, + sourceService.getProperty(org.osgi.framework.Constants.OBJECTCLASS)); + if (sourceService.getProperty(org.osgi.framework.Constants.SERVICE_PID) != null) { + table.put(EventConstants.SERVICE_PID, + sourceService.getProperty(org.osgi.framework.Constants.SERVICE_PID)); + } + } + + // source bundle information (if available) + if (sourceBundle != null) { + table.put(EventConstants.BUNDLE_SYMBOLICNAME, + sourceBundle.getSymbolicName()); + } + + // timestamp the event + table.put(EventConstants.TIMESTAMP, + new Long(System.currentTimeMillis())); + + // create the event + return new Event(eventName, table); + } + +} diff --git a/src/main/java/org/apache/sling/osgi/commons/SlingAdaptable.java b/src/main/java/org/apache/sling/osgi/commons/SlingAdaptable.java new file mode 100644 index 0000000..0705d9c --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/SlingAdaptable.java @@ -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.sling.osgi.commons; + +import org.apache.sling.api.adapter.Adaptable; +import org.apache.sling.osgi.commons.internal.AdapterManagerImpl; + +/** + * The <code>SlingAdaptable</code> class is an (abstract) default + * implementation of the <code>Adaptable</code> interface. It just uses the + * default {@link AdapterManager} implemented in this bundle to adapt the itself + * to the requested type. + * <p> + * Extensions of this class may overwrite the {@link #adaptTo(Class)} method + * using their own knowledge of adapters and may call this base class + * implementation to fall back to an extended adapters. + */ +public abstract class SlingAdaptable implements Adaptable { + + public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) { + return AdapterManagerImpl.getInstance().getAdapter(this, type); + } + +} diff --git a/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptor.java b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptor.java new file mode 100644 index 0000000..1408fe5 --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptor.java @@ -0,0 +1,47 @@ +/* + * 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.sling.osgi.commons.internal; + +import org.apache.sling.osgi.commons.AdapterFactory; + +/** + * The <code>AdapterFactoryDescriptor</code> is an entry in the + * {@link AdapterFactoryDescriptorMap} conveying the list of adapter (target) + * types and the respective {@link AdapterFactory}. + */ +public class AdapterFactoryDescriptor { + + private AdapterFactory factory; + + private String[] adapters; + + public AdapterFactoryDescriptor(AdapterFactory factory, String[] adapters) { + this.factory = factory; + this.adapters = adapters; + } + + public AdapterFactory getFactory() { + return factory; + } + + public String[] getAdapters() { + return adapters; + } + +} diff --git a/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptorKey.java b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptorKey.java new file mode 100644 index 0000000..d12c9e8 --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptorKey.java @@ -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.sling.osgi.commons.internal; + +import org.apache.sling.osgi.commons.OsgiUtil; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; + +/** + * The <code>AdapterFactoryDescriptorKey</code> provides the indexing + * functionality for the {@link AdapterFactoryDescriptorMap}. The key consists + * of the OSGi <code>service.id</code> of the + * {@link org.apache.sling.osgi.commons.AdapterFactory} service and the ID of + * the the bundle providing the service. + * <p> + * Sort order among the keys is defined primarily by the bundle id and + * secondarily by the service id. + */ +public class AdapterFactoryDescriptorKey implements + Comparable<AdapterFactoryDescriptorKey> { + + private long bundleId; + + private long serviceId; + + public AdapterFactoryDescriptorKey(ServiceReference ref) { + bundleId = ref.getBundle().getBundleId(); + serviceId = OsgiUtil.getProperty(ref, Constants.SERVICE_ID, -1); + } + + public int compareTo(AdapterFactoryDescriptorKey o) { + if (o.equals(this)) { + return 0; + } + + // result for differing bundleId + if (bundleId < o.bundleId) { + return -1; + } else if (bundleId > o.bundleId) { + return 1; + } + + // result for differing serviceId, we do not expect the two + // serviceId values to be equal because otherwise the equals + // test above would have yielded true + if (serviceId < o.serviceId) { + return -1; + } + + // serviceId is larger than the other object's, we do not expect + // the two serviceId values to be equal because otherwise the equals + // test above would have yielded true + return 1; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } else if (o instanceof AdapterFactoryDescriptorKey) { + AdapterFactoryDescriptorKey oKey = (AdapterFactoryDescriptorKey) o; + return bundleId == oKey.bundleId && serviceId == oKey.serviceId; + } + + return false; + } + + @Override + public int hashCode() { + return (int) (bundleId * 33 + serviceId); + } +} diff --git a/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptorMap.java b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptorMap.java new file mode 100644 index 0000000..747feee --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterFactoryDescriptorMap.java @@ -0,0 +1,39 @@ +/* + * 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.sling.osgi.commons.internal; + +import java.util.TreeMap; + +/** + * The <code>AdapterFactoryDescriptorMap</code> is a sorted map of + * {@link AdapterFactoryDescriptor} instances indexed (and ordered) by their + * {@link AdapterFactoryDescriptorKey}. This map is used to organize the + * registered {@link org.apache.sling.osgi.commons.AdapterFactory} services for + * a given adaptable type. + * <p> + * Each entry in the map is a {@link AdapterFactoryDescriptor} thus enabling the + * registration of multiple factories for the same (adaptable, adapter) type + * tuple. Of course only the first entry (this is the reason for having a sorted + * map) for such a given tuple is actually being used. If that first instance is + * removed the eventual second instance may actually be used instead. + */ +public class AdapterFactoryDescriptorMap extends + TreeMap<AdapterFactoryDescriptorKey, AdapterFactoryDescriptor> { + +} diff --git a/src/main/java/org/apache/sling/osgi/commons/internal/AdapterManagerImpl.java b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterManagerImpl.java new file mode 100644 index 0000000..cf0f0e9 --- /dev/null +++ b/src/main/java/org/apache/sling/osgi/commons/internal/AdapterManagerImpl.java @@ -0,0 +1,428 @@ +/* + * 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.sling.osgi.commons.internal; + +import static org.apache.sling.osgi.commons.AdapterFactory.ADAPTABLE_CLASSES; +import static org.apache.sling.osgi.commons.AdapterFactory.ADAPTER_CLASSES; + +import java.io.PrintStream; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.apache.sling.osgi.commons.AdapterFactory; +import org.apache.sling.osgi.commons.AdapterManager; +import org.apache.sling.osgi.commons.OsgiUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.log.LogService; + +/** + * The <code>AdapterManagerImpl</code> class implements the + * {@link AdapterManager} interface and is registered as a service for that + * interface to be used by any clients. + * + * @scr.component metatype="no" + * @scr.property name="service.description" value="Sling Adapter Manager" + * @scr.property name="service.vendor" value="The Apache Software Foundation" + * @scr.service + * @scr.reference name="AdapterFactory" + * interface="org.apache.sling.osgi.commons.AdapterFactory" + * cardinality="0..n" policy="dynamic" + */ +public class AdapterManagerImpl implements AdapterManager { + + /** + * The singleton instance of this manager. This field is set when the + * instance is {@link #activate(ComponentContext) activated} and cleared + * when the instance is {@link #deactivate(ComponentContext) deactivated}. + */ + private static AdapterManager INSTANCE; + + /** + * Returns the instance of this class or <code>null</code> if no activate + * yet. + */ + public static AdapterManager getInstance() { + return INSTANCE; + } + + /** @scr.reference cardinality="0..1" policy="dynamic" */ + private LogService log; + + /** Whether to debug this class or not */ + private boolean debug = false; + + /** + * The OSGi <code>ComponentContext</code> to retrieve + * {@link AdapterFactory} service instances. + */ + private ComponentContext context; + + /** + * A list of {@link AdapterFactory} services bound to this manager before + * the manager has been activated. These bound services will be accessed as + * soon as the manager is being activated. + */ + private List<ServiceReference> boundAdapterFactories = new LinkedList<ServiceReference>(); + + /** + * A map of {@link AdapterFactoryDescriptorMap} instances. The map is + * indexed by the fully qualified class names listed in the + * {@link AdapterFactory#ADAPTABLE_CLASSES} property of the + * {@link AdapterFactory} services. + * + * @see AdapterFactoryDescriptorMap + */ + private Map<String, AdapterFactoryDescriptorMap> factories = new HashMap<String, AdapterFactoryDescriptorMap>(); + + /** + * Matrix of {@link AdapterFactory} instances primarily indexed by the fully + * qualified name of the class to be adapted and secondarily indexed by the + * fully qualified name of the class to adapt to (the target class). + * <p> + * This cache is built on demand by calling the + * {@link #getAdapterFactories(Class)} class. It is removed altogether + * whenever an adapter factory is registered on unregistered. + */ + private Map<String, Map<String, AdapterFactory>> factoryCache; + + // ---------- AdapterManager interface ------------------------------------- + + /** + * Returns the adapted <code>adaptable</code> or <code>null</code> if + * the object cannot be adapted. + */ + public <AdapterType> AdapterType getAdapter(Object adaptable, + Class<AdapterType> type) { + + // get the adapter factories for the type of adaptable object + Map<String, AdapterFactory> factories = getAdapterFactories(adaptable.getClass()); + + // get the factory for the target type + AdapterFactory factory = factories.get(type.getName()); + + // have the factory adapt the adaptable if the factory exists + if (factory != null) { + if (debug) { + log(LogService.LOG_DEBUG, "Using adapter factory " + factory + + " to map " + adaptable + " to " + type, null); + } + + return factory.getAdapter(adaptable, type); + } + + // no factory has been found, so we cannot adapt + if (debug) { + log(LogService.LOG_DEBUG, "No adapter factory found to map " + + adaptable + " to " + type, null); + } + + return null; + } + + // ----------- SCR integration --------------------------------------------- + + protected synchronized void activate(ComponentContext context) { + this.context = context; + + // register all adapter factories bound before activation + for (ServiceReference reference : boundAdapterFactories) { + registerAdapterFactory(context, reference); + } + boundAdapterFactories.clear(); + + // final "enable" this manager by setting the instance + // do not overwrite the field if already set (this is unexpected + // actually) + if (AdapterManagerImpl.INSTANCE == null) { + AdapterManagerImpl.INSTANCE = this; + } else { + log(LogService.LOG_WARNING, + "Not setting Instance field: Set to another manager " + + AdapterManagerImpl.INSTANCE, null); + } + } + + /** + * @param context Not used + */ + protected synchronized void deactivate(ComponentContext context) { + // "disable" the manager by clearing the instance + // do not clear the field if not set to this instance + if (AdapterManagerImpl.INSTANCE == this) { + AdapterManagerImpl.INSTANCE = null; + } else { + log(LogService.LOG_WARNING, + "Not clearing instance field: Set to another manager " + + AdapterManagerImpl.INSTANCE, null); + } + + this.context = null; + } + + protected synchronized void bindAdapterFactory(ServiceReference reference) { + if (context == null) { + boundAdapterFactories.add(reference); + } else { + registerAdapterFactory(context, reference); + } + } + + protected synchronized void unbindAdapterFactory(ServiceReference reference) { + unregisterAdapterFactory(reference); + } + + // ---------- unit testing stuff only -------------------------------------- + + /** + * Returns the active adapter factories of this manager. + * <p> + * <strong><em>THIS METHOD IS FOR UNIT TESTING ONLY. IT MAY BE REMOVED OR + * MODIFIED WITHOUT NOTICE.</em></strong> + */ + Map<String, AdapterFactoryDescriptorMap> getFactories() { + return factories; + } + + /** + * Returns the current adapter factory cache. + * <p> + * <strong><em>THIS METHOD IS FOR UNIT TESTING ONLY. IT MAY BE REMOVED OR + * MODIFIED WITHOUT NOTICE.</em></strong> + */ + Map<String, Map<String, AdapterFactory>> getFactoryCache() { + return factoryCache; + } + + // ---------- internal ----------------------------------------------------- + + private void log(int level, String message, Throwable t) { + LogService logger = this.log; + if (logger != null) { + logger.log(level, message, t); + } else { + System.out.println(message); + if (t != null) { + t.printStackTrace(System.out); + } + } + } + + /** + * Unregisters the {@link AdapterFactory} referred to by the service + * <code>reference</code> from the registry. + */ + private void registerAdapterFactory(ComponentContext context, + ServiceReference reference) { + String[] adaptables = OsgiUtil.getProperty(reference, ADAPTABLE_CLASSES); + String[] adapters = OsgiUtil.getProperty(reference, ADAPTER_CLASSES); + + if (adaptables == null || adaptables.length == 0 || adapters == null + || adapters.length == 0) { + return; + } + + AdapterFactory factory = (AdapterFactory) context.locateService( + "AdapterFactory", reference); + + AdapterFactoryDescriptorKey factoryKey = new AdapterFactoryDescriptorKey( + reference); + AdapterFactoryDescriptor factoryDesc = new AdapterFactoryDescriptor( + factory, adapters); + + synchronized (factories) { + for (String adaptable : adaptables) { + AdapterFactoryDescriptorMap adfMap = factories.get(adaptable); + if (adfMap == null) { + adfMap = new AdapterFactoryDescriptorMap(); + factories.put(adaptable, adfMap); + } + adfMap.put(factoryKey, factoryDesc); + } + } + + // clear the factory cache to force rebuild on next access + factoryCache = null; + } + + /** + * Unregisters the {@link AdapterFactory} referred to by the service + * <code>reference</code> from the registry. + */ + private void unregisterAdapterFactory(ServiceReference reference) { + boundAdapterFactories.remove(reference); + + String[] adaptables = OsgiUtil.getProperty(reference, ADAPTABLE_CLASSES); + + if (adaptables == null || adaptables.length == 0) { + return; + } + + AdapterFactoryDescriptorKey factoryKey = new AdapterFactoryDescriptorKey( + reference); + + boolean factoriesModified = false; + synchronized (factories) { + for (String adaptable : adaptables) { + AdapterFactoryDescriptorMap adfMap = factories.get(adaptable); + if (adfMap != null) { + factoriesModified |= (adfMap.remove(factoryKey) != null); + if (adfMap.isEmpty()) { + factories.remove(adaptable); + } + } + } + } + + // only remove cache if some adapter factories have actually been + // removed + if (factoriesModified) { + factoryCache = null; + } + } + + /** + * Returns a map of {@link AdapterFactory} instances for the given class to + * be adapted. The returned map is indexed by the fully qualified name of + * the target classes (to adapt to) registered. + * + * @param clazz The type of the object for which the registered adapter + * factories are requested + * @return The map of adapter factories. If there is no adapter factory + * registered for this type, the returned map is empty. + */ + private Map<String, AdapterFactory> getAdapterFactories(Class<?> clazz) { + Map<String, Map<String, AdapterFactory>> cache = factoryCache; + if (cache == null) { + cache = new HashMap<String, Map<String, AdapterFactory>>(); + factoryCache = cache; + } + + synchronized (cache) { + return getAdapterFactories(clazz, cache); + } + } + + /** + * Returns the map of adapter factories index by adapter (target) class name + * for the given adaptable <code>clazz</code>. If no adapter exists for + * the <code>clazz</code> and empty map is returned. + * + * @param clazz The adaptable <code>Class</code> for which to return the + * adapter factory map by target class name. + * @param cache The cache of already defined adapter factory mappings + * @return The map of adapter factories by target class name. The map may be + * empty if there is no adapter factory for the adaptable + * <code>clazz</code>. + */ + private Map<String, AdapterFactory> getAdapterFactories(Class<?> clazz, + Map<String, Map<String, AdapterFactory>> cache) { + + String className = clazz.getName(); + Map<String, AdapterFactory> entry = cache.get(className); + if (entry == null) { + // create entry + entry = createAdapterFactoryMap(clazz, cache); + cache.put(className, entry); + } + + return entry; + } + + /** + * Creates a new target adapter factory map for the given <code>clazz</code>. + * First all factories defined to support the adaptable class by + * registration are taken. Next all factories for the implemented interfaces + * and finally all base class factories are copied. Later adapter factory + * entries do NOT overwrite earlier entries. + * + * @param clazz The adaptable <code>Class</code> for which to build the + * adapter factory map by target class name. + * @param cache The cache of already defined adapter factory mappings + * @return The map of adapter factories by target class name. The map may be + * empty if there is no adapter factory for the adaptable + * <code>clazz</code>. + */ + private Map<String, AdapterFactory> createAdapterFactoryMap(Class<?> clazz, + Map<String, Map<String, AdapterFactory>> cache) { + Map<String, AdapterFactory> afm = new HashMap<String, AdapterFactory>(); + + // AdapterFactories for this class + AdapterFactoryDescriptorMap afdMap; + synchronized (factories) { + afdMap = factories.get(clazz.getName()); + } + if (afdMap != null) { + for (AdapterFactoryDescriptor afd : afdMap.values()) { + String[] adapters = afd.getAdapters(); + for (String adapter : adapters) { + if (!afm.containsKey(adapter)) { + afm.put(adapter, afd.getFactory()); + } + } + } + } + + // AdapterFactories for the interfaces + Class<?>[] interfaces = clazz.getInterfaces(); + for (Class<?> iFace : interfaces) { + copyAdapterFactories(afm, iFace, cache); + } + + // AdapterFactories for the super class + Class<?> superClazz = clazz.getSuperclass(); + if (superClazz != null) { + copyAdapterFactories(afm, superClazz, cache); + } + + return afm; + } + + /** + * Copies all adapter factories for the given <code>clazz</code> from the + * <code>cache</code> to the <code>dest</code> map except for those + * factories whose target class already exists in the <code>dest</code> + * map. + * + * @param dest The map of target class name to adapter factory into which + * additional factories are copied. Existing factories are not + * replaced. + * @param clazz The adaptable class whose adapter factories are considered + * for adding into <code>dest</code>. + * @param cache The adapter factory cache providing the adapter factories + * for <code>clazz</code> to consider for copying into + * <code>dest</code>. + */ + private void copyAdapterFactories(Map<String, AdapterFactory> dest, + Class<?> clazz, Map<String, Map<String, AdapterFactory>> cache) { + + // get the adapter factories for the adaptable clazz + Map<String, AdapterFactory> scMap = getAdapterFactories(clazz, cache); + + // for each target class copy the entry to dest if dest does + // not contain the target class already + for (Map.Entry<String, AdapterFactory> entry : scMap.entrySet()) { + if (!dest.containsKey(entry.getKey())) { + dest.put(entry.getKey(), entry.getValue()); + } + } + } +} diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/src/main/resources/META-INF/LICENSE @@ -0,0 +1,202 @@ + + 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. diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE new file mode 100644 index 0000000..0a17b94 --- /dev/null +++ b/src/main/resources/META-INF/NOTICE @@ -0,0 +1,5 @@ +Apache Sling OSGi Commons +Copyright 2007 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/src/test/java/org/apache/sling/osgi/commons/internal/AdapterManagerTest.java b/src/test/java/org/apache/sling/osgi/commons/internal/AdapterManagerTest.java new file mode 100644 index 0000000..067ec16 --- /dev/null +++ b/src/test/java/org/apache/sling/osgi/commons/internal/AdapterManagerTest.java @@ -0,0 +1,245 @@ +/* + * 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.sling.osgi.commons.internal; + +import java.util.Map; + +import junit.framework.TestCase; + +import org.apache.sling.osgi.commons.AdapterFactory; +import org.apache.sling.osgi.commons.SlingAdaptable; +import org.apache.sling.osgi.commons.mock.MockBundle; +import org.apache.sling.osgi.commons.mock.MockComponentContext; +import org.apache.sling.osgi.commons.mock.MockServiceReference; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; +import org.osgi.service.component.ComponentContext; + +public class AdapterManagerTest extends TestCase { + + private AdapterManagerImpl am; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + am = new AdapterManagerImpl(); + } + + @Override + protected void tearDown() throws Exception { + if (AdapterManagerImpl.getInstance() == am) { + am.deactivate(null); // not correct, but argument unused + } + + super.tearDown(); + } + + public void testUnitialized() { + assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories()); + assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty()); + assertNull("AdapterFactory cache must be null", am.getFactoryCache()); + } + + public void testInitialized() { + ComponentContext cc = new MockComponentContext(); + am.activate(cc); + + assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories()); + assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty()); + assertNull("AdapterFactory cache must be null", am.getFactoryCache()); + } + + public void testBindBeforeActivate() { + Bundle bundle = new MockBundle(1L); + MockServiceReference ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 1L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, ITestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + // no cache and no factories yet + assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories()); + assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty()); + assertNull("AdapterFactory cache must be null", am.getFactoryCache()); + + // this should register the factory + ComponentContext cc = new MockComponentContext(); + am.activate(cc); + + // expect the factory, but cache is empty + assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories()); + assertEquals("AdapterFactoryDescriptors must contain one entry", 1, am.getFactories().size()); + assertNull("AdapterFactory cache must be null", am.getFactoryCache()); + } + + public void testBindAfterActivate() { + ComponentContext cc = new MockComponentContext(); + am.activate(cc); + + // no cache and no factories yet + assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories()); + assertTrue("AdapterFactoryDescriptors must be empty", am.getFactories().isEmpty()); + assertNull("AdapterFactory cache must be null", am.getFactoryCache()); + + Bundle bundle = new MockBundle(1L); + MockServiceReference ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 1L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, ITestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + // expect the factory, but cache is empty + assertNotNull("AdapterFactoryDescriptors must not be null", am.getFactories()); + assertEquals("AdapterFactoryDescriptors must contain one entry", 1, am.getFactories().size()); + assertNull("AdapterFactory cache must be null", am.getFactoryCache()); + + Map<String, AdapterFactoryDescriptorMap> f = am.getFactories(); + AdapterFactoryDescriptorMap afdm = f.get(TestSlingAdaptable.class.getName()); + assertNotNull(afdm); + + AdapterFactoryDescriptor afd = afdm.get(new AdapterFactoryDescriptorKey(ref)); + assertNotNull(afd); + assertNotNull(afd.getFactory()); + assertNotNull(afd.getAdapters()); + assertEquals(1, afd.getAdapters().length); + assertEquals(ITestAdapter.class.getName(), afd.getAdapters()[0]); + + assertNull(f.get(TestSlingAdaptable2.class.getName())); + } + + public void testAdaptBase() { + + ComponentContext cc = new MockComponentContext(); + am.activate(cc); + + TestSlingAdaptable data = new TestSlingAdaptable(); + assertNull("Expect no adapter", am.getAdapter(data, ITestAdapter.class)); + + Bundle bundle = new MockBundle(1L); + MockServiceReference ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 1L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, ITestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + Object adapter = am.getAdapter(data, ITestAdapter.class); + assertNotNull(adapter); + assertTrue(adapter instanceof ITestAdapter); + } + + public void testAdaptExtended() { + + ComponentContext cc = new MockComponentContext(); + am.activate(cc); + + TestSlingAdaptable2 data = new TestSlingAdaptable2(); + assertNull("Expect no adapter", am.getAdapter(data, ITestAdapter.class)); + + Bundle bundle = new MockBundle(1L); + MockServiceReference ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 1L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, ITestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + Object adapter = am.getAdapter(data, ITestAdapter.class); + assertNotNull(adapter); + assertTrue(adapter instanceof ITestAdapter); + } + + public void testAdaptBase2() { + + ComponentContext cc = new MockComponentContext(); + am.activate(cc); + + TestSlingAdaptable data = new TestSlingAdaptable(); + assertNull("Expect no adapter", am.getAdapter(data, ITestAdapter.class)); + + Bundle bundle = new MockBundle(1L); + MockServiceReference ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 1L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, ITestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 2L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable2.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, TestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + Object adapter = am.getAdapter(data, ITestAdapter.class); + assertNotNull(adapter); + assertTrue(adapter instanceof ITestAdapter); + } + + public void testAdaptExtended2() { + + ComponentContext cc = new MockComponentContext(); + am.activate(cc); + + Bundle bundle = new MockBundle(1L); + MockServiceReference ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 1L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, ITestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + ref = new MockServiceReference(bundle); + ref.setProperty(Constants.SERVICE_ID, 2L); + ref.setProperty(AdapterFactory.ADAPTABLE_CLASSES, new String[]{ TestSlingAdaptable2.class.getName() }); + ref.setProperty(AdapterFactory.ADAPTER_CLASSES, TestAdapter.class.getName()); + am.bindAdapterFactory(ref); + + TestSlingAdaptable data = new TestSlingAdaptable(); + Object adapter = am.getAdapter(data, ITestAdapter.class); + assertNotNull(adapter); + assertTrue(adapter instanceof ITestAdapter); + adapter = am.getAdapter(data, TestAdapter.class); + assertNull(adapter); + + TestSlingAdaptable2 data2 = new TestSlingAdaptable2(); + adapter = am.getAdapter(data2, ITestAdapter.class); + assertNotNull(adapter); + assertTrue(adapter instanceof ITestAdapter); + adapter = am.getAdapter(data2, TestAdapter.class); + assertNotNull(adapter); + assertTrue(adapter instanceof TestAdapter); + } + + //---------- Test Adaptable and Adapter Classes --------------------------- + + public static class TestSlingAdaptable extends SlingAdaptable { + + } + + public static class TestSlingAdaptable2 extends TestSlingAdaptable { + + } + + public static interface ITestAdapter { + + } + + public static class TestAdapter { + + } + +} diff --git a/src/test/java/org/apache/sling/osgi/commons/mock/MockAdapterFactory.java b/src/test/java/org/apache/sling/osgi/commons/mock/MockAdapterFactory.java new file mode 100644 index 0000000..e833c96 --- /dev/null +++ b/src/test/java/org/apache/sling/osgi/commons/mock/MockAdapterFactory.java @@ -0,0 +1,53 @@ +/* + * 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.sling.osgi.commons.mock; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; + +import org.apache.sling.osgi.commons.AdapterFactory; + +public class MockAdapterFactory implements AdapterFactory { + + private static final InvocationHandler NOP_INVOCATION_HANDLER = new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable { + return null; + } + }; + + @SuppressWarnings("unchecked") + public <AdapterType> AdapterType getAdapter(Object adaptable, + Class<AdapterType> type) { + + try { + if (type.isInterface()) { + return (AdapterType) Proxy.newProxyInstance(type.getClassLoader(), + new Class[] { type }, NOP_INVOCATION_HANDLER); + } + + return type.newInstance(); + } catch (Exception e) { + // ignore + } + + return null; + } +} diff --git a/src/test/java/org/apache/sling/osgi/commons/mock/MockBundle.java b/src/test/java/org/apache/sling/osgi/commons/mock/MockBundle.java new file mode 100644 index 0000000..13fda4f --- /dev/null +++ b/src/test/java/org/apache/sling/osgi/commons/mock/MockBundle.java @@ -0,0 +1,124 @@ +/* + * 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.sling.osgi.commons.mock; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Dictionary; +import java.util.Enumeration; + +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleException; +import org.osgi.framework.ServiceReference; + +public class MockBundle implements Bundle { + + private long bundleId; + + public MockBundle(long bundleId) { + this.bundleId = bundleId; + } + + public long getBundleId() { + return bundleId; + } + + public Enumeration<?> findEntries(String path, String filePattern, + boolean recurse) { + return null; + } + + public URL getEntry(String name) { + return null; + } + + public Enumeration<?> getEntryPaths(String path) { + return null; + } + + public Dictionary<?, ?> getHeaders() { + return null; + } + + public Dictionary<?, ?> getHeaders(String locale) { + return null; + } + + public long getLastModified() { + return 0; + } + + public String getLocation() { + return null; + } + + public ServiceReference[] getRegisteredServices() { + return null; + } + + public URL getResource(String name) { + return null; + } + + public Enumeration<?> getResources(String name) { + return null; + } + + public ServiceReference[] getServicesInUse() { + return null; + } + + public int getState() { + return 0; + } + + public String getSymbolicName() { + return null; + } + + public boolean hasPermission(Object permission) { + return false; + } + + public Class<?> loadClass(String name) throws ClassNotFoundException { + throw new ClassNotFoundException(name); + } + + public void start() { + + } + + public void stop() { + + } + + public void uninstall() { + + } + + public void update() { + + } + + public void update(InputStream in) { + + } + +} diff --git a/src/test/java/org/apache/sling/osgi/commons/mock/MockComponentContext.java b/src/test/java/org/apache/sling/osgi/commons/mock/MockComponentContext.java new file mode 100644 index 0000000..159fe44 --- /dev/null +++ b/src/test/java/org/apache/sling/osgi/commons/mock/MockComponentContext.java @@ -0,0 +1,79 @@ +/* + * 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.sling.osgi.commons.mock; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Map; + +import org.apache.sling.osgi.commons.AdapterFactory; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.component.ComponentInstance; + +public class MockComponentContext implements ComponentContext { + + private Map<ServiceReference, AdapterFactory> services = new HashMap<ServiceReference, AdapterFactory>(); + + public Object locateService(String name, ServiceReference reference) { + AdapterFactory af = services.get(reference); + if (af == null) { + af = new MockAdapterFactory(); + services.put(reference, af); + } + return af; + } + + public void disableComponent(String name) { + } + + public void enableComponent(String name) { + } + + public BundleContext getBundleContext() { + return null; + } + + public ComponentInstance getComponentInstance() { + return null; + } + + public Dictionary<?, ?> getProperties() { + return null; + } + + public ServiceReference getServiceReference() { + return null; + } + + public Bundle getUsingBundle() { + return null; + } + + public Object locateService(String name) { + return null; + } + + public Object[] locateServices(String name) { + return null; + } + +} diff --git a/src/test/java/org/apache/sling/osgi/commons/mock/MockServiceReference.java b/src/test/java/org/apache/sling/osgi/commons/mock/MockServiceReference.java new file mode 100644 index 0000000..e503d96 --- /dev/null +++ b/src/test/java/org/apache/sling/osgi/commons/mock/MockServiceReference.java @@ -0,0 +1,62 @@ +/* + * 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.sling.osgi.commons.mock; + +import java.util.Collections; +import java.util.Dictionary; +import java.util.Hashtable; + +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceReference; + +public class MockServiceReference implements ServiceReference { + + private Bundle bundle; + private Dictionary<String, Object> props; + + public MockServiceReference(Bundle bundle) { + this.bundle = bundle; + this.props = new Hashtable<String, Object>(); + } + + public Bundle getBundle() { + return bundle; + } + + public void setProperty(String key, Object value) { + props.put(key, value); + } + + public Object getProperty(String key) { + return props.get(key); + } + + public String[] getPropertyKeys() { + return Collections.list(props.keys()).toArray(new String[props.size()]); + } + + public Bundle[] getUsingBundles() { + return null; + } + + public boolean isAssignableTo(Bundle bundle, String className) { + return false; + } + +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
