View Javadoc
1   /*
2    * #%L
3    * wcm.io
4    * %%
5    * Copyright (C) 2014 wcm.io
6    * %%
7    * Licensed under the Apache License, Version 2.0 (the "License");
8    * you may not use this file except in compliance with the License.
9    * You may obtain a copy of the License at
10   *
11   *      http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * #L%
19   */
20  package io.wcm.handler.url.rewriter.impl;
21  
22  import java.net.URLDecoder;
23  import java.nio.charset.StandardCharsets;
24  
25  import org.apache.commons.lang3.StringUtils;
26  import org.apache.sling.rewriter.ProcessingComponentConfiguration;
27  import org.apache.sling.rewriter.ProcessingContext;
28  import org.apache.sling.rewriter.Transformer;
29  import org.slf4j.Logger;
30  import org.slf4j.LoggerFactory;
31  import org.xml.sax.Attributes;
32  import org.xml.sax.ContentHandler;
33  import org.xml.sax.Locator;
34  import org.xml.sax.SAXException;
35  import org.xml.sax.helpers.AttributesImpl;
36  import org.xml.sax.helpers.DefaultHandler;
37  
38  import io.wcm.handler.url.UrlHandler;
39  
40  /**
41   * HTML transformer that rewrites URLs in certain HTML element attributes.
42   */
43  class UrlExternalizerTransformer implements Transformer {
44  
45    private UrlExternalizerTransformerConfig transformerConfig;
46    private ContentHandler contentHandler = EMPTY_CONTENT_HANDLER;
47    private UrlHandler urlHandler;
48  
49    private static final Logger log = LoggerFactory.getLogger(UrlExternalizerTransformer.class.getName());
50    private static final ContentHandler EMPTY_CONTENT_HANDLER = new DefaultHandler();
51  
52    @Override
53    public void init(ProcessingContext pipelineContext, ProcessingComponentConfiguration config) {
54      log.trace("Initialize UrlExternalizerTransformer with config: {}", config.getConfiguration());
55      transformerConfig = new UrlExternalizerTransformerConfig(config.getConfiguration());
56      urlHandler = pipelineContext.getRequest().adaptTo(UrlHandler.class);
57    }
58  
59    @Override
60    public void setContentHandler(ContentHandler contentHandler) {
61      this.contentHandler = contentHandler;
62    }
63  
64    @Override
65    @SuppressWarnings("PMD.UseStringBufferForStringAppends")
66    public void startElement(String nsUri, String name, String raw, Attributes attrs) throws SAXException {
67  
68      // check if for this element an attribute for rewriting is configured
69      String rewriteAttr = transformerConfig.getElementAttributeNames().get(name);
70      if (rewriteAttr == null) {
71        log.trace("Rewrite element {}: Skip - No rewrite attribute configured.", name);
72        contentHandler.startElement(nsUri, name, raw, attrs);
73        return;
74      }
75  
76      // validate URL handler
77      if (urlHandler == null) {
78        log.warn("Rewrite element {}: Skip - Unable to get URL handler/Integrator handler instance.", name);
79        contentHandler.startElement(nsUri, name, raw, attrs);
80        return;
81      }
82  
83      // check if attribute exists
84      int attributeIndex = attrs.getIndex(rewriteAttr);
85      if (attributeIndex < 0) {
86        log.trace("Rewrite element {}: Skip - Attribute does not exist: {}", name, rewriteAttr);
87        contentHandler.startElement(nsUri, name, raw, attrs);
88        return;
89      }
90  
91      // rewrite URL
92      String url = attrs.getValue(attributeIndex);
93      if (StringUtils.isEmpty(url)) {
94        log.trace("Rewrite element {}: Skip - URL is empty.", name);
95        contentHandler.startElement(nsUri, name, raw, attrs);
96        return;
97      }
98  
99      // split off query string or fragment that may be appended to the URL
100     String urlRemainder = null;
101     int urlRemainderPos = StringUtils.indexOfAny(url, '?', '#');
102     if (urlRemainderPos >= 0) {
103       urlRemainder = url.substring(urlRemainderPos);
104       url = url.substring(0, urlRemainderPos);
105     }
106 
107     // decode URL (without URL remainder)
108     url = URLDecoder.decode(url, StandardCharsets.UTF_8);
109 
110     // externalize URL (if it is not already externalized)
111     String rewrittenUrl = urlHandler.get(url).buildExternalResourceUrl();
112     if (urlRemainder != null) {
113       if (rewrittenUrl == null) {
114         rewrittenUrl = urlRemainder;
115       }
116       else {
117         rewrittenUrl += urlRemainder;
118       }
119     }
120 
121     if (StringUtils.equals(url, rewrittenUrl)) {
122       log.debug("Rewrite element {}: Skip - URL is already externalized: {}", name, url);
123       contentHandler.startElement(nsUri, name, raw, attrs);
124       return;
125     }
126 
127     // set new attribute value
128     log.debug("Rewrite element {}: Rewrite URL {} to {}", name, url, rewrittenUrl);
129     AttributesImpl newAttrs = new AttributesImpl(attrs);
130     newAttrs.setValue(attributeIndex, rewrittenUrl);
131     contentHandler.startElement(nsUri, name, raw, newAttrs);
132   }
133 
134   @Override
135   public void setDocumentLocator(Locator locator) {
136     this.contentHandler.setDocumentLocator(locator);
137   }
138 
139   @Override
140   public void startDocument() throws SAXException {
141     this.contentHandler.startDocument();
142   }
143 
144   @Override
145   public void endDocument() throws SAXException {
146     this.contentHandler.endDocument();
147   }
148 
149   @Override
150   public void startPrefixMapping(String prefix, String uri) throws SAXException {
151     this.contentHandler.startPrefixMapping(prefix, uri);
152   }
153 
154   @Override
155   public void endPrefixMapping(String prefix) throws SAXException {
156     this.contentHandler.endPrefixMapping(prefix);
157   }
158 
159   @Override
160   public void endElement(String uri, String localName, String qName) throws SAXException {
161     this.contentHandler.endElement(uri, localName, qName);
162   }
163 
164   @Override
165   public void characters(char[] ch, int start, int length) throws SAXException {
166     this.contentHandler.characters(ch, start, length);
167   }
168 
169   @Override
170   public void ignorableWhitespace(char[] ch, int start, int length) throws SAXException {
171     this.contentHandler.ignorableWhitespace(ch, start, length);
172   }
173 
174   @Override
175   public void processingInstruction(String target, String data) throws SAXException {
176     this.contentHandler.processingInstruction(target, data);
177   }
178 
179   @Override
180   public void skippedEntity(String name) throws SAXException {
181     this.contentHandler.skippedEntity(name);
182   }
183 
184   @Override
185   public void dispose() {
186     // nothing to do
187   }
188 
189 }