This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-discovery-standalone.git
commit 304f635bac146ecd91a40ded6ebd69f698e91fcf Author: Carsten Ziegeler <[email protected]> AuthorDate: Tue May 14 07:47:45 2013 +0000 Create new standalone module git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1482239 13f79535-47bb-0310-9956-ffa450edef68 --- .../impl/standalone/NoClusterDiscoveryService.java | 392 +++++++++++++++++++++ .../impl/standalone/StandardPropertyProvider.java | 219 ++++++++++++ 2 files changed, 611 insertions(+) diff --git a/src/main/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryService.java b/src/main/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryService.java new file mode 100644 index 0000000..cfb3c6b --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/impl/standalone/NoClusterDiscoveryService.java @@ -0,0 +1,392 @@ +/* + * 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.discovery.impl; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.ConfigurationPolicy; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.ReferencePolicy; +import org.apache.felix.scr.annotations.Service; +import org.apache.sling.discovery.ClusterView; +import org.apache.sling.discovery.DiscoveryService; +import org.apache.sling.discovery.InstanceDescription; +import org.apache.sling.discovery.InstanceFilter; +import org.apache.sling.discovery.PropertyProvider; +import org.apache.sling.discovery.TopologyEvent; +import org.apache.sling.discovery.TopologyEvent.Type; +import org.apache.sling.discovery.TopologyEventListener; +import org.apache.sling.discovery.TopologyView; +import org.apache.sling.settings.SlingSettingsService; +import org.osgi.framework.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This is a simple implementation of the discovery service + * which can be used for a cluster less installation (= single instance). + * It is disabled by default and can be enabled through a OSGi configuration. + */ +@Component(policy = ConfigurationPolicy.REQUIRE, immediate=true) +@Service(value = {DiscoveryService.class}) +public class NoClusterDiscoveryService implements DiscoveryService { + + /** The logger. */ + private final Logger logger = LoggerFactory.getLogger(this.getClass()); + + /** + * Sling settings service to get the Sling ID and run modes. + */ + @Reference + private SlingSettingsService settingsService; + + /** + * All topology event listeners. + */ + @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC) + private TopologyEventListener[] listeners = new TopologyEventListener[0]; + + /** + * All property providers. + */ + @Reference(cardinality = ReferenceCardinality.OPTIONAL_MULTIPLE, policy = ReferencePolicy.DYNAMIC, + referenceInterface=PropertyProvider.class, updated="updatedPropertyProvider") + private List<ProviderInfo> providerInfos = new ArrayList<ProviderInfo>(); + + /** + * Special lock object to sync data structure access + */ + private final Object lock = new Object(); + + /** + * The current topology view. + */ + private TopologyView topologyView; + + private Map<String, String> cachedProperties = new HashMap<String, String>(); + + /** + * Activate this service + * Create a new description. + */ + @Activate + protected void activate() { + logger.debug("NoClusterDiscoveryService started."); + final InstanceDescription myDescription = new InstanceDescription() { + + public boolean isLocal() { + return true; + } + + public boolean isLeader() { + return true; + } + + public String getSlingId() { + return settingsService.getSlingId(); + } + + public String getProperty(final String name) { + synchronized(lock) { + return cachedProperties.get(name); + } + } + + public Map<String, String> getProperties() { + synchronized(lock) { + return Collections.unmodifiableMap(cachedProperties); + } + } + + public ClusterView getClusterView() { + final Collection<ClusterView> clusters = topologyView.getClusterViews(); + if (clusters==null || clusters.size()==0) { + return null; + } + return clusters.iterator().next(); + } + }; + final Set<InstanceDescription> instances = new HashSet<InstanceDescription>(); + instances.add(myDescription); + + final TopologyEventListener[] registeredServices; + synchronized ( lock ) { + registeredServices = this.listeners; + final ClusterView clusterView = new ClusterView() { + + public InstanceDescription getLeader() { + return myDescription; + } + + public List<InstanceDescription> getInstances() { + return new LinkedList<InstanceDescription>(instances); + } + + public String getId() { + return "0"; + } + }; + this.topologyView = new TopologyView() { + + public InstanceDescription getLocalInstance() { + return myDescription; + } + + public boolean isCurrent() { + return true; + } + + public Set<InstanceDescription> getInstances() { + return instances; + } + + public Set<InstanceDescription> findInstances(InstanceFilter picker) { + Set<InstanceDescription> result = new HashSet<InstanceDescription>(); + for (Iterator<InstanceDescription> it = getTopology().getInstances().iterator(); it.hasNext();) { + InstanceDescription instance = it.next(); + if (picker.accept(instance)) { + result.add(instance); + } + } + return result; + } + + public Set<ClusterView> getClusterViews() { + Set<ClusterView> clusters = new HashSet<ClusterView>(); + clusters.add(clusterView); + return clusters; + } + + }; + } + for(final TopologyEventListener da: registeredServices) { + da.handleTopologyEvent(new TopologyEvent(Type.TOPOLOGY_INIT, null, topologyView)); + } + } + + /** + * Deactivate this service. + */ + @Deactivate + protected void deactivate() { + logger.debug("NoClusterDiscoveryService stopped."); + this.topologyView = null; + } + + /** + * Bind a new property provider. + */ + @SuppressWarnings("unused") + private void bindPropertyProvider(final PropertyProvider propertyProvider, final Map<String, Object> props) { + logger.debug("bindPropertyProvider: Binding PropertyProvider {}", propertyProvider); + + final TopologyEventListener[] awares; + synchronized (lock) { + final ProviderInfo info = new ProviderInfo(propertyProvider, props); + this.providerInfos.add(info); + Collections.sort(this.providerInfos); + this.updatePropertiesCache(); + if ( this.topologyView == null ) { + awares = null; + } else { + awares = this.listeners; + } + } + if ( awares != null ) { + for(final TopologyEventListener da : awares) { + da.handleTopologyEvent(new TopologyEvent(Type.PROPERTIES_CHANGED, this.topologyView, this.topologyView)); + } + } + } + + /** + * Update a property provider. + */ + @SuppressWarnings("unused") + private void updatedPropertyProvider(final PropertyProvider propertyProvider, final Map<String, Object> props) { + logger.debug("bindPropertyProvider: Updating PropertyProvider {}", propertyProvider); + + this.unbindPropertyProvider(propertyProvider, props, false); + this.bindPropertyProvider(propertyProvider, props); + } + + /** + * Unbind a property provider + */ + @SuppressWarnings("unused") + private void unbindPropertyProvider(final PropertyProvider propertyProvider, final Map<String, Object> props) { + this.unbindPropertyProvider(propertyProvider, props, true); + } + + /** + * Unbind a property provider + */ + @SuppressWarnings("unused") + private void unbindPropertyProvider(final PropertyProvider propertyProvider, + final Map<String, Object> props, + final boolean inform) { + logger.debug("unbindPropertyProvider: Releasing PropertyProvider {}", propertyProvider); + + final TopologyEventListener[] awares; + synchronized (lock) { + final ProviderInfo info = new ProviderInfo(propertyProvider, props); + this.providerInfos.remove(info); + this.updatePropertiesCache(); + if ( this.topologyView == null ) { + awares = null; + } else { + awares = this.listeners; + } + } + if ( inform && awares != null ) { + for(final TopologyEventListener da : awares) { + da.handleTopologyEvent(new TopologyEvent(Type.PROPERTIES_CHANGED, this.topologyView, this.topologyView)); + } + } + } + + private void updatePropertiesCache() { + final Map<String, String> newProps = new HashMap<String, String>(); + for(final ProviderInfo info : this.providerInfos) { + newProps.putAll(info.properties); + } + this.cachedProperties = newProps; + if ( this.logger.isDebugEnabled() ) { + this.logger.debug("New properties: {}", this.cachedProperties); + } + } + + @SuppressWarnings("unused") + private void bindTopologyEventListener(final TopologyEventListener clusterAware) { + + logger.debug("bindTopologyEventListener: Binding TopologyEventListener {}", clusterAware); + + boolean inform = true; + synchronized (lock) { + List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>( + Arrays.asList(listeners)); + currentList.add(clusterAware); + this.listeners = currentList.toArray(new TopologyEventListener[currentList.size()]); + if ( this.topologyView == null ) { + inform = false; + } + } + + if ( inform ) { + clusterAware.handleTopologyEvent(new TopologyEvent(Type.TOPOLOGY_INIT, null, topologyView)); + } + } + + @SuppressWarnings("unused") + private void unbindTopologyEventListener(final TopologyEventListener clusterAware) { + + logger.debug("unbindTopologyEventListener: Releasing TopologyEventListener {}", clusterAware); + + synchronized (lock) { + List<TopologyEventListener> currentList = new ArrayList<TopologyEventListener>( + Arrays.asList(listeners)); + currentList.remove(clusterAware); + this.listeners = currentList.toArray(new TopologyEventListener[currentList.size()]); + } + } + + /** + * @see DiscoveryService#getTopology() + */ + public TopologyView getTopology() { + return topologyView; + } + + /** + * Internal class caching some provider infos like service id and ranking. + */ + private final static class ProviderInfo implements Comparable<ProviderInfo> { + + public final PropertyProvider provider; + public final int ranking; + public final long serviceId; + public final Map<String, String> properties = new HashMap<String, String>(); + + public ProviderInfo(final PropertyProvider provider, final Map<String, Object> serviceProps) { + this.provider = provider; + final Object sr = serviceProps.get(Constants.SERVICE_RANKING); + if ( sr == null || !(sr instanceof Integer)) { + this.ranking = 0; + } else { + this.ranking = (Integer)sr; + } + this.serviceId = (Long)serviceProps.get(Constants.SERVICE_ID); + final Object namesObj = serviceProps.get(PropertyProvider.PROPERTY_PROPERTIES); + if ( namesObj instanceof String ) { + final String val = provider.getProperty((String)namesObj); + if ( val != null ) { + this.properties.put((String)namesObj, val); + } + } else if ( namesObj instanceof String[] ) { + for(final String name : (String[])namesObj ) { + final String val = provider.getProperty(name); + if ( val != null ) { + this.properties.put(name, val); + } + } + } + } + + /** + * @see java.lang.Comparable#compareTo(java.lang.Object) + */ + public int compareTo(final ProviderInfo o) { + // Sort by rank in ascending order. + if ( this.ranking < o.ranking ) { + return -1; // lower rank + } else if (this.ranking > o.ranking ) { + return 1; // higher rank + } + // If ranks are equal, then sort by service id in descending order. + return (this.serviceId < o.serviceId) ? 1 : -1; + } + + @Override + public boolean equals(final Object obj) { + if ( obj instanceof ProviderInfo ) { + return ((ProviderInfo)obj).serviceId == this.serviceId; + } + return false; + } + + @Override + public int hashCode() { + return provider.hashCode(); + } + } +} diff --git a/src/main/java/org/apache/sling/discovery/impl/standalone/StandardPropertyProvider.java b/src/main/java/org/apache/sling/discovery/impl/standalone/StandardPropertyProvider.java new file mode 100644 index 0000000..798ff0a --- /dev/null +++ b/src/main/java/org/apache/sling/discovery/impl/standalone/StandardPropertyProvider.java @@ -0,0 +1,219 @@ +/* + * 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.discovery.impl; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.List; +import java.util.Map; + +import org.apache.felix.scr.annotations.Activate; +import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Deactivate; +import org.apache.felix.scr.annotations.Modified; +import org.apache.felix.scr.annotations.Reference; +import org.apache.felix.scr.annotations.ReferenceCardinality; +import org.apache.felix.scr.annotations.ReferencePolicy; +import org.apache.sling.discovery.InstanceDescription; +import org.apache.sling.discovery.PropertyProvider; +import org.osgi.framework.Constants; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.ServiceRegistration; +import org.osgi.service.component.ComponentContext; +import org.osgi.service.http.HttpService; + +/** + * This service provides the standard instance properties (if available) + */ +@Component(immediate=true) +@Reference(referenceInterface=HttpService.class, + cardinality=ReferenceCardinality.OPTIONAL_MULTIPLE, + policy=ReferencePolicy.DYNAMIC) +public class StandardPropertyProvider { + + /** Endpoint service registration property from RFC 189 */ + private static final String REG_PROPERTY_ENDPOINTS = "osgi.http.service.endpoints"; + + private volatile long changeCount; + + private String instanceName; + + private String instanceDescription; + + private ServiceRegistration propagationService; + + private final Map<Long, String[]> endpoints = new HashMap<Long, String[]>(); + + private String endpointString; + + private Dictionary<String, Object> getRegistrationProperties() { + final List<String> names = new ArrayList<String>(); + if ( this.instanceName != null ) { + names.add(InstanceDescription.PROPERTY_NAME); + } + if ( this.instanceDescription != null ) { + names.add(InstanceDescription.PROPERTY_DESCRIPTION); + } + names.add(InstanceDescription.PROPERTY_ENDPOINTS); + + final StringBuilder sb = new StringBuilder(); + boolean first = true; + synchronized ( this.endpoints ) { + for(final String[] points : endpoints.values()) { + for(final String point : points) { + if ( first ) { + first = false; + } else { + sb.append(","); + } + sb.append(point); + } + } + } + this.endpointString = sb.toString(); + + final Dictionary<String, Object> serviceProps = new Hashtable<String, Object>(); + serviceProps.put(PropertyProvider.PROPERTY_PROPERTIES, names.toArray(new String[names.size()])); + // we add a changing property to the service registration + // to make sure a modification event is really sent + synchronized ( this ) { + serviceProps.put("changeCount", this.changeCount++); + } + return serviceProps; + } + + private String getPropertyValue(final ComponentContext bc, final String key) { + Object value = bc.getProperties().get(key); + if ( value == null ) { + value = bc.getBundleContext().getProperty(key); + } + if ( value != null ) { + return value.toString(); + } + return null; + } + + @Activate + protected void activate(final ComponentContext cc) { + this.modified(cc); + } + + @Modified + protected void modified(final ComponentContext cc) { + this.instanceName = this.getPropertyValue(cc, "sling.name"); + this.instanceDescription = this.getPropertyValue(cc, "sling.description"); + + this.propagationService = cc.getBundleContext().registerService(PropertyProvider.class.getName(), + new PropertyProvider() { + + public String getProperty(final String name) { + if ( InstanceDescription.PROPERTY_DESCRIPTION.equals(name) ) { + return instanceDescription; + } + if ( InstanceDescription.PROPERTY_NAME.equals(name) ) { + return instanceName; + } + if ( InstanceDescription.PROPERTY_ENDPOINTS.equals(name) ) { + return endpointString; + } + return null; + } + }, this.getRegistrationProperties()); + } + + @Deactivate + protected void deactivate() { + if ( this.propagationService != null ) { + this.propagationService.unregister(); + this.propagationService = null; + } + } + + /** + * Bind a http service + */ + protected void bindHttpService(final ServiceReference reference) { + final String[] endpointUrls = toStringArray(reference.getProperty(REG_PROPERTY_ENDPOINTS)); + if ( endpointUrls != null ) { + synchronized ( this.endpoints ) { + this.endpoints.put((Long)reference.getProperty(Constants.SERVICE_ID), endpointUrls); + } + if ( this.propagationService != null ) { + this.propagationService.setProperties(this.getRegistrationProperties()); + } + } + } + + /** + * Unbind a http service + */ + protected void unbindHttpService(final ServiceReference reference) { + boolean changed = false; + synchronized ( this.endpoints ) { + if ( this.endpoints.remove(reference.getProperty(Constants.SERVICE_ID)) != null ) { + changed = true; + } + } + if ( changed && this.propagationService != null ) { + this.propagationService.setProperties(this.getRegistrationProperties()); + } + } + + private String[] toStringArray(final Object propValue) { + if (propValue == null) { + // no value at all + return null; + + } else 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 Collection<?>) { + // collection + Collection<?> valueCollection = (Collection<?>) propValue; + List<String> valueList = new ArrayList<String>(valueCollection.size()); + for (Object value : valueCollection) { + if (value != null) { + valueList.add(value.toString()); + } + } + return valueList.toArray(new String[valueList.size()]); + } + + return null; + } +} -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
