| /* |
| * 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.io.PrintWriter; |
| import java.util.Arrays; |
| import java.util.HashSet; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.sling.api.resource.Resource; |
| import org.apache.sling.api.resource.ResourceResolver; |
| import org.apache.sling.api.resource.ResourceUtil; |
| import org.apache.sling.api.resource.ResourceWrapper; |
| import org.apache.sling.api.resource.ValueMap; |
| import org.apache.sling.rewriter.PipelineConfiguration; |
| import org.apache.sling.rewriter.ProcessingComponentConfiguration; |
| import org.apache.sling.rewriter.ProcessingContext; |
| |
| /** |
| * Configuration of a processor. |
| * This configuration consists either of a pipeline (generator, transformer |
| * and serializer) or a processor. A processor is configured with mime types |
| * indicating when to apply this processor. |
| */ |
| public class ProcessorConfigurationImpl implements PipelineConfiguration { |
| |
| static final String PROPERTY_ORDER = "order"; |
| |
| static final String PROPERTY_PATHS = "paths"; |
| |
| static final String PROPERTY_EXTENSIONS = "extensions"; |
| |
| static final String PROPERTY_PROCESSOR_TYPE = "processorType"; |
| |
| static final String PROPERTY_CONTENT_TYPES = "contentTypes"; |
| |
| static final String PROPERTY_RESOURCE_TYPES = "resourceTypes"; |
| |
| static final String PROPERTY_UNWRAP_RESOURCES = "unwrapResources"; |
| |
| static final String PROPERTY_SELECTORS = "selectors"; |
| |
| static final String PROPERTY_TRANFORMERS = "transformerTypes"; |
| |
| static final String PROPERTY_GENERATOR = "generatorType"; |
| |
| static final String PROPERTY_SERIALIZER = "serializerType"; |
| |
| static final String PROPERTY_ACTIVE = "enabled"; |
| |
| static final String PROPERTY_PROCESS_ERROR = "processError"; |
| |
| |
| /** For which content types should this processor be applied. */ |
| private final String[] contentTypes; |
| |
| /** For which paths should this processor be applied. */ |
| private final String[] paths; |
| |
| /** For which extensions should this processor be applied. */ |
| private final String[] extensions; |
| |
| /** For which resource types should this processor be applied. */ |
| private final String[] resourceTypes; |
| |
| /** Whether unwrapped resources should be validated as well when checking for resource types. */ |
| private final boolean unwrapResources; |
| |
| /** For which selectors should this processor be applied. */ |
| private final String[] selectors; |
| |
| /** The order of this processor */ |
| private final int order; |
| |
| /** The generator for the pipeline. */ |
| private final ProcessingComponentConfiguration generatorConfiguration; |
| |
| /** The transformers for the pipeline. */ |
| private final ProcessingComponentConfiguration[] transformerConfigurations; |
| |
| /** The serializer for the pipeline. */ |
| private final ProcessingComponentConfiguration serializerConfiguration; |
| |
| /** The processor configuration. */ |
| private final ProcessingComponentConfiguration processorConfig; |
| |
| /** Is this configuration active? */ |
| private final boolean isActive; |
| |
| /** Is this configuration valid? */ |
| private final boolean isValid; |
| |
| /** Is this a pipeline configuration? */ |
| private final boolean isPipeline; |
| |
| private final boolean processErrorResponse; |
| |
| private final String descString; |
| |
| /** |
| * This is the constructor for a pipeline |
| */ |
| public ProcessorConfigurationImpl(String[] contentTypes, |
| String[] paths, |
| String[] extensions, |
| String[] resourceTypes, |
| boolean unwrapResources, |
| String[] selectors, |
| int order, |
| ProcessingComponentConfiguration generatorConfig, |
| ProcessingComponentConfiguration[] transformerConfigs, |
| ProcessingComponentConfiguration serializerConfig, |
| boolean processErrorResponse) { |
| this.contentTypes = contentTypes; |
| this.resourceTypes = resourceTypes; |
| this.unwrapResources = unwrapResources; |
| this.selectors = selectors; |
| this.paths = paths; |
| this.extensions = extensions; |
| this.order = order; |
| this.generatorConfiguration = generatorConfig; |
| this.transformerConfigurations = transformerConfigs; |
| this.serializerConfiguration = serializerConfig; |
| this.processorConfig = null; |
| this.isActive = true; |
| this.isValid = true; |
| this.isPipeline = true; |
| this.processErrorResponse = processErrorResponse; |
| this.descString = this.buildDescString(); |
| } |
| |
| /** |
| * This is the constructor for a pipeline |
| */ |
| public ProcessorConfigurationImpl(String[] contentTypes, |
| String[] paths, |
| String[] extensions, |
| String[] resourceTypes, |
| String[] selectors) { |
| this(contentTypes, paths, extensions, resourceTypes, false, selectors, 0, null, null, null, false); |
| } |
| |
| /** |
| * Constructor. |
| * This constructor reads the configuration from the specified resource. |
| */ |
| public ProcessorConfigurationImpl(final Resource resource) { |
| final ValueMap properties = ResourceUtil.getValueMap(resource); |
| this.contentTypes = properties.get(PROPERTY_CONTENT_TYPES, String[].class); |
| this.resourceTypes = properties.get(PROPERTY_RESOURCE_TYPES, String[].class); |
| this.unwrapResources = properties.get(PROPERTY_UNWRAP_RESOURCES, false); |
| this.selectors = properties.get(PROPERTY_SELECTORS, String[].class); |
| this.paths = properties.get(PROPERTY_PATHS, String[].class); |
| this.extensions = properties.get(PROPERTY_EXTENSIONS, String[].class); |
| |
| this.processorConfig = this.getComponentConfig(resource, PROPERTY_PROCESSOR_TYPE, "processor"); |
| this.generatorConfiguration = this.getComponentConfig(resource, PROPERTY_GENERATOR, "generator"); |
| this.transformerConfigurations = this.getComponentConfigs(resource, PROPERTY_TRANFORMERS, "transformer"); |
| this.serializerConfiguration = this.getComponentConfig(resource, PROPERTY_SERIALIZER, "serializer"); |
| |
| this.order = properties.get(PROPERTY_ORDER, 0); |
| this.isActive = properties.get(PROPERTY_ACTIVE, true); |
| this.processErrorResponse = properties.get(PROPERTY_PROCESS_ERROR, true); |
| this.isPipeline = this.processorConfig == null; |
| |
| // let's do a sanity check! |
| if ( this.isPipeline ) { |
| if ( this.generatorConfiguration == null |
| || this.generatorConfiguration.getType() == null |
| || this.generatorConfiguration.getType().length() == 0 ) { |
| this.isValid = false; |
| } else if ( this.serializerConfiguration == null |
| || this.generatorConfiguration.getType() == null |
| || this.generatorConfiguration.getType().length() == 0 ) { |
| this.isValid = false; |
| } else { |
| this.isValid = true; |
| } |
| } else { |
| this.isValid = (this.processorConfig != null); |
| } |
| this.descString = this.buildDescString(); |
| } |
| |
| void printConfiguration(final PrintWriter pw) { |
| if ( this.contentTypes != null ) { |
| pw.print("Content Types : "); |
| pw.println(Arrays.toString(this.contentTypes)); |
| } |
| if ( this.resourceTypes != null ) { |
| pw.print("Resource Types : "); |
| pw.println(Arrays.toString(this.resourceTypes)); |
| } |
| if ( this.selectors != null ) { |
| pw.print("Selectors : "); |
| pw.println(Arrays.toString(this.selectors)); |
| } |
| if ( this.paths != null ) { |
| pw.print("Paths : "); |
| pw.println(Arrays.toString(this.paths)); |
| } |
| if ( this.extensions != null ) { |
| pw.print("Extensions : "); |
| pw.println(Arrays.toString(this.extensions)); |
| } |
| pw.print("Order : "); |
| pw.println(this.order); |
| pw.print("Active : "); |
| pw.println(this.isActive); |
| pw.print("Valid : "); |
| pw.println(this.isValid); |
| pw.print("Process Error Response : "); |
| pw.println(this.processErrorResponse); |
| if ( this.isPipeline ) { |
| pw.println("Pipeline : "); |
| pw.println(" Generator : "); |
| pw.print(" "); |
| printConfiguration(pw, this.generatorConfiguration); |
| pw.println(" Transformers : "); |
| for(int i=0; i<this.transformerConfigurations.length; i++) { |
| pw.print(" "); |
| printConfiguration(pw, this.transformerConfigurations[i]); |
| } |
| pw.println(" Serializer : "); |
| pw.print(" "); |
| printConfiguration(pw, this.serializerConfiguration); |
| } else { |
| pw.print("Configuration : "); |
| printConfiguration(pw, this.processorConfig); |
| } |
| |
| } |
| |
| private void printConfiguration(final PrintWriter pw, final ProcessingComponentConfiguration config) { |
| if ( config instanceof ProcessingComponentConfigurationImpl ) { |
| ((ProcessingComponentConfigurationImpl)config).printConfiguration(pw); |
| } else { |
| pw.println(config); |
| } |
| } |
| |
| private String buildDescString() { |
| final StringBuilder sb = new StringBuilder(); |
| sb.append("ProcessorConfiguration: {"); |
| if ( this.contentTypes != null ) { |
| sb.append("contentTypes="); |
| sb.append(Arrays.toString(this.contentTypes)); |
| sb.append(", "); |
| } |
| if ( this.resourceTypes != null ) { |
| sb.append("resourceTypes="); |
| sb.append(Arrays.toString(this.resourceTypes)); |
| sb.append(", "); |
| } |
| if ( this.selectors != null ) { |
| sb.append("selectors="); |
| sb.append(Arrays.toString(this.selectors)); |
| sb.append(", "); |
| } |
| if ( this.paths != null ) { |
| sb.append("paths="); |
| sb.append(Arrays.toString(this.paths)); |
| sb.append(", "); |
| } |
| if ( this.extensions != null ) { |
| sb.append("extensions="); |
| sb.append(Arrays.toString(this.extensions)); |
| sb.append(", "); |
| } |
| sb.append("order="); |
| sb.append(this.order); |
| sb.append(", active="); |
| sb.append(this.isActive); |
| sb.append(", valid="); |
| sb.append(this.isValid); |
| sb.append(", processErrorResponse="); |
| sb.append(this.processErrorResponse); |
| if ( this.isPipeline ) { |
| sb.append(", pipeline=(generator="); |
| sb.append(this.generatorConfiguration); |
| sb.append(", transformers=("); |
| if ( this.transformerConfigurations != null ) { |
| for(int i=0; i<this.transformerConfigurations.length; i++) { |
| if ( i > 0 ) { |
| sb.append(", "); |
| } |
| sb.append(this.transformerConfigurations[i]); |
| } |
| } |
| sb.append(", serializer="); |
| sb.append(this.serializerConfiguration); |
| sb.append(')'); |
| } else { |
| sb.append(", config="); |
| sb.append(this.processorConfig); |
| } |
| sb.append("}"); |
| return sb.toString(); |
| } |
| |
| protected ProcessingComponentConfiguration getComponentConfig(final Resource configResource, |
| final String propertyName, |
| final String prefix) { |
| ProcessingComponentConfiguration[] configs = this.getComponentConfigs(configResource, propertyName, prefix); |
| if ( configs != null && configs.length > 0 ) { |
| return configs[0]; |
| } |
| return null; |
| } |
| |
| protected ProcessingComponentConfiguration[] getComponentConfigs(final Resource configResource, |
| final String propertyName, |
| final String prefix) { |
| final ValueMap properties = ResourceUtil.getValueMap(configResource); |
| final String[] types = properties.get(propertyName, String[].class); |
| if ( types != null && types.length > 0 ) { |
| final ProcessingComponentConfiguration[] configs = new ProcessingComponentConfiguration[types.length]; |
| for(int i=0; i<types.length; i++) { |
| // there are two possible ways for a component configuration: |
| // 1. {prefix}-{type}, like generator-html |
| // 2. {prefix}-{index}, like generator-1 (with the index starting at 1) |
| // while usually the first way is sufficient, the second one is required if the |
| // same transformer is used more than once in a pipeline. |
| final String resourceName = prefix + '-' + types[i]; |
| Resource childResource = configResource.getResourceResolver().getResource(configResource, resourceName); |
| if ( childResource == null ) { |
| final String secondResourceName = prefix + '-' + (i+1); |
| childResource = configResource.getResourceResolver().getResource(configResource, secondResourceName); |
| } |
| final ValueMap config; |
| if ( childResource != null ) { |
| final ValueMap childProps = ResourceUtil.getValueMap(childResource); |
| config = childProps; |
| } else { |
| config = null; |
| } |
| configs[i] = new ProcessingComponentConfigurationImpl(types[i], config); |
| } |
| return configs; |
| } |
| return null; |
| } |
| |
| /** |
| * Return the order of this configuration for sorting. |
| */ |
| public int getOrder() { |
| return this.order; |
| } |
| |
| /** |
| * @see org.apache.sling.rewriter.ProcessorConfiguration#match(org.apache.sling.rewriter.ProcessingContext) |
| */ |
| public boolean match(final ProcessingContext processContext) { |
| if ( !this.processErrorResponse && processContext.getRequest().getAttribute("javax.servlet.error.status_code") != null ) { |
| return false; |
| } |
| String contentType = processContext.getContentType(); |
| // if no content type is supplied, we assume html |
| if ( contentType == null ) { |
| contentType = ProcessorManagerImpl.MIME_TYPE_HTML; |
| } else { |
| final int idx = contentType.indexOf(';'); |
| if (idx != -1) { |
| contentType = contentType.substring(0, idx); |
| } |
| } |
| |
| // check content type first |
| // if no content type is configured we apply to all |
| if ( this.contentTypes != null && this.contentTypes.length > 0 ) { |
| int index = 0; |
| boolean found = false; |
| while ( !found && index < this.contentTypes.length ) { |
| if ( this.contentTypes[index].equals("*") ) { |
| found = true; |
| } else if ( this.contentTypes[index].equals(contentType) ) { |
| found = true; |
| } |
| index++; |
| } |
| if ( !found ) { |
| return false; |
| } |
| } |
| // now check extenstions |
| // if no extenstion is configured, we apply to all extenstions |
| if ( this.extensions != null && this.extensions.length > 0 ) { |
| boolean found = false; |
| int index = 0; |
| while ( !found && index < this.extensions.length ) { |
| if ( this.extensions[index].equals(processContext.getRequest().getRequestPathInfo().getExtension()) ) { |
| found = true; |
| } |
| index++; |
| } |
| if ( !found ) { |
| return false; |
| } |
| } |
| // check resource types |
| if ( this.resourceTypes != null && this.resourceTypes.length > 0 ) { |
| final ResourceResolver resourceResolver = processContext.getRequest().getResourceResolver(); |
| final Resource resource = processContext.getRequest().getResource(); |
| boolean found = false; |
| int index = 0; |
| while ( !found && index < this.resourceTypes.length ) { |
| if ( resourceResolver.isResourceType(resource, resourceTypes[index]) ) { |
| found = true; |
| } |
| else if ( unwrapResources && resource instanceof ResourceWrapper ) { |
| // accept resource as well if type was overridden and unwrapped resource has a matching type |
| final Resource unwrappedResource = unwrap(resource); |
| if ( resourceResolver.isResourceType(unwrappedResource, resourceTypes[index]) ) { |
| found = true; |
| } |
| } |
| index++; |
| } |
| if ( !found ) { |
| return false; |
| } |
| } |
| |
| // now check for path |
| // if no path is configured, we apply to all paths |
| if ( this.paths != null && this.paths.length > 0 ) { |
| final String path = processContext.getRequest().getRequestPathInfo().getResourcePath(); |
| int index = 0; |
| boolean found = false; |
| while ( !found && index < this.paths.length ) { |
| if ( this.paths[index].equals("*") ) { |
| found = true; |
| } else if ( path.startsWith(this.paths[index]) ) { |
| found = true; |
| } |
| index++; |
| } |
| if ( !found ) { |
| return false; |
| } |
| } |
| |
| // now check for selectors |
| if( this.selectors != null && this.selectors.length > 0 ) { |
| final String selectorString = processContext.getRequest().getRequestPathInfo().getSelectorString(); |
| if ( selectorString == null || "".equals(selectorString )) { |
| // selectors required but not set |
| return false; |
| } |
| |
| final Set<String> selectors = new HashSet<String>(Arrays.asList(selectorString.split("\\."))); |
| int index = 0; |
| boolean found = false; |
| while ( !found && index < this.selectors.length ) { |
| final String selector = this.selectors[index]; |
| if( selectors.contains(selector) ) { |
| found = true; |
| } |
| index++; |
| } |
| |
| if( !found ) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * Unwrap the resource and return the wrapped implementation. |
| * Copied from ResourceUtil.unwrap which is available in Sling API 2.7.0 and up. |
| * @param rsrc The resource to unwrap |
| * @return The unwrapped resource |
| */ |
| private static Resource unwrap(final Resource rsrc) { |
| Resource result = rsrc; |
| while (result instanceof ResourceWrapper) { |
| result = ((ResourceWrapper)result).getResource(); |
| } |
| return result; |
| } |
| |
| /** |
| * The configuration for the generator. |
| */ |
| public ProcessingComponentConfiguration getGeneratorConfiguration() { |
| return this.generatorConfiguration; |
| } |
| |
| /** |
| * The configuration for the serializer. |
| */ |
| public ProcessingComponentConfiguration getSerializerConfiguration() { |
| return this.serializerConfiguration; |
| } |
| |
| /** |
| * The configuration for the transformers. |
| */ |
| public ProcessingComponentConfiguration[] getTransformerConfigurations() { |
| return this.transformerConfigurations; |
| } |
| |
| /** |
| * Is this a pipeline? |
| */ |
| public boolean isPipeline() { |
| return this.isPipeline; |
| } |
| |
| /** |
| * Is this component active? |
| */ |
| public boolean isActive() { |
| return this.isValid & this.isActive; |
| } |
| |
| |
| /** |
| * @see org.apache.sling.rewriter.ProcessorConfiguration#getConfiguration() |
| */ |
| public Map<String, Object> getConfiguration() { |
| if ( this.isPipeline ) { |
| return ProcessingComponentConfigurationImpl.EMPTY_CONFIG; |
| } |
| return this.processorConfig.getConfiguration(); |
| } |
| |
| /** |
| * @see org.apache.sling.rewriter.ProcessorConfiguration#getType() |
| */ |
| public String getType() { |
| if ( this.isPipeline ) { |
| return "{pipeline}"; |
| } |
| return this.processorConfig.getType(); |
| } |
| |
| @Override |
| public String toString() { |
| return this.descString; |
| } |
| } |