001: /*
002: * Copyright 2001-2006 C:1 Financial Services GmbH
003: *
004: * This software is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License Version 2.1, as published by the Free Software Foundation.
007: *
008: * This software is distributed in the hope that it will be useful,
009: * but WITHOUT ANY WARRANTY; without even the implied warranty of
010: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
011: * Lesser General Public License for more details.
012: *
013: * You should have received a copy of the GNU Lesser General Public
014: * License along with this library; if not, write to the Free Software
015: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA
016: */
017:
018: package de.finix.contelligent.core;
019:
020: import java.util.ArrayList;
021: import java.util.HashSet;
022: import java.util.Iterator;
023: import java.util.LinkedList;
024: import java.util.List;
025: import java.util.Map;
026: import java.util.Set;
027: import java.util.Stack;
028:
029: import de.finix.contelligent.CallData;
030: import de.finix.contelligent.Component;
031: import de.finix.contelligent.ComponentContext;
032: import de.finix.contelligent.ComponentManager;
033: import de.finix.contelligent.ComponentNotFoundException;
034: import de.finix.contelligent.ComponentPath;
035: import de.finix.contelligent.ComponentPathException;
036: import de.finix.contelligent.Container;
037: import de.finix.contelligent.ExternalRelationSource;
038: import de.finix.contelligent.GlobalComponentPath;
039: import de.finix.contelligent.ModificationVetoException;
040: import de.finix.contelligent.RelationsManager;
041: import de.finix.contelligent.category.CategoryException;
042: import de.finix.contelligent.category.CategoryManager;
043: import de.finix.contelligent.exception.ComponentPersistenceException;
044: import de.finix.contelligent.exception.ContelligentException;
045: import de.finix.contelligent.exception.ContelligentExceptionID;
046: import de.finix.contelligent.logging.LoggingService;
047: import de.finix.contelligent.persistence.ComponentPersistenceAdapter;
048: import de.finix.contelligent.persistence.RelationsPersistenceAdapter;
049: import de.finix.contelligent.render.Template;
050: import de.finix.contelligent.resource.Resource;
051: import de.finix.contelligent.resource.StringResource;
052: import de.finix.contelligent.resource.TextResource;
053:
054: public class RelationsManagerImpl implements RelationsManager {
055: final static org.apache.log4j.Logger log = LoggingService
056: .getLogger(RelationsManagerImpl.class);
057:
058: final RelationsPersistenceAdapter relationsAdapter;
059:
060: final CategoryManager categoryManager;
061:
062: /**
063: * Creates a new <code>RelationsManagerImpl</code> instance.
064: */
065: RelationsManagerImpl(RelationsPersistenceAdapter relationsAdapter,
066: CategoryManager categoryManager) {
067: this .relationsAdapter = relationsAdapter;
068: this .categoryManager = categoryManager;
069: }
070:
071: /**
072: * Returns true if there are any relations in any manager to the given
073: * target tree.
074: *
075: * @param targetTree
076: * a <code>ComponentPath</code> value
077: * @return a <code>boolean</code> value
078: * @exception ComponentPersistenceException
079: * if an error occurs
080: */
081: public boolean existRelationsToTree(ComponentPath targetTree)
082: throws ComponentPersistenceException {
083: return relationsAdapter.existRelationsToTree(targetTree);
084: }
085:
086: /**
087: * Returns a <code>Map</code> containing (String,Set) entries mapping any
088: * sub-path of the given target-tree where relations exist to a set of
089: * <code>GlobalComponentPath</code> representing the source-paths of the
090: * relations. Only relations from the given manager-id or higher manager are
091: * considered. <BR>
092: * Note that a {@link GlobalComponentPath} contains the id of the
093: * ComponentManager where the associated component exists in and that a mere
094: * ComponentPath never equals a GlobalComponentPath no matter whether the
095: * paths are equal or not!
096: *
097: * @param targetTree
098: * a <code>ComponentPath</code> value
099: * @param includeSelf
100: * a <code>boolean</code> value
101: * @param managerId
102: * only relations from components from this manager or higher
103: * managers are returned.
104: * @return a <code>Map</code> of (String,Set) entries where each set
105: * contains <code>GlobalComponentPath</code> instances
106: * @exception ComponentPersistenceException
107: * if an error occurs
108: */
109: public Map getGlobalPathsRelatedToTree(ComponentPath targetTree,
110: boolean includeSelf, long managerId)
111: throws ComponentPersistenceException {
112: Map relations = relationsAdapter.getGlobalPathsRelatedToTree(
113: targetTree, includeSelf);
114:
115: Iterator keys = relations.keySet().iterator();
116: while (keys.hasNext()) {
117: ComponentPath targetPath = new ComponentPath((String) keys
118: .next());
119: if (!(targetPath.equals(targetTree) || targetPath
120: .isSubPathOf(targetTree))) {
121: // This filtering is necessary, because for performance reasons
122: // we do the actual database query through a LIKE and thus
123: // might get extra entries.
124: keys.remove();
125: }
126: }
127:
128: Iterator it = relations.values().iterator();
129: while (it.hasNext()) {
130: Set relatedPaths = (Set) it.next();
131: Iterator relPaths = relatedPaths.iterator();
132: while (relPaths.hasNext()) {
133: GlobalComponentPath path = (GlobalComponentPath) relPaths
134: .next();
135: long pathCMId = path.getPersistenceManagerId();
136: // This makes sure Production moves are not blocked by
137: // changes in an edit context, by removing all paths
138: // that come from neither our context nor Production.
139: if ((pathCMId != managerId) && (pathCMId != -1)) {
140: relPaths.remove();
141: }
142: }
143: if (relatedPaths.isEmpty()) {
144: it.remove();
145: }
146: }
147: if (log.isDebugEnabled()) {
148: log.debug("getGlobalPathsRelatedToTree() - ending with "
149: + relations.size() + " map entries for tree '"
150: + targetTree + "'. (relations-map=" + relations
151: + ")");
152: }
153: return relations;
154: }
155:
156: /**
157: * Same as {@link #getGlobalPathsRelatedToTree} but returns a set of
158: * <code>ComponentPath</code> instances which is the union of all values
159: * of the map where all <code>GlobalComponentPath</code> instances are
160: * replaced by a ComponentPath.
161: *
162: * @param targetTree
163: * a <code>ComponentPath</code> value
164: * @param includeSelf
165: * a <code>boolean</code> value
166: * @param managerId
167: * a <code>long</code> value
168: * @return a <code>Set</code> value
169: * @exception ComponentPersistenceException
170: * if an error occurs
171: */
172: public Set getPathsRelatedToTree(ComponentPath targetTree,
173: boolean includeSelf, long managerId)
174: throws ComponentPersistenceException {
175: Map relationsMap = this .getGlobalPathsRelatedToTree(targetTree,
176: includeSelf, managerId);
177: Set result = new HashSet();
178: Iterator it = relationsMap.values().iterator();
179: while (it.hasNext()) {
180: Set relatedPaths = (Set) it.next();
181: // we cannot make a real union because we have to replace global by
182: // normal componentpath !
183: // result.addAll((Set)it.next());
184: Iterator relPaths = relatedPaths.iterator();
185: while (relPaths.hasNext()) {
186: GlobalComponentPath path = (GlobalComponentPath) relPaths
187: .next();
188: result.add(new ComponentPath(path.toPath()));
189: }
190: }
191: if (log.isDebugEnabled()) {
192: log.debug("getPathsRelatedToTree() - ending with "
193: + result.size() + " relations to tree '"
194: + targetTree + "'. (relations=" + result + ")");
195: }
196: return result;
197: }
198:
199: /**
200: * Returns a <code>Set</code> of <code>GlobalComponentPath</code>
201: * instances each representing the path of a components which has a relation
202: * to the given target path. Only relations from the given manager-id or
203: * higher manager are considered.
204: *
205: * @param target
206: * a <code>ComponentPath</code> value
207: * @param managerId
208: * a <code>long</code> value
209: * @return a <code>Set</code> value
210: * @exception ComponentPersistenceException
211: * if an error occurs
212: */
213: public Set getGlobalPathsRelatedTo(ComponentPath target,
214: long managerId) throws ComponentPersistenceException {
215: ContelligentImpl ci = ContelligentImpl.getInstance();
216: String managerName = ci.getComponentManagerName(managerId);
217: ComponentManager cm = ci.getComponentManager(managerName);
218: Set relatedPaths = relationsAdapter
219: .getGlobalPathsRelatedTo(target);
220: Iterator it = relatedPaths.iterator();
221: while (it.hasNext()) {
222: GlobalComponentPath path = (GlobalComponentPath) it.next();
223: long pathCMId = path.getPersistenceManagerId();
224: if (!(pathCMId == managerId || pathCMId == ComponentPersistenceAdapter.ROOT_ID)) { // ignore
225: // any
226: // path
227: // which
228: // is
229: // not
230: // in
231: // the
232: // actual
233: // or
234: // root
235: // manager
236: it.remove();
237: } else if ((managerId != ComponentPersistenceAdapter.ROOT_ID)
238: && (pathCMId == ComponentPersistenceAdapter.ROOT_ID)) {
239: // check if source component was locally modified
240: if (cm.componentExists(path, false)) {
241: it.remove();
242: }
243: }
244: }
245: return relatedPaths;
246: }
247:
248: /**
249: * retrieve all relations referenced from within a tree
250: *
251: * @param source
252: * the trees root node
253: * @param managerId
254: * the managerId to look into
255: * @return a set of relations which are references in the source tree
256: */
257: public Set getTreeRelations(ComponentPath source, long managerId)
258: throws ComponentPersistenceException {
259: Set relationPaths = relationsAdapter.getTreeRelations(source,
260: managerId);
261: return relationPaths;
262: }
263:
264: public Set getTreeRelationSources(ComponentPath source,
265: long managerId) throws ComponentPersistenceException {
266: Set relationPaths = relationsAdapter.getTreeRelationSources(
267: source, managerId);
268: return relationPaths;
269: }
270:
271: void removeRelationsOfTree(ComponentPath sourceTree, long sourceCM)
272: throws ComponentPersistenceException {
273: relationsAdapter.removeRelationsOfTree(sourceTree, sourceCM);
274: }
275:
276: void removeRelations(ComponentPath sourceTree, long sourceCM)
277: throws ComponentPersistenceException {
278: relationsAdapter.removeRelations(sourceTree, sourceCM);
279: }
280:
281: public void moveRelationsOfTree(ComponentPath path,
282: long sourceManagerId, long targetManagerId,
283: boolean recursive) throws ComponentPersistenceException {
284: relationsAdapter.moveRelationsOfTree(path, sourceManagerId,
285: targetManagerId, recursive);
286: }
287:
288: public Map getDeadRelations(ComponentPath path, long[] managerIds,
289: boolean completeSubtree)
290: throws ComponentPersistenceException {
291: return relationsAdapter.getDeadRelations(path, managerIds,
292: completeSubtree);
293: }
294:
295: public void updateRelations(Component c, CallData callData,
296: boolean recursive) throws ComponentPersistenceException {
297: ComponentManager manager = callData.getActualManager();
298: Stack componentStack = new Stack();
299: componentStack.push(c);
300: while (!componentStack.empty()) {
301: Component component = (Component) componentStack.pop();
302: try {
303: ComponentContext ctx = component.getComponentContext();
304: Set relatedPaths = new HashSet(
305: extractRelatedPaths(component)); // extractRelatedPaths()
306: // returns
307: // a
308: // list
309: ComponentPath path = ctx.getPath();
310: long cmId = ctx.getPersistenceManager().getId();
311: if (log.isDebugEnabled()) {
312: log
313: .debug("updateRelations() - updating relations of component '"
314: + path
315: + "' from manager "
316: + cmId
317: + " (relations="
318: + relatedPaths
319: + ") ...");
320: }
321: relationsAdapter.updateRelations(path, cmId,
322: relatedPaths);
323:
324: if (recursive && component instanceof Container) {
325: Container container = (Container) component;
326: Iterator subNames = container
327: .getSubcomponentNames();
328: while (subNames.hasNext()) {
329: String subComponentName = (String) subNames
330: .next();
331: try {
332: Component sub = manager.getSubcomponent(
333: container, subComponentName,
334: callData, false);
335: // don't follow links
336: componentStack.push(sub);
337: } catch (ComponentNotFoundException e) {
338: log
339: .error("updateRelations() - subcomponent '"
340: + subComponentName
341: + "' of container '"
342: + container
343: + "' not found although we used method 'getSubcomponentNames()' (skipping this tree): "
344: + e);
345: }
346: }
347: }
348: } catch (CategoryException e) {
349: log.error(
350: "updateRelations() - CategoryException while extracting relations of '"
351: + component + "': ", e);
352: throw new ComponentPersistenceException(component
353: .getComponentContext().getPath(), e);
354: } catch (ComponentPersistenceException e) {
355: // Keep this from getting caught as ContelligentException
356: throw e;
357: } catch (ContelligentException e) {
358: log.error(
359: "updateRelations() - ContelligentException while extracting relations of '"
360: + component + "': ", e);
361: throw new ComponentPersistenceException(component
362: .getComponentContext().getPath(), e);
363: }
364: }
365: }
366:
367: /**
368: * Updates all relations of the given component which would be affected by a
369: * move of <tt>source</tt> to </tt>target</tt> before the move actually
370: * happened.
371: *
372: * @param component
373: * the component to change relations of.
374: * @param source
375: * the source of the move.
376: * @param target
377: * the target of the move.
378: * @exception ComponentPersistenceException
379: * if an error occurs
380: * @exception ModificationVetoException
381: * if an error occurs
382: * @exception ComponentPathException
383: * if an error occurs
384: */
385: public void updateRelationsForMove(Component component,
386: ComponentPath source, ComponentPath target)
387: throws ComponentPersistenceException,
388: ModificationVetoException, ContelligentException {
389: final boolean debugEnabled = log.isDebugEnabled();
390: if (debugEnabled)
391: log.debug("updateRelationsForMove() - checking component '"
392: + component + "' ...");
393:
394: long currentTime = TimeService.getInstance()
395: .currentTimeMillis();
396: ComponentContext ctx = component.getComponentContext();
397: ComponentPath componentPath = ctx.getPath();
398: ComponentPath effectivePath = null;
399: if (debugEnabled)
400: log.debug("updateRelationsForMove() - comparing path '"
401: + componentPath + "' and '" + source + "' ...");
402:
403: // Note that "effectivePath" means "new location of componentPath",
404: // which is the root path used for relative references. "componentPath"
405: // is the root path of the reference before the move takes place.
406:
407: if (source.equals(componentPath)) {
408: effectivePath = target;
409: } else if (componentPath.isSubPathOf(source)) {
410: effectivePath = componentPath
411: .exchangeParent(source, target);
412: } else {
413: effectivePath = componentPath;
414: }
415: if (debugEnabled)
416: log.debug("updateRelationsForMove() - EXCHANGE: path='"
417: + componentPath + "', effective='" + effectivePath
418: + "' ...");
419:
420: if (component instanceof ExternalRelationSource) {
421: ExternalRelationSource externalRelationSource = (ExternalRelationSource) component;
422: List relatedPaths = externalRelationSource.relatedPaths();
423: ArrayList newRelatedPaths = new ArrayList(relatedPaths
424: .size());
425: Iterator paths = relatedPaths.iterator();
426: while (paths.hasNext()) {
427: ComponentPath path = (ComponentPath) paths.next();
428: ComponentPath replacedPath = replacePath(path, source,
429: target, ctx.getPath(), effectivePath);
430: if (replacedPath != null) {
431: if (debugEnabled) {
432: log
433: .debug("updateRelationsForMove() - replaced path '"
434: + path
435: + "' of component '"
436: + component
437: + "' with '"
438: + replacedPath
439: + "' (while moving '"
440: + source
441: + "' to '" + target + "')");
442: }
443: newRelatedPaths.add(replacedPath);
444: } else {
445: if (debugEnabled) {
446: log.debug("updateRelationsForMove() - path '"
447: + path + "' of component '" + component
448: + "' not replaced (while moving '"
449: + source + "' to '" + target + "')");
450: }
451: newRelatedPaths.add(path);
452: }
453: }
454: externalRelationSource.relatedPaths(newRelatedPaths); // throws
455: // ModificationVetoException;
456: } else {
457: // the template-resources:
458: Iterator it = ctx.getTemplateResourceIdentifiers()
459: .iterator();
460: while (it.hasNext()) {
461: int replaced = 0;
462: String identifier = (String) it.next();
463: Resource resource = (Resource) ctx
464: .getTemplateResource(identifier);
465: if (resource.isBinary() || resource.isNumber()) {
466: if (debugEnabled) {
467: log
468: .debug("updateRelationsForMove() - ignoring binary|number resource '"
469: + identifier
470: + "' of component '"
471: + component + "' ...");
472: }
473: } else if (resource.isText()) {
474: StringBuffer text = new StringBuffer(
475: ((TextResource) resource).getString());
476: replaced = updateContents(source, target, text, ctx
477: .getPath(), effectivePath);
478: if (replaced > 0) {
479: resource = new TextResource(text.toString(),
480: currentTime);
481: }
482: } else if (resource.isString()) {
483: StringBuffer text = new StringBuffer(
484: ((StringResource) resource).getString());
485: replaced = updateContents(source, target, text, ctx
486: .getPath(), effectivePath);
487: if (replaced > 0) {
488: resource = new StringResource(text.toString(),
489: currentTime);
490: }
491: } else { // The resource type is unknown.
492: log.error("updateRelationsForMove() - resource '"
493: + identifier + "' of component '"
494: + component
495: + "' has unknown resource type (ignored).");
496: }
497: if (replaced > 0) {
498: ctx.setTemplateResource(identifier, resource);
499: }
500: }
501:
502: // now the content-resources:
503: it = ctx.getContentResourceIdentifiers().iterator();
504: while (it.hasNext()) {
505: int replaced = 0;
506: String identifier = (String) it.next();
507: Resource resource = (Resource) ctx
508: .getContentResource(identifier);
509: if (resource.isBinary() || resource.isNumber()) {
510: if (debugEnabled) {
511: log
512: .debug("updateRelationsForMove() - ignoring binary|number resource '"
513: + identifier
514: + "' of component '"
515: + component + "' ...");
516: }
517: } else if (resource.isText()) {
518: StringBuffer text = new StringBuffer(
519: ((TextResource) resource).getString());
520: replaced = updateContents(source, target, text, ctx
521: .getPath(), effectivePath);
522: if (replaced > 0) {
523: resource = new TextResource(text.toString(),
524: currentTime);
525: }
526: } else if (resource.isString()) {
527: StringBuffer text = new StringBuffer(
528: ((StringResource) resource).getString());
529: replaced = updateContents(source, target, text, ctx
530: .getPath(), effectivePath);
531: if (replaced > 0) {
532: resource = new StringResource(text.toString(),
533: currentTime);
534: }
535: } else {
536: log.error("updateRelationsForMove() - resource '"
537: + identifier + "' of component '"
538: + component
539: + "' has unknown resource type (ignored).");
540: }
541: if (replaced > 0) {
542: ctx.setContentResource(identifier, resource);
543: }
544: }
545: }
546: }
547:
548: /**
549: * Extracts the relations of the given component. Any resource associated
550: * with the component except {@link Resource#isBinary binary}
551: * {@link Resource#isNumber number} resources are automatically scanned for
552: * related paths. If the component implements {@link ExternalRelationSource}
553: * the component itself is asked instead.
554: *
555: * @return a <code>List</code> containing <code>ComponentPath</code>
556: * instances where every path represents a relation of the
557: * component. All paths are absolute and any category-token gets
558: * replaced by its default value.
559: */
560: public List extractRelatedPaths(Component component)
561: throws ComponentPersistenceException, CategoryException,
562: ContelligentException {
563: final boolean debugEnabled = log.isDebugEnabled();
564: if (debugEnabled)
565: log.debug("extractRelatedPaths() - checking component '"
566: + component + "' ...");
567:
568: ComponentContext ctx = component.getComponentContext();
569: List relatedPathSet = new LinkedList();
570:
571: if (component instanceof ExternalRelationSource) {
572: ExternalRelationSource externalRelationSource = (ExternalRelationSource) component;
573: List relatedPaths = externalRelationSource.relatedPaths();
574: relatedPathSet.addAll(relatedPaths);
575: if (debugEnabled) {
576: log
577: .debug("extractRelatedPaths() - done with ExternalRelationSource '"
578: + component + "'.");
579: }
580: } else {
581: Set templateResources = ctx
582: .getTemplateResourceIdentifiers();
583: Set contentResources = ctx.getContentResourceIdentifiers();
584: if (debugEnabled) {
585: log.debug("extractRelatedPaths() - component '"
586: + component + "' has "
587: + templateResources.size() + " template- and "
588: + contentResources.size()
589: + " content-resource.");
590: }
591: Resource[] resources = new Resource[templateResources
592: .size()
593: + contentResources.size()];
594: int i = 0;
595: Iterator it = templateResources.iterator();
596: while (it.hasNext()) {
597: String identifier = (String) it.next();
598: resources[i++] = ctx.getTemplateResource(identifier);
599: }
600: it = contentResources.iterator();
601: while (it.hasNext()) {
602: String identifier = (String) it.next();
603: resources[i++] = ctx.getContentResource(identifier);
604: }
605:
606: for (i = 0; i < resources.length; i++) {
607: Resource resource = resources[i];
608: if (resource.isBinary() || resource.isNumber()) {
609: if (debugEnabled) {
610: log
611: .debug("extractRelatedPaths() - ignoring binary|number resource of component '"
612: + component + "' ...");
613: }
614: } else if (resource.isString() || resource.isText()) {
615: String resContent = ((StringResource) resource)
616: .getString();
617: if (resContent != null && resContent.length() > 0) {
618: Template template = new Template(resContent); // throws
619: // ContelligentException
620: relatedPathSet.addAll(template
621: .getPathsOfRelationTargets());
622: } else {
623: if (debugEnabled)
624: log
625: .debug("extractRelatedPaths() - ignoring empty resource of component '"
626: + component + "' ...");
627: }
628: } else {
629: // The resource type is unknown.
630: log
631: .error("extractRelatedPaths() - a resource of component '"
632: + component
633: + "' has an unknown resource-type (ignored).");
634: }
635: }
636: }
637:
638: List result = new LinkedList();
639: // handling of categories and relative paths:
640: Iterator relatedPaths = relatedPathSet.iterator();
641: while (relatedPaths.hasNext()) {
642: ComponentPath path = (ComponentPath) relatedPaths.next();
643: // XXX: this is a hack to prevent tags with dynamicly created path
644: // attributes for example
645: // in XSL templates to be added as a relation. (20030131, rs)
646: if (path.toString().indexOf("{") != -1) {
647: if (debugEnabled)
648: log
649: .debug("extractRelatedPaths() - ignoring relation '"
650: + path + "' (dynamic XSL?) ...");
651: relatedPaths.remove();
652: continue;
653: }
654: if (categoryManager.containsCategory(path)) {
655: ComponentPath oldPath = path;
656: path = categoryManager.expandCategories(path,
657: categoryManager.getDefaultCategoryMap());
658: if (debugEnabled)
659: log
660: .debug("extractRelatedPaths() - expanded categories in path '"
661: + oldPath
662: + "' to '"
663: + path
664: + "' ...");
665: }
666: if (path.isRelative()) {
667: path = path.toAbsolutePath(ctx.getPath());
668: }
669:
670: result.add(path);
671: }
672: return result;
673: }
674:
675: /**
676: * Replaces all paths in the given <tt>contents</tt> which are affected by
677: * moving <tt>oldPath</tt> to <tt>newPath</tt>. <BR>
678: * Note that the given component is expected to rely in the <b>target tree</b>
679: * which is important when relative paths must be changed.
680: *
681: * @param oldPath
682: * The source path of the move operation.
683: * @param newPath
684: * The target path of the move operation.
685: * @param contents
686: * The content whose contained references are to be updated.
687: * @param oldRootPath
688: * Location of the component holding the reference as before the
689: * move.
690: * @param newRootPath
691: * Location of the component holding the reference as after the
692: * move. If oldRootPath is not a subpath of oldPath, this must be
693: * the same as oldRootPath.
694: * @return an <code>int</code> value
695: * @exception ComponentPathException
696: * if an error occurs
697: */
698: private int updateContents(ComponentPath oldPath,
699: ComponentPath newPath, StringBuffer contents,
700: ComponentPath oldRootPath, ComponentPath newRootPath)
701: throws ContelligentException {
702: final boolean debugEnabled = log.isDebugEnabled();
703:
704: final String TAG_TOSEARCH = Template.FINIX_TAG_PREFIX
705: + Template.RENDER_IDENTIFIER;
706: final int TAG_TOSEARCH_LENGTH = TAG_TOSEARCH.length();
707:
708: if (debugEnabled)
709: log
710: .debug("updateContents() - searching related paths in contents ["
711: + contents + "] ...");
712:
713: int replaced = 0;
714: int pos = 0;
715: while (pos < contents.length()) {
716: int i = contents.substring(pos).indexOf(TAG_TOSEARCH);
717: if (i == -1)
718: break;
719:
720: if (debugEnabled)
721: log.debug("updateContents() - found tag '"
722: + TAG_TOSEARCH + "' at position " + i + " ...");
723:
724: pos += (i + TAG_TOSEARCH_LENGTH);
725:
726: while (pos < contents.length()
727: && Character.isWhitespace(contents.charAt(pos)))
728: ++pos; // skip whitespaces
729:
730: if (!(contents.substring(pos, pos
731: + Template.PATH_ATTRIBUTE.length())
732: .equals(Template.PATH_ATTRIBUTE))) {
733: log.error("updateContents() - attribute '"
734: + Template.PATH_ATTRIBUTE
735: + "' is missing at position " + pos
736: + " in tag " + TAG_TOSEARCH + " in contents ["
737: + contents + "]!");
738: throw new ContelligentException(
739: ContelligentExceptionID.component_invalidRenderTag);
740: }
741: pos += Template.PATH_ATTRIBUTE.length(); // add length of path
742: // attribute
743: while (pos < contents.length()
744: && Character.isWhitespace(contents.charAt(pos)))
745: ++pos; // skip whitespaces
746:
747: if (contents.charAt(pos++) != '=') {
748: log.error("updateContents() - attribute '"
749: + Template.PATH_ATTRIBUTE
750: + "' must be followed by '='!");
751: throw new ContelligentException(
752: ContelligentExceptionID.component_invalidRenderTag);
753: }
754: while (pos < contents.length()
755: && Character.isWhitespace(contents.charAt(pos)))
756: ++pos; // skip whitespaces
757:
758: // we allow both ' and "
759: i = -1;
760: if (contents.charAt(pos) == '"') {
761: i = contents.substring(++pos).indexOf('"');
762: } else if (contents.charAt(pos) == '\'') {
763: i = contents.substring(++pos).indexOf('\'');
764: }
765: if (i == -1) {
766: log
767: .error("updateContents() - value of attribute '"
768: + Template.PATH_ATTRIBUTE
769: + "' must be included in quotes [\" or '] (in component '"
770: + oldRootPath + "')");
771: throw new ContelligentException(
772: ContelligentExceptionID.component_invalidRenderTag);
773: }
774:
775: String s = contents.substring(pos, pos + i);
776: int questionMark = s.indexOf('?');
777: ComponentPath pathToReplace = (questionMark == -1) ? new ComponentPath(
778: s)
779: : new ComponentPath(s.substring(0, questionMark));
780: ComponentPath replacedPath = replacePath(pathToReplace,
781: oldPath, newPath, oldRootPath, newRootPath);
782: if (replacedPath != null) {
783: if (questionMark == -1) {
784: contents.replace(pos, pos + i, replacedPath
785: .toString());
786: } else {
787: contents.replace(pos, pos + questionMark,
788: replacedPath.toString());
789: }
790: pos += replacedPath.toString().length() + 1;
791: replaced++;
792: } else {
793: pos += (i + 1);
794: }
795: }
796:
797: if (debugEnabled)
798: log.debug("updateContents() - replaced " + replaced
799: + " paths in contents [" + contents + "] ...");
800: return replaced;
801: }
802:
803: /**
804: * Checks the specified pathFromTag which is expected to originate from the
805: * given component's content whether it must be changed when moving a
806: * component tree from oldPath to newPath. <BR>
807: * Returns null if the given path is not affected by the move or else the
808: * modified path.
809: *
810: * @param pathFromTag
811: * The path (relative or absolute) as stored in the reference to
812: * be changed.
813: * @param oldPath
814: * The source path of the move operation.
815: * @param newPath
816: * The target path of the move operation.
817: * @param oldRootPath
818: * Location of the component holding the reference as before the
819: * move.
820: * @param newRootPath
821: * Location of the component holding the reference as after the
822: * move. If oldRootPath is not either a subpath of oldPath or
823: * equal to oldPath, this must be the same as oldRootPath,
824: * otherwise it must be a subpath of or equal to newPath.
825: * @return a new relative or absolute path or null if the path needs no
826: * modification
827: * @exception ComponentPathException
828: * if an error occurs
829: */
830: final private ComponentPath replacePath(ComponentPath pathFromTag,
831: ComponentPath oldPath, ComponentPath newPath,
832: ComponentPath oldRootPath, ComponentPath newRootPath)
833: throws ComponentPathException {
834: ComponentPath path = (pathFromTag.isAbsolute() ? pathFromTag
835: : pathFromTag.toAbsolutePath(oldRootPath));
836:
837: // Some parameter contract checks coming up
838: if (!oldPath.isAbsolute()) {
839: throw new IllegalArgumentException("oldPath " + oldPath
840: + " is not an absolute path.");
841: }
842:
843: if (!newPath.isAbsolute()) {
844: throw new IllegalArgumentException("newPath " + newPath
845: + " is not an absolute path.");
846: }
847:
848: if (!oldRootPath.isAbsolute()) {
849: throw new IllegalArgumentException("oldRootPath "
850: + oldRootPath + " is not an absolute path.");
851: }
852:
853: if (!newRootPath.isAbsolute()) {
854: throw new IllegalArgumentException("newRootPath "
855: + newRootPath + " is not an absolute path.");
856: }
857:
858: if ((!(oldRootPath.isSubPathOf(oldPath) || oldRootPath
859: .equals(oldPath)))
860: && (!oldRootPath.equals(newRootPath))) {
861: throw new IllegalArgumentException(
862: "oldRootPath "
863: + oldRootPath
864: + " is outside the tree to be moved but still has a different newRootPath.");
865: }
866:
867: if ((oldRootPath.isSubPathOf(oldPath) || oldRootPath
868: .equals(oldPath))
869: && (!(newRootPath.isSubPathOf(newPath) || newRootPath
870: .equals(newPath)))) {
871: throw new IllegalArgumentException(
872: "oldRootPath "
873: + newRootPath
874: + " is inside the tree to be moved but newRootPath does not fall within the move destination.");
875: }
876:
877: // We might have to make an adjustment to the reference if it is either
878: // targetting a location within the tree to be moved OR is both
879: // sourced within the tree to be moved and relative (in the latter
880: // case it is the .. paths pointing to outside the tree that we are
881: // concerned about)
882: if (path.equals(oldPath)
883: || path.isSubPathOf(oldPath)
884: || ((oldRootPath.equals(oldPath) || oldRootPath
885: .isSubPathOf(oldPath)) && pathFromTag
886: .isRelative())) {
887:
888: // Prefill result in case the reference target is not being moved
889: ComponentPath result = path;
890:
891: // If the target is being moved, adjust it
892: if (path.equals(oldPath) || path.isSubPathOf(oldPath)) {
893: result = path.equals(oldPath) ? newPath : path
894: .exchangeParent(oldPath, newPath);
895: }
896:
897: // At this point, the result variable contains the absolute path of
898: // the reference target as it will be after the move
899:
900: // Convert the result to a relative path, if appropriate
901: // TODO: Think about whether always keeping relative paths relative
902: // is
903: // the right way to go; this should be the only place the code needs
904: // to be touched to change this at the time of writing.
905: if (pathFromTag.isRelative()) {
906: result = result.toRelativePath(newRootPath);
907: }
908: if (log.isDebugEnabled()) {
909: log
910: .debug("replacePath() - replaced path in component '"
911: + oldRootPath
912: + "' ('"
913: + pathFromTag
914: + "' => '"
915: + result
916: + "' while moving '"
917: + oldPath
918: + "' => '" + newPath + "')");
919: }
920: return result;
921: } else {
922: if (log.isDebugEnabled()) {
923: log.debug("replacePath() - path '" + pathFromTag
924: + "' in component '" + oldRootPath
925: + "' must not be changed. (while moving '"
926: + oldPath + "' => '" + newPath + "')");
927: }
928: return null; // null indicates the path has not to be changed!
929: }
930: }
931:
932: }
|