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.io.IOException;
021: import java.net.MalformedURLException;
022: import java.util.Map;
023: import java.util.StringTokenizer;
024:
025: import org.apache.avalon.framework.activity.Disposable;
026: import org.apache.avalon.framework.parameters.Parameters;
027: import org.apache.cocoon.ProcessingException;
028: import org.apache.cocoon.environment.ObjectModelHelper;
029: import org.apache.cocoon.environment.Request;
030: import org.apache.cocoon.environment.SourceResolver;
031: import org.apache.lenya.ac.AccessControlException;
032: import org.apache.lenya.cms.linking.Link;
033: import org.apache.lenya.cms.linking.LinkResolver;
034: import org.apache.lenya.cms.linking.LinkRewriter;
035: import org.apache.lenya.cms.linking.LinkTarget;
036: import org.apache.lenya.cms.publication.Document;
037: import org.apache.lenya.cms.publication.DocumentFactory;
038: import org.apache.lenya.cms.publication.DocumentUtil;
039: import org.apache.lenya.cms.publication.Publication;
040: import org.apache.lenya.cms.publication.URLInformation;
041: import org.apache.lenya.cms.repository.RepositoryUtil;
042: import org.apache.lenya.cms.repository.Session;
043: import org.apache.lenya.util.Query;
044: import org.apache.lenya.util.ServletHelper;
045: import org.xml.sax.SAXException;
046: import org.xml.sax.helpers.AttributesImpl;
047:
048: /**
049: * <p>
050: * UUID to URL transformer.
051: * </p>
052: *
053: * <p>
054: * This transformer is applied to an XHMTL document. It processes all links following the
055: * {@link LinkResolver} syntax which are configured using <code><transform/></code> elements
056: * (see below).
057: * </p>
058: * <p>
059: * These links are resolved using the following rules:
060: * </p>
061: * <ul>
062: * <li>The current area (obtained from the page envelope) is used.</li>
063: * <li>A URL prefix is added depending on the proxy configuration of the publication.</li>
064: * <li>If the target document does not exist and is in the authoring area, the href attribute is
065: * removed and a class="brokenlink" attribute is added to the <code><a/></code> element.</li>
066: * <li>If the target document does not exist and is in the live area, the <code><a/></code>
067: * element is removed to disable the link.</li>
068: * </ul>
069: *
070: * <p>
071: * You can add the query parameter <code>uuid2url.extension</code> to <code>lenya-document:</code>
072: * URLs to set a specific link extension.
073: * </p>
074: * <p>
075: * The resulting URLs are absolute web application URLs (without the servlet context path).
076: * </p>
077: *
078: * $Id: LinkRewritingTransformer.java,v 1.7 2004/03/16 11:12:16 gregor
079: */
080: public class UuidToUrlTransformer extends AbstractLinkTransformer
081: implements Disposable {
082:
083: protected static final String BROKEN_ATTRIB = "class";
084: protected static final String BROKEN_VALUE = "brokenlink";
085: protected static final String EXTENSION_PARAM = "uuid2url.extension";
086:
087: private String currentUrl;
088:
089: private DocumentFactory factory;
090: private LinkResolver linkResolver;
091: private Document currentDoc;
092:
093: /**
094: * @see org.apache.cocoon.sitemap.SitemapModelComponent#setup(org.apache.cocoon.environment.SourceResolver,
095: * java.util.Map, java.lang.String, org.apache.avalon.framework.parameters.Parameters)
096: */
097: public void setup(SourceResolver _resolver, Map _objectModel,
098: String _source, Parameters _parameters)
099: throws ProcessingException, SAXException, IOException {
100: super .setup(_resolver, _objectModel, _source, _parameters);
101:
102: Request _request = ObjectModelHelper.getRequest(_objectModel);
103:
104: try {
105: Session session = RepositoryUtil.getSession(this .manager,
106: _request);
107: this .factory = DocumentUtil.createDocumentFactory(
108: this .manager, session);
109: this .currentUrl = ServletHelper.getWebappURI(_request);
110: if (this .factory.isDocument(this .currentUrl)) {
111: this .currentDoc = this .factory
112: .getFromURL(this .currentUrl);
113: }
114: this .linkResolver = (LinkResolver) this .manager
115: .lookup(LinkResolver.ROLE);
116: } catch (final Exception e) {
117: throw new ProcessingException(e);
118: }
119: }
120:
121: protected void handleLink(String linkUrl,
122: AttributeConfiguration config, AttributesImpl newAttrs)
123: throws Exception {
124:
125: URLInformation info = new URLInformation(this .currentUrl);
126: if (linkUrl.startsWith("lenya-document:")) {
127:
128: String anchor = null;
129: String url = null;
130:
131: int anchorIndex = linkUrl.indexOf("#");
132: if (anchorIndex > -1) {
133: url = linkUrl.substring(0, anchorIndex);
134: anchor = linkUrl.substring(anchorIndex + 1);
135: } else {
136: url = linkUrl;
137: }
138:
139: StringTokenizer tokenizer = new StringTokenizer(url, "?");
140: String linkUri = tokenizer.nextToken();
141: String queryString = null;
142: String requiredExtension = null;
143: if (tokenizer.hasMoreTokens()) {
144: queryString = tokenizer.nextToken();
145: Query query = new Query(queryString);
146: requiredExtension = query.getValue(EXTENSION_PARAM);
147: query.removeValue(EXTENSION_PARAM);
148: queryString = query.toString();
149: }
150:
151: LinkTarget target;
152: if (this .currentDoc != null) {
153: target = this .linkResolver.resolve(this .currentDoc,
154: linkUri);
155: } else {
156: Link link = getAbsoluteLink(info, linkUri);
157: target = this .linkResolver.resolve(this .factory, link
158: .getUri());
159: }
160:
161: if (target.exists() && target.getDocument().hasLink()) {
162: Document targetDocument = target.getDocument();
163: String extension = getExtension(targetDocument,
164: requiredExtension);
165: rewriteLink(newAttrs, config.attribute, targetDocument,
166: anchor, queryString, extension);
167: } else if (info.getArea()
168: .equals(Publication.AUTHORING_AREA)) {
169: markBrokenLink(newAttrs, config.attribute, linkUrl);
170: } else {
171: this .ignoreLinkElement = true;
172: }
173: } else {
174: /*
175: * This is legacy code. It rewrites links to non-document images (in resources/shared).
176: * These images shouldn't be referenced in documents since this violates the separation
177: * between content and layout.
178: */
179: String prefix = "/" + info.getPublicationId() + "/";
180: if (linkUrl.startsWith(prefix)) {
181: String pubUrl = linkUrl.substring(prefix.length());
182: StringTokenizer tokenizer = new StringTokenizer(pubUrl,
183: "/");
184: String area = tokenizer.nextToken();
185:
186: // don't rewrite /{pub}/modules/...
187: if (area.equals(Publication.AUTHORING_AREA)) {
188: String areaUrl = pubUrl.substring(area.length());
189: String newUrl = prefix + area + areaUrl;
190: setAttribute(newAttrs, config.attribute, newUrl);
191: }
192: }
193: }
194: }
195:
196: /**
197: * The link is constructed from the linkUri string. If it lacks the area or publication ID
198: * information, these are obtained from the current URL information.
199: * @param info The current URL information.
200: * @param linkUri The link URI to use.
201: * @return A link.
202: * @throws MalformedURLException if the linkUri parameter is malformed.
203: */
204: protected Link getAbsoluteLink(URLInformation info, String linkUri)
205: throws MalformedURLException {
206: Link link = new Link(linkUri);
207: if (link.getPubId() == null) {
208: link.setPubId(info.getPublicationId());
209: }
210: if (link.getArea() == null) {
211: link.setArea(info.getArea());
212: }
213: return link;
214: }
215:
216: /**
217: * Get the extension of a document. Caution: resolving the extension is expensive!
218: * @param targetDocument The document.
219: * @param requiredExtension The required extension.
220: * @return The required extension or, if it is null, the document's default extension.
221: */
222: protected String getExtension(Document targetDocument,
223: String requiredExtension) {
224: String extension = requiredExtension != null ? requiredExtension
225: : targetDocument.getExtension();
226: if (extension.length() > 0) {
227: extension = "." + extension;
228: }
229: return extension;
230: }
231:
232: /**
233: * Marks a <code><a/></code> element as broken and removes href attribute.
234: *
235: * @param newAttrs The new attributes.
236: * @param attrName The attribute name.
237: * @param brokenUrl The broken link URI.
238: * @throws AccessControlException when something went wrong.
239: */
240: protected void markBrokenLink(AttributesImpl newAttrs,
241: String attrName, String brokenUrl)
242: throws AccessControlException {
243: if (newAttrs.getIndex(BROKEN_ATTRIB) > -1)
244: newAttrs.removeAttribute(newAttrs.getIndex(BROKEN_ATTRIB));
245: if (newAttrs.getIndex("title") > -1)
246: newAttrs.removeAttribute(newAttrs.getIndex("title"));
247: if (newAttrs.getIndex(attrName) > -1)
248: newAttrs.setAttribute(newAttrs.getIndex(attrName), "",
249: attrName, attrName, "CDATA", "");
250: String warning = "Broken Link: " + brokenUrl;
251: newAttrs.addAttribute("", "title", "title", "CDATA", warning);
252: newAttrs.addAttribute("", BROKEN_ATTRIB, BROKEN_ATTRIB,
253: "CDATA", BROKEN_VALUE);
254: }
255:
256: /**
257: * Rewrites a link.
258: *
259: * @param newAttrs The new attributes.
260: * @param attributeName The name of the attribute to rewrite.
261: * @param targetDocument The target document.
262: * @param anchor The anchor (the string after the # character in the URL).
263: * @param queryString The query string without question mark.
264: * @param extension The extension to use.
265: * @throws AccessControlException when something went wrong.
266: */
267: protected void rewriteLink(AttributesImpl newAttrs,
268: String attributeName, Document targetDocument,
269: String anchor, String queryString, String extension)
270: throws AccessControlException {
271:
272: String webappUrl = targetDocument.getCanonicalWebappURL();
273:
274: int lastDotIndex = webappUrl.lastIndexOf(".");
275: if (lastDotIndex > -1) {
276: webappUrl = webappUrl.substring(0, lastDotIndex)
277: + extension;
278: }
279:
280: if (anchor != null) {
281: webappUrl += "#" + anchor;
282: }
283:
284: if (queryString != null && queryString.length() > 0) {
285: webappUrl += "?" + queryString;
286: }
287:
288: if (getLogger().isDebugEnabled()) {
289: getLogger().debug(
290: this .indent + "Rewriting URL to: [" + webappUrl
291: + "]");
292: }
293:
294: setAttribute(newAttrs, attributeName, webappUrl);
295: }
296:
297: /**
298: * Sets the value of the href attribute.
299: *
300: * @param attr The attributes.
301: * @param name The attribute name.
302: * @param value The value.
303: * @throws IllegalArgumentException if the href attribute is not contained in this attributes.
304: */
305: protected void setAttribute(AttributesImpl attr, String name,
306: String value) {
307: int position = attr.getIndex(name);
308: if (position == -1) {
309: throw new IllegalArgumentException(
310: "The href attribute is not available!");
311: }
312: attr.setValue(position, value);
313: }
314:
315: /**
316: * @see org.apache.avalon.framework.activity.Disposable#dispose()
317: */
318: public void dispose() {
319: if (this .linkResolver != null) {
320: this .manager.release(this .linkResolver);
321: }
322: }
323:
324: /**
325: * @see org.apache.avalon.excalibur.pool.Recyclable#recycle()
326: */
327: public void recycle() {
328: this .ignoreLinkElement = false;
329: this .currentUrl = null;
330: }
331:
332: protected LinkRewriter getLinkRewriter() {
333: return null;
334: }
335: }
|