| /* |
| * 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.stanbol.enhancer.servicesapi.impl; |
| |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.Set; |
| import java.util.concurrent.locks.ReadWriteLock; |
| import java.util.concurrent.locks.ReentrantReadWriteLock; |
| |
| import org.apache.stanbol.enhancer.servicesapi.Chain; |
| import org.apache.stanbol.enhancer.servicesapi.EnhancementEngine; |
| import org.osgi.framework.BundleContext; |
| import org.osgi.framework.Constants; |
| import org.osgi.framework.Filter; |
| import org.osgi.framework.ServiceReference; |
| import org.osgi.framework.ServiceRegistration; |
| import org.osgi.util.tracker.ServiceTracker; |
| import org.osgi.util.tracker.ServiceTrackerCustomizer; |
| |
| /** |
| * Used to manage the state of ServiceReferences for services that are |
| * accessed based on the value of a specific property. <p> |
| * This can be used to track both {@link EnhancementEngine}s as well as |
| * {@link Chain}s.<p> |
| * This implementation supports the use of {@link #readLock()} on returned |
| * values. Also the <code>null</code> as value for the parsed name property. |
| * |
| * @author Rupert Westenthaler |
| * |
| */ |
| public class NameBasedServiceTrackingState extends ServiceTracker implements ServiceTrackerCustomizer { |
| // /** |
| // * Allows to forward to an other customiser after this class has finished |
| // * his work |
| // */ |
| private final ServiceTrackerCustomizer customizer; |
| /** |
| * Lock used to protect acces to {@link #state} and {@link #tracked} |
| */ |
| ReadWriteLock lock = new ReentrantReadWriteLock(); |
| /** |
| * This member uses lazzy initialisation and is set back to <code>null</code> |
| * after every change of a tracked service. Use {@link #getState()} |
| * to access this the internal state.<p> |
| * Mapping from the names to the {@link ServiceReference}s of the |
| * Engines. ServiveReferences are sorted by {@link Constants#SERVICE_RANKING}.<p> |
| * Note that values of Entries are not modified on changes but replaced |
| * by new instances. Because of this one needs only sync the retrieval |
| * but not further accesses to the list. |
| */ |
| private Map<String, List<ServiceReference>> state_; |
| /** |
| * Property used to retrieve the name of the component via the properties |
| * provided by the {@link ServiceReference} |
| */ |
| private final String property; |
| /** |
| * Creates a trackingState with an optional customiser |
| * @param context the {@link BundleContext} used for tracking. MUST NOT |
| * be <code>null</code>. |
| * @param filter The filter used for the ServiceTracker |
| * @param nameProperty the property used to lookup the name of the tracked |
| * services. This MUST NOT be <code>null</code> nor empty. |
| * @param customizer optionally a customiser used with this tracker |
| * @throws IllegalArgumentException it the parsed nameProperty is |
| * <code>null</code> or an empty string. |
| */ |
| public NameBasedServiceTrackingState(BundleContext context, Filter filter, String nameProperty, ServiceTrackerCustomizer customizer){ |
| super(context, filter, null); |
| if(nameProperty == null || nameProperty.isEmpty()){ |
| throw new IllegalArgumentException("the property to lookup the " + |
| "name of tracked services MUST NOT be NULL nor emtoy!"); |
| } |
| this.property = nameProperty; |
| this.customizer = customizer; |
| } |
| /** |
| * Creates a trackingState with an optional customiser |
| * @param context the {@link BundleContext} used for tracking. MUST NOT |
| * be <code>null</code>. |
| * @param clazz The type of the tracked services |
| * @param nameProperty the property used to lookup the name of the tracked |
| * services. This MUST NOT be <code>null</code> nor empty. |
| * @param customizer optionally a customiser used with this tracker |
| * @throws IllegalArgumentException it the parsed nameProperty is |
| * <code>null</code> or an empty string. |
| */ |
| public NameBasedServiceTrackingState(BundleContext context, String clazz, String nameProperty, ServiceTrackerCustomizer customizer){ |
| super(context, clazz, null); |
| if(nameProperty == null || nameProperty.isEmpty()){ |
| throw new IllegalArgumentException("the property to lookup the " + |
| "name of tracked services MUST NOT be NULL nor emtoy!"); |
| } |
| this.property = nameProperty; |
| this.customizer = customizer; |
| } |
| /** |
| * Getter for the read only set of names of all currently active and |
| * tracked engines |
| * @return the names of all currently active and tracked engines |
| */ |
| public Set<String> getNames() { |
| Map<String, List<ServiceReference>> state = getState(); |
| lock.writeLock().lock(); |
| try { |
| return Collections.unmodifiableSet(state.keySet()); |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| } |
| /** |
| * Getter for the read only names -> {@link ServiceReference} of the |
| * currently active and tracked engines. |
| * @return the name -> {@link ServiceReference} mapping of all active |
| * engines. |
| */ |
| public Map<String,ServiceReference> getActive() { |
| Map<String,List<ServiceReference>> state = getState(); |
| Map<String,ServiceReference> active = new HashMap<String,ServiceReference>(state.size()); |
| for(Entry<String,List<ServiceReference>> entry : state.entrySet()){ |
| active.put(entry.getKey(), entry.getValue().get(0)); |
| } |
| return Collections.unmodifiableMap(active); |
| } |
| @Override |
| public Object addingService(ServiceReference reference) { |
| lock.writeLock().lock(); |
| try { |
| this.state_ = null; |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| final Object service; |
| if(customizer != null){ |
| service = customizer.addingService(reference); |
| } else { |
| service = context.getService(reference); |
| } |
| return service; |
| } |
| @Override |
| public void modifiedService(ServiceReference reference, Object service) { |
| lock.writeLock().lock(); |
| try { |
| this.state_ = null; |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| if(customizer != null){ |
| customizer.modifiedService(reference, service); |
| } //else nothing to do |
| } |
| /** |
| * Looks like {@link #modifiedService(ServiceReference, Object)} is not |
| * called on property changes (add is used instead). Parsed |
| * {@link ServiceReference} objects are not equals even if they are for the |
| * same {@link ServiceRegistration}. So the only way to keep the state in |
| * sync is to rebuild it after every change to a tracked service. |
| */ |
| private Map<String,List<ServiceReference>> getState() { |
| lock.writeLock().lock(); |
| try { |
| if(this.state_ == null){ |
| //Temporary map to collect the values |
| Map<String,List<ServiceReference>> tmp = new HashMap<String,List<ServiceReference>>(); |
| ServiceReference[] serviceRefs = getServiceReferences(); |
| if(serviceRefs != null){ |
| for(ServiceReference ref : serviceRefs){ |
| String name = (String)ref.getProperty(property); |
| List<ServiceReference> refs = tmp.get(name); |
| if(refs == null){ |
| refs = new ArrayList<ServiceReference>(3); |
| tmp.put(name, refs); |
| } |
| refs.add(ref); |
| } |
| } |
| //now iterate a second time to sort and make values immutable an |
| Map<String,List<ServiceReference>> state = new HashMap<String,List<ServiceReference>>(tmp.size()); |
| for(Entry<String,List<ServiceReference>> entry : tmp.entrySet()){ |
| if(entry.getValue().size() > 1){ |
| Collections.sort(entry.getValue(),ServiceReferenceRankingComparator.INSTANCE); |
| } |
| state.put(entry.getKey(), Collections.unmodifiableList(entry.getValue())); |
| } |
| this.state_ = Collections.unmodifiableMap(state); |
| } |
| return state_; |
| }finally{ |
| lock.writeLock().unlock(); |
| } |
| } |
| @Override |
| public void removedService(ServiceReference reference, Object service) { |
| lock.writeLock().lock(); |
| try { |
| this.state_ = null; |
| } finally { |
| lock.writeLock().unlock(); |
| } |
| if(customizer != null){ |
| customizer.removedService(reference, service); |
| } else { |
| context.ungetService(reference); |
| } |
| } |
| |
| public List<ServiceReference> getReferences(String name){ |
| Map<String,List<ServiceReference>> state = getState(); |
| List<ServiceReference> refs = state.get(name); |
| return refs == null ? null : Collections.unmodifiableList(refs); |
| } |
| public ServiceReference getReference(String name){ |
| Map<String,List<ServiceReference>> state = getState(); |
| List<ServiceReference> refs = state.get(name); |
| return refs == null ? null : refs.get(0); |
| } |
| } |