blob: e8502f639ca55067fad683d0cba9a2dd71e0a9ba [file] [log] [blame]
/*
* 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 -&gt; {@link ServiceReference} of the
* currently active and tracked engines.
* @return the name -&gt; {@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);
}
}