001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.workplace.tools.modules;
029
030import org.opencms.ade.configuration.CmsADEManager;
031import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
032import org.opencms.configuration.CmsConfigurationCopyResource;
033import org.opencms.db.CmsExportPoint;
034import org.opencms.file.CmsFile;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProject;
037import org.opencms.file.CmsProperty;
038import org.opencms.file.CmsResource;
039import org.opencms.file.CmsResourceFilter;
040import org.opencms.file.CmsVfsResourceNotFoundException;
041import org.opencms.file.types.A_CmsResourceType;
042import org.opencms.file.types.CmsResourceTypeFolder;
043import org.opencms.file.types.CmsResourceTypeUnknown;
044import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
045import org.opencms.file.types.CmsResourceTypeXmlContent;
046import org.opencms.file.types.I_CmsResourceType;
047import org.opencms.i18n.CmsLocaleManager;
048import org.opencms.i18n.CmsVfsBundleManager;
049import org.opencms.loader.CmsLoaderException;
050import org.opencms.lock.CmsLock;
051import org.opencms.main.CmsException;
052import org.opencms.main.CmsLog;
053import org.opencms.main.I_CmsEventListener;
054import org.opencms.main.OpenCms;
055import org.opencms.module.CmsModule;
056import org.opencms.module.Messages;
057import org.opencms.report.A_CmsReportThread;
058import org.opencms.report.I_CmsReport;
059import org.opencms.util.CmsStringUtil;
060import org.opencms.util.CmsUUID;
061import org.opencms.workplace.CmsWorkplace;
062import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
063import org.opencms.xml.CmsXmlException;
064import org.opencms.xml.content.CmsXmlContent;
065import org.opencms.xml.content.CmsXmlContentFactory;
066
067import java.io.UnsupportedEncodingException;
068import java.util.ArrayList;
069import java.util.Collections;
070import java.util.HashMap;
071import java.util.List;
072import java.util.Map;
073import java.util.Random;
074import java.util.StringTokenizer;
075import java.util.regex.Matcher;
076import java.util.regex.Pattern;
077
078import org.apache.commons.logging.Log;
079
080import com.google.common.base.Function;
081import com.google.common.base.Functions;
082
083/**
084 * The report thread to clone a module.<p>
085 */
086public class CmsCloneModuleThread extends A_CmsReportThread {
087
088    /**
089     * String replacement function.<p>
090     */
091    class ReplaceAll implements Function<String, String> {
092
093        /** Regex to match. */
094        private String m_from;
095
096        /** Replacement for the regex. */
097        private String m_to;
098
099        /**
100         * Creates a new instance.<p>
101         *
102         * @param from the regex to match
103         * @param to the replacement
104         */
105        public ReplaceAll(String from, String to) {
106
107            m_from = from;
108            m_to = to;
109        }
110
111        /**
112         * @see com.google.common.base.Function#apply(java.lang.Object)
113         */
114        public String apply(String input) {
115
116            return input.replaceAll(m_from, m_to);
117        }
118
119    }
120
121    /** The icon path. */
122    public static final String ICON_PATH = CmsWorkplace.VFS_PATH_RESOURCES + CmsWorkplace.RES_PATH_FILETYPES;
123
124    /** Classes folder within the module. */
125    public static final String PATH_CLASSES = "classes/";
126
127    /** The log object for this class. */
128    private static final Log LOG = CmsLog.getLog(CmsCloneModuleThread.class);
129
130    /** The clone module information. */
131    private CmsCloneModuleInfo m_cloneInfo;
132
133    /**
134     * Constructor.<p>
135     *
136     * @param cms the cms context
137     * @param cloneInfo the clone module information
138     */
139    protected CmsCloneModuleThread(CmsObject cms, CmsCloneModuleInfo cloneInfo) {
140
141        super(cms, cloneInfo.getName());
142        m_cloneInfo = cloneInfo;
143        initHtmlReport(cms.getRequestContext().getLocale());
144    }
145
146    /**
147     * Returns a list of all module names.<p>
148     *
149     * @return a list of all module names
150     */
151    public List<String> getAllModuleNames() {
152
153        List<String> sortedModuleNames = new ArrayList<String>(OpenCms.getModuleManager().getModuleNames());
154        java.util.Collections.sort(sortedModuleNames);
155        return sortedModuleNames;
156    }
157
158    /**
159     * @see org.opencms.report.A_CmsReportThread#getReportUpdate()
160     */
161    @Override
162    public String getReportUpdate() {
163
164        return getReport().getReportUpdate();
165    }
166
167    /**
168     * @see java.lang.Thread#run()
169     */
170    @Override
171    public void run() {
172
173        CmsModule sourceModule = OpenCms.getModuleManager().getModule(m_cloneInfo.getSourceModuleName());
174
175        // clone the module object
176        CmsModule targetModule = sourceModule.clone();
177        targetModule.setName(m_cloneInfo.getName());
178        targetModule.setNiceName(m_cloneInfo.getNiceName());
179        targetModule.setDescription(m_cloneInfo.getDescription());
180        targetModule.setAuthorEmail(m_cloneInfo.getAuthorEmail());
181        targetModule.setAuthorName(m_cloneInfo.getAuthorName());
182        targetModule.setGroup(m_cloneInfo.getGroup());
183        targetModule.setActionClass(m_cloneInfo.getActionClass());
184
185        CmsObject cms = getCms();
186        CmsProject currentProject = cms.getRequestContext().getCurrentProject();
187        try {
188            CmsProject workProject = cms.createProject(
189                "Clone_module_work_project",
190                "Clone modulee work project",
191                OpenCms.getDefaultUsers().getGroupAdministrators(),
192                OpenCms.getDefaultUsers().getGroupAdministrators(),
193                CmsProject.PROJECT_TYPE_TEMPORARY);
194            cms.getRequestContext().setCurrentProject(workProject);
195
196            // store the module paths
197            String sourceModulePath = CmsWorkplace.VFS_PATH_MODULES + sourceModule.getName() + "/";
198            String targetModulePath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/";
199
200            // store the package name as path part
201            String sourcePathPart = sourceModule.getName().replaceAll("\\.", "/");
202            String targetPathPart = targetModule.getName().replaceAll("\\.", "/");
203
204            // store the classes folder paths
205            String sourceClassesPath = targetModulePath + PATH_CLASSES + sourcePathPart + "/";
206            String targetClassesPath = targetModulePath + PATH_CLASSES + targetPathPart + "/";
207
208            // copy the resources
209            cms.copyResource(sourceModulePath, targetModulePath);
210
211            // check if we have to create the classes folder
212            if (cms.existsResource(sourceClassesPath)) {
213                // in the source module a classes folder was defined,
214                // now create all sub-folders for the package structure in the new module folder
215                createTargetClassesFolder(targetModule, sourceClassesPath, targetModulePath + PATH_CLASSES);
216                // delete the origin classes folder
217                deleteSourceClassesFolder(targetModulePath, sourcePathPart, targetPathPart);
218            }
219
220            // TODO: clone module dependencies
221
222            // adjust the export points
223            cloneExportPoints(sourceModule, targetModule, sourcePathPart, targetPathPart);
224
225            // adjust the resource type names and IDs
226            Map<String, String> descKeys = new HashMap<String, String>();
227            Map<I_CmsResourceType, I_CmsResourceType> resTypeMap = cloneResourceTypes(
228                sourceModule,
229                targetModule,
230                sourcePathPart,
231                targetPathPart,
232                descKeys);
233
234            // adjust the explorer type names and store referred icons and message keys
235            Map<String, String> iconPaths = new HashMap<String, String>();
236            cloneExplorerTypes(targetModule, iconPaths, descKeys);
237
238            // rename the icon file names
239            cloneExplorerTypeIcons(iconPaths);
240
241            // adjust the module resources
242            adjustModuleResources(sourceModule, targetModule, sourcePathPart, targetPathPart, iconPaths);
243
244            // search and replace the localization keys
245            if (getCms().existsResource(targetClassesPath)) {
246                List<CmsResource> props = cms.readResources(targetClassesPath, CmsResourceFilter.DEFAULT_FILES);
247                replacesMessages(descKeys, props);
248            }
249
250            int type = OpenCms.getResourceManager().getResourceType(CmsVfsBundleManager.TYPE_XML_BUNDLE).getTypeId();
251            CmsResourceFilter filter = CmsResourceFilter.requireType(type);
252            List<CmsResource> resources = cms.readResources(targetModulePath, filter);
253            replacesMessages(descKeys, resources);
254            renameXmlVfsBundles(resources, targetModule, sourceModule.getName());
255
256            List<CmsResource> allModuleResources = cms.readResources(targetModulePath, CmsResourceFilter.ALL);
257            replacePath(sourceModulePath, targetModulePath, allModuleResources);
258
259            // search and replace paths
260            replaceModuleName();
261
262            // replace formatter paths
263            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_cloneInfo.getFormatterTargetModule())
264                && !targetModule.getResourceTypes().isEmpty()) {
265                replaceFormatterPaths(targetModule);
266            }
267
268            adjustConfigs(targetModule, resTypeMap);
269
270            // now unlock and publish the project
271            getReport().println(
272                Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0),
273                I_CmsReport.FORMAT_HEADLINE);
274            cms.unlockProject(workProject.getUuid());
275            OpenCms.getPublishManager().publishProject(cms, getReport());
276            OpenCms.getPublishManager().waitWhileRunning();
277
278            getReport().println(
279                Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0),
280                I_CmsReport.FORMAT_HEADLINE);
281
282            //  add the imported module to the module manager
283            OpenCms.getModuleManager().addModule(cms, targetModule);
284
285            // reinitialize the resource manager with additional module resource types if necessary
286            if (targetModule.getResourceTypes() != Collections.EMPTY_LIST) {
287                OpenCms.getResourceManager().initialize(cms);
288            }
289            // reinitialize the workplace manager with additional module explorer types if necessary
290            if (targetModule.getExplorerTypes() != Collections.EMPTY_LIST) {
291                OpenCms.getWorkplaceManager().addExplorerTypeSettings(targetModule);
292            }
293
294            // re-initialize the workplace
295            OpenCms.getWorkplaceManager().initialize(cms);
296            // fire "clear caches" event to reload all cached resource bundles
297            OpenCms.fireCmsEvent(I_CmsEventListener.EVENT_CLEAR_CACHES, new HashMap<String, Object>());
298
299            // following changes will not be published right now, switch back to previous project
300            cms.getRequestContext().setCurrentProject(currentProject);
301
302            // change resource types and schema locations
303            if (isTrue(m_cloneInfo.getChangeResourceTypes())) {
304                changeResourceTypes(resTypeMap);
305            }
306            // adjust container pages
307            CmsObject cloneCms = OpenCms.initCmsObject(cms);
308            if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) {
309                cloneCms.getRequestContext().setSiteRoot("/");
310            }
311
312            if (m_cloneInfo.isRewriteContainerPages()) {
313                CmsResourceFilter f = CmsResourceFilter.requireType(
314                    CmsResourceTypeXmlContainerPage.getContainerPageTypeId());
315                List<CmsResource> allContainerPages = cloneCms.readResources("/", f);
316                replacePath(sourceModulePath, targetModulePath, allContainerPages);
317            }
318        } catch (Throwable e) {
319            LOG.error(e.getLocalizedMessage(), e);
320            getReport().addError(e);
321        } finally {
322            cms.getRequestContext().setCurrentProject(currentProject);
323        }
324    }
325
326    /**
327     * Returns <code>true</code> if the module has been created successful.<p>
328     *
329     * @return <code>true</code> if the module has been created successful
330     */
331    public boolean success() {
332
333        return OpenCms.getModuleManager().getModule(m_cloneInfo.getName()) != null;
334    }
335
336    /**
337     * Adjusts the module configuration file and the formatter configurations.<p>
338     *
339     * @param targetModule the target module
340     * @param resTypeMap the resource type mapping
341     *
342     * @throws CmsException if something goes wrong
343     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
344     */
345    private void adjustConfigs(CmsModule targetModule, Map<I_CmsResourceType, I_CmsResourceType> resTypeMap)
346    throws CmsException, UnsupportedEncodingException {
347
348        String modPath = CmsWorkplace.VFS_PATH_MODULES + targetModule.getName() + "/";
349        CmsObject cms = getCms();
350        if (((m_cloneInfo.getSourceNamePrefix() != null) && (m_cloneInfo.getTargetNamePrefix() != null))
351            || !m_cloneInfo.getSourceNamePrefix().equals(m_cloneInfo.getTargetNamePrefix())) {
352            // replace resource type names in formatter configurations
353            List<CmsResource> resources = cms.readResources(
354                modPath,
355                CmsResourceFilter.requireType(
356                    OpenCms.getResourceManager().getResourceType(
357                        CmsFormatterConfigurationCache.TYPE_FORMATTER_CONFIG)));
358            String source = "<Type><!\\[CDATA\\[" + m_cloneInfo.getSourceNamePrefix();
359            String target = "<Type><!\\[CDATA\\[" + m_cloneInfo.getTargetNamePrefix();
360            Function<String, String> replaceType = new ReplaceAll(source, target);
361
362            for (CmsResource resource : resources) {
363                transformResource(resource, replaceType);
364            }
365            resources.clear();
366        }
367
368        // replace resource type names in module configuration
369        try {
370            CmsResource config = cms.readResource(
371                modPath + CmsADEManager.CONFIG_FILE_NAME,
372                CmsResourceFilter.requireType(
373                    OpenCms.getResourceManager().getResourceType(CmsADEManager.MODULE_CONFIG_TYPE)));
374            Function<String, String> substitution = Functions.identity();
375            // compose the substitution functions from simple substitution functions for each type
376
377            for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) {
378                substitution = Functions.compose(
379                    new ReplaceAll(mapping.getKey().getTypeName(), mapping.getValue().getTypeName()),
380                    substitution);
381            }
382
383            // Either replace prefix in or prepend it to the folder name value
384
385            Function<String, String> replaceFolderName = new ReplaceAll(
386                "(<Folder>[ \n]*<Name><!\\[CDATA\\[)(" + m_cloneInfo.getSourceNamePrefix() + ")?",
387                "$1" + m_cloneInfo.getTargetNamePrefix());
388            substitution = Functions.compose(replaceFolderName, substitution);
389            transformResource(config, substitution);
390        } catch (CmsVfsResourceNotFoundException e) {
391            LOG.info(e.getLocalizedMessage(), e);
392        }
393    }
394
395    /**
396     * Adjusts the paths of the module resources from the source path to the target path.<p>
397     *
398     * @param targetResources the paths to adjust
399     * @param sourceModuleName the source module name
400     * @param targetModuleName the target module name
401     * @param sourcePathPart the path part of the source module
402     * @param targetPathPart the path part of the target module
403     * @param iconPaths the path where resource type icons are located
404     * @return the adjusted paths
405     */
406    private List<String> adjustModuleResourcePaths(
407        List<String> targetResources,
408        String sourceModuleName,
409        String targetModuleName,
410        String sourcePathPart,
411        String targetPathPart,
412        Map<String, String> iconPaths) {
413
414        List<String> newTargetResources = new ArrayList<String>();
415        for (String modRes : targetResources) {
416            String nIcon = iconPaths.get(modRes.substring(modRes.lastIndexOf('/') + 1));
417            if (nIcon != null) {
418                // the referenced resource is an resource type icon, add the new icon path
419                newTargetResources.add(ICON_PATH + nIcon);
420            } else if (modRes.contains(sourceModuleName)) {
421                // there is the name in it
422                newTargetResources.add(modRes.replaceAll(sourceModuleName, targetModuleName));
423            } else if (modRes.contains(sourcePathPart)) {
424                // there is a path in it
425                newTargetResources.add(modRes.replaceAll(sourcePathPart, targetPathPart));
426            } else {
427                // there is whether the path nor the name in it
428                newTargetResources.add(modRes);
429            }
430        }
431        return newTargetResources;
432    }
433
434    /**
435     * Adjusts the paths of the module resources from the source path to the target path.<p>
436     *
437     * @param sourceModule the source module
438     * @param targetModule the target module
439     * @param sourcePathPart the path part of the source module
440     * @param targetPathPart the path part of the target module
441     * @param iconPaths the path where resource type icons are located
442     */
443    private void adjustModuleResources(
444        CmsModule sourceModule,
445        CmsModule targetModule,
446        String sourcePathPart,
447        String targetPathPart,
448        Map<String, String> iconPaths) {
449
450        List<String> newTargetResources = adjustModuleResourcePaths(
451            targetModule.getResources(),
452            sourceModule.getName(),
453            targetModule.getName(),
454            sourcePathPart,
455            targetPathPart,
456            iconPaths);
457        targetModule.setResources(newTargetResources);
458
459        List<String> newTargetExcludeResources = adjustModuleResourcePaths(
460            targetModule.getExcludeResources(),
461            sourceModule.getName(),
462            targetModule.getName(),
463            sourcePathPart,
464            targetPathPart,
465            iconPaths);
466        targetModule.setExcludeResources(newTargetExcludeResources);
467    }
468
469    /**
470     * Manipulates a string by cutting of a prefix, if present, and adding a new prefix.
471     *
472     * @param word the string to be manipulated
473     * @param oldPrefix the old prefix that should be replaced
474     * @param newPrefix the new prefix that is added
475     * @return the manipulated string
476     */
477    private String alterPrefix(String word, String oldPrefix, String newPrefix) {
478
479        if (word.startsWith(oldPrefix)) {
480            return word.replaceFirst(oldPrefix, newPrefix);
481        }
482        return (newPrefix + word);
483    }
484
485    /**
486     * Changes the resource types and the schema locations of existing content.<p>
487     *
488     * @param resTypeMap a map containing the source types as keys and the target types as values
489     *
490     * @throws CmsException if something goes wrong
491     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
492     */
493    private void changeResourceTypes(Map<I_CmsResourceType, I_CmsResourceType> resTypeMap)
494    throws CmsException, UnsupportedEncodingException {
495
496        CmsObject cms = getCms();
497        CmsObject cloneCms = OpenCms.initCmsObject(cms);
498
499        if (isTrue(m_cloneInfo.getApplyChangesEverywhere())) {
500            cloneCms.getRequestContext().setSiteRoot("/");
501        }
502
503        for (Map.Entry<I_CmsResourceType, I_CmsResourceType> mapping : resTypeMap.entrySet()) {
504            CmsResourceFilter filter = CmsResourceFilter.requireType(mapping.getKey());
505            List<CmsResource> resources = cloneCms.readResources("/", filter);
506            String sourceSchemaPath = mapping.getKey().getConfiguration().get("schema");
507            String targetSchemaPath = mapping.getValue().getConfiguration().get("schema");
508            for (CmsResource res : resources) {
509                if (lockResource(cms, res)) {
510                    CmsFile file = cms.readFile(res);
511                    if (CmsResourceTypeXmlContent.isXmlContent(file)) {
512                        CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
513                        xmlContent.setAutoCorrectionEnabled(true);
514                        file = xmlContent.correctXmlStructure(getCms());
515                    }
516                    String encoding = CmsLocaleManager.getResourceEncoding(cms, file);
517                    String content = new String(file.getContents(), encoding);
518                    content = content.replaceAll(sourceSchemaPath, targetSchemaPath);
519                    file.setContents(content.getBytes(encoding));
520                    try {
521                        cms.writeFile(file);
522                    } catch (CmsXmlException e) {
523                        LOG.error(e.getMessage(), e);
524                    }
525                    res.setType(mapping.getValue().getTypeId());
526                    cms.writeResource(res);
527                }
528            }
529        }
530    }
531
532    /**
533     * Copies the explorer type icons.<p>
534     *
535     * @param iconPaths the path to the location where the icons are located
536     *
537     * @throws CmsException if something goes wrong
538     */
539    private void cloneExplorerTypeIcons(Map<String, String> iconPaths) throws CmsException {
540
541        for (Map.Entry<String, String> entry : iconPaths.entrySet()) {
542            String source = ICON_PATH + entry.getKey();
543            String target = ICON_PATH + entry.getValue();
544            if (getCms().existsResource(source) && !getCms().existsResource(target)) {
545                getCms().copyResource(source, target);
546            }
547        }
548    }
549
550    /**
551     * Copies the explorer type definitions.<p>
552     *
553     * @param targetModule the target module
554     * @param iconPaths the path to the location where the icons are located
555     * @param descKeys a map that contains a mapping of the explorer type definitions messages
556     */
557    private void cloneExplorerTypes(
558        CmsModule targetModule,
559        Map<String, String> iconPaths,
560        Map<String, String> descKeys) {
561
562        List<CmsExplorerTypeSettings> targetExplorerTypes = targetModule.getExplorerTypes();
563        for (CmsExplorerTypeSettings expSetting : targetExplorerTypes) {
564            descKeys.put(
565                expSetting.getKey(),
566                alterPrefix(expSetting.getKey(), m_cloneInfo.getSourceNamePrefix(), m_cloneInfo.getTargetNamePrefix()));
567            String newIcon = alterPrefix(
568                expSetting.getIcon(),
569                m_cloneInfo.getSourceNamePrefix(),
570                m_cloneInfo.getTargetNamePrefix());
571            String newBigIcon = alterPrefix(
572                expSetting.getBigIcon(),
573                m_cloneInfo.getSourceNamePrefix(),
574                m_cloneInfo.getTargetNamePrefix());
575            iconPaths.put(expSetting.getIcon(), newIcon);
576            iconPaths.put(expSetting.getBigIcon(), newBigIcon);
577            String oldExpTypeName = expSetting.getName();
578            String newExpTypeName = alterPrefix(
579                oldExpTypeName,
580                m_cloneInfo.getSourceNamePrefix(),
581                m_cloneInfo.getTargetNamePrefix());
582            expSetting.setName(newExpTypeName);
583
584            expSetting.setKey(expSetting.getKey().replaceFirst(oldExpTypeName, newExpTypeName));
585            expSetting.setIcon(
586                alterPrefix(
587                    expSetting.getIcon(),
588                    m_cloneInfo.getSourceNamePrefix(),
589                    m_cloneInfo.getTargetNamePrefix()));
590            expSetting.setBigIcon(
591                alterPrefix(
592                    expSetting.getBigIcon(),
593                    m_cloneInfo.getSourceNamePrefix(),
594                    m_cloneInfo.getTargetNamePrefix()));
595            expSetting.setInfo(expSetting.getInfo().replaceFirst(oldExpTypeName, newExpTypeName));
596        }
597    }
598
599    /**
600     * Clones the export points of the module and adjusts its paths.<p>
601     *
602     * @param sourceModule the source module
603     * @param targetModule the target module
604     * @param sourcePathPart the source path part
605     * @param targetPathPart the target path part
606     */
607    private void cloneExportPoints(
608        CmsModule sourceModule,
609        CmsModule targetModule,
610        String sourcePathPart,
611        String targetPathPart) {
612
613        for (CmsExportPoint exp : targetModule.getExportPoints()) {
614            if (exp.getUri().contains(sourceModule.getName())) {
615                exp.setUri(exp.getUri().replaceAll(sourceModule.getName(), targetModule.getName()));
616            }
617            if (exp.getUri().contains(sourcePathPart)) {
618                exp.setUri(exp.getUri().replaceAll(sourcePathPart, targetPathPart));
619            }
620        }
621    }
622
623    /**
624     * Clones/copies the resource types.<p>
625
626     * @param sourceModule the source module
627     * @param targetModule the target module
628     * @param sourcePathPart the source path part
629     * @param targetPathPart the target path part
630     * @param keys the map where to put in the messages of the resource type
631     *
632     * @return a map with source resource types as key and the taregt resource types as value
633     */
634    private Map<I_CmsResourceType, I_CmsResourceType> cloneResourceTypes(
635        CmsModule sourceModule,
636        CmsModule targetModule,
637        String sourcePathPart,
638        String targetPathPart,
639        Map<String, String> keys) {
640
641        Map<I_CmsResourceType, I_CmsResourceType> resourceTypeMapping = new HashMap<I_CmsResourceType, I_CmsResourceType>();
642
643        List<I_CmsResourceType> targetResourceTypes = new ArrayList<I_CmsResourceType>();
644        for (I_CmsResourceType sourceResType : targetModule.getResourceTypes()) {
645
646            // get the class name attribute
647            String className = sourceResType.getClassName();
648            // create the class instance
649            I_CmsResourceType targetResType;
650            try {
651                if (className != null) {
652                    className = className.trim();
653                }
654
655                int newId = -1;
656                boolean exists = true;
657                do {
658                    newId = new Random().nextInt((99999)) + 10000;
659                    try {
660                        OpenCms.getResourceManager().getResourceType(newId);
661                    } catch (CmsLoaderException e) {
662                        exists = false;
663                    }
664                } while (exists);
665
666                targetResType = (I_CmsResourceType)Class.forName(className).newInstance();
667
668                for (String mapping : sourceResType.getConfiguredMappings()) {
669                    targetResType.addMappingType(mapping);
670                }
671
672                targetResType.setAdjustLinksFolder(sourceResType.getAdjustLinksFolder());
673
674                if (targetResType instanceof A_CmsResourceType) {
675                    A_CmsResourceType concreteTargetResType = (A_CmsResourceType)targetResType;
676                    for (CmsProperty prop : sourceResType.getConfiguredDefaultProperties()) {
677                        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(prop.getValue())) {
678                            prop.setStructureValue(
679                                prop.getStructureValue().replaceAll(
680                                    sourceModule.getName(),
681                                    targetModule.getName()).replaceAll(sourcePathPart, targetPathPart));
682                            prop.setResourceValue(
683                                prop.getResourceValue().replaceAll(
684                                    sourceModule.getName(),
685                                    targetModule.getName()).replaceAll(sourcePathPart, targetPathPart));
686                        }
687                        concreteTargetResType.addDefaultProperty(prop);
688                    }
689                    for (CmsConfigurationCopyResource conres : sourceResType.getConfiguredCopyResources()) {
690                        concreteTargetResType.addCopyResource(
691                            conres.getSource(),
692                            conres.getTarget(),
693                            conres.getTypeString());
694                    }
695                }
696
697                for (Map.Entry<String, String> entry : sourceResType.getConfiguration().entrySet()) {
698                    targetResType.addConfigurationParameter(
699                        entry.getKey(),
700                        entry.getValue().replaceAll(sourceModule.getName(), targetModule.getName()));
701                }
702
703                targetResType.setAdditionalModuleResourceType(true);
704                targetResType.initConfiguration(
705                    alterPrefix(
706                        sourceResType.getTypeName(),
707                        m_cloneInfo.getSourceNamePrefix(),
708                        m_cloneInfo.getTargetNamePrefix()),
709                    newId + "",
710                    sourceResType.getClassName());
711
712                keys.put(sourceResType.getTypeName(), targetResType.getTypeName());
713                targetResourceTypes.add(targetResType);
714
715                resourceTypeMapping.put(sourceResType, targetResType);
716
717            } catch (Exception e) {
718                // resource type is unknown, use dummy class to import the module resources
719                targetResType = new CmsResourceTypeUnknown();
720                // write an error to the log
721                LOG.error(
722                    org.opencms.configuration.Messages.get().getBundle().key(
723                        org.opencms.configuration.Messages.ERR_UNKNOWN_RESTYPE_CLASS_2,
724                        className,
725                        targetResType.getClass().getName()),
726                    e);
727            }
728        }
729        targetModule.setResourceTypes(targetResourceTypes);
730        return resourceTypeMapping;
731    }
732
733    /**
734     * Creates the target folder for the module clone.<p>
735     *
736     * @param targetModule the target module
737     * @param sourceClassesPath the source module class path
738     * @param targetBaseClassesPath the 'classes' folder of the target module
739     *
740     * @throws CmsException if something goes wrong
741     */
742    private void createTargetClassesFolder(
743        CmsModule targetModule,
744        String sourceClassesPath,
745        String targetBaseClassesPath)
746    throws CmsException {
747
748        StringTokenizer tok = new StringTokenizer(targetModule.getName(), ".");
749        int folderId = CmsResourceTypeFolder.getStaticTypeId();
750        String targetClassesPath = targetBaseClassesPath;
751
752        while (tok.hasMoreTokens()) {
753            String folder = tok.nextToken();
754            targetClassesPath += folder + "/";
755            if (!getCms().existsResource(targetClassesPath)) {
756                getCms().createResource(targetClassesPath, folderId);
757            }
758        }
759        // move exiting content into new classes sub-folder
760        List<CmsResource> propertyFiles = getCms().readResources(sourceClassesPath, CmsResourceFilter.ALL);
761        for (CmsResource res : propertyFiles) {
762            if (!getCms().existsResource(targetClassesPath + res.getName())) {
763                getCms().copyResource(res.getRootPath(), targetClassesPath + res.getName());
764            }
765        }
766    }
767
768    /**
769     * Deletes the temporarily copied classes files.<p>
770     *
771     * @param targetModulePath the target module path
772     * @param sourcePathPart the path part of the source module
773     * @param targetPathPart the target path part
774     *
775     * @throws CmsException if something goes wrong
776     */
777    private void deleteSourceClassesFolder(String targetModulePath, String sourcePathPart, String targetPathPart)
778    throws CmsException {
779
780        String sourceFirstFolder = sourcePathPart.substring(0, sourcePathPart.indexOf('/'));
781        String targetFirstFolder = sourcePathPart.substring(0, sourcePathPart.indexOf('/'));
782        if (!sourceFirstFolder.equals(targetFirstFolder)) {
783            getCms().deleteResource(
784                targetModulePath + PATH_CLASSES + sourceFirstFolder,
785                CmsResource.DELETE_PRESERVE_SIBLINGS);
786            return;
787        }
788        String[] targetPathParts = CmsStringUtil.splitAsArray(targetPathPart, '/');
789        String[] sourcePathParts = CmsStringUtil.splitAsArray(sourcePathPart, '/');
790        int sourceLength = sourcePathParts.length;
791        int diff = 0;
792        for (int i = 0; i < targetPathParts.length; i++) {
793            if (sourceLength >= i) {
794                if (!targetPathParts[i].equals(sourcePathParts[i])) {
795                    diff = i + 1;
796                }
797            }
798        }
799        String topSourceClassesPath = targetModulePath
800            + PATH_CLASSES
801            + sourcePathPart.substring(0, sourcePathPart.indexOf('/'))
802            + "/";
803
804        if (diff != 0) {
805            topSourceClassesPath = targetModulePath + PATH_CLASSES;
806            for (int i = 0; i < diff; i++) {
807                topSourceClassesPath += sourcePathParts[i] + "/";
808            }
809        }
810        getCms().deleteResource(topSourceClassesPath, CmsResource.DELETE_PRESERVE_SIBLINGS);
811    }
812
813    /**
814     * Returns <code>true</code> if form input is selected, checked, on or yes.<p>
815     *
816     * @param value the value to check
817     *
818     * @return <code>true</code> if form input is selected, checked, on or yes
819     */
820    private boolean isTrue(String value) {
821
822        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(value)) {
823            if (Boolean.valueOf(value.toLowerCase()).booleanValue()
824                || value.toLowerCase().equals("on")
825                || value.toLowerCase().equals("yes")
826                || value.toLowerCase().equals("checked")
827                || value.toLowerCase().equals("selected")) {
828                return true;
829            }
830        }
831        return false;
832    }
833
834    /**
835     * Locks the current resource.<p>
836     *
837     * @param cms the current CmsObject
838     * @param cmsResource the resource to lock
839     *
840     * @return <code>true</code> if the given resource was locked was successfully
841     *
842     * @throws CmsException if some goes wrong
843     */
844    private boolean lockResource(CmsObject cms, CmsResource cmsResource) throws CmsException {
845
846        CmsLock lock = cms.getLock(cms.getSitePath(cmsResource));
847        // check the lock
848        if ((lock != null)
849            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
850            && lock.isOwnedInProjectBy(
851                cms.getRequestContext().getCurrentUser(),
852                cms.getRequestContext().getCurrentProject())) {
853            // prove is current lock from current user in current project
854            return true;
855        } else if ((lock != null) && !lock.isUnlocked() && !lock.isOwnedBy(cms.getRequestContext().getCurrentUser())) {
856            // the resource is not locked by the current user, so can not lock it
857            return false;
858        } else if ((lock != null)
859            && !lock.isUnlocked()
860            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
861            && !lock.isOwnedInProjectBy(
862                cms.getRequestContext().getCurrentUser(),
863                cms.getRequestContext().getCurrentProject())) {
864            // prove is current lock from current user but not in current project
865            // file is locked by current user but not in current project
866
867            cms.changeLock(cms.getSitePath(cmsResource));
868        } else if ((lock != null) && lock.isUnlocked()) {
869            // lock resource from current user in current project
870            cms.lockResource(cms.getSitePath(cmsResource));
871        }
872        lock = cms.getLock(cms.getSitePath(cmsResource));
873        if ((lock != null)
874            && lock.isOwnedBy(cms.getRequestContext().getCurrentUser())
875            && !lock.isOwnedInProjectBy(
876                cms.getRequestContext().getCurrentUser(),
877                cms.getRequestContext().getCurrentProject())) {
878            // resource could not be locked
879            return false;
880        }
881        // resource is locked successfully
882        return true;
883    }
884
885    /**
886     * Renames the vfs resource bundle files within the target module according to the new module's name.<p>
887     *
888     * @param resources the vfs resource bundle files
889     * @param targetModule the target module
890     * @param name the package name of the source module
891     *
892     * @return a list of all xml vfs bundles within the given module
893     * @throws CmsException if something gows wrong
894     */
895    private List<CmsResource> renameXmlVfsBundles(List<CmsResource> resources, CmsModule targetModule, String name)
896    throws CmsException {
897
898        for (CmsResource res : resources) {
899            String newName = res.getName().replaceAll(name, targetModule.getName());
900            String targetRootPath = CmsResource.getFolderPath(res.getRootPath()) + newName;
901            if (!getCms().existsResource(targetRootPath)) {
902                getCms().moveResource(res.getRootPath(), targetRootPath);
903            }
904        }
905        return resources;
906
907    }
908
909    /**
910     * Replaces the referenced formatters within the new XSD files with the new formatter paths.<p>
911     *
912     * @param targetModule the target module
913     *
914     * @throws CmsException if something goes wrong
915     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
916     */
917    private void replaceFormatterPaths(CmsModule targetModule) throws CmsException, UnsupportedEncodingException {
918
919        CmsResource formatterSourceFolder = getCms().readResource(
920            "/system/modules/" + m_cloneInfo.getFormatterSourceModule() + "/");
921        CmsResource formatterTargetFolder = getCms().readResource(
922            "/system/modules/" + m_cloneInfo.getFormatterTargetModule() + "/");
923        for (I_CmsResourceType type : targetModule.getResourceTypes()) {
924            String schemaPath = type.getConfiguration().get("schema");
925            CmsResource res = getCms().readResource(schemaPath);
926            CmsFile file = getCms().readFile(res);
927            if (CmsResourceTypeXmlContent.isXmlContent(file)) {
928                CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
929                xmlContent.setAutoCorrectionEnabled(true);
930                file = xmlContent.correctXmlStructure(getCms());
931            }
932            String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
933            String content = new String(file.getContents(), encoding);
934            content = content.replaceAll(formatterSourceFolder.getRootPath(), formatterTargetFolder.getRootPath());
935            file.setContents(content.getBytes(encoding));
936            getCms().writeFile(file);
937        }
938    }
939
940    /**
941     * Initializes a thread to find and replace all occurrence of the module's path.<p>
942     *
943     * @throws CmsException in case writing the file fails
944     * @throws UnsupportedEncodingException in case of the wrong encoding
945
946     */
947    private void replaceModuleName() throws CmsException, UnsupportedEncodingException {
948
949        CmsResourceFilter filter = CmsResourceFilter.ALL.addRequireFile().addExcludeState(
950            CmsResource.STATE_DELETED).addRequireTimerange().addRequireVisible();
951        List<CmsResource> resources = getCms().readResources(
952            CmsWorkplace.VFS_PATH_MODULES + m_cloneInfo.getName() + "/",
953            filter);
954        for (CmsResource resource : resources) {
955            CmsFile file = getCms().readFile(resource);
956            if (CmsResourceTypeXmlContent.isXmlContent(file)) {
957                CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
958                xmlContent.setAutoCorrectionEnabled(true);
959                file = xmlContent.correctXmlStructure(getCms());
960            }
961            byte[] contents = file.getContents();
962            String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
963            String content = new String(contents, encoding);
964            Matcher matcher = Pattern.compile(m_cloneInfo.getSourceModuleName()).matcher(content);
965            if (matcher.find()) {
966                contents = matcher.replaceAll(m_cloneInfo.getName()).getBytes(encoding);
967                if (lockResource(getCms(), file)) {
968                    file.setContents(contents);
969                    getCms().writeFile(file);
970                }
971            }
972        }
973    }
974
975    /**
976     * Replaces the paths within all the given resources and removes all UUIDs by an regex.<p>
977     *
978     * @param sourceModulePath the search path
979     * @param targetModulePath the replace path
980     * @param resources the resources
981     *
982     * @throws CmsException if something goes wrong
983     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
984     */
985    private void replacePath(String sourceModulePath, String targetModulePath, List<CmsResource> resources)
986    throws CmsException, UnsupportedEncodingException {
987
988        for (CmsResource resource : resources) {
989            if (resource.isFile()) {
990                CmsFile file = getCms().readFile(resource);
991                if (CmsResourceTypeXmlContent.isXmlContent(file)) {
992                    CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
993                    xmlContent.setAutoCorrectionEnabled(true);
994                    file = xmlContent.correctXmlStructure(getCms());
995                }
996                String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
997                String oldContent = new String(file.getContents(), encoding);
998                String newContent = oldContent.replaceAll(sourceModulePath, targetModulePath);
999                Matcher matcher = Pattern.compile(CmsUUID.UUID_REGEX).matcher(newContent);
1000                newContent = matcher.replaceAll("");
1001                newContent = newContent.replaceAll("<uuid></uuid>", "");
1002                if (!oldContent.equals(newContent)) {
1003                    file.setContents(newContent.getBytes(encoding));
1004                    if (!resource.getRootPath().startsWith(CmsWorkplace.VFS_PATH_SYSTEM)) {
1005                        if (lockResource(getCms(), resource)) {
1006                            getCms().writeFile(file);
1007                        }
1008                    } else {
1009                        getCms().writeFile(file);
1010                    }
1011                }
1012            }
1013        }
1014    }
1015
1016    /**
1017     * Replaces the messages for the given resources.<p>
1018     *
1019     * @param descKeys the replacement mapping
1020     * @param resources the resources to consult
1021     *
1022     * @throws CmsException if something goes wrong
1023     * @throws UnsupportedEncodingException if the file content could not be read with the determined encoding
1024
1025     */
1026    private void replacesMessages(Map<String, String> descKeys, List<CmsResource> resources)
1027    throws CmsException, UnsupportedEncodingException {
1028
1029        for (CmsResource resource : resources) {
1030            CmsFile file = getCms().readFile(resource);
1031            String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
1032            String content = new String(file.getContents(), encoding);
1033            for (Map.Entry<String, String> entry : descKeys.entrySet()) {
1034                content = content.replaceAll(entry.getKey(), entry.getValue());
1035            }
1036            file.setContents(content.getBytes(encoding));
1037            getCms().writeFile(file);
1038        }
1039    }
1040
1041    /**
1042     * Reads a file into a string, applies a transformation to the string, and writes the string back to the file.<p>
1043     *
1044     * @param resource the resource to transform
1045     * @param transformation the transformation to apply
1046     * @throws CmsException if something goes wrong
1047     * @throws UnsupportedEncodingException in case the encoding is not supported
1048     */
1049    private void transformResource(CmsResource resource, Function<String, String> transformation)
1050    throws CmsException, UnsupportedEncodingException {
1051
1052        CmsFile file = getCms().readFile(resource);
1053        if (CmsResourceTypeXmlContent.isXmlContent(file)) {
1054            CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(getCms(), file);
1055            xmlContent.setAutoCorrectionEnabled(true);
1056            file = xmlContent.correctXmlStructure(getCms());
1057        }
1058        String encoding = CmsLocaleManager.getResourceEncoding(getCms(), file);
1059        String content = new String(file.getContents(), encoding);
1060        content = transformation.apply(content);
1061        file.setContents(content.getBytes(encoding));
1062        lockResource(getCms(), file);
1063        getCms().writeFile(file);
1064
1065    }
1066}