/*
* #%L
* ACS AEM Commons Bundle
* %%
* Copyright (C) 2013 Adobe
* %%
* Licensed 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.
* #L%
*/
package com.adobe.acs.commons.rewriter.impl;
import java.util.Dictionary;
import java.util.Map;
import org.apache.commons.lang.ArrayUtils;
import org.apache.felix.scr.annotations.Activate;
import org.apache.felix.scr.annotations.Component;
import org.apache.felix.scr.annotations.ConfigurationPolicy;
import org.apache.felix.scr.annotations.Properties;
import org.apache.felix.scr.annotations.Property;
import org.apache.felix.scr.annotations.PropertyUnbounded;
import org.apache.felix.scr.annotations.Service;
import org.apache.sling.commons.osgi.PropertiesUtil;
import org.apache.sling.rewriter.Transformer;
import org.apache.sling.rewriter.TransformerFactory;
import org.osgi.service.component.ComponentContext;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.AttributesImpl;
import com.adobe.acs.commons.rewriter.AbstractTransformer;
import com.adobe.acs.commons.util.ParameterUtil;
/**
* Rewriter pipeline component which rewrites static references.
*
*/
@Component(
label = "ACS AEM Commons - Static Reference Rewriter",
description = "Rewriter pipeline component which rewrites host name on static references " +
"for cookie-less domain support",
metatype = true, configurationFactory = true, policy = ConfigurationPolicy.REQUIRE)
@Service
@Properties({
@Property(
name = "pipeline.type", label = "Rewriter Pipeline Type",
description = "Type identifier to be referenced in rewriter pipeline configuration."),
@Property(
name = "webconsole.configurationFactory.nameHint",
value = "Pipeline: {pipeline.type}")
})
public final class StaticReferenceRewriteTransformerFactory implements TransformerFactory {
public final class StaticReferenceRewriteTransformer extends AbstractTransformer {
public void startElement(String namespaceURI, String localName, String qName, Attributes atts)
throws SAXException {
getContentHandler().startElement(namespaceURI, localName, qName, rebuildAttributes(localName, atts));
}
}
private static final String ATTR_CLASS = "class";
private static final String CLASS_NOSTATIC = "nostatic";
private static final String[] DEFAULT_ATTRIBUTES = new String[] { "img:src", "link:href", "script:src" };
private static final int DEFAULT_HOST_COUNT = 1;
@Property(label = "Rewrite Attributes", description = "List of element/attribute pairs to rewrite", value = {
"img:src", "link:href", "script:src" })
private static final String PROP_ATTRIBUTES = "attributes";
@Property(intValue = DEFAULT_HOST_COUNT, label = "Static Host Count",
description = "Number of static hosts available.")
private static final String PROP_HOST_COUNT = "host.count";
@Property(label = "Static Host Pattern", description = "Pattern for generating static host domain names. "
+ "'{}' will be replaced with the host number. If more than one is provided, the host count is ignored.", unbounded = PropertyUnbounded.ARRAY)
private static final String PROP_HOST_NAME_PATTERN = "host.pattern";
@Property(unbounded = PropertyUnbounded.ARRAY, label = "Path Prefixes",
description = "Path prefixes to rewrite.")
private static final String PROP_PREFIXES = "prefixes";
private Map<String, String[]> attributes;
private String[] prefixes;
private int staticHostCount;
private String[] staticHostPattern;
public Transformer createTransformer() {
return new StaticReferenceRewriteTransformer();
}
private static String getShardValue(final String filePath, final int shardCount, final ShardNameProvider sharder) {
int result = 1;
if (shardCount > 1) {
final int fileHash = ((filePath.hashCode() & Integer.MAX_VALUE) % shardCount) + 1;
String hostNumberString = Integer.toString(fileHash);
if (hostNumberString.length() >= 2) {
// get the 2nd digit as the 1st digit will not contain "0"
Character c = hostNumberString.charAt(1);
hostNumberString = c.toString();
// If there are more than 10 hosts, convert it back to base10
// so we do not have alpha
hostNumberString = Integer.toString(Integer.parseInt(hostNumberString, shardCount));
result = Integer.parseInt(hostNumberString) + 1;
} else {
result = fileHash;
}
}
return sharder.lookup(result);
}
private String prependHostName(String value) {
if (staticHostPattern != null && staticHostPattern.length > 0) {
final String host;
if (staticHostPattern.length == 1) {
final String hostNum = getShardValue(value, staticHostCount, toStringShardNameProvider);
host = staticHostPattern[0].replace("{}", hostNum);
} else {
host = getShardValue(value, staticHostPattern.length, lookupShardNameProvider);
}
return String.format("//%s%s", host, value);
} else {
return value;
}
}
private Attributes rebuildAttributes(final String elementName, final Attributes attrs) {
if (attributes.containsKey(elementName)) {
final String[] modifyableAttributes = attributes.get(elementName);
// clone the attributes
final AttributesImpl newAttrs = new AttributesImpl(attrs);
final int len = newAttrs.getLength();
// first - check for the nostatic class
boolean rewriteStatic = true;
for (int i = 0; i < len; i++) {
final String attrName = newAttrs.getLocalName(i);
if (ATTR_CLASS.equals(attrName)) {
String attrValue = newAttrs.getValue(i);
if (attrValue.contains(CLASS_NOSTATIC)) {
rewriteStatic = false;
}
}
}
if (rewriteStatic) {
for (int i = 0; i < len; i++) {
final String attrName = newAttrs.getLocalName(i);
if (ArrayUtils.contains(modifyableAttributes, attrName)) {
final String attrValue = newAttrs.getValue(i);
for (String prefix : prefixes) {
if (attrValue.startsWith(prefix)) {
newAttrs.setValue(i, prependHostName(attrValue));
}
}
}
}
}
return newAttrs;
} else {
return attrs;
}
}
@Activate
protected void activate(final ComponentContext componentContext) {
final Dictionary<?, ?> properties = componentContext.getProperties();
final String[] attrProp = PropertiesUtil
.toStringArray(properties.get(PROP_ATTRIBUTES), DEFAULT_ATTRIBUTES);
this.attributes = ParameterUtil.toMap(attrProp, ":", ",");
this.prefixes = PropertiesUtil.toStringArray(properties.get(PROP_PREFIXES), new String[0]);
this.staticHostPattern = PropertiesUtil.toStringArray(properties.get(PROP_HOST_NAME_PATTERN), null);
this.staticHostCount = PropertiesUtil.toInteger(properties.get(PROP_HOST_COUNT), DEFAULT_HOST_COUNT);
}
private static interface ShardNameProvider {
String lookup(int idx);
}
private static final ShardNameProvider toStringShardNameProvider = new ShardNameProvider() {
@Override
public String lookup(int idx) {
return Integer.toString(idx);
}
};
private ShardNameProvider lookupShardNameProvider = new ShardNameProvider() {
@Override
public String lookup(int idx) {
return staticHostPattern[idx - 1];
}
};
}