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.bean;
018:
019: import java.util.TreeMap;
020:
021: import org.apache.cocoon.Constants;
022: import org.apache.cocoon.util.MIMEUtils;
023: import org.apache.cocoon.util.NetUtils;
024: import org.apache.cocoon.ProcessingException;
025:
026: /**
027: * A Target is a single page for generation. It encapsulates the URI
028: * arithmetic required to transform the URI of the page to be generated
029: * (the source URI) into the URI to which the resulting page should be
030: * written (the destination URI).
031: *
032: * @author <a href="mailto:uv@upaya.co.uk">Upayavira</a>
033: * @version CVS $Id: Target.java 433543 2006-08-22 06:22:54Z crossley $
034: */
035: public class Target {
036: // Defult type is append
037: private static final String APPEND_TYPE = "append";
038: private static final String REPLACE_TYPE = "replace";
039: private static final String INSERT_TYPE = "insert";
040:
041: private final String type;
042: private final String root;
043: private final String sourceURI;
044: private final String destURI;
045: private final String deparameterizedSourceURI;
046: private final TreeMap parameters;
047:
048: private String parentURI = null;
049: private String originalURI = null;
050: private String mimeType = null;
051: private String defaultFilename = Constants.INDEX_URI;
052: private String finalDestinationURI = null;
053: private String extension = null;
054:
055: private boolean followLinks;
056: private boolean confirmExtension;
057: private String logger;
058:
059: private transient int _hashCode;
060: private transient String _toString;
061:
062: public Target(String type, String root, String sourceURI,
063: String destURI) throws IllegalArgumentException {
064: this .type = type;
065: this .root = root;
066:
067: if (destURI == null || destURI.length() == 0) {
068: throw new IllegalArgumentException(
069: "You must specify a destination directory when defining a target");
070: }
071: if (!destURI.endsWith("/")) {
072: destURI += "/";
073: }
074: this .destURI = destURI;
075:
076: this .parameters = new TreeMap();
077:
078: // Normalize sourceURI, and make sure that parameters is always in the same order
079: sourceURI = NetUtils.normalize(root + sourceURI);
080: this .deparameterizedSourceURI = NetUtils.deparameterize(
081: sourceURI, this .parameters);
082: this .sourceURI = NetUtils.parameterize(
083: this .deparameterizedSourceURI, this .parameters);
084: }
085:
086: public Target(String type, String sourceURI, String destURI)
087: throws IllegalArgumentException {
088: this (type, "", sourceURI, destURI);
089: }
090:
091: public Target(String sourceURI, String destURI)
092: throws IllegalArgumentException {
093: this (APPEND_TYPE, "", sourceURI, destURI);
094: }
095:
096: public Target getDerivedTarget(String originalLinkURI)
097: throws IllegalArgumentException {
098:
099: String linkURI = originalLinkURI;
100: // Fix relative links starting with "?"
101: if (linkURI.startsWith("?")) {
102: linkURI = this .getPageURI() + linkURI;
103: }
104: linkURI = NetUtils.normalize(NetUtils.absolutize(
105: this .getPath(), linkURI));
106:
107: // Ignore pages outside the root folder
108: if (!linkURI.startsWith(this .root)) {
109: return null;
110: }
111: linkURI = linkURI.substring(root.length());
112:
113: Target target = new Target(this .type, this .root, linkURI,
114: this .destURI);
115: target.setOriginalURI(originalLinkURI);
116: target.setParentURI(this .sourceURI);
117: target.setConfirmExtension(this .confirmExtension);
118: target.setFollowLinks(this .followLinks);
119: target.setDefaultFilename(this .defaultFilename);
120: target.setLogger(this .logger);
121: return target;
122: }
123:
124: /**
125: * Sets the original URI. This is used to record the URI that
126: * caused the creation of this Target, for example as a link
127: * in another page. It is needed for doing link translation, as
128: * this is the URI that must be replaced by the translated one.
129: */
130: public void setOriginalURI(String uri) {
131: this .originalURI = uri;
132: }
133:
134: /**
135: * Sets the URI of the page that contained the link to this
136: * URI. Used for reporting purposes.
137: */
138: public void setParentURI(String uri) {
139: this .parentURI = uri;
140: }
141:
142: /**
143: * Sets the mime type for the resource referenced by this target.
144: * If a mime type is specified, the file extension of the
145: * destination URI will be checked to see that it matches the
146: * default extension for the specified mime type. If it doesn't,
147: * the default extension will be appended to the destination URI.
148: *
149: * This URI change will be taken into account in pages that link
150: * to the current page.
151: *
152: * If the mime type is not specified (and thus null), no extension
153: * checking will take place.
154: */
155: public void setMimeType(String mimeType) {
156: this .mimeType = mimeType;
157: this .finalDestinationURI = null;
158: }
159:
160: /**
161: * Sets a file extension to be appended to the end of the destination
162: * URI. The main use of this is to create broken link error files that
163: * stand out, within the file structure of the generated site, by, for
164: * example, adding '.error' to the end of the filename.
165: */
166: public void setExtraExtension(String extension) {
167: this .extension = extension;
168: this .finalDestinationURI = null;
169: }
170:
171: /**
172: * Sets the default filename. This filename is appended to URIs
173: * that refer to a directory, i.e. end with a slash, as resources
174: * referred to by such a URI cannot be written to a file system
175: * without a filename.
176: *
177: * This URI change will be taken into account in pages that link
178: * to the current page.
179: *
180: * If no default is specified, the Cocoon constants value will
181: * be used.
182: */
183: public void setDefaultFilename(String filename) {
184: this .defaultFilename = filename;
185: }
186:
187: /**
188: * Gets the filename from the source URI, without the path.
189: * This is used to fill out relative URIs that have
190: * parameters but no filename such as ?page=123
191: */
192: public String getPageURI() {
193: String pageURI = this .getSourceURI();
194: if (pageURI.indexOf("/") != -1) {
195: pageURI = pageURI.substring(pageURI.lastIndexOf("/") + 1);
196: if (pageURI.length() == 0) {
197: pageURI = "./";
198: }
199: }
200: return pageURI;
201: }
202:
203: /**
204: * Gets the path from the source URI, without the filename.
205: * This is used when absolutizing/relativizing link URIs.
206: */
207: public String getPath() {
208: return NetUtils.getPath(this .getSourceURI());
209: }
210:
211: /**
212: * Gets the file extension for the source URI
213: */
214: public String getExtension() {
215: return NetUtils.getExtension(this .getSourceURI());
216: }
217:
218: /**
219: * Gets the parent URI (the URI of the page that contained
220: * a link to this URI). null is returned if this page was
221: * not referred to in a link.
222: */
223: public String getParentURI() {
224: return this .parentURI;
225: }
226:
227: /**
228: * Calculates the destination URI - the URI to which the generated
229: * page should be written. This will be a URI that, when resolved
230: * by a SourceResolver, will return a modifiableSource.
231: *
232: * This calculation is only done once per target. It is therefore
233: * necessary to ensure that the mime type has been set (if required)
234: * before this method is called.
235: */
236: public String getDestinationURI() throws ProcessingException {
237:
238: if (this .finalDestinationURI == null) {
239:
240: String actualSourceURI = this .sourceURI;
241: if (!actualSourceURI.startsWith(root)) {
242: throw new ProcessingException(
243: "Derived target does not share same root: "
244: + actualSourceURI);
245: }
246: actualSourceURI = actualSourceURI.substring(root.length());
247: actualSourceURI = mangle(actualSourceURI);
248:
249: String destinationURI;
250: if (APPEND_TYPE.equals(this .type)) {
251: destinationURI = destURI + actualSourceURI;
252: } else if (REPLACE_TYPE.equals(this .type)) {
253: destinationURI = destURI;
254: } else if (INSERT_TYPE.equals(this .type)) {
255: int starPos = destURI.indexOf("*");
256: if (starPos == -1) {
257: throw new ProcessingException(
258: "Missing * in replace mapper uri");
259: } else if (starPos == destURI.length() - 1) {
260: destinationURI = destURI.substring(0, starPos)
261: + actualSourceURI;
262: } else {
263: destinationURI = destURI.substring(0, starPos)
264: + actualSourceURI
265: + destURI.substring(starPos + 1);
266: }
267: } else {
268: throw new ProcessingException("Unknown mapper type: "
269: + this .type);
270: }
271: if (mimeType != null) {
272: final String ext = NetUtils
273: .getExtension(destinationURI);
274: final String defaultExt = MIMEUtils
275: .getDefaultExtension(mimeType);
276: if (defaultExt != null) {
277: if ((ext == null) || (!ext.equals(defaultExt))) {
278: destinationURI += defaultExt;
279: }
280: }
281: }
282: if (this .extension != null) {
283: destinationURI += this .extension;
284: }
285: this .finalDestinationURI = destinationURI;
286: }
287: return this .finalDestinationURI;
288: }
289:
290: /**
291: * Gets a translated version of a link, ready for insertion
292: * into another page as a link. This link needs to be
293: * relative to the original page.
294: */
295: public String getTranslatedURI(String path)
296: throws ProcessingException {
297:
298: String actualSourceURI = this .sourceURI;
299: if (!actualSourceURI.startsWith(root)) {
300: return actualSourceURI;
301: }
302: actualSourceURI = mangle(actualSourceURI);
303:
304: if (mimeType != null) {
305: final String ext = NetUtils.getExtension(actualSourceURI);
306: final String defaultExt = MIMEUtils
307: .getDefaultExtension(mimeType);
308: if (defaultExt != null) {
309: if ((ext == null) || (!ext.equals(defaultExt))) {
310: actualSourceURI += defaultExt;
311: }
312: }
313: }
314: return NetUtils.relativize(path, actualSourceURI);
315: }
316:
317: /**
318: *
319: * @return destination URI after all authentication details have been
320: * removed
321: */
322: public String getAuthlessDestURI() throws ProcessingException {
323: return NetUtils.removeAuthorisation(this .getDestinationURI());
324: }
325:
326: /**
327: * Gets the original URI used to create this Target.
328: * This URI is completely unprocessed.
329: */
330: public String getOriginalSourceURI() {
331: return this .originalURI;
332: }
333:
334: /**
335: * Gets the source URI for this target, after
336: * the URI has been 'prepared' by normalisation,
337: * absolutization and deparameterization followed
338: * by reparameterization. This final step is to
339: * ensure that all parameters appear in a consistent
340: * order. For example page?a=1&b=2 and page?b=2&a=1
341: * should be considered the same resource, and thus
342: * have the same sourceURI.
343: */
344: public String getSourceURI() {
345: return this .sourceURI;
346: }
347:
348: /**
349: * Gets the source URI for this target, with
350: * parameters removed. This is the URI that is
351: * to be passed to Cocoon in order to generate
352: * the page.
353: */
354: public String getDeparameterizedSourceURI() {
355: return this .deparameterizedSourceURI;
356: }
357:
358: /**
359: * Gets the parameters that have been removed from
360: * the URI. These need to be passed to Cocoon when
361: * generating a page.
362: */
363: public TreeMap getParameters() {
364: return this .parameters;
365: }
366:
367: /**
368: * Mangle a URI.
369: *
370: * @param uri a URI to mangle
371: * @return a mangled URI
372: */
373: private String mangle(String uri) {
374: if (uri.length() == 0 || uri.charAt(uri.length() - 1) == '/') {
375: uri += defaultFilename;
376: }
377: uri = uri.replace('"', '\'');
378: uri = uri.replace('?', '_');
379: uri = uri.replace(':', '_');
380:
381: return uri;
382: }
383:
384: public boolean equals(Object o) {
385: return (o instanceof Target) && o.toString().equals(toString());
386: }
387:
388: public int hashCode() {
389: if (_hashCode == 0) {
390: return _hashCode = toString().hashCode();
391: }
392: return _hashCode;
393: }
394:
395: public String toString() {
396: if (_toString == null) {
397: return _toString = "<" + type + "|" + root + "|"
398: + sourceURI + "|" + destURI + ">";
399: }
400: return _toString;
401: }
402:
403: /**
404: * @return boolean
405: */
406: public boolean confirmExtensions() {
407: return confirmExtension;
408: }
409:
410: public boolean followLinks() {
411: return followLinks;
412: }
413:
414: public String getLogger() {
415: return logger;
416: }
417:
418: public void setConfirmExtension(boolean b) {
419: confirmExtension = b;
420: }
421:
422: public void setFollowLinks(boolean b) {
423: followLinks = b;
424: }
425:
426: public void setLogger(String string) {
427: logger = string;
428: }
429: }
|