blob: 30e1d436cbcbef62357d3d37cd2f6142ba832305 [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.sling.rewriter.impl;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.sling.commons.osgi.OsgiUtil;
import org.apache.sling.rewriter.Generator;
import org.apache.sling.rewriter.GeneratorFactory;
import org.apache.sling.rewriter.ProcessingContext;
import org.apache.sling.rewriter.Processor;
import org.apache.sling.rewriter.ProcessorConfiguration;
import org.apache.sling.rewriter.ProcessorFactory;
import org.apache.sling.rewriter.Serializer;
import org.apache.sling.rewriter.SerializerFactory;
import org.apache.sling.rewriter.Transformer;
import org.apache.sling.rewriter.TransformerFactory;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.framework.ServiceReference;
import org.osgi.util.tracker.ServiceTracker;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* This is an utility class for accessing the various pipeline components.
* it also acts like a cache for the factories.
*/
public class FactoryCache {
/** The required property containing the component type. */
private static final String PROPERTY_TYPE = "pipeline.type";
/** The optional property for the pipeline mode (global) */
private static final String PROPERTY_MODE = "pipeline.mode";
/** The global mode. */
private static final String MODE_GLOBAL = "global";
/** The optional property for the paths the component should apply to */
private static final String PROPERTY_PATHS = "pipeline.paths";
/** The optional property for the extensions the component should apply to */
private static final String PROPERTY_EXTENSIONS = "pipeline.extensions";
/** The optional property for the content types the component should apply to */
private static final String PROPERTY_CONTENT_TYPES = "pipeline.contentTypes";
/** The optional property for the selectors the component should apply to */
private static final String PROPERTY_SELECTORS = "pipeline.selectors";
/** The optional property for the resource types the component should apply to */
private static final String PROPERTY_RESOURCE_TYPES = "pipeline.resourceTypes";
/** The logger. */
private static final Logger LOGGER = LoggerFactory.getLogger(FactoryCache.class);
/** The tracker for generator factories. */
private final HashingServiceTrackerCustomizer<GeneratorFactory> generatorTracker;
/** The tracker for serializers factories. */
private final HashingServiceTrackerCustomizer<SerializerFactory> serializerTracker;
/** The tracker for transformer factories. */
private final TransformerFactoryServiceTracker<TransformerFactory> transformerTracker;
/** The tracker for processor factories. */
private final HashingServiceTrackerCustomizer<ProcessorFactory> processorTracker;
public FactoryCache(final BundleContext context)
throws InvalidSyntaxException {
this.generatorTracker = new HashingServiceTrackerCustomizer<GeneratorFactory>(context,
GeneratorFactory.class.getName());
this.serializerTracker = new HashingServiceTrackerCustomizer<SerializerFactory>(context,
SerializerFactory.class.getName());
this.transformerTracker = new TransformerFactoryServiceTracker<TransformerFactory>(context,
TransformerFactory.class.getName());
this.processorTracker = new HashingServiceTrackerCustomizer<ProcessorFactory>(context,
ProcessorFactory.class.getName());
}
/**
* Start tracking
*/
public void start() {
this.generatorTracker.open();
this.serializerTracker.open();
this.transformerTracker.open();
this.processorTracker.open();
}
/**
* Stop tracking
*/
public void stop() {
this.generatorTracker.close();
this.serializerTracker.close();
this.transformerTracker.close();
this.processorTracker.close();
}
/**
* Get the generator of the given type.
* @param type The generator type.
* @return The generator or null if the generator is not available.
*/
public Generator getGenerator(final String type) {
final GeneratorFactory factory = this.generatorTracker.getFactory(type);
if ( factory == null ) {
LOGGER.debug("Requested generator factory for type '{}' not found.", type);
return null;
}
return factory.createGenerator();
}
/**
* Get the serializer of the given type.
* @param type The serializer type.
* @return The serializer or null if the serializer is not available.
*/
public Serializer getSerializer(final String type) {
final SerializerFactory factory = this.serializerTracker.getFactory(type);
if ( factory == null ) {
LOGGER.debug("Requested serializer factory for type '{}' not found.", type);
return null;
}
return factory.createSerializer();
}
/**
* Get the transformer of the given type.
* @param type The transformer type.
* @return The transformer or null if the transformer is not available.
*/
public Transformer getTransformer(final String type) {
final TransformerFactory factory = this.transformerTracker.getFactory(type);
if ( factory == null ) {
LOGGER.debug("Requested transformer factory for type '{}' not found.", type);
return null;
}
return factory.createTransformer();
}
/**
* Get the processor of the given type.
* @param type The processor type.
* @return The processor or null if the processor is not available.
*/
public Processor getProcessor(final String type) {
final ProcessorFactory factory = this.processorTracker.getFactory(type);
if ( factory == null ) {
LOGGER.debug("Requested processor factory for type '{}' not found.", type);
return null;
}
return factory.createProcessor();
}
private static final Transformer[] EMPTY_ARRAY = new Transformer[0];
private static final Transformer[][] EMPTY_DOUBLE_ARRAY = new Transformer[][] {EMPTY_ARRAY, EMPTY_ARRAY};
/**
* Lookup all global transformers that apply to the current request and return
* the transformer instances in two arrays.
* The first array contains all pre transformers and the second one contains
* all post transformers.
* @param context The current processing context.
*/
public Transformer[][] getGlobalTransformers(final ProcessingContext context) {
final TransformerFactory[][] factories = this.transformerTracker.getGlobalTransformerFactories(context);
return createTransformers(factories);
}
/**
* Create new instances from the factories
* @param factories The transformer factories
* @return The transformer instances
*/
private Transformer[][] createTransformers(final TransformerFactory[][] factories) {
if ( factories == EMPTY_DOUBLE_ARRAY ) {
return FactoryCache.EMPTY_DOUBLE_ARRAY;
}
final Transformer[][] transformers = new Transformer[2][];
for(int arrayIndex = 0; arrayIndex < 2; arrayIndex++) {
int count = factories[arrayIndex].length;
for(final TransformerFactory factory : factories[arrayIndex]) {
if ( factory == null ) count--;
}
if ( count == 0 ) {
transformers[arrayIndex] = FactoryCache.EMPTY_ARRAY;
} else {
transformers[arrayIndex] = new Transformer[count];
for(int i=0; i < factories[arrayIndex].length; i++) {
final TransformerFactory factory = factories[arrayIndex][i];
if ( factory != null ) {
transformers[arrayIndex][i] = factory.createTransformer();
}
}
}
}
return transformers;
}
/**
* This service tracker stores all services into a hash map.
*/
private static class HashingServiceTrackerCustomizer<T> extends ServiceTracker {
/** The services hashed by their name property. */
private final Map<String, T> services = new ConcurrentHashMap<String, T>();
/** The bundle context. */
protected final BundleContext context;
public HashingServiceTrackerCustomizer(final BundleContext bc, final String serviceClassName) {
super(bc, serviceClassName, null);
this.context = bc;
}
public T getFactory(final String type) {
return services.get(type);
}
private String getType(final ServiceReference ref) {
final String type = (String) ref.getProperty(PROPERTY_TYPE);
return type;
}
/**
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(org.osgi.framework.ServiceReference)
*/
public Object addingService(final ServiceReference reference) {
final String type = this.getType(reference);
@SuppressWarnings("unchecked")
final T factory = (type == null ? null : (T) this.context.getService(reference));
if ( factory != null ) {
if ( LOGGER.isDebugEnabled() ) {
LOGGER.debug("Found service {}, type={}.", factory, type);
}
this.services.put(type, factory);
}
return factory;
}
/**
* @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
*/
public void removedService(final ServiceReference reference, final Object service) {
final String type = this.getType(reference);
if ( type != null ) {
this.services.remove(type);
this.context.ungetService(reference);
}
}
}
private static final class TransformerFactoryServiceTracker<T> extends HashingServiceTrackerCustomizer<T> {
private String getMode(final ServiceReference ref) {
final String mode = (String) ref.getProperty(PROPERTY_MODE);
return mode;
}
private boolean isGlobal(final ServiceReference ref) {
return MODE_GLOBAL.equalsIgnoreCase(this.getMode(ref));
}
public static final TransformerFactoryEntry[] EMPTY_ENTRY_ARRAY = new TransformerFactoryEntry[0];
public static final TransformerFactoryEntry[][] EMPTY_DOUBLE_ENTRY_ARRAY = new TransformerFactoryEntry[][] {EMPTY_ENTRY_ARRAY, EMPTY_ENTRY_ARRAY};
public static final TransformerFactory[] EMPTY_FACTORY_ARRAY = new TransformerFactory[0];
public static final TransformerFactory[][] EMPTY_DOUBLE_FACTORY_ARRAY = new TransformerFactory[][] {EMPTY_FACTORY_ARRAY, EMPTY_FACTORY_ARRAY};
private TransformerFactoryEntry[][] cached = EMPTY_DOUBLE_ENTRY_ARRAY;
/** flag for cache. */
private boolean cacheIsValid = true;
public TransformerFactoryServiceTracker(final BundleContext bc, final String serviceClassName) {
super(bc, serviceClassName);
}
/**
* @see org.osgi.util.tracker.ServiceTracker#addingService(org.osgi.framework.ServiceReference)
*/
public Object addingService(ServiceReference reference) {
final boolean isGlobal = isGlobal(reference);
if ( isGlobal ) {
this.cacheIsValid = false;
}
Object obj = super.addingService(reference);
if ( obj == null && isGlobal ) {
obj = this.context.getService(reference);
}
return obj;
}
/**
* @see org.osgi.util.tracker.ServiceTracker#removedService(org.osgi.framework.ServiceReference, java.lang.Object)
*/
public void removedService(ServiceReference reference, Object service) {
if ( isGlobal(reference) ) {
this.cacheIsValid = false;
}
super.removedService(reference, service);
}
/**
* Get all global transformer factories.
* @return Two arrays of transformer factories
*/
public TransformerFactoryEntry[][] getGlobalTransformerFactoryEntries() {
if ( !this.cacheIsValid ) {
synchronized ( this ) {
if ( !this.cacheIsValid ) {
final ServiceReference[] refs = this.getServiceReferences();
if ( refs == null || refs.length == 0 ) {
this.cached = EMPTY_DOUBLE_ENTRY_ARRAY;
} else {
Arrays.sort(refs, ServiceReferenceComparator.INSTANCE);
int preCount = 0;
int postCount = 0;
for(final ServiceReference ref : refs) {
if ( isGlobal(ref) ) {
final Object r = ref.getProperty(Constants.SERVICE_RANKING);
int ranking = (r instanceof Integer ? (Integer)r : 0);
if ( ranking < 0 ) {
preCount++;
} else {
postCount++;
}
}
}
final TransformerFactoryEntry[][] globalFactories = new TransformerFactoryEntry[2][];
if ( preCount == 0 ) {
globalFactories[0] = EMPTY_ENTRY_ARRAY;
} else {
globalFactories[0] = new TransformerFactoryEntry[preCount];
}
if ( postCount == 0) {
globalFactories[1] = EMPTY_ENTRY_ARRAY;
} else {
globalFactories[1] = new TransformerFactoryEntry[postCount];
}
int index = 0;
for(final ServiceReference ref : refs) {
if ( isGlobal(ref) ) {
if ( index < preCount ) {
globalFactories[0][index] = new TransformerFactoryEntry((TransformerFactory) this.getService(ref), ref);
} else {
globalFactories[1][index - preCount] = new TransformerFactoryEntry((TransformerFactory) this.getService(ref), ref);
}
index++;
}
}
this.cached = globalFactories;
}
}
this.cacheIsValid = true;
}
}
return this.cached;
}
/**
* Get all global transformer factories that apply to the current request.
* @param context The current processing context.
* @return Two arrays containing the transformer factories.
*/
public TransformerFactory[][] getGlobalTransformerFactories(final ProcessingContext context) {
final TransformerFactoryEntry[][] globalFactoryEntries = this.getGlobalTransformerFactoryEntries();
// quick check
if ( globalFactoryEntries == EMPTY_DOUBLE_ENTRY_ARRAY ) {
return EMPTY_DOUBLE_FACTORY_ARRAY;
}
final TransformerFactory[][] factories = new TransformerFactory[2][];
for(int i=0; i<2; i++) {
if ( globalFactoryEntries[i] == EMPTY_ENTRY_ARRAY ) {
factories[i] = EMPTY_FACTORY_ARRAY;
} else {
factories[i] = new TransformerFactory[globalFactoryEntries[i].length];
for(int m=0; m<globalFactoryEntries[i].length; m++) {
final TransformerFactoryEntry entry = globalFactoryEntries[i][m];
if ( entry.match(context) ) {
factories[i][m] = entry.factory;
}
}
}
}
return factories;
}
}
/**
* Comparator for service references.
*/
private static final class ServiceReferenceComparator implements Comparator<ServiceReference> {
public static ServiceReferenceComparator INSTANCE = new ServiceReferenceComparator();
public int compare(ServiceReference o1, ServiceReference o2) {
return o1.compareTo(o2);
}
}
private static final class TransformerFactoryEntry {
public final TransformerFactory factory;
private final ProcessorConfiguration configuration;
public TransformerFactoryEntry(final TransformerFactory factory, final ServiceReference ref) {
this.factory = factory;
final String[] paths = OsgiUtil.toStringArray(ref.getProperty(PROPERTY_PATHS), null);
final String[] extensions = OsgiUtil.toStringArray(ref.getProperty(PROPERTY_EXTENSIONS), null);
final String[] contentTypes = OsgiUtil.toStringArray(ref.getProperty(PROPERTY_CONTENT_TYPES), null);
final String[] resourceTypes = OsgiUtil.toStringArray(ref.getProperty(PROPERTY_RESOURCE_TYPES), null);
final String[] selectors = OsgiUtil.toStringArray(ref.getProperty(PROPERTY_SELECTORS), null);
final boolean noCheckRequired = (paths == null || paths.length == 0) &&
(extensions == null || extensions.length == 0) &&
(contentTypes == null || contentTypes.length == 0) &&
(resourceTypes == null || resourceTypes.length == 0) &&
(selectors == null || selectors.length == 0);
if ( !noCheckRequired ) {
this.configuration = new ProcessorConfigurationImpl(contentTypes, paths, extensions, resourceTypes, selectors);
} else {
this.configuration = null;
}
}
public boolean match(final ProcessingContext context) {
if ( configuration == null ) {
return true;
}
return configuration.match(context);
}
}
}