/* * 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.cocoon.portal.transformation; import java.io.IOException; import java.net.MalformedURLException; import java.util.Map; import java.util.Stack; import org.apache.avalon.framework.parameters.Parameters; import org.apache.avalon.framework.service.ServiceException; import org.apache.avalon.framework.service.ServiceManager; import org.apache.avalon.framework.service.Serviceable; import org.apache.cocoon.ProcessingException; import org.apache.cocoon.environment.ObjectModelHelper; import org.apache.cocoon.environment.SourceResolver; import org.apache.cocoon.portal.Constants; import org.apache.cocoon.portal.coplet.CopletInstanceData; import org.apache.cocoon.transformation.AbstractTransformer; import org.apache.cocoon.xml.AttributesImpl; import org.xml.sax.Attributes; import org.xml.sax.SAXException; /** * This Transformer deals with tags containing links to external applications that need to be converted so * that not the external application will be called directly but the request gets routed via the cocoon portal * (either via proxy transformer or proxy reader). * The link transformer therefore cooperates with the event link transformer. * Tags that include a link for which a link event needs to be generated will be converted to * <eventlink> elements. * Examples:<br><br> * * <pre> * <img src="images/logo.jpg"> will be converted to use the proxy reader: * <img src="proxy-images/logo.jpg&cocoon-portal-copletid=xxx&cocoon-portal-portalname=yyy * <br> * <form action="/submitted.jsp"> will be converted to be processed by the event link transformer * <eventlink action="/submitted.jsp" attribute="action" element="form"> * </pre> * * @author <a href="mailto:gernot.koller@rizit.at">Gernot Koller</a> * @author <a href="mailto:friedrich.klenner@rzb.at">Friedrich Klenner</a> * * @version CVS $Id$ */ public class LinkTransformer extends AbstractTransformer implements Serviceable { /** * Namespace prefix usef vor NewEventLinkTransformer-Namespace */ public static final String NAMESPACE_PREFIX = "ev"; /** * Used for appending a request parameter containing the coplet id */ protected String copletIdParamString; /** * Used for appending a request parameter containing the portal name */ protected String portalNameParamString; /** * The coplet instance data */ protected CopletInstanceData copletInstanceData; /** * The html document base uri */ protected String documentBase; /** * Used to store elements' name between startTransformingElement and endTransformingElement. */ protected Stack elementStack = new Stack(); /** * The avalon service manager */ protected ServiceManager manager; /* (non-Javadoc) * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager) */ public void service(ServiceManager manager) throws ServiceException { this.manager = manager; } /** The prefix */ protected String prefix; /** Handle target self as no target? */ protected boolean ignoreTargetSelf; /** * @see AbstractTransformer#setup(SourceResolver, Map, String, Parameters) */ public void setup(SourceResolver resolver, Map objectModel, String src, Parameters par) throws ProcessingException, SAXException, IOException { this.ignoreTargetSelf = par.getParameterAsBoolean("ignore-target-self", false); this.copletInstanceData = ProxyTransformer.getInstanceData( this.manager, objectModel, par); this.copletIdParamString = ProxyTransformer.COPLETID + "=" + copletInstanceData.getId(); Map context = (Map) objectModel.get(ObjectModelHelper.PARENT_CONTEXT); this.portalNameParamString = ProxyTransformer.PORTALNAME + "=" + (String) context.get(Constants.PORTAL_NAME_KEY); this.prefix = par.getParameter("prefix", ProxyTransformer.PROXY_PREFIX); } /** * Recycle this component. * All instance variables are set to <code>null</code>. */ public void recycle() { super.recycle(); this.copletInstanceData = null; this.elementStack.clear(); this.copletIdParamString = null; this.portalNameParamString = null; } /** * @see org.xml.sax.ContentHandler#startDocument() */ public void startDocument() throws SAXException { super.startDocument(); documentBase = (String)copletInstanceData.getAttribute(ProxyTransformer.DOCUMENT_BASE); super.startPrefixMapping(NAMESPACE_PREFIX, NewEventLinkTransformer.NAMESPACE_URI); } /** * @see org.xml.sax.ContentHandler#endDocument() */ public void endDocument() throws SAXException { super.endPrefixMapping(NAMESPACE_PREFIX); super.endDocument(); } /** * @see org.xml.sax.ContentHandler#startElement(String, String, String, Attributes) */ public void startElement(String uri, String name, String raw, Attributes attributes) throws SAXException { if ("form".equalsIgnoreCase(name)) { handleTag( "action", uri, name, raw, attributes, true, (attributes.getIndex("target") > -1)); } else if ("script".equalsIgnoreCase(name)) { handleTag("src", uri, name, raw, attributes, false, false); } else if ("img".equalsIgnoreCase(name)) { handleTag("src", uri, name, raw, attributes, false, false); } else if ("link".equalsIgnoreCase(name)) { handleTag("href", uri, name, raw, attributes, false, false); } else if ("a".equalsIgnoreCase(name)) { boolean direct; final String v = attributes.getValue("target"); if ( v == null || (this.ignoreTargetSelf && v.equals("self")) ) { direct = false; } else { direct = true; } handleTag("href", uri, name, raw, attributes, true, direct); } else if ("menu-item".equalsIgnoreCase(name)) { handleTag("href", uri, name, raw, attributes, true, false); } else if ("input".equalsIgnoreCase(name)) { handleTag("src", uri, name, raw, attributes, false, false); } else if ("applet".equalsIgnoreCase(name)) { if (attributes.getIndex("codebase") > -1) { handleTag("codebase", uri, name, raw, attributes, false, true); } } else { super.startElement(uri, name, raw, attributes); } } /** * @see org.xml.sax.ContentHandler#endElement(String, String, String) */ public void endElement(String uri, String name, String raw) throws SAXException { String elementName = null; if (!elementStack.empty()) { elementName = (String) elementStack.peek(); } if (elementName != null && elementName.equals(name)) { elementStack.pop(); super.endElement( NewEventLinkTransformer.NAMESPACE_URI, NewEventLinkTransformer.EVENT_ELEM, NAMESPACE_PREFIX + ":" + NewEventLinkTransformer.EVENT_ELEM); } else { super.endElement(uri, name, raw); } } /** * The handleTag method is responsible for preparing tags so that they can either be conveted to * link events by the event link transformer or that the proxy reader is called directly. * Tags with absolute links (starting with "http://", "ftp://", etc.) will not be touched. * Tags which contain a target attribute will be modified to call the uri directly * (no proxy reader or proxy transformer involved). * Tags (like <a href="uri"&ht; or <form action="uri">, etc.) that require a link event will be converted to * <eventlink> elements, so that the event link transformer can create the necessary link event * and the proxy transformer will be used. * Tags (like <img src="uri">) that shoud call the proxy reader will be converted to do so. * * Examples:<br><br> * * <pre> * <a href="http://www.somewhere.com"> will not be converted because of absolute url * <br> * <img src="images/logo.jpg"> will be converted to use the proxy reader: * <img src="proxy-images/logo.jpg&cocoon-portal-copletid=xxx&cocoon-portal-portalname=yyy * <br> * <form action="/submitted.jsp"> will be converted to use proxy transformer: * <eventlink action="/submitted.jsp" attribute="action" element="form"> * </pre> * * @param attributeName Name oft the attribute containing the link to be converted * @param uri Namespace URI * @param elementName Name of the element (tag) * @param raw Raw name of the element (including namespace prefix) * @param attributes Attributes of the element * @param eventLink True signals that the tag sould be converted to an event link tag. * @param direct True signals that the uri should point directly to the external resource (no proxys) * @throws SAXException if an invalid URL was detected. */ public void handleTag(String attributeName, String uri, String elementName, String raw, Attributes attributes, boolean eventLink, boolean direct) throws SAXException { String remoteURI = attributes.getValue(attributeName); if ((remoteURI == null) || remoteURI.startsWith("http://") || remoteURI.startsWith("https://") || remoteURI.startsWith("#") || remoteURI.startsWith("ftp://") || remoteURI.startsWith("javascript:") || remoteURI.startsWith("mailto:")) { super.startElement(uri, elementName, raw, attributes); } else { boolean evalTarget; final String v = attributes.getValue("target"); if ( v == null || (this.ignoreTargetSelf && v.equals("self")) ) { evalTarget = false; } else { evalTarget = true; } if (evalTarget || direct) { try { remoteURI = ProxyTransformer.resolveURI(remoteURI, documentBase); eventLink = false; } catch (MalformedURLException ex) { throw new SAXException( "Invalid URL encountered: " + remoteURI, ex); } } else { remoteURI = this.buildUrlString(remoteURI, !eventLink); } Attributes newAttributes = modifyLinkAttribute(attributeName, remoteURI, attributes); if (eventLink) { this.startEventLinkElement( elementName, attributeName, newAttributes); } else { super.startElement(uri, elementName, raw, newAttributes); } } } /** * Replaces the link of given attribute whith the new uri. * @param attribute Name of the attribute containing the link * @param remoteURI The new uri * @param attributes List of attributes * @return The modified List of attributes */ protected Attributes modifyLinkAttribute(String attribute, String remoteURI, Attributes attributes) { AttributesImpl newAttributes = new AttributesImpl(attributes); int index = newAttributes.getIndex(attribute); newAttributes.setValue(index, remoteURI); return newAttributes; } /** * Replaces the given element with an eventlink element adding the attribute and element attribute within * the SAX stream. * @param element Original name of the element * @param attribute Name of the attribute containing the link * @param attributes Original list of attributes * @throws SAXException */ protected void startEventLinkElement(String element, String attribute, Attributes attributes) throws SAXException { elementStack.push(element); AttributesImpl eventAttributes = null; if (attributes instanceof AttributesImpl) { eventAttributes = (AttributesImpl) attributes; } else { eventAttributes = new AttributesImpl(attributes); } eventAttributes.addCDATAAttribute(NewEventLinkTransformer.ATTRIBUTE_ATTR, attribute); eventAttributes.addCDATAAttribute(NewEventLinkTransformer.ELEMENT_ATTR, element); eventAttributes.addCDATAAttribute("coplet", this.copletInstanceData.getId()); super.startElement( NewEventLinkTransformer.NAMESPACE_URI, NewEventLinkTransformer.EVENT_ELEM, NAMESPACE_PREFIX + ":" + NewEventLinkTransformer.EVENT_ELEM, eventAttributes); } /** * Retrieves and stores any session token, appends proxy reader prefix and parameters if necessary * @param uri the url to be converted * @param applyPrefixAndPortalParams true signals that the url should be converted to call the proxy-reader * @return the converted uri * FIXME: anchors (#) should be treated right! */ protected String buildUrlString(String uri, boolean applyPrefixAndPortalParams) { StringBuffer uriBuffer = new StringBuffer(uri.length()); int index_semikolon = uri.indexOf(";"); int index_question = uri.indexOf("?"); if ((index_semikolon > -1)) { String sessionToken = uri.substring( index_semikolon + 1, (index_question == -1 ? uri.length() : index_question)); copletInstanceData.getPersistentAspectData().put( ProxyTransformer.SESSIONTOKEN, sessionToken); } if (applyPrefixAndPortalParams) { uriBuffer.append(this.prefix); } uriBuffer.append(uri); if (applyPrefixAndPortalParams) { uriBuffer.append((index_question == -1 ? '?' : '&')); uriBuffer.append(this.copletIdParamString); uriBuffer.append('&'); uriBuffer.append(this.portalNameParamString); } return uriBuffer.toString(); } }