001: /*
002: * File : $Source: /usr/local/cvs/opencms/src/org/opencms/relations/CmsLink.java,v $
003: * Date : $Date: 2008-02-27 12:05:42 $
004: * Version: $Revision: 1.7 $
005: *
006: * This library is part of OpenCms -
007: * the Open Source Content Management System
008: *
009: * Copyright (c) 2002 - 2008 Alkacon Software GmbH (http://www.alkacon.com)
010: *
011: * This library is free software; you can redistribute it and/or
012: * modify it under the terms of the GNU Lesser General Public
013: * License as published by the Free Software Foundation; either
014: * version 2.1 of the License, or (at your option) any later version.
015: *
016: * This library is distributed in the hope that it will be useful,
017: * but WITHOUT ANY WARRANTY; without even the implied warranty of
018: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019: * Lesser General Public License for more details.
020: *
021: * For further information about Alkacon Software GmbH, please see the
022: * company website: http://www.alkacon.com
023: *
024: * For further information about OpenCms, please see the
025: * project website: http://www.opencms.org
026: *
027: * You should have received a copy of the GNU Lesser General Public
028: * License along with this library; if not, write to the Free Software
029: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
030: */
031:
032: package org.opencms.relations;
033:
034: import org.opencms.file.CmsObject;
035: import org.opencms.file.CmsRequestContext;
036: import org.opencms.file.CmsResource;
037: import org.opencms.file.CmsResourceFilter;
038: import org.opencms.file.wrapper.CmsObjectWrapper;
039: import org.opencms.main.CmsException;
040: import org.opencms.main.CmsLog;
041: import org.opencms.main.OpenCms;
042: import org.opencms.staticexport.CmsLinkProcessor;
043: import org.opencms.util.CmsRequestUtil;
044: import org.opencms.util.CmsStringUtil;
045: import org.opencms.util.CmsUUID;
046: import org.opencms.util.CmsUriSplitter;
047:
048: import java.util.Map;
049: import java.util.Set;
050:
051: import org.apache.commons.logging.Log;
052:
053: import org.dom4j.Attribute;
054: import org.dom4j.Element;
055:
056: /**
057: * A single link entry in the link table.<p>
058: *
059: * @author Carsten Weinholz
060: * @author Michael Moossen
061: *
062: * @version $Revision: 1.7 $
063: *
064: * @since 6.0.0
065: */
066: public class CmsLink {
067:
068: /** Name of the internal attribute of the link node. */
069: public static final String ATTRIBUTE_INTERNAL = "internal";
070:
071: /** Name of the name attribute of the elements node. */
072: public static final String ATTRIBUTE_NAME = "name";
073:
074: /** Name of the type attribute of the elements node. */
075: public static final String ATTRIBUTE_TYPE = "type";
076:
077: /** Default link name. */
078: public static final String DEFAULT_NAME = "ref";
079:
080: /** Default link type. */
081: public static final CmsRelationType DEFAULT_TYPE = CmsRelationType.XML_WEAK;
082:
083: /** Name of the anchor node. */
084: public static final String NODE_ANCHOR = "anchor";
085:
086: /** Name of the query node. */
087: public static final String NODE_QUERY = "query";
088:
089: /** Name of the target node. */
090: public static final String NODE_TARGET = "target";
091:
092: /** Name of the UUID node. */
093: public static final String NODE_UUID = "uuid";
094:
095: /** Constant for the NULL link. */
096: public static final CmsLink NULL_LINK = new CmsLink();
097:
098: /** The log object for this class. */
099: private static final Log LOG = CmsLog.getLog(CmsLink.class);
100:
101: /** The anchor of the URI, if any. */
102: private String m_anchor;
103:
104: /** The XML element reference. */
105: private Element m_element;
106:
107: /** Indicates if the link is an internal link within the OpenCms VFS. */
108: private boolean m_internal;
109:
110: /** The internal name of the link. */
111: private String m_name;
112:
113: /** The parameters of the query , if any. */
114: private Map m_parameters;
115:
116: /** The query, if any. */
117: private String m_query;
118:
119: /** The site root of the (internal) link. */
120: private String m_siteRoot;
121:
122: /** The structure id of the linked resource. */
123: private CmsUUID m_structureId;
124:
125: /** The link target (destination). */
126: private String m_target;
127:
128: /** The type of the link. */
129: private CmsRelationType m_type;
130:
131: /** The raw uri. */
132: private String m_uri;
133:
134: /**
135: * Reconstructs a link object from the given XML node.<p>
136: *
137: * @param element the XML node containing the link information
138: */
139: public CmsLink(Element element) {
140:
141: m_element = element;
142: Attribute attrName = element.attribute(ATTRIBUTE_NAME);
143: if (attrName != null) {
144: m_name = attrName.getValue();
145: } else {
146: m_name = DEFAULT_NAME;
147: }
148: Attribute attrType = element.attribute(ATTRIBUTE_TYPE);
149: if (attrType != null) {
150: m_type = CmsRelationType.valueOfXml(attrType.getValue());
151: } else {
152: m_type = DEFAULT_TYPE;
153: }
154: Attribute attrInternal = element.attribute(ATTRIBUTE_INTERNAL);
155: if (attrInternal != null) {
156: m_internal = Boolean.valueOf(attrInternal.getValue())
157: .booleanValue();
158: } else {
159: m_internal = true;
160: }
161:
162: Element uuid = element.element(NODE_UUID);
163: Element target = element.element(NODE_TARGET);
164: Element anchor = element.element(NODE_ANCHOR);
165: Element query = element.element(NODE_QUERY);
166:
167: m_structureId = (uuid != null) ? new CmsUUID(uuid.getText())
168: : null;
169: m_target = (target != null) ? target.getText() : null;
170: m_anchor = (anchor != null) ? anchor.getText() : null;
171: setQuery((query != null) ? query.getText() : null);
172:
173: // update the uri from the components
174: setUri();
175: }
176:
177: /**
178: * Creates a new link object without a reference to the xml page link element.<p>
179: *
180: * @param name the internal name of this link
181: * @param type the type of this link
182: * @param structureId the structure id of the link
183: * @param uri the link uri
184: * @param internal indicates if the link is internal within OpenCms
185: */
186: public CmsLink(String name, CmsRelationType type,
187: CmsUUID structureId, String uri, boolean internal) {
188:
189: m_element = null;
190: m_name = name;
191: m_type = type;
192: m_internal = internal;
193: m_structureId = structureId;
194: m_uri = uri;
195: // update component members from the uri
196: setComponents();
197: }
198:
199: /**
200: * Creates a new link object without a reference to the xml page link element.<p>
201: *
202: * @param name the internal name of this link
203: * @param type the type of this link
204: * @param uri the link uri
205: * @param internal indicates if the link is internal within OpenCms
206: */
207: public CmsLink(String name, CmsRelationType type, String uri,
208: boolean internal) {
209:
210: this (name, type, null, uri, internal);
211: }
212:
213: /**
214: * Empty constructor for NULL constant.<p>
215: */
216: private CmsLink() {
217:
218: // empty constructor for NULL constant
219: }
220:
221: /**
222: * Checks and updates the structure id or the path of the target.<p>
223: *
224: * @param cms the cms context
225: */
226: public void checkConsistency(CmsObject cms) {
227:
228: if (!m_internal || (cms == null)) {
229: return;
230: }
231: try {
232: if (m_structureId == null) {
233: // try by path
234: throw new CmsException(Messages.get().container(
235: Messages.LOG_BROKEN_LINK_NO_ID_0));
236: }
237: // first look for the resource with the given structure id
238: CmsResource res = cms.readResource(m_structureId,
239: CmsResourceFilter.ALL);
240: if (!res.getRootPath().equals(m_target)) {
241: // update path if needed
242: if (LOG.isDebugEnabled()) {
243: LOG
244: .debug(Messages
245: .get()
246: .getBundle()
247: .key(
248: Messages.LOG_BROKEN_LINK_UPDATED_BY_ID_3,
249: m_structureId, m_target,
250: res.getRootPath()));
251: }
252: // set the new target
253: m_target = res.getRootPath();
254: setUri();
255: // update xml node
256: CmsLinkUpdateUtil.updateXml(this , m_element, true);
257: }
258: } catch (CmsException e) {
259: if (LOG.isDebugEnabled()) {
260: LOG.debug(Messages.get().getBundle().key(
261: Messages.LOG_BROKEN_LINK_BY_ID_2, m_target,
262: m_structureId), e);
263: }
264: if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_target)) {
265: // no correction is possible
266: return;
267: }
268: // go on with the resource with the given path
269: String siteRoot = cms.getRequestContext().getSiteRoot();
270: try {
271: cms.getRequestContext().setSiteRoot("");
272: // now look for the resource with the given path
273: CmsResource res = cms.readResource(m_target,
274: CmsResourceFilter.ALL);
275: if (!res.getStructureId().equals(m_structureId)) {
276: // update structure id if needed
277: if (LOG.isDebugEnabled()) {
278: LOG
279: .debug(Messages
280: .get()
281: .getBundle()
282: .key(
283: Messages.LOG_BROKEN_LINK_UPDATED_BY_NAME_3,
284: m_target,
285: m_structureId,
286: res.getStructureId()));
287: }
288: m_target = res.getRootPath(); // could change by a translation rule
289: m_structureId = res.getStructureId();
290: CmsLinkUpdateUtil.updateXml(this , m_element, true);
291: }
292: } catch (CmsException e1) {
293: // no correction was possible
294: if (LOG.isDebugEnabled()) {
295: LOG.debug(Messages.get().getBundle().key(
296: Messages.LOG_BROKEN_LINK_BY_NAME_1,
297: m_target), e1);
298: }
299: m_structureId = null;
300: } finally {
301: cms.getRequestContext().setSiteRoot(siteRoot);
302: }
303: }
304: }
305:
306: /**
307: * A link is considered equal if the link target and the link type is equal.<p>
308: *
309: * @see java.lang.Object#equals(java.lang.Object)
310: */
311: public boolean equals(Object obj) {
312:
313: if (obj == this ) {
314: return true;
315: }
316: if (obj instanceof CmsLink) {
317: CmsLink other = (CmsLink) obj;
318: return (m_type == other.m_type)
319: && CmsStringUtil.isEqual(m_target, other.m_target);
320: }
321: return false;
322: }
323:
324: /**
325: * Returns the anchor of this link.<p>
326: *
327: * @return the anchor or null if undefined
328: */
329: public String getAnchor() {
330:
331: return m_anchor;
332: }
333:
334: /**
335: * Returns the xml node element representing this link object.<p>
336: *
337: * @return the xml node element representing this link object
338: */
339: public Element getElement() {
340:
341: return m_element;
342: }
343:
344: /**
345: * Returns the processed link.<p>
346: *
347: * @param cms the current OpenCms user context, can be <code>null</code>
348: *
349: * @return the processed link
350: */
351: public String getLink(CmsObject cms) {
352:
353: if (m_internal) {
354:
355: // if we have a local link, leave it unchanged
356: // cms may be null for unit tests
357: if ((cms == null) || (m_uri.length() == 0)
358: || (m_uri.charAt(0) == '#')) {
359: return m_uri;
360: }
361:
362: checkConsistency(cms);
363:
364: CmsObjectWrapper wrapper = (CmsObjectWrapper) cms
365: .getRequestContext().getAttribute(
366: CmsObjectWrapper.ATTRIBUTE_NAME);
367: if (wrapper != null) {
368: // if an object wrapper is used, rewrite the URI
369: m_uri = wrapper.rewriteLink(m_uri);
370: }
371:
372: if ((cms.getRequestContext().getSiteRoot().length() == 0)
373: && (cms.getRequestContext().getAttribute(
374: CmsRequestContext.ATTRIBUTE_EDITOR) == null)) {
375: // Explanation why this check is required:
376: // If the site root name length is 0, this means that a user has switched
377: // the site root to the root site "/" in the Workplace.
378: // In this case the workplace site must also be the active site.
379: // If the editor is opened in the root site, because of this code the links are
380: // always generated _with_ server name / port so that the source code looks identical to code
381: // that would normally be created when running in a regular site.
382: // If normal link processing would be used, the site information in the link
383: // would be lost.
384: return OpenCms.getLinkManager().substituteLink(cms,
385: m_uri);
386: }
387:
388: // get the site root for this URI / link
389: // if there is no site root, we either have a /system link, or the site was deleted,
390: // return the full URI prefixed with the opencms context
391: String siteRoot = getSiteRoot();
392: if (siteRoot == null) {
393: return OpenCms.getLinkManager().substituteLink(cms,
394: m_uri);
395: }
396:
397: if (cms.getRequestContext().getAttribute(
398: CmsRequestContext.ATTRIBUTE_FULLLINKS) != null) {
399: // full links should be generated even if we are in the same site
400: return OpenCms.getLinkManager().getServerLink(cms,
401: m_uri);
402: }
403:
404: // return the link with the server prefix, if necessary
405: return OpenCms.getLinkManager().substituteLink(cms,
406: getVfsUri(), siteRoot);
407: } else {
408:
409: // don't touch external links
410: return m_uri;
411: }
412: }
413:
414: /**
415: * Returns the processed link.<p>
416: *
417: * @param cms the current OpenCms user context, can be <code>null</code>
418: * @param processEditorLinks this parameter is not longer used
419: *
420: * @return the processed link
421: *
422: * @deprecated use {@link #getLink(CmsObject)} instead,
423: * the process editor option is set using the OpenCms request context attributes
424: */
425: public String getLink(CmsObject cms, boolean processEditorLinks) {
426:
427: return getLink(cms);
428: }
429:
430: /**
431: * Returns the macro name of this link.<p>
432: *
433: * @return the macro name name of this link
434: */
435: public String getName() {
436:
437: return m_name;
438: }
439:
440: /**
441: * Returns the first parameter value for the given parameter name.<p>
442: *
443: * @param name the name of the parameter
444: * @return the first value for this name or <code>null</code>
445: */
446: public String getParameter(String name) {
447:
448: String[] p = (String[]) getParameterMap().get(name);
449: if (p != null) {
450: return p[0];
451: }
452:
453: return null;
454: }
455:
456: /**
457: * Returns the map of parameters of this link.<p>
458: *
459: * @return the map of parameters (<code>Map(String[])</code>)
460: */
461: public Map getParameterMap() {
462:
463: if (m_parameters == null) {
464: m_parameters = CmsRequestUtil.createParameterMap(m_query);
465: }
466: return m_parameters;
467: }
468:
469: /**
470: * Returns the set of available parameter names for this link.<p>
471: *
472: * @return a <code>Set</code> of parameter names
473: */
474: public Set getParameterNames() {
475:
476: return getParameterMap().keySet();
477: }
478:
479: /**
480: * Returns all parameter values for the given name.<p>
481: *
482: * @param name the name of the parameter
483: * @return a <code>String[]</code> of all parameter values or <code>null</code>
484: */
485: public String[] getParameterValues(String name) {
486:
487: return (String[]) getParameterMap().get(name);
488: }
489:
490: /**
491: * Returns the query of this link.<p>
492: *
493: * @return the query or null if undefined
494: */
495: public String getQuery() {
496:
497: return m_query;
498: }
499:
500: /**
501: * Return the site root if the target of this link is internal, or <code>null</code> otherwise.<p>
502: *
503: * @return the site root if the target of this link is internal, or <code>null</code> otherwise
504: */
505: public String getSiteRoot() {
506:
507: if (m_internal && (m_siteRoot == null)) {
508: m_siteRoot = OpenCms.getSiteManager().getSiteRoot(m_target);
509: if (m_siteRoot == null) {
510: m_siteRoot = "";
511: }
512: }
513: return m_siteRoot;
514: }
515:
516: /**
517: * The structure id of the linked resource.<p>
518: *
519: * @return structure id of the linked resource
520: */
521: public CmsUUID getStructureId() {
522:
523: return m_structureId;
524: }
525:
526: /**
527: * Returns the target (destination) of this link.<p>
528: *
529: * @return the target the target (destination) of this link
530: */
531: public String getTarget() {
532:
533: return m_target;
534: }
535:
536: /**
537: * Returns the type of this link.<p>
538: *
539: * @return the type of this link
540: */
541: public CmsRelationType getType() {
542:
543: return m_type;
544: }
545:
546: /**
547: * Returns the raw uri of this link.<p>
548: *
549: * @return the uri
550: */
551: public String getUri() {
552:
553: return m_uri;
554: }
555:
556: /**
557: * Returns the vfs link of the target if it is internal.<p>
558: *
559: * @return the full link destination or null if the link is not internal.
560: */
561: public String getVfsUri() {
562:
563: if (m_internal) {
564: String siteRoot = getSiteRoot();
565: if (siteRoot != null) {
566: return m_uri.substring(siteRoot.length());
567: } else {
568: return m_uri;
569: }
570: }
571:
572: return null;
573: }
574:
575: /**
576: * @see java.lang.Object#hashCode()
577: */
578: public int hashCode() {
579:
580: int result = m_type.hashCode();
581: if (m_target != null) {
582: result += m_target.hashCode();
583: }
584: return result;
585: }
586:
587: /**
588: * Returns if the link is internal.<p>
589: *
590: * @return true if the link is a local link
591: */
592: public boolean isInternal() {
593:
594: return m_internal;
595: }
596:
597: /**
598: * @see java.lang.Object#toString()
599: */
600: public String toString() {
601:
602: return m_uri;
603: }
604:
605: /**
606: * Updates the uri of this link with a new value.<p>
607: *
608: * Also updates the structure of the underlying XML page document this link belongs to.<p>
609: *
610: * Note that you can <b>not</b> update the "internal" or "type" values of the link,
611: * so the new link must be of same type (A, IMG) and also remain either an internal or external link.<p>
612: *
613: * @param uri the uri to update this link with <code>scheme://authority/path#anchor?query</code>
614: */
615: public void updateLink(String uri) {
616:
617: // set the uri
618: m_uri = uri;
619:
620: // update the components
621: setComponents();
622:
623: // update the xml
624: CmsLinkUpdateUtil.updateXml(this , m_element, true);
625: }
626:
627: /**
628: * Updates the uri of this link with a new target, anchor and query.<p>
629: *
630: * If anchor and/or query are <code>null</code>, this features are not used.<p>
631: *
632: * Note that you can <b>not</b> update the "internal" or "type" values of the link,
633: * so the new link must be of same type (A, IMG) and also remain either an internal or external link.<p>
634: *
635: * Also updates the structure of the underlying XML page document this link belongs to.<p>
636: *
637: * @param target the target (destination) of this link
638: * @param anchor the anchor or null if undefined
639: * @param query the query or null if undefined
640: */
641: public void updateLink(String target, String anchor, String query) {
642:
643: // set the components
644: m_target = target;
645: m_anchor = anchor;
646: setQuery(query);
647:
648: // create the uri from the components
649: setUri();
650:
651: // update the xml
652: CmsLinkUpdateUtil.updateXml(this , m_element, true);
653: }
654:
655: /**
656: * Sets the component member variables (target, anchor, query)
657: * by splitting the uri <code>scheme://authority/path#anchor?query</code>.<p>
658: */
659: private void setComponents() {
660:
661: CmsUriSplitter splitter = new CmsUriSplitter(m_uri, true);
662: m_target = splitter.getPrefix();
663: m_anchor = splitter.getAnchor();
664: setQuery(splitter.getQuery());
665: }
666:
667: /**
668: * Sets the query of the link.<p>
669: *
670: * @param query the query to set.
671: */
672: private void setQuery(String query) {
673:
674: m_query = CmsLinkProcessor.unescapeLink(query);
675: m_parameters = null;
676: }
677:
678: /**
679: * Joins the internal target, anchor and query components
680: * to one uri string, setting the internal uri and parameters fields.<p>
681: */
682: private void setUri() {
683:
684: StringBuffer uri = new StringBuffer(64);
685: uri.append(m_target);
686: if (m_query != null) {
687: uri.append('?');
688: uri.append(m_query);
689: }
690: if (m_anchor != null) {
691: uri.append('#');
692: uri.append(m_anchor);
693: }
694: m_uri = uri.toString();
695: }
696: }
|