001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.file;
029
030import org.opencms.file.types.A_CmsResourceTypeLinkParseable;
031import org.opencms.file.types.CmsResourceTypeJsp;
032import org.opencms.file.types.CmsResourceTypeXmlContent;
033import org.opencms.file.types.I_CmsResourceType;
034import org.opencms.i18n.CmsEncoder;
035import org.opencms.loader.CmsLoaderException;
036import org.opencms.lock.CmsLock;
037import org.opencms.main.CmsException;
038import org.opencms.main.CmsIllegalArgumentException;
039import org.opencms.main.CmsLog;
040import org.opencms.main.OpenCms;
041import org.opencms.relations.CmsRelation;
042import org.opencms.relations.CmsRelationFilter;
043import org.opencms.relations.CmsRelationType;
044import org.opencms.relations.I_CmsLinkParseable;
045import org.opencms.util.CmsFileUtil;
046import org.opencms.util.CmsPair;
047import org.opencms.util.CmsStringUtil;
048import org.opencms.util.CmsUUID;
049import org.opencms.util.I_CmsRegexSubstitution;
050import org.opencms.xml.CmsXmlEntityResolver;
051import org.opencms.xml.CmsXmlException;
052import org.opencms.xml.CmsXmlUtils;
053import org.opencms.xml.content.CmsXmlContent;
054import org.opencms.xml.content.CmsXmlContentFactory;
055import org.opencms.xml.content.Messages;
056
057import java.io.UnsupportedEncodingException;
058import java.util.ArrayList;
059import java.util.Collection;
060import java.util.HashMap;
061import java.util.HashSet;
062import java.util.List;
063import java.util.Map;
064import java.util.Set;
065import java.util.regex.Matcher;
066import java.util.regex.Pattern;
067
068import org.apache.commons.logging.Log;
069
070import org.dom4j.Document;
071
072import com.google.common.collect.ArrayListMultimap;
073import com.google.common.collect.Lists;
074import com.google.common.collect.Multimap;
075
076/**
077 * A class used to rewrite links and relations in one subtree such that relations from that subtree to another given subtree
078 * replaced with relations to the first subtree.<p>
079 */
080public class CmsLinkRewriter {
081
082    /** The logger instance for this class. */
083    private static final Log LOG = CmsLog.getLog(CmsLinkRewriter.class);
084
085    /** A map from source folder structure ids to corresponding target folder resources. */
086    protected Map<CmsUUID, CmsResource> m_translationsById = new HashMap<CmsUUID, CmsResource>();
087
088    /** A map from source folder root paths to the corresponding target folder resources. */
089    protected Map<String, CmsResource> m_translationsByPath = new HashMap<String, CmsResource>();
090
091    /** A map of resources which have been cached by structure id. */
092    private Map<CmsUUID, CmsResource> m_cachedResources = new HashMap<CmsUUID, CmsResource>();
093
094    /** The CMS object used for file operations. */
095    private CmsObject m_cms;
096
097    /** If true, all XML contents will be rewritten instead of just those containing links to correct. */
098    private boolean m_rewriteAllXmlContents = true;
099
100    /** The set of structure ids of resources whose content has been rewritten. */
101    private Set<CmsUUID> m_rewrittenContent = new HashSet<CmsUUID>();
102
103    /** A list of path pairs, each containing a source and a target of a copy operation. */
104    private List<CmsPair<String, String>> m_sourceTargetPairs = new ArrayList<CmsPair<String, String>>();
105
106    /** The target folder root path. */
107    private String m_targetPath;
108
109    /**
110     * Creates a link rewriter for use after a multi-copy operation.<p>
111     *
112     * @param cms the current CMS context
113     * @param sources the list of source root paths
114     * @param target the target parent folder root path
115     */
116    public CmsLinkRewriter(CmsObject cms, List<String> sources, String target) {
117
118        m_sourceTargetPairs = new ArrayList<CmsPair<String, String>>();
119        for (String source : sources) {
120            checkNotSubPath(source, target);
121            String targetSub = CmsStringUtil.joinPaths(target, CmsResource.getName(source));
122            m_sourceTargetPairs.add(CmsPair.create(source, targetSub));
123        }
124        m_targetPath = target;
125        m_cms = cms;
126    }
127
128    /**
129     * Creates a new link rewriter for a list of sources and corresponding targets.<p>
130     *
131     * @param cms the current CMS context
132     * @param targetPath the target root path
133     * @param sourceTargetPairs the list of source-target pairs
134     */
135    public CmsLinkRewriter(CmsObject cms, String targetPath, List<CmsPair<String, String>> sourceTargetPairs) {
136
137        m_cms = cms;
138        m_targetPath = targetPath;
139        m_sourceTargetPairs = sourceTargetPairs;
140    }
141
142    /**
143     * Creates a link rewriter for use after a single copy operation.<p>
144     *
145     * @param cms the current CMS context
146     * @param source the source folder root path
147     * @param target the target folder root path
148     */
149    public CmsLinkRewriter(CmsObject cms, String source, String target) {
150
151        m_sourceTargetPairs = new ArrayList<CmsPair<String, String>>();
152        checkNotSubPath(source, target);
153
154        m_sourceTargetPairs.add(CmsPair.create(source, target));
155        m_targetPath = target;
156        m_cms = cms;
157    }
158
159    /**
160     * Checks whether a given resource is a folder and throws an exception otherwise.<p>
161     *
162     * @param resource the resource to check
163     * @throws CmsException if something goes wrong
164     */
165    protected static void checkIsFolder(CmsResource resource) throws CmsException {
166
167        if (!isFolder(resource)) {
168            throw new CmsIllegalArgumentException(
169                Messages.get().container(
170                    org.opencms.file.Messages.ERR_REWRITE_LINKS_ROOT_NOT_FOLDER_1,
171                    resource.getRootPath()));
172        }
173    }
174
175    /**
176     * Helper method to check whether a given resource is a folder.<p>
177     *
178     * @param resource the resouce to check
179     * @return true if the resource is a folder
180     *
181     * @throws CmsLoaderException if the resource type couldn't be found
182     */
183    protected static boolean isFolder(CmsResource resource) throws CmsLoaderException {
184
185        I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(resource.getTypeId());
186        return resourceType.isFolder();
187    }
188
189    /**
190     * Starts the link rewriting process.<p>
191     *
192     * @throws CmsException if something goes wrong
193     */
194    public void rewriteLinks() throws CmsException {
195
196        init();
197        List<CmsRelation> relationsToCorrect = findRelationsFromTargetToSource();
198        // group relations by the structure id of their source
199        Multimap<CmsUUID, CmsRelation> relationsBySourceId = ArrayListMultimap.create();
200        for (CmsRelation relation : relationsToCorrect) {
201            LOG.info(
202                "Found relation which needs to be corrected: "
203                    + relation.getSourcePath()
204                    + " -> "
205                    + relation.getTargetPath()
206                    + " ["
207                    + relation.getType().getName()
208                    + "]");
209            relationsBySourceId.put(relation.getSourceId(), relation);
210        }
211
212        // make sure we have a lock on the target folder before doing any write operations
213        CmsLock lock = m_cms.getLock(m_targetPath);
214        if (lock.isUnlocked() || !lock.isOwnedBy(m_cms.getRequestContext().getCurrentUser())) {
215            // fail if locked by another user
216            m_cms.lockResource(m_targetPath);
217        }
218
219        for (CmsUUID structureId : relationsBySourceId.keySet()) {
220
221            Collection<CmsRelation> relationsForResource = relationsBySourceId.get(structureId);
222            CmsResource resource = null;
223            try {
224                resource = getResource(structureId);
225                rewriteLinks(resource, relationsForResource);
226            } catch (Exception e) {
227                LOG.error(e.getLocalizedMessage(), e);
228            }
229        }
230        if (!m_rewriteAllXmlContents) {
231            return;
232        }
233        for (Map.Entry<CmsUUID, CmsResource> entry : m_cachedResources.entrySet()) {
234            CmsUUID key = entry.getKey();
235            CmsResource resource = entry.getValue();
236            if (isInTargets(resource.getRootPath()) && !m_rewrittenContent.contains(key)) {
237                I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(resource.getTypeId());
238                // rewrite content for other files so
239                if (resType instanceof A_CmsResourceTypeLinkParseable) {
240                    try {
241                        CmsFile file = m_cms.readFile(resource);
242                        if (resType instanceof CmsResourceTypeXmlContent) {
243                            CmsXmlContent content = CmsXmlContentFactory.unmarshal(m_cms, file);
244                            try {
245                                content.validateXmlStructure(new CmsXmlEntityResolver(m_cms));
246                            } catch (CmsException e) {
247                                LOG.info("XML content was corrected automatically for resource " + file.getRootPath());
248                                content.setAutoCorrectionEnabled(true);
249                                content.correctXmlStructure(m_cms);
250                                file.setContents(content.marshal());
251                            }
252                        }
253                        m_cms.writeFile(file);
254                    } catch (CmsException e) {
255                        LOG.error(e.getLocalizedMessage(), e);
256                    }
257                }
258            }
259        }
260        copyLocaleRelations();
261    }
262
263    /**
264     * Sets the 'rewriteAllContents' flag, which controls whether all XML contents will be rewritten
265     * or just those whose links need to be corrected.<p>
266     *
267     * @param rewriteAllContents if true, all contents will be rewritten
268     */
269    public void setRewriteAllContents(boolean rewriteAllContents) {
270
271        m_rewriteAllXmlContents = rewriteAllContents;
272    }
273
274    /**
275     * Checks that the target path is not a subfolder of the source path.<p>
276     *
277     * @param source the source path
278     * @param target the target path
279     */
280    protected void checkNotSubPath(String source, String target) {
281
282        source = CmsStringUtil.joinPaths("/", source, "/");
283        target = CmsStringUtil.joinPaths("/", target, "/");
284        if (target.startsWith(source)) {
285            throw new CmsIllegalArgumentException(
286                org.opencms.file.Messages.get().container(
287                    org.opencms.file.Messages.ERR_REWRITE_LINKS_ROOTS_DEPENDENT_2,
288                    source,
289                    target));
290        }
291    }
292
293    /**
294     * Separate method for copying locale relations..<p>
295     *
296     * This is necessary because the default copy mechanism does not copy locale relations.
297     *
298     * @throws CmsException if something goes wrong
299     */
300    protected void copyLocaleRelations() throws CmsException {
301
302        long start = System.currentTimeMillis();
303        List<CmsRelation> localeRelations = m_cms.readRelations(
304            CmsRelationFilter.ALL.filterType(CmsRelationType.LOCALE_VARIANT));
305        for (CmsRelation rel : localeRelations) {
306            if (isInSources(rel.getSourcePath()) && isInSources(rel.getTargetPath())) {
307                CmsResource newRelationSource = m_translationsById.get(rel.getSourceId());
308                CmsResource newRelationTarget = m_translationsById.get(rel.getTargetId());
309                if ((newRelationSource != null) && (newRelationTarget != null)) {
310                    try {
311                        m_cms.addRelationToResource(
312                            newRelationSource,
313                            newRelationTarget,
314                            CmsRelationType.LOCALE_VARIANT.getName());
315                    } catch (CmsException e) {
316                        LOG.error("Could not transfer locale relation: " + e.getLocalizedMessage(), e);
317                    }
318                } else {
319                    LOG.warn("Could not transfer locale relation because source/target not found in copy: " + rel);
320                }
321            }
322        }
323        long end = System.currentTimeMillis();
324        LOG.info("Copied locale relations, took " + (end - start) + "ms");
325    }
326
327    /**
328     * Decodes a byte array into a string with a given encoding, or the default encoding if that fails.<p>
329     *
330     * @param bytes the byte array
331     * @param encoding the encoding to use
332     *
333     * @return the decoded string
334     */
335    protected String decode(byte[] bytes, String encoding) {
336
337        try {
338            return new String(bytes, encoding);
339        } catch (UnsupportedEncodingException e) {
340            return new String(bytes);
341        }
342    }
343
344    /**
345     * Decodes a file's contents and return the content string and the encoding to use for writing the file
346     * back to the VFS.<p>
347     *
348     * @param file the file to decode
349     * @return a pair (content, encoding)
350     * @throws CmsException if something goes wrong
351     */
352    protected CmsPair<String, String> decode(CmsFile file) throws CmsException {
353
354        String content = null;
355        String encoding = getConfiguredEncoding(m_cms, file);
356        I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(file.getTypeId());
357        if (resType instanceof CmsResourceTypeJsp) {
358            content = decode(file.getContents(), encoding);
359        } else {
360            try {
361                CmsXmlEntityResolver resolver = new CmsXmlEntityResolver(m_cms);
362                // parse the XML and serialize it back to  a string with the configured encoding
363                Document doc = CmsXmlUtils.unmarshalHelper(file.getContents(), resolver);
364                content = CmsXmlUtils.marshal(doc, encoding);
365            } catch (Exception e) {
366                // invalid xml structure, just use the configured encoding
367                content = decode(file.getContents(), encoding);
368            }
369        }
370        return CmsPair.create(content, encoding);
371    }
372
373    /**
374     * Finds relations from the target root folder or its children to the source root folder or its children.<p>
375     *
376     * @return the list of relations from the target to the source
377     *
378     * @throws CmsException if something goes wrong
379     */
380    protected List<CmsRelation> findRelationsFromTargetToSource() throws CmsException {
381
382        List<CmsRelation> relations = m_cms.readRelations(
383            CmsRelationFilter.SOURCES.filterPath(m_targetPath).filterIncludeChildren());
384        List<CmsRelation> result = new ArrayList<CmsRelation>();
385        for (CmsRelation rel : relations) {
386            if (isInTargets(rel.getSourcePath()) && isInSources(rel.getTargetPath())) {
387                result.add(rel);
388            }
389        }
390        return result;
391    }
392
393    /**
394     * Gets the encoding which is configured at the location of a given resource.<p>
395     *
396     * @param cms the current CMS context
397     * @param resource the resource for which the configured encoding should be retrieved
398     * @return the configured encoding for the resource
399     *
400     * @throws CmsException if something goes wrong
401     */
402    protected String getConfiguredEncoding(CmsObject cms, CmsResource resource) throws CmsException {
403
404        String encoding = null;
405        try {
406            encoding = cms.readPropertyObject(
407                resource.getRootPath(),
408                CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING,
409                true).getValue();
410        } catch (CmsException e) {
411            // encoding will be null
412        }
413        if (encoding == null) {
414            encoding = OpenCms.getSystemInfo().getDefaultEncoding();
415        } else {
416            encoding = CmsEncoder.lookupEncoding(encoding, null);
417            if (encoding == null) {
418                throw new CmsXmlException(
419                    Messages.get().container(Messages.ERR_XMLCONTENT_INVALID_ENC_1, resource.getRootPath()));
420            }
421        }
422        return encoding;
423    }
424
425    /**
426     * Gets a list of resource pairs whose paths relative to the source/target roots passed match.<p>
427     *
428     * @param source the source root
429     * @param target the target root
430     *
431     * @return the list of matching resources
432     *
433     * @throws CmsException if something goes wrong
434     */
435    protected List<CmsPair<CmsResource, CmsResource>> getMatchingResources(String source, String target)
436    throws CmsException {
437
438        List<CmsResource> sourceResources = readTree(source);
439        Map<String, CmsResource> sourceRelative = getResourcesByRelativePath(sourceResources, source);
440
441        List<CmsResource> targetResources = readTree(target);
442        Map<String, CmsResource> targetRelative = getResourcesByRelativePath(targetResources, target);
443
444        List<CmsPair<CmsResource, CmsResource>> result = new ArrayList<CmsPair<CmsResource, CmsResource>>();
445        sourceRelative.keySet().retainAll(targetRelative.keySet());
446        for (Map.Entry<String, CmsResource> entry : sourceRelative.entrySet()) {
447            String key = entry.getKey();
448            CmsResource sourceRes = entry.getValue();
449            CmsResource targetRes = targetRelative.get(key);
450            result.add(CmsPair.create(sourceRes, targetRes));
451        }
452        return result;
453    }
454
455    /**
456     * Computes the relative path given an ancestor folder path.<p>
457     *
458     * @param ancestor the ancestor folder
459     * @param rootPath the path for which the relative path should be computed
460     *
461     * @return the relative path
462     */
463    protected String getRelativePath(String ancestor, String rootPath) {
464
465        String result = rootPath.substring(ancestor.length());
466        result = CmsStringUtil.joinPaths("/", result, "/");
467        return result;
468    }
469
470    /**
471     * Accesses a resource by structure id.<p>
472     *
473     * @param structureId the structure id of the resource
474     * @return the resource with the given structure id
475     *
476     * @throws CmsException if the resource couldn't be read
477     */
478    protected CmsResource getResource(CmsUUID structureId) throws CmsException {
479
480        if (m_cachedResources.containsKey(structureId)) {
481            return m_cachedResources.get(structureId);
482        }
483        return m_cms.readResource(structureId);
484    }
485
486    /**
487     * Collects a list of resources in a map where the key for each resource is the path relative to a given folder.<p>
488     *
489     * @param resources the resources to put in the map
490     * @param basePath the path relative to which the keys of the resulting map should be computed
491     *
492     * @return a map from relative paths to resources
493     */
494    protected Map<String, CmsResource> getResourcesByRelativePath(List<CmsResource> resources, String basePath) {
495
496        Map<String, CmsResource> result = new HashMap<String, CmsResource>();
497        for (CmsResource resource : resources) {
498            String relativeSubPath = CmsStringUtil.getRelativeSubPath(basePath, resource.getRootPath());
499            if (relativeSubPath != null) {
500                result.put(relativeSubPath, resource);
501            }
502        }
503        return result;
504    }
505
506    /**
507     * Reads the data needed for rewriting the relations from the VFS.<p>
508     *
509     * @throws CmsException if something goes wrong
510     */
511    protected void init() throws CmsException {
512
513        m_cms = OpenCms.initCmsObject(m_cms);
514        // we want to use autocorrection when writing XML contents back
515        //m_cms.getRequestContext().setAttribute(CmsXmlContent.AUTO_CORRECTION_ATTRIBUTE, Boolean.TRUE);
516        m_cms.getRequestContext().setSiteRoot("");
517        List<CmsPair<CmsResource, CmsResource>> allMatchingResources = Lists.newArrayList();
518        for (CmsPair<String, String> pair : m_sourceTargetPairs) {
519            List<CmsPair<CmsResource, CmsResource>> matchingResources = getMatchingResources(
520                pair.getFirst(),
521                pair.getSecond());
522            allMatchingResources.addAll(matchingResources);
523        }
524        for (CmsPair<CmsResource, CmsResource> resPair : allMatchingResources) {
525            CmsResource source = resPair.getFirst();
526            CmsResource target = resPair.getSecond();
527            m_translationsById.put(source.getStructureId(), target);
528            m_translationsByPath.put(source.getRootPath(), target);
529        }
530    }
531
532    /**
533     * Checks if a path belongs to one of the sources.<p>
534     *
535     * @param path a root path
536     *
537     * @return true if the path belongs to the sources
538     */
539    protected boolean isInSources(String path) {
540
541        for (CmsPair<String, String> sourceTargetPair : m_sourceTargetPairs) {
542            String source = sourceTargetPair.getFirst();
543            if (CmsStringUtil.joinPaths(path, "/").startsWith(CmsStringUtil.joinPaths(source, "/"))) {
544                return true;
545            }
546        }
547        return false;
548    }
549
550    /**
551     * Checks if a path belongs to one of the targets.<p>
552     *
553     * @param path a root path
554     *
555     * @return true if the path belongs to the targets
556     */
557    protected boolean isInTargets(String path) {
558
559        for (CmsPair<String, String> sourceTargetPair : m_sourceTargetPairs) {
560            String target = sourceTargetPair.getSecond();
561            if (CmsStringUtil.joinPaths(path, "/").startsWith(CmsStringUtil.joinPaths(target, "/"))) {
562                return true;
563            }
564        }
565        return false;
566    }
567
568    /**
569     * Reads the resources in a subtree.<p>
570     *
571     * @param rootPath the root of the subtree
572     *
573     * @return the list of resources from the subtree
574     *
575     * @throws CmsException if something goes wrong
576     */
577    protected List<CmsResource> readTree(String rootPath) throws CmsException {
578
579        rootPath = CmsFileUtil.removeTrailingSeparator(rootPath);
580        CmsResource base = m_cms.readResource(rootPath);
581
582        I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(base);
583        List<CmsResource> result = new ArrayList<CmsResource>();
584        if (resType.isFolder()) {
585            rootPath = CmsStringUtil.joinPaths(rootPath, "/");
586            List<CmsResource> subResources = m_cms.readResources(rootPath, CmsResourceFilter.ALL, true);
587            result.add(base);
588            result.addAll(subResources);
589        } else {
590            result.add(base);
591        }
592        for (CmsResource resource : result) {
593            m_cachedResources.put(resource.getStructureId(), resource);
594        }
595
596        return result;
597    }
598
599    /**
600     * Rewrites the links included in the content itself.<p>
601     *
602     * @param file the file for which the links should be replaced
603     * @param relations the original relations
604     *
605     * @throws CmsException if something goes wrong
606     */
607    protected void rewriteContent(CmsFile file, Collection<CmsRelation> relations) throws CmsException {
608
609        LOG.info("Rewriting in-content links for " + file.getRootPath());
610        CmsPair<String, String> contentAndEncoding = decode(file);
611
612        String content = "";
613
614        if (OpenCms.getResourceManager().getResourceType(file) instanceof CmsResourceTypeXmlContent) {
615            CmsXmlContent contentXml = CmsXmlContentFactory.unmarshal(m_cms, file);
616            try {
617                contentXml.validateXmlStructure(new CmsXmlEntityResolver(m_cms));
618            } catch (CmsException e) {
619                LOG.info("XML content was corrected automatically for resource " + file.getRootPath());
620                contentXml.setAutoCorrectionEnabled(true);
621                contentXml.correctXmlStructure(m_cms);
622                try {
623                    content = new String(contentXml.marshal(), contentAndEncoding.getSecond());
624                } catch (UnsupportedEncodingException e1) {
625                    //
626                }
627            }
628        }
629
630        if (content.isEmpty()) {
631            content = contentAndEncoding.getFirst();
632        }
633        String encodingForSave = contentAndEncoding.getSecond();
634        String newContent = rewriteContentString(content);
635        byte[] newContentBytes;
636        try {
637            newContentBytes = newContent.getBytes(encodingForSave);
638        } catch (UnsupportedEncodingException e) {
639            newContentBytes = newContent.getBytes();
640        }
641        file.setContents(newContentBytes);
642        m_cms.writeFile(file);
643
644    }
645
646    /**
647     * Replaces structure ids of resources in the source subtree with the structure ids of the corresponding
648     * resources in the target subtree inside a content string.<p>
649     *
650     * @param originalContent the original content
651     *
652     * @return the content with the new structure ids
653     */
654    protected String rewriteContentString(String originalContent) {
655
656        Pattern uuidPattern = Pattern.compile(CmsUUID.UUID_REGEX);
657        I_CmsRegexSubstitution substitution = new I_CmsRegexSubstitution() {
658
659            public String substituteMatch(String text, Matcher matcher) {
660
661                String uuidString = text.substring(matcher.start(), matcher.end());
662                CmsUUID uuid = new CmsUUID(uuidString);
663                String result = uuidString;
664                if (m_translationsById.containsKey(uuid)) {
665                    result = m_translationsById.get(uuid).getStructureId().toString();
666                }
667                return result;
668            }
669        };
670        return CmsStringUtil.substitute(uuidPattern, originalContent, substitution);
671    }
672
673    /**
674     * Rewrites the links for a single resource.<p>
675     *
676     * @param resource the resource for which the links should be rewritten
677     * @param relations the relations to the source folder which have this resource as its source
678     *
679     * @throws CmsException if something goes wrong
680     */
681    protected void rewriteLinks(CmsResource resource, Collection<CmsRelation> relations) throws CmsException {
682
683        LOG.info("Rewriting relations for resource " + resource.getRootPath());
684        I_CmsResourceType resourceType = OpenCms.getResourceManager().getResourceType(resource.getTypeId());
685        boolean hasContentLinks = false;
686        boolean hasOtherLinks = false;
687
688        for (CmsRelation relation : relations) {
689            if (relation.getType().isDefinedInContent()) {
690                hasContentLinks = true;
691            } else {
692                hasOtherLinks = true;
693            }
694        }
695        if (hasContentLinks) {
696            LOG.info("The resource " + resource.getRootPath() + " has links in the content.");
697        }
698        if (hasOtherLinks) {
699            LOG.info("The resource " + resource.getRootPath() + " has non-content links.");
700        }
701
702        if (hasContentLinks) {
703            if (resourceType instanceof I_CmsLinkParseable) {
704                CmsFile file = m_cms.readFile(resource);
705                rewriteContent(file, relations);
706                m_rewrittenContent.add(file.getStructureId());
707            }
708        }
709        if (hasOtherLinks) {
710            rewriteOtherRelations(resource, relations);
711        }
712    }
713
714    /**
715     * Rewrites relations which are not derived from links in the content itself.<p>
716     *
717     * @param res the resource for which to rewrite the relations
718     * @param relations the original relations
719     *
720     * @throws CmsException if something goes wrong
721     */
722    protected void rewriteOtherRelations(CmsResource res, Collection<CmsRelation> relations) throws CmsException {
723
724        LOG.info("Rewriting non-content links for " + res.getRootPath());
725        for (CmsRelation rel : relations) {
726            CmsUUID targetId = rel.getTargetId();
727            CmsResource newTargetResource = m_translationsById.get(targetId);
728            CmsRelationType relType = rel.getType();
729            if (!relType.isDefinedInContent()) {
730                if (newTargetResource != null) {
731                    m_cms.deleteRelationsFromResource(
732                        rel.getSourcePath(),
733                        CmsRelationFilter.TARGETS.filterStructureId(rel.getTargetId()).filterType(relType));
734                    m_cms.addRelationToResource(
735                        rel.getSourcePath(),
736                        newTargetResource.getRootPath(),
737                        relType.getName());
738                }
739            }
740        }
741    }
742}