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: package org.apache.cocoon.portal.transformation;
018:
019: import java.io.IOException;
020: import java.net.MalformedURLException;
021: import java.util.Map;
022: import java.util.Stack;
023:
024: import org.apache.avalon.framework.parameters.Parameters;
025: import org.apache.avalon.framework.service.ServiceException;
026: import org.apache.avalon.framework.service.ServiceManager;
027: import org.apache.avalon.framework.service.Serviceable;
028: import org.apache.cocoon.ProcessingException;
029: import org.apache.cocoon.environment.ObjectModelHelper;
030: import org.apache.cocoon.environment.SourceResolver;
031: import org.apache.cocoon.portal.Constants;
032: import org.apache.cocoon.portal.coplet.CopletInstanceData;
033: import org.apache.cocoon.transformation.AbstractTransformer;
034: import org.apache.cocoon.xml.AttributesImpl;
035: import org.xml.sax.Attributes;
036: import org.xml.sax.SAXException;
037:
038: /**
039: * This Transformer deals with tags containing links to external applications that need to be converted so
040: * that not the external application will be called directly but the request gets routed via the cocoon portal
041: * (either via proxy transformer or proxy reader).
042: * The link transformer therefore cooperates with the event link transformer.
043: * Tags that include a link for which a link event needs to be generated will be converted to
044: * <eventlink> elements.
045: * Examples:<br><br>
046: *
047: * <pre>
048: * <img src="images/logo.jpg"> will be converted to use the proxy reader:
049: * <img src="proxy-images/logo.jpg&cocoon-portal-copletid=xxx&cocoon-portal-portalname=yyy
050: * <br>
051: * <form action="/submitted.jsp"> will be converted to be processed by the event link transformer
052: * <eventlink action="/submitted.jsp" attribute="action" element="form">
053: * </pre>
054: *
055: * @author <a href="mailto:gernot.koller@rizit.at">Gernot Koller</a>
056: * @author <a href="mailto:friedrich.klenner@rzb.at">Friedrich Klenner</a>
057: *
058: * @version CVS $Id: LinkTransformer.java 433543 2006-08-22 06:22:54Z crossley $
059: */
060: public class LinkTransformer extends AbstractTransformer implements
061: Serviceable {
062:
063: /**
064: * Namespace prefix usef vor NewEventLinkTransformer-Namespace
065: */
066: public static final String NAMESPACE_PREFIX = "ev";
067:
068: /**
069: * Used for appending a request parameter containing the coplet id
070: */
071: protected String copletIdParamString;
072:
073: /**
074: * Used for appending a request parameter containing the portal name
075: */
076: protected String portalNameParamString;
077:
078: /**
079: * The coplet instance data
080: */
081: protected CopletInstanceData copletInstanceData;
082:
083: /**
084: * The html document base uri
085: */
086: protected String documentBase;
087:
088: /**
089: * Used to store elements' name between startTransformingElement and endTransformingElement.
090: */
091: protected Stack elementStack = new Stack();
092:
093: /**
094: * The avalon service manager
095: */
096: protected ServiceManager manager;
097:
098: /* (non-Javadoc)
099: * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
100: */
101: public void service(ServiceManager manager) throws ServiceException {
102: this .manager = manager;
103: }
104:
105: /** The prefix */
106: protected String prefix;
107:
108: /** Handle target self as no target? */
109: protected boolean ignoreTargetSelf;
110:
111: /**
112: * @see AbstractTransformer#setup(SourceResolver, Map, String, Parameters)
113: */
114: public void setup(SourceResolver resolver, Map objectModel,
115: String src, Parameters par) throws ProcessingException,
116: SAXException, IOException {
117: this .ignoreTargetSelf = par.getParameterAsBoolean(
118: "ignore-target-self", false);
119:
120: this .copletInstanceData = ProxyTransformer.getInstanceData(
121: this .manager, objectModel, par);
122: this .copletIdParamString = ProxyTransformer.COPLETID + "="
123: + copletInstanceData.getId();
124:
125: Map context = (Map) objectModel
126: .get(ObjectModelHelper.PARENT_CONTEXT);
127: this .portalNameParamString = ProxyTransformer.PORTALNAME + "="
128: + (String) context.get(Constants.PORTAL_NAME_KEY);
129: this .prefix = par.getParameter("prefix",
130: ProxyTransformer.PROXY_PREFIX);
131: }
132:
133: /**
134: * Recycle this component.
135: * All instance variables are set to <code>null</code>.
136: */
137: public void recycle() {
138: super .recycle();
139: this .copletInstanceData = null;
140: this .elementStack.clear();
141: this .copletIdParamString = null;
142: this .portalNameParamString = null;
143: }
144:
145: /**
146: * @see org.xml.sax.ContentHandler#startDocument()
147: */
148: public void startDocument() throws SAXException {
149: super .startDocument();
150: documentBase = (String) copletInstanceData
151: .getAttribute(ProxyTransformer.DOCUMENT_BASE);
152: super .startPrefixMapping(NAMESPACE_PREFIX,
153: NewEventLinkTransformer.NAMESPACE_URI);
154: }
155:
156: /**
157: * @see org.xml.sax.ContentHandler#endDocument()
158: */
159: public void endDocument() throws SAXException {
160: super .endPrefixMapping(NAMESPACE_PREFIX);
161: super .endDocument();
162: }
163:
164: /**
165: * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes)
166: */
167: public void startElement(String uri, String name, String raw,
168: Attributes attributes) throws SAXException {
169:
170: if ("form".equalsIgnoreCase(name)) {
171: handleTag("action", uri, name, raw, attributes, true,
172: (attributes.getIndex("target") > -1));
173: } else if ("script".equalsIgnoreCase(name)) {
174: handleTag("src", uri, name, raw, attributes, false, false);
175: } else if ("img".equalsIgnoreCase(name)) {
176: handleTag("src", uri, name, raw, attributes, false, false);
177: } else if ("link".equalsIgnoreCase(name)) {
178: handleTag("href", uri, name, raw, attributes, false, false);
179: } else if ("a".equalsIgnoreCase(name)) {
180: boolean direct;
181: final String v = attributes.getValue("target");
182: if (v == null
183: || (this .ignoreTargetSelf && v.equals("self"))) {
184: direct = false;
185: } else {
186: direct = true;
187: }
188: handleTag("href", uri, name, raw, attributes, true, direct);
189: } else if ("menu-item".equalsIgnoreCase(name)) {
190: handleTag("href", uri, name, raw, attributes, true, false);
191: } else if ("input".equalsIgnoreCase(name)) {
192: handleTag("src", uri, name, raw, attributes, false, false);
193: } else if ("applet".equalsIgnoreCase(name)) {
194: if (attributes.getIndex("codebase") > -1) {
195: handleTag("codebase", uri, name, raw, attributes,
196: false, true);
197: }
198: } else {
199: super .startElement(uri, name, raw, attributes);
200: }
201: }
202:
203: /**
204: * @see org.xml.sax.ContentHandler#endElement(String, String, String)
205: */
206: public void endElement(String uri, String name, String raw)
207: throws SAXException {
208: String elementName = null;
209:
210: if (!elementStack.empty()) {
211: elementName = (String) elementStack.peek();
212: }
213:
214: if (elementName != null && elementName.equals(name)) {
215: elementStack.pop();
216: super .endElement(NewEventLinkTransformer.NAMESPACE_URI,
217: NewEventLinkTransformer.EVENT_ELEM,
218: NAMESPACE_PREFIX + ":"
219: + NewEventLinkTransformer.EVENT_ELEM);
220: } else {
221: super .endElement(uri, name, raw);
222: }
223: }
224:
225: /**
226: * The handleTag method is responsible for preparing tags so that they can either be conveted to
227: * link events by the event link transformer or that the proxy reader is called directly.
228: * Tags with absolute links (starting with "http://", "ftp://", etc.) will not be touched.
229: * Tags which contain a target attribute will be modified to call the uri directly
230: * (no proxy reader or proxy transformer involved).
231: * Tags (like <a href="uri"&ht; or <form action="uri">, etc.) that require a link event will be converted to
232: * <eventlink> elements, so that the event link transformer can create the necessary link event
233: * and the proxy transformer will be used.
234: * Tags (like <img src="uri">) that shoud call the proxy reader will be converted to do so.
235: *
236: * Examples:<br><br>
237: *
238: * <pre>
239: * <a href="http://www.somewhere.com"> will not be converted because of absolute url
240: * <br>
241: * <img src="images/logo.jpg"> will be converted to use the proxy reader:
242: * <img src="proxy-images/logo.jpg&cocoon-portal-copletid=xxx&cocoon-portal-portalname=yyy
243: * <br>
244: * <form action="/submitted.jsp"> will be converted to use proxy transformer:
245: * <eventlink action="/submitted.jsp" attribute="action" element="form">
246: * </pre>
247: *
248: * @param attributeName Name oft the attribute containing the link to be converted
249: * @param uri Namespace URI
250: * @param elementName Name of the element (tag)
251: * @param raw Raw name of the element (including namespace prefix)
252: * @param attributes Attributes of the element
253: * @param eventLink True signals that the tag sould be converted to an event link tag.
254: * @param direct True signals that the uri should point directly to the external resource (no proxys)
255: * @throws SAXException if an invalid URL was detected.
256: */
257: public void handleTag(String attributeName, String uri,
258: String elementName, String raw, Attributes attributes,
259: boolean eventLink, boolean direct) throws SAXException {
260: String remoteURI = attributes.getValue(attributeName);
261:
262: if ((remoteURI == null) || remoteURI.startsWith("http://")
263: || remoteURI.startsWith("https://")
264: || remoteURI.startsWith("#")
265: || remoteURI.startsWith("ftp://")
266: || remoteURI.startsWith("javascript:")
267: || remoteURI.startsWith("mailto:")) {
268: super .startElement(uri, elementName, raw, attributes);
269: } else {
270: boolean evalTarget;
271: final String v = attributes.getValue("target");
272: if (v == null
273: || (this .ignoreTargetSelf && v.equals("self"))) {
274: evalTarget = false;
275: } else {
276: evalTarget = true;
277: }
278: if (evalTarget || direct) {
279: try {
280: remoteURI = ProxyTransformer.resolveURI(remoteURI,
281: documentBase);
282: eventLink = false;
283: } catch (MalformedURLException ex) {
284: throw new SAXException("Invalid URL encountered: "
285: + remoteURI, ex);
286: }
287: } else {
288: remoteURI = this .buildUrlString(remoteURI, !eventLink);
289: }
290:
291: Attributes newAttributes = modifyLinkAttribute(
292: attributeName, remoteURI, attributes);
293:
294: if (eventLink) {
295: this .startEventLinkElement(elementName, attributeName,
296: newAttributes);
297: } else {
298: super
299: .startElement(uri, elementName, raw,
300: newAttributes);
301: }
302: }
303: }
304:
305: /**
306: * Replaces the link of given attribute whith the new uri.
307: * @param attribute Name of the attribute containing the link
308: * @param remoteURI The new uri
309: * @param attributes List of attributes
310: * @return The modified List of attributes
311: */
312: protected Attributes modifyLinkAttribute(String attribute,
313: String remoteURI, Attributes attributes) {
314: AttributesImpl newAttributes = new AttributesImpl(attributes);
315:
316: int index = newAttributes.getIndex(attribute);
317: newAttributes.setValue(index, remoteURI);
318: return newAttributes;
319: }
320:
321: /**
322: * Replaces the given element with an eventlink element adding the attribute and element attribute within
323: * the SAX stream.
324: * @param element Original name of the element
325: * @param attribute Name of the attribute containing the link
326: * @param attributes Original list of attributes
327: * @throws SAXException
328: */
329: protected void startEventLinkElement(String element,
330: String attribute, Attributes attributes)
331: throws SAXException {
332: elementStack.push(element);
333: AttributesImpl eventAttributes = null;
334: if (attributes instanceof AttributesImpl) {
335: eventAttributes = (AttributesImpl) attributes;
336: } else {
337: eventAttributes = new AttributesImpl(attributes);
338: }
339: eventAttributes.addCDATAAttribute(
340: NewEventLinkTransformer.ATTRIBUTE_ATTR, attribute);
341: eventAttributes.addCDATAAttribute(
342: NewEventLinkTransformer.ELEMENT_ATTR, element);
343: eventAttributes.addCDATAAttribute("coplet",
344: this .copletInstanceData.getId());
345:
346: super .startElement(NewEventLinkTransformer.NAMESPACE_URI,
347: NewEventLinkTransformer.EVENT_ELEM, NAMESPACE_PREFIX
348: + ":" + NewEventLinkTransformer.EVENT_ELEM,
349: eventAttributes);
350: }
351:
352: /**
353: * Retrieves and stores any session token, appends proxy reader prefix and parameters if necessary
354: * @param uri the url to be converted
355: * @param applyPrefixAndPortalParams true signals that the url should be converted to call the proxy-reader
356: * @return the converted uri
357: * FIXME: anchors (#) should be treated right!
358: */
359: protected String buildUrlString(String uri,
360: boolean applyPrefixAndPortalParams) {
361: StringBuffer uriBuffer = new StringBuffer(uri.length());
362:
363: int index_semikolon = uri.indexOf(";");
364: int index_question = uri.indexOf("?");
365:
366: if ((index_semikolon > -1)) {
367: String sessionToken = uri.substring(index_semikolon + 1,
368: (index_question == -1 ? uri.length()
369: : index_question));
370: copletInstanceData.getPersistentAspectData().put(
371: ProxyTransformer.SESSIONTOKEN, sessionToken);
372: }
373:
374: if (applyPrefixAndPortalParams) {
375: uriBuffer.append(this .prefix);
376: }
377:
378: uriBuffer.append(uri);
379:
380: if (applyPrefixAndPortalParams) {
381: uriBuffer.append((index_question == -1 ? '?' : '&'));
382: uriBuffer.append(this .copletIdParamString);
383: uriBuffer.append('&');
384: uriBuffer.append(this.portalNameParamString);
385: }
386:
387: return uriBuffer.toString();
388: }
389: }
|