001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: *
017: */
018: package org.apache.lenya.cms.cocoon.transformation;
019:
020: import java.util.Collections;
021: import java.util.HashMap;
022: import java.util.HashSet;
023: import java.util.Iterator;
024: import java.util.Map;
025: import java.util.Set;
026:
027: import org.apache.avalon.framework.configuration.Configuration;
028: import org.apache.avalon.framework.configuration.ConfigurationException;
029: import org.apache.cocoon.transformation.AbstractSAXTransformer;
030: import org.apache.lenya.cms.linking.LinkRewriter;
031: import org.xml.sax.Attributes;
032: import org.xml.sax.SAXException;
033: import org.xml.sax.helpers.AttributesImpl;
034:
035: /**
036: * <p>
037: * This transformer processes all links which are configured using
038: * <code><transform/></code> elements:
039: * </p>
040: * <code><pre>
041: * <map:transformer ... >
042: * <transform namespace="http://www.w3.org/1999/xhtml" element="a" attribute="href"/>
043: * <transform namespace="..." ... />
044: * </map:transformer>
045: * </pre></code>
046: */
047: public abstract class AbstractLinkTransformer extends
048: AbstractSAXTransformer {
049:
050: /**
051: * Set of supported local names for quick pre-checks.
052: */
053: private Set localNames = new HashSet();
054:
055: public void configure(Configuration config)
056: throws ConfigurationException {
057: super .configure(config);
058: Configuration[] transformConfigs = config
059: .getChildren("transform");
060: for (int i = 0; i < transformConfigs.length; i++) {
061: String namespace = transformConfigs[i]
062: .getAttribute("namespace");
063: String element = transformConfigs[i]
064: .getAttribute("element");
065: String attribute = transformConfigs[i]
066: .getAttribute("attribute");
067: AttributeConfiguration attrConfig = new AttributeConfiguration(
068: namespace, element, attribute);
069: String key = getCacheKey(namespace, element);
070: Set configs = (Set) this .namespaceLocalname2configSet
071: .get(key);
072: if (configs == null) {
073: configs = new HashSet();
074: this .localNames.add(element);
075: this .namespaceLocalname2configSet.put(key, configs);
076: }
077: configs.add(attrConfig);
078: }
079: }
080:
081: /**
082: * Declaration of an attribute which should be transformed.
083: */
084: public static class AttributeConfiguration {
085:
086: protected final String element;
087: protected final String namespace;
088: protected final String attribute;
089:
090: /**
091: * @param namespace The namespace of the element.
092: * @param element The local name of the element.
093: * @param attribute The name of the attribute to transform.
094: */
095: public AttributeConfiguration(String namespace, String element,
096: String attribute) {
097: this .namespace = namespace;
098: this .element = element;
099: this .attribute = attribute;
100: }
101:
102: /**
103: * @param uri The namespace URI.
104: * @param name The local name.
105: * @param attrs The attributes.
106: * @return If this configuration matches the parameters.
107: */
108: public boolean matches(String uri, String name, Attributes attrs) {
109: return this .namespace.equals(uri)
110: && this .element.equals(name)
111: && attrs.getValue(this .attribute) != null;
112: }
113:
114: }
115:
116: /**
117: * @param namespace The namespace URI.
118: * @param localName The local name.
119: * @param attrs The attributes.
120: * @return A set of {@link AttributeConfiguration} objects.
121: */
122: protected Set getMatchingConfigurations(String namespace,
123: String localName, Attributes attrs) {
124:
125: // pre-check for performance reasons
126: if (!existsMatchingConfiguration(namespace, localName)) {
127: return Collections.EMPTY_SET;
128: }
129:
130: String key = getCacheKey(namespace, localName);
131:
132: // don't initialize yet for performance reasons
133: Set configs = null;
134: Set allConfigs = (Set) this .namespaceLocalname2configSet
135: .get(key);
136: for (Iterator i = allConfigs.iterator(); i.hasNext();) {
137: AttributeConfiguration config = (AttributeConfiguration) i
138: .next();
139: if (config.matches(namespace, localName, attrs)) {
140: if (configs == null) {
141: configs = new HashSet();
142: }
143: configs.add(config);
144: }
145: }
146: if (configs == null) {
147: configs = Collections.EMPTY_SET;
148: }
149: return configs;
150: }
151:
152: /**
153: * Cache to improve performance.
154: */
155: private Map namespaceLocalname2configSet = new HashMap();
156:
157: protected boolean existsMatchingConfiguration(String namespace,
158: String localName) {
159: // quick pre-check
160: if (!this .localNames.contains(localName)) {
161: return false;
162: }
163:
164: // more expensive check
165: String key = getCacheKey(namespace, localName);
166: return this .namespaceLocalname2configSet.containsKey(key);
167: }
168:
169: protected String getCacheKey(String namespace, String localName) {
170: return namespace + " " + localName;
171: }
172:
173: protected String indent = "";
174: protected boolean ignoreLinkElement = false;
175:
176: /**
177: * @see org.xml.sax.ContentHandler#startElement(java.lang.String,
178: * java.lang.String, java.lang.String, org.xml.sax.Attributes)
179: */
180: public void startElement(String uri, String name, String qname,
181: Attributes attrs) throws SAXException {
182:
183: if (getLogger().isDebugEnabled()) {
184: getLogger().debug(
185: this .indent + "<" + qname + "> (ignoreAElement = "
186: + this .ignoreLinkElement + ")");
187: this .indent += " ";
188: }
189:
190: AttributesImpl newAttrs = null;
191:
192: Set configs = getMatchingConfigurations(uri, name, attrs);
193:
194: if (!configs.isEmpty()) {
195:
196: this .ignoreLinkElement = false;
197:
198: for (Iterator i = configs.iterator(); i.hasNext();) {
199: AttributeConfiguration config = (AttributeConfiguration) i
200: .next();
201: String linkUrl = attrs.getValue(config.attribute);
202: try {
203: if (getLogger().isDebugEnabled()) {
204: getLogger().debug(
205: this .indent + "link URL: [" + linkUrl
206: + "]");
207: }
208: newAttrs = new AttributesImpl(attrs);
209: handleLink(linkUrl, config, newAttrs);
210: } catch (final Exception e) {
211: getLogger().error("startElement failed: ", e);
212: throw new SAXException(e);
213: }
214: }
215: }
216:
217: if (getLogger().isDebugEnabled()) {
218: getLogger().debug(
219: this .indent + "ignoreAElement: "
220: + this .ignoreLinkElement);
221: }
222:
223: if (!(!configs.isEmpty() && this .ignoreLinkElement)) {
224: if (newAttrs != null) {
225: attrs = newAttrs;
226: }
227: super .startElement(uri, name, qname, attrs);
228: if (getLogger().isDebugEnabled()) {
229: getLogger().debug(this .indent + "<" + qname + "> sent");
230: }
231: }
232: }
233:
234: /**
235: * Handle a link in the source SAX stream.
236: * @param linkUrl The link URL.
237: * @param config The attribute configuration which matched the link.
238: * @param newAttrs The new attributes which will be added to the result
239: * element.
240: * @throws Exception if an error occurs.
241: */
242: protected void handleLink(String linkUrl,
243: AttributeConfiguration config, AttributesImpl newAttrs)
244: throws Exception {
245: if (getLinkRewriter().matches(linkUrl)) {
246: setAttribute(newAttrs, config.attribute, getLinkRewriter()
247: .rewrite(linkUrl));
248: }
249: }
250:
251: /**
252: * @see org.xml.sax.ContentHandler#endElement(java.lang.String,
253: * java.lang.String, java.lang.String)
254: */
255: public void endElement(String uri, String name, String qname)
256: throws SAXException {
257: if (getLogger().isDebugEnabled()) {
258: this .indent = this .indent.substring(2);
259: getLogger().debug(this .indent + "</" + qname + ">");
260: }
261: if (existsMatchingConfiguration(uri, name)
262: && this .ignoreLinkElement) {
263: this .ignoreLinkElement = false;
264: } else {
265: if (getLogger().isDebugEnabled()) {
266: getLogger()
267: .debug(this .indent + "</" + qname + "> sent");
268: }
269: super .endElement(uri, name, qname);
270: }
271: }
272:
273: /**
274: * Sets the value of a certain attribute.
275: *
276: * @param attr The attributes.
277: * @param name The attribute name.
278: * @param value The value.
279: * @throws IllegalArgumentException if the href attribute is not contained
280: * in this attributes.
281: */
282: protected void setAttribute(AttributesImpl attr, String name,
283: String value) {
284: int position = attr.getIndex(name);
285: if (position == -1) {
286: throw new IllegalArgumentException("The attribute [" + name
287: + "] is not available!");
288: }
289: attr.setValue(position, value);
290: }
291:
292: /**
293: * @return The link rewriter used by this transformer.
294: */
295: protected abstract LinkRewriter getLinkRewriter();
296:
297: }
|