/* * 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.entityhub.web.writer; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; 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 javax.ws.rs.core.MediaType; import org.apache.stanbol.entityhub.servicesapi.model.Representation; import org.apache.stanbol.entityhub.web.ModelWriter; import org.osgi.framework.BundleContext; import org.osgi.framework.ServiceReference; import org.osgi.util.tracker.ServiceTracker; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class ModelWriterTracker extends ServiceTracker { private final Logger log = LoggerFactory.getLogger(getClass()); /** * Holds the config */ private final Map<String, Map<MediaType,List<ServiceReference>>> writers = new HashMap<String,Map<MediaType,List<ServiceReference>>>(); /** * Caches requests for MediaTypes and types */ private final Map<CacheKey, Collection<ServiceReference>> cache = new HashMap<CacheKey,Collection<ServiceReference>>(); /** * lock for {@link #writers} and {@link #cache} */ private final ReadWriteLock lock = new ReentrantReadWriteLock(); @Override public Object addingService(ServiceReference reference) { Object service = super.addingService(reference); Set<MediaType> mediaTypes = parseMediaTypes(((ModelWriter)service).supportedMediaTypes()); Class<? extends Representation> nativeType = ((ModelWriter)service).getNativeType(); if(!mediaTypes.isEmpty()){ lock.writeLock().lock(); try { for(MediaType mediaType : mediaTypes){ addModelWriter(nativeType, mediaType, reference); } } finally { lock.writeLock().unlock(); } return service; } else { //else no MediaTypes registered return null; //ignore this service } } @Override public void removedService(ServiceReference reference, Object service) { if(service != null){ Set<MediaType> mediaTypes = parseMediaTypes(((ModelWriter)service).supportedMediaTypes()); Class<? extends Representation> nativeType = ((ModelWriter)service).getNativeType(); if(!mediaTypes.isEmpty()){ lock.writeLock().lock(); try { for(MediaType mediaType : mediaTypes){ removeModelWriter(nativeType, mediaType, reference); } } finally { lock.writeLock().unlock(); } } } super.removedService(reference, service); } @Override public final void modifiedService(ServiceReference reference, Object service) { super.modifiedService(reference, service); if(service != null){ Set<MediaType> mediaTypes = parseMediaTypes(((ModelWriter)service).supportedMediaTypes()); Class<? extends Representation> nativeType = ((ModelWriter)service).getNativeType(); if(!mediaTypes.isEmpty()){ lock.writeLock().lock(); try { for(MediaType mediaType : mediaTypes){ updateModelWriter(nativeType, mediaType, reference); } } finally { lock.writeLock().unlock(); } } } } /** * @param reference * @param key */ private void addModelWriter(Class<? extends Representation> nativeType, MediaType mediaType, ServiceReference reference) { //we want to have all ModelWriters under the null key log.debug(" > add ModelWriter format: {}, bundle: {}, nativeType: {}", new Object[]{mediaType, reference.getBundle(), nativeType != null ? nativeType.getName() : "none"}); Map<MediaType,List<ServiceReference>> typeWriters = writers.get(null); addTypeWriter(typeWriters, mediaType, reference); if(nativeType != null){ //register also as native type writers typeWriters = writers.get(nativeType.getName()); if(typeWriters == null){ typeWriters = new HashMap<MediaType,List<ServiceReference>>(); writers.put(nativeType.getName(), typeWriters); } addTypeWriter(typeWriters, mediaType, reference); } cache.clear(); //clear the cache after a change } /** * @param typeWriters * @param mediaType * @param reference */ private void addTypeWriter(Map<MediaType,List<ServiceReference>> typeWriters, MediaType mediaType, ServiceReference reference) { List<ServiceReference> l; l = typeWriters.get(mediaType); if(l == null){ l = new ArrayList<ServiceReference>(); typeWriters.put(mediaType, l); } l.add(reference); Collections.sort(l); //service ranking based sorting } /** * @param key * @param reference */ private void removeModelWriter(Class<? extends Representation> nativeType, MediaType mediaType, ServiceReference reference) { log.debug(" > remove ModelWriter format: {}, service: {}, nativeType: {}", new Object[]{mediaType, reference, nativeType != null ? nativeType.getClass().getName() : "none"}); Map<MediaType,List<ServiceReference>> typeWriters = writers.get(null); removeTypeWriter(typeWriters, mediaType, reference); if(nativeType != null){ typeWriters = writers.get(nativeType.getName()); if(typeWriters != null){ removeTypeWriter(typeWriters, mediaType, reference); if(typeWriters.isEmpty()){ writers.remove(nativeType.getName()); } } } cache.clear(); //clear the cache after a change } /** * @param typeWriters * @param mediaType * @param reference */ private void removeTypeWriter(Map<MediaType,List<ServiceReference>> typeWriters, MediaType mediaType, ServiceReference reference) { List<ServiceReference> l = typeWriters.get(mediaType); if(l != null && l.remove(reference) && l.isEmpty()){ writers.remove(mediaType); //remove empty mediaTypes } } /** * @param key * @param reference */ private void updateModelWriter(Class<? extends Representation> nativeType, MediaType mediaType, ServiceReference reference) { log.debug(" > update ModelWriter format: {}, service: {}, nativeType: {}", new Object[]{mediaType, reference, nativeType != null ? nativeType.getClass().getName() : "none"}); Map<MediaType,List<ServiceReference>> typeWriters = writers.get(null); updateTypeWriter(typeWriters, mediaType, reference); if(nativeType != null){ typeWriters = writers.get(nativeType.getName()); if(typeWriters != null){ updateTypeWriter(typeWriters, mediaType, reference); } } cache.clear(); //clear the cache after a change } /** * @param typeWriters * @param mediaType * @param reference */ private void updateTypeWriter(Map<MediaType,List<ServiceReference>> typeWriters, MediaType mediaType, ServiceReference reference) { List<ServiceReference> l = typeWriters.get(mediaType); if(l != null && l.contains(reference)){ Collections.sort(l); //maybe service.ranking has changed } } public ModelWriterTracker(BundleContext context) { super(context, ModelWriter.class.getName(), null); //add the union key value mapping writers.put(null, new HashMap<MediaType,List<ServiceReference>>()); } /** * @param mts * @return */ private Set<MediaType> parseMediaTypes(Collection<MediaType> mts) { if(mts == null || mts.isEmpty()){ return Collections.emptySet(); } Set<MediaType> mediaTypes = new HashSet<MediaType>(mts.size()); for(MediaType mt : mts){ if(mt != null){ //strip all parameters MediaType mediaType = mt.getParameters().isEmpty() ? mt : new MediaType(mt.getType(),mt.getSubtype()); mediaTypes.add(mediaType); } } return mediaTypes; } /** * Getter for a sorted list of References to {@link ModelWriter} that can * serialise Representations to the parsed {@link MediaType}. If a * nativeType of the Representation is given {@link ModelWriter} for that * specific type will be preferred. * @param mediaType The {@link MediaType}. Wildcards are supported * @param nativeType optionally the native type of the {@link Representation} * @return A sorted collection of references to compatible {@link ModelWriter}. * Use {@link #getService()} to receive the actual service. However note that * such calls may return <code>null</code> if the service was deactivated in * the meantime. */ public Collection<ServiceReference> getModelWriters(MediaType mediaType, Class<? extends Representation> nativeType){ Collection<ServiceReference> refs; String nativeTypeName = nativeType == null ? null : nativeType.getName(); CacheKey key = new CacheKey(mediaType, nativeTypeName); lock.readLock().lock(); try { refs = cache.get(key); } finally { lock.readLock().unlock(); } if(refs == null){ //not found in cache refs = new ArrayList<ServiceReference>(); Map<MediaType, List<ServiceReference>> typeWriters = writers.get( nativeTypeName); if(typeWriters != null){ //there are some native writers for this type refs.addAll(getTypeWriters(typeWriters, mediaType)); } if(nativeType != null){ //if we have a native type //also add writers for the generic type to the end refs.addAll(getTypeWriters(writers.get(null), mediaType)); } refs = Collections.unmodifiableCollection(refs); lock.writeLock().lock(); try { cache.put(key, refs); } finally { lock.writeLock().unlock(); } } return refs; } private Collection<ServiceReference> getTypeWriters( Map<MediaType,List<ServiceReference>> typeWriters, MediaType mediaType) { //use a linked has set to keep order but filter duplicates Collection<ServiceReference> refs = new LinkedHashSet<ServiceReference>(); boolean wildcard = mediaType.isWildcardSubtype() || mediaType.isWildcardType(); lock.readLock().lock(); try { if(!wildcard){ //add writer that explicitly mention this type first List<ServiceReference> l = typeWriters.get(mediaType); if(l != null){ refs.addAll(l); } } List<ServiceReference> wildcardMatches = null; int count = 0; for(Entry<MediaType,List<ServiceReference>> entry : typeWriters.entrySet()){ MediaType mt = entry.getKey(); if(mt.isCompatible(mediaType) && //ignore exact matches already treated above (wildcard || !mt.equals(mediaType))){ if(count == 0){ wildcardMatches = entry.getValue(); } else { if(count == 1){ wildcardMatches = new ArrayList<ServiceReference>(wildcardMatches); } wildcardMatches.addAll(entry.getValue()); } } } if(count > 1){ //sort matches for different media types Collections.sort(wildcardMatches); } //add wildcard matches to the linked has set if(count > 0){ refs.addAll(wildcardMatches); } } finally { lock.readLock().unlock(); } return refs; } @Override public ModelWriter getService() { return (ModelWriter)super.getService(); } @Override public ModelWriter getService(ServiceReference reference) { return (ModelWriter)super.getService(reference); } /** * Used as key for {@link ModelWriterTracker#cache} */ private static class CacheKey { final String nativeType; final MediaType mediaType; CacheKey(MediaType mediaType, String nativeType){ this.nativeType = nativeType; this.mediaType = mediaType; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + mediaType.hashCode(); result = prime * result + ((nativeType == null) ? 0 : nativeType.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; CacheKey other = (CacheKey) obj; if (!mediaType.equals(other.mediaType)) return false; if (nativeType == null) { if (other.nativeType != null) return false; } else if (!nativeType.equals(other.nativeType)) return false; return true; } } }