001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH (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 GmbH, 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.module;
029
030import org.opencms.configuration.CmsConfigurationException;
031import org.opencms.configuration.CmsConfigurationManager;
032import org.opencms.configuration.CmsModuleConfiguration;
033import org.opencms.db.CmsExportPoint;
034import org.opencms.file.CmsObject;
035import org.opencms.file.CmsProject;
036import org.opencms.file.CmsResource;
037import org.opencms.i18n.CmsMessageContainer;
038import org.opencms.importexport.CmsImportExportManager;
039import org.opencms.lock.CmsLock;
040import org.opencms.lock.CmsLockException;
041import org.opencms.lock.CmsLockFilter;
042import org.opencms.main.CmsException;
043import org.opencms.main.CmsIllegalArgumentException;
044import org.opencms.main.CmsIllegalStateException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.CmsRuntimeException;
047import org.opencms.main.OpenCms;
048import org.opencms.report.I_CmsReport;
049import org.opencms.security.CmsRole;
050import org.opencms.security.CmsRoleViolationException;
051import org.opencms.security.CmsSecurityException;
052import org.opencms.util.CmsStringUtil;
053
054import java.io.File;
055import java.util.ArrayList;
056import java.util.Collections;
057import java.util.HashMap;
058import java.util.HashSet;
059import java.util.Hashtable;
060import java.util.Iterator;
061import java.util.List;
062import java.util.Map;
063import java.util.Set;
064
065import org.apache.commons.logging.Log;
066
067/**
068 * Manages the modules of an OpenCms installation.<p>
069 *
070 * @since 6.0.0
071 */
072public class CmsModuleManager {
073
074    /** Indicates dependency check for module deletion. */
075    public static final int DEPENDENCY_MODE_DELETE = 0;
076
077    /** Indicates dependency check for module import. */
078    public static final int DEPENDENCY_MODE_IMPORT = 1;
079
080    /** The log object for this class. */
081    private static final Log LOG = CmsLog.getLog(CmsModuleManager.class);
082
083    /** The import/export repository. */
084    private CmsModuleImportExportRepository m_importExportRepository = new CmsModuleImportExportRepository();
085
086    /** The list of module export points. */
087    private Set<CmsExportPoint> m_moduleExportPoints;
088
089    /** The map of configured modules. */
090    private Map<String, CmsModule> m_modules;
091
092    /**
093     * Basic constructor.<p>
094     *
095     * @param configuredModules the list of configured modules
096     */
097    public CmsModuleManager(List<CmsModule> configuredModules) {
098
099        if (CmsLog.INIT.isInfoEnabled()) {
100            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_MOD_MANAGER_CREATED_0));
101        }
102
103        m_modules = new Hashtable<String, CmsModule>();
104        for (int i = 0; i < configuredModules.size(); i++) {
105            CmsModule module = configuredModules.get(i);
106            m_modules.put(module.getName(), module);
107            if (CmsLog.INIT.isInfoEnabled()) {
108                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_MOD_CONFIGURED_1, module.getName()));
109            }
110        }
111
112        if (CmsLog.INIT.isInfoEnabled()) {
113            CmsLog.INIT.info(
114                Messages.get().getBundle().key(Messages.INIT_NUM_MODS_CONFIGURED_1, new Integer(m_modules.size())));
115        }
116        m_moduleExportPoints = Collections.emptySet();
117    }
118
119    /**
120     * Returns a map of dependencies.<p>
121     *
122     * The module dependencies are get from the installed modules or
123     * from the module manifest.xml files found in the given FRS path.<p>
124     *
125     * Two types of dependency lists can be generated:<br>
126     * <ul>
127     *   <li>Forward dependency lists: a list of modules that depends on a module</li>
128     *   <li>Backward dependency lists: a list of modules that a module depends on</li>
129     * </ul>
130     *
131     * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules
132     * @param mode if <code>true</code> a list of forward dependency is build, is not a list of backward dependency
133     *
134     * @return a Map of module names as keys and a list of dependency names as values
135     *
136     * @throws CmsConfigurationException if something goes wrong
137     */
138    public static Map<String, List<String>> buildDepsForAllModules(String rfsAbsPath, boolean mode)
139    throws CmsConfigurationException {
140
141        Map<String, List<String>> ret = new HashMap<String, List<String>>();
142        List<CmsModule> modules;
143        if (rfsAbsPath == null) {
144            modules = OpenCms.getModuleManager().getAllInstalledModules();
145        } else {
146            modules = new ArrayList<CmsModule>(getAllModulesFromPath(rfsAbsPath).keySet());
147        }
148        Iterator<CmsModule> itMods = modules.iterator();
149        while (itMods.hasNext()) {
150            CmsModule module = itMods.next();
151
152            // if module a depends on module b, and module c depends also on module b:
153            // build a map with a list containing "a" and "c" keyed by "b" to get a
154            // list of modules depending on module "b"...
155            Iterator<CmsModuleDependency> itDeps = module.getDependencies().iterator();
156            while (itDeps.hasNext()) {
157                CmsModuleDependency dependency = itDeps.next();
158                // module dependency package name
159                String moduleDependencyName = dependency.getName();
160
161                if (mode) {
162                    // get the list of dependent modules
163                    List<String> moduleDependencies = ret.get(moduleDependencyName);
164                    if (moduleDependencies == null) {
165                        // build a new list if "b" has no dependent modules yet
166                        moduleDependencies = new ArrayList<String>();
167                        ret.put(moduleDependencyName, moduleDependencies);
168                    }
169                    // add "a" as a module depending on "b"
170                    moduleDependencies.add(module.getName());
171                } else {
172                    List<String> moduleDependencies = ret.get(module.getName());
173                    if (moduleDependencies == null) {
174                        moduleDependencies = new ArrayList<String>();
175                        ret.put(module.getName(), moduleDependencies);
176                    }
177                    moduleDependencies.add(dependency.getName());
178                }
179            }
180        }
181        itMods = modules.iterator();
182        while (itMods.hasNext()) {
183            CmsModule module = itMods.next();
184            if (ret.get(module.getName()) == null) {
185                ret.put(module.getName(), new ArrayList<String>());
186            }
187        }
188        return ret;
189    }
190
191    /**
192     * Returns a map of dependencies between the given modules.<p>
193     *
194     * The module dependencies are get from the installed modules or
195     * from the module manifest.xml files found in the given FRS path.<p>
196     *
197     * Two types of dependency lists can be generated:<br>
198     * <ul>
199     *   <li>Forward dependency lists: a list of modules that depends on a module</li>
200     *   <li>Backward dependency lists: a list of modules that a module depends on</li>
201     * </ul>
202     *
203     * @param moduleNames a list of module names
204     * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules
205     * @param mode if <code>true</code> a list of forward dependency is build, is not a list of backward dependency
206     *
207     * @return a Map of module names as keys and a list of dependency names as values
208     *
209     * @throws CmsConfigurationException if something goes wrong
210     */
211    public static Map<String, List<String>> buildDepsForModulelist(
212        List<String> moduleNames,
213        String rfsAbsPath,
214        boolean mode) throws CmsConfigurationException {
215
216        Map<String, List<String>> ret = buildDepsForAllModules(rfsAbsPath, mode);
217        Iterator<CmsModule> itMods;
218        if (rfsAbsPath == null) {
219            itMods = OpenCms.getModuleManager().getAllInstalledModules().iterator();
220        } else {
221            itMods = getAllModulesFromPath(rfsAbsPath).keySet().iterator();
222        }
223        while (itMods.hasNext()) {
224            CmsModule module = itMods.next();
225            if (!moduleNames.contains(module.getName())) {
226                Iterator<List<String>> itDeps = ret.values().iterator();
227                while (itDeps.hasNext()) {
228                    List<String> dependencies = itDeps.next();
229                    dependencies.remove(module.getName());
230                }
231                ret.remove(module.getName());
232            }
233        }
234        return ret;
235    }
236
237    /**
238     * Returns a map of modules found in the given RFS absolute path.<p>
239     *
240     * @param rfsAbsPath the path to look for module distributions
241     *
242     * @return a map of <code>{@link CmsModule}</code> objects for keys and filename for values
243     *
244     * @throws CmsConfigurationException if something goes wrong
245     */
246    public static Map<CmsModule, String> getAllModulesFromPath(String rfsAbsPath) throws CmsConfigurationException {
247
248        Map<CmsModule, String> modules = new HashMap<CmsModule, String>();
249        if (rfsAbsPath == null) {
250            return modules;
251        }
252        File folder = new File(rfsAbsPath);
253        if (folder.exists()) {
254            // list all child resources in the given folder
255            File[] folderFiles = folder.listFiles();
256            if (folderFiles != null) {
257                for (int i = 0; i < folderFiles.length; i++) {
258                    File moduleFile = folderFiles[i];
259                    if (moduleFile.isFile() && !(moduleFile.getAbsolutePath().toLowerCase().endsWith(".zip"))) {
260                        // skip non-ZIP files
261                        continue;
262                    }
263                    if (moduleFile.isDirectory()) {
264                        File manifest = new File(moduleFile, CmsImportExportManager.EXPORT_MANIFEST);
265                        if (!manifest.exists() || !manifest.canRead()) {
266                            // skip unused directories
267                            continue;
268                        }
269                    }
270                    modules.put(
271                        CmsModuleImportExportHandler.readModuleFromImport(moduleFile.getAbsolutePath()),
272                        moduleFile.getName());
273                }
274            }
275        }
276        return modules;
277    }
278
279    /**
280     * Sorts a given list of module names by dependencies,
281     * so that the resulting list can be imported in that given order,
282     * that means modules without dependencies first.<p>
283     *
284     * The module dependencies are get from the installed modules or
285     * from the module manifest.xml files found in the given FRS path.<p>
286     *
287     * @param moduleNames a list of module names
288     * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules
289     *
290     * @return a sorted list of module names
291     *
292     * @throws CmsConfigurationException if something goes wrong
293     */
294    public static List<String> topologicalSort(List<String> moduleNames, String rfsAbsPath)
295    throws CmsConfigurationException {
296
297        List<String> modules = new ArrayList<String>(moduleNames);
298        List<String> retList = new ArrayList<String>();
299        Map<String, List<String>> moduleDependencies = buildDepsForModulelist(moduleNames, rfsAbsPath, true);
300        boolean finished = false;
301        while (!finished) {
302            finished = true;
303            Iterator<String> itMods = modules.iterator();
304            while (itMods.hasNext()) {
305                String moduleName = itMods.next();
306                List<String> deps = moduleDependencies.get(moduleName);
307                if ((deps == null) || deps.isEmpty()) {
308                    retList.add(moduleName);
309                    Iterator<List<String>> itDeps = moduleDependencies.values().iterator();
310                    while (itDeps.hasNext()) {
311                        List<String> dependencies = itDeps.next();
312                        dependencies.remove(moduleName);
313                    }
314                    finished = false;
315                    itMods.remove();
316                }
317            }
318        }
319        if (!modules.isEmpty()) {
320            throw new CmsIllegalStateException(
321                Messages.get().container(Messages.ERR_MODULE_DEPENDENCY_CYCLE_1, modules.toString()));
322        }
323        Collections.reverse(retList);
324        return retList;
325    }
326
327    /**
328     * Adds a new module to the module manager.<p>
329     *
330     * @param cms must be initialized with "Admin" permissions
331     * @param module the module to add
332     *
333     * @throws CmsSecurityException if the required permissions are not available (i.e. no "Admin" CmsObject has been provided)
334     * @throws CmsConfigurationException if a module with this name is already configured
335     */
336    public synchronized void addModule(CmsObject cms, CmsModule module)
337    throws CmsSecurityException, CmsConfigurationException {
338
339        // check the role permissions
340        OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
341
342        if (m_modules.containsKey(module.getName())) {
343            // module is currently configured, no create possible
344            throw new CmsConfigurationException(
345                Messages.get().container(Messages.ERR_MODULE_ALREADY_CONFIGURED_1, module.getName()));
346
347        }
348
349        if (LOG.isInfoEnabled()) {
350            LOG.info(Messages.get().getBundle().key(Messages.LOG_CREATE_NEW_MOD_1, module.getName()));
351        }
352
353        // initialize the module
354        module.initialize(cms);
355
356        m_modules.put(module.getName(), module);
357
358        try {
359            I_CmsModuleAction moduleAction = module.getActionInstance();
360            // handle module action instance if initialized
361            if (moduleAction != null) {
362                moduleAction.moduleUpdate(module);
363            }
364        } catch (Throwable t) {
365            LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_ERR_1, module.getName()), t);
366        }
367
368        // initialize the export points
369        initModuleExportPoints();
370
371        // update the configuration
372        updateModuleConfiguration();
373    }
374
375    /**
376     * Checks if a modules dependencies are fulfilled.<p>
377     *
378     * The possible values for the <code>mode</code> parameter are:<dl>
379     * <dt>{@link #DEPENDENCY_MODE_DELETE}</dt>
380     *      <dd>Check for module deleting, i.e. are other modules dependent on the
381     *          given module?</dd>
382     * <dt>{@link #DEPENDENCY_MODE_IMPORT}</dt>
383     *      <dd>Check for module importing, i.e. are all dependencies required by the given
384     *          module available?</dd></dl>
385     *
386     * @param module the module to check the dependencies for
387     * @param mode the dependency check mode
388     * @return a list of dependencies that are not fulfilled, if empty all dependencies are fulfilled
389     */
390    public List<CmsModuleDependency> checkDependencies(CmsModule module, int mode) {
391
392        List<CmsModuleDependency> result = new ArrayList<CmsModuleDependency>();
393
394        if (mode == DEPENDENCY_MODE_DELETE) {
395            // delete mode, check if other modules depend on this module
396            Iterator<CmsModule> i = m_modules.values().iterator();
397            while (i.hasNext()) {
398                CmsModule otherModule = i.next();
399                CmsModuleDependency dependency = otherModule.checkDependency(module);
400                if (dependency != null) {
401                    // dependency found, add to list
402                    result.add(new CmsModuleDependency(otherModule.getName(), otherModule.getVersion()));
403                }
404            }
405
406        } else if (mode == DEPENDENCY_MODE_IMPORT) {
407            // import mode, check if all module dependencies are fulfilled
408            Iterator<CmsModule> i = m_modules.values().iterator();
409            // add all dependencies that must be found
410            result.addAll(module.getDependencies());
411            while (i.hasNext() && (result.size() > 0)) {
412                CmsModule otherModule = i.next();
413                CmsModuleDependency dependency = module.checkDependency(otherModule);
414                if (dependency != null) {
415                    // dependency found, remove from list
416                    result.remove(dependency);
417                }
418            }
419        } else {
420            // invalid mode selected
421            throw new CmsRuntimeException(
422                Messages.get().container(Messages.ERR_CHECK_DEPENDENCY_INVALID_MODE_1, new Integer(mode)));
423        }
424
425        return result;
426    }
427
428    /**
429     * Checks the module selection list for consistency, that means
430     * that if a module is selected, all its dependencies are also selected.<p>
431     *
432     * The module dependencies are get from the installed modules or
433     * from the module manifest.xml files found in the given FRS path.<p>
434     *
435     * @param moduleNames a list of module names
436     * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules
437     * @param forDeletion there are two modes, one for installation of modules, and one for deletion.
438     *
439     * @throws CmsIllegalArgumentException if the module list is not consistent
440     * @throws CmsConfigurationException if something goes wrong
441     */
442    public void checkModuleSelectionList(List<String> moduleNames, String rfsAbsPath, boolean forDeletion)
443    throws CmsIllegalArgumentException, CmsConfigurationException {
444
445        Map<String, List<String>> moduleDependencies = buildDepsForAllModules(rfsAbsPath, forDeletion);
446        Iterator<String> itMods = moduleNames.iterator();
447        while (itMods.hasNext()) {
448            String moduleName = itMods.next();
449            List<String> dependencies = moduleDependencies.get(moduleName);
450            if (dependencies != null) {
451                List<String> depModules = new ArrayList<String>(dependencies);
452                depModules.removeAll(moduleNames);
453                if (!depModules.isEmpty()) {
454                    throw new CmsIllegalArgumentException(
455                        Messages.get().container(
456                            Messages.ERR_MODULE_SELECTION_INCONSISTENT_2,
457                            moduleName,
458                            depModules.toString()));
459                }
460            }
461        }
462    }
463
464    /**
465     * Deletes a module from the configuration.<p>
466     *
467     * @param cms must be initialized with "Admin" permissions
468     * @param moduleName the name of the module to delete
469     * @param replace indicates if the module is replaced (true) or finally deleted (false)
470     * @param report the report to print progress messages to
471     *
472     * @throws CmsRoleViolationException if the required module manager role permissions are not available
473     * @throws CmsConfigurationException if a module with this name is not available for deleting
474     * @throws CmsLockException if the module resources can not be locked
475     */
476    public synchronized void deleteModule(
477        CmsObject cms,
478        String moduleName,
479        boolean replace,
480        boolean preserveLibs,
481        I_CmsReport report) throws CmsRoleViolationException, CmsConfigurationException, CmsLockException {
482
483        // check for module manager role permissions
484        OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
485
486        if (!m_modules.containsKey(moduleName)) {
487            // module is not currently configured, no update possible
488            throw new CmsConfigurationException(
489                Messages.get().container(Messages.ERR_MODULE_NOT_CONFIGURED_1, moduleName));
490        }
491
492        if (LOG.isInfoEnabled()) {
493            LOG.info(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_1, moduleName));
494        }
495
496        CmsModule module = m_modules.get(moduleName);
497        String importSite = module.getImportSite();
498        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(importSite)) {
499            CmsObject newCms;
500            try {
501                newCms = OpenCms.initCmsObject(cms);
502                newCms.getRequestContext().setSiteRoot(importSite);
503                cms = newCms;
504            } catch (CmsException e) {
505                LOG.error(e.getLocalizedMessage(), e);
506            }
507        }
508
509        if (!replace) {
510            // module is deleted, not replaced
511
512            // perform dependency check
513            List<CmsModuleDependency> dependencies = checkDependencies(module, DEPENDENCY_MODE_DELETE);
514            if (!dependencies.isEmpty()) {
515                StringBuffer message = new StringBuffer();
516                Iterator<CmsModuleDependency> it = dependencies.iterator();
517                while (it.hasNext()) {
518                    message.append("  ").append(it.next().getName()).append("\r\n");
519                }
520                throw new CmsConfigurationException(
521                    Messages.get().container(Messages.ERR_MOD_DEPENDENCIES_2, moduleName, message.toString()));
522            }
523            try {
524                I_CmsModuleAction moduleAction = module.getActionInstance();
525                // handle module action instance if initialized
526                if (moduleAction != null) {
527                    moduleAction.moduleUninstall(module);
528                }
529            } catch (Throwable t) {
530                LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName), t);
531                report.println(
532                    Messages.get().container(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName),
533                    I_CmsReport.FORMAT_WARNING);
534            }
535        }
536
537        boolean removeResourceTypes = !module.getResourceTypes().isEmpty();
538        if (removeResourceTypes) {
539            // mark the resource manager to reinitialize if necessary
540            OpenCms.getWorkplaceManager().removeExplorerTypeSettings(module);
541        }
542
543        CmsProject previousProject = cms.getRequestContext().getCurrentProject();
544        // try to create a new offline project for deletion
545        CmsProject deleteProject = null;
546        try {
547            // try to read a (leftover) module delete project
548            deleteProject = cms.readProject(
549                Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
550                    Messages.GUI_DELETE_MODULE_PROJECT_NAME_1,
551                    new Object[] {moduleName}));
552        } catch (CmsException e) {
553            try {
554                // create a Project to delete the module
555                deleteProject = cms.createProject(
556                    Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
557                        Messages.GUI_DELETE_MODULE_PROJECT_NAME_1,
558                        new Object[] {moduleName}),
559                    Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
560                        Messages.GUI_DELETE_MODULE_PROJECT_DESC_1,
561                        new Object[] {moduleName}),
562                    OpenCms.getDefaultUsers().getGroupAdministrators(),
563                    OpenCms.getDefaultUsers().getGroupAdministrators(),
564                    CmsProject.PROJECT_TYPE_TEMPORARY);
565            } catch (CmsException e1) {
566                throw new CmsConfigurationException(e1.getMessageContainer(), e1);
567            }
568        }
569
570        try {
571            cms.getRequestContext().setCurrentProject(deleteProject);
572
573            // check locks
574            List<String> lockedResources = new ArrayList<String>();
575            CmsLockFilter filter1 = CmsLockFilter.FILTER_ALL.filterNotLockableByUser(
576                cms.getRequestContext().getCurrentUser());
577            CmsLockFilter filter2 = CmsLockFilter.FILTER_INHERITED;
578            List<String> moduleResources = module.getResources();
579            for (int iLock = 0; iLock < moduleResources.size(); iLock++) {
580                String resourceName = moduleResources.get(iLock);
581                try {
582                    lockedResources.addAll(cms.getLockedResources(resourceName, filter1));
583                    lockedResources.addAll(cms.getLockedResources(resourceName, filter2));
584                } catch (CmsException e) {
585                    // may happen if the resource has already been deleted
586                    if (LOG.isDebugEnabled()) {
587                        LOG.debug(e.getMessageContainer(), e);
588                    }
589                    report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING);
590                }
591            }
592            if (!lockedResources.isEmpty()) {
593                CmsMessageContainer msg = Messages.get().container(
594                    Messages.ERR_DELETE_MODULE_CHECK_LOCKS_2,
595                    moduleName,
596                    CmsStringUtil.collectionAsString(lockedResources, ","));
597                report.addError(msg.key(cms.getRequestContext().getLocale()));
598                report.println(msg);
599                cms.getRequestContext().setCurrentProject(previousProject);
600                try {
601                    cms.deleteProject(deleteProject.getUuid());
602                } catch (CmsException e1) {
603                    throw new CmsConfigurationException(e1.getMessageContainer(), e1);
604                }
605                throw new CmsLockException(msg);
606            }
607        } finally {
608            cms.getRequestContext().setCurrentProject(previousProject);
609        }
610
611        // now remove the module
612        module = m_modules.remove(moduleName);
613
614        if (preserveLibs) {
615            // to preserve the module libs, remove the responsible export points, before deleting module resources
616            Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>(m_moduleExportPoints);
617            Iterator<CmsExportPoint> it = exportPoints.iterator();
618            while (it.hasNext()) {
619                CmsExportPoint point = it.next();
620                if ((point.getUri().endsWith(module.getName() + "/lib/")
621                    || point.getUri().endsWith(module.getName() + "/lib"))
622                    && point.getConfiguredDestination().equals("WEB-INF/lib/")) {
623                    it.remove();
624                }
625            }
626
627            m_moduleExportPoints = Collections.unmodifiableSet(exportPoints);
628        }
629
630        try {
631            cms.getRequestContext().setCurrentProject(deleteProject);
632
633            // copy the module resources to the project
634            List<CmsResource> moduleResources = CmsModule.calculateModuleResources(cms, module);
635            for (CmsResource resource : moduleResources) {
636                try {
637                    cms.copyResourceToProject(resource);
638                } catch (CmsException e) {
639                    // may happen if the resource has already been deleted
640                    if (LOG.isDebugEnabled()) {
641                        LOG.debug(
642                            Messages.get().getBundle().key(
643                                Messages.LOG_MOVE_RESOURCE_FAILED_1,
644                                cms.getSitePath(resource)));
645                    }
646                    report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING);
647                }
648            }
649
650            report.print(Messages.get().container(Messages.RPT_DELETE_MODULE_BEGIN_0), I_CmsReport.FORMAT_HEADLINE);
651            report.println(
652                org.opencms.report.Messages.get().container(
653                    org.opencms.report.Messages.RPT_ARGUMENT_HTML_ITAG_1,
654                    moduleName),
655                I_CmsReport.FORMAT_HEADLINE);
656
657            // move through all module resources and delete them
658            for (CmsResource resource : moduleResources) {
659                String sitePath = cms.getSitePath(resource);
660                try {
661                    if (LOG.isDebugEnabled()) {
662                        LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_RESOURCE_1, sitePath));
663                    }
664                    CmsLock lock = cms.getLock(resource);
665                    if (lock.isUnlocked()) {
666                        // lock the resource
667                        cms.lockResource(resource);
668                    } else if (lock.isLockableBy(cms.getRequestContext().getCurrentUser())) {
669                        // steal the resource
670                        cms.changeLock(resource);
671                    }
672                    if (!resource.getState().isDeleted()) {
673                        // delete the resource
674                        cms.deleteResource(sitePath, CmsResource.DELETE_PRESERVE_SIBLINGS);
675                    }
676                    // update the report
677                    report.print(Messages.get().container(Messages.RPT_DELETE_0), I_CmsReport.FORMAT_NOTE);
678                    report.println(
679                        org.opencms.report.Messages.get().container(
680                            org.opencms.report.Messages.RPT_ARGUMENT_1,
681                            sitePath));
682                    if (!resource.getState().isNew()) {
683                        // unlock the resource (so it gets deleted with next publish)
684                        cms.unlockResource(resource);
685                    }
686                } catch (CmsException e) {
687                    // ignore the exception and delete the next resource
688                    LOG.error(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_EXC_1, sitePath), e);
689                    report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING);
690                }
691            }
692
693            report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0), I_CmsReport.FORMAT_HEADLINE);
694
695            // now unlock and publish the project
696            cms.unlockProject(deleteProject.getUuid());
697            OpenCms.getPublishManager().publishProject(cms, report);
698            OpenCms.getPublishManager().waitWhileRunning();
699
700            report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0), I_CmsReport.FORMAT_HEADLINE);
701            report.println(Messages.get().container(Messages.RPT_DELETE_MODULE_END_0), I_CmsReport.FORMAT_HEADLINE);
702        } catch (CmsException e) {
703            throw new CmsConfigurationException(e.getMessageContainer(), e);
704        } finally {
705            cms.getRequestContext().setCurrentProject(previousProject);
706        }
707
708        // initialize the export points (removes export points from deleted module)
709        initModuleExportPoints();
710
711        // update the configuration
712        updateModuleConfiguration();
713
714        // reinit the manager is necessary
715        if (removeResourceTypes) {
716            OpenCms.getResourceManager().initialize(cms);
717        }
718    }
719
720    /**
721     * Deletes a module from the configuration.<p>
722     *
723     * @param cms must be initialized with "Admin" permissions
724     * @param moduleName the name of the module to delete
725     * @param replace indicates if the module is replaced (true) or finally deleted (false)
726     * @param report the report to print progress messages to
727     *
728     * @throws CmsRoleViolationException if the required module manager role permissions are not available
729     * @throws CmsConfigurationException if a module with this name is not available for deleting
730     * @throws CmsLockException if the module resources can not be locked
731     */
732    public synchronized void deleteModule(CmsObject cms, String moduleName, boolean replace, I_CmsReport report)
733    throws CmsRoleViolationException, CmsConfigurationException, CmsLockException {
734
735        deleteModule(cms, moduleName, replace, false, report);
736    }
737
738    /**
739     * Returns a list of installed modules.<p>
740     *
741     * @return a list of <code>{@link CmsModule}</code> objects
742     */
743    public List<CmsModule> getAllInstalledModules() {
744
745        return new ArrayList<CmsModule>(m_modules.values());
746    }
747
748    /**
749     * Returns the (immutable) list of configured module export points.<p>
750     *
751     * @return the (immutable) list of configured module export points
752     * @see CmsExportPoint
753     */
754    public Set<CmsExportPoint> getExportPoints() {
755
756        return m_moduleExportPoints;
757    }
758
759    /**
760     * Returns the importExportRepository.<p>
761     *
762     * @return the importExportRepository
763     */
764    public CmsModuleImportExportRepository getImportExportRepository() {
765
766        return m_importExportRepository;
767    }
768
769    /**
770     * Returns the module with the given module name,
771     * or <code>null</code> if no module with the given name is configured.<p>
772     *
773     * @param name the name of the module to return
774     * @return the module with the given module name
775     */
776    public CmsModule getModule(String name) {
777
778        return m_modules.get(name);
779    }
780
781    /**
782     * Returns the set of names of all the installed modules.<p>
783     *
784     * @return the set of names of all the installed modules
785     */
786    public Set<String> getModuleNames() {
787
788        synchronized (m_modules) {
789            return new HashSet<String>(m_modules.keySet());
790        }
791    }
792
793    /**
794     * Checks if this module manager has a module with the given name installed.<p>
795     *
796     * @param name the name of the module to check
797     * @return true if this module manager has a module with the given name installed
798     */
799    public boolean hasModule(String name) {
800
801        return m_modules.containsKey(name);
802    }
803
804    /**
805     * Initializes all module instance classes managed in this module manager.<p>
806     *
807     * @param cms an initialized CmsObject with "manage modules" role permissions
808     * @param configurationManager the initialized OpenCms configuration manager
809     *
810     * @throws CmsRoleViolationException if the provided OpenCms context does not have "manage modules" role permissions
811     */
812    public synchronized void initialize(CmsObject cms, CmsConfigurationManager configurationManager)
813    throws CmsRoleViolationException {
814
815        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
816            // certain test cases won't have an OpenCms context
817            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
818        }
819
820        Iterator<String> it;
821        int count = 0;
822        it = m_modules.keySet().iterator();
823        while (it.hasNext()) {
824            // get the module description
825            CmsModule module = m_modules.get(it.next());
826
827            if (module.getActionClass() != null) {
828                // create module instance class
829                I_CmsModuleAction moduleAction = module.getActionInstance();
830                if (module.getActionClass() != null) {
831                    try {
832                        moduleAction = (I_CmsModuleAction)Class.forName(module.getActionClass()).newInstance();
833                    } catch (Exception e) {
834                        CmsLog.INIT.info(
835                            Messages.get().getBundle().key(Messages.INIT_CREATE_INSTANCE_FAILED_1, module.getName()),
836                            e);
837                    }
838                }
839                if (moduleAction != null) {
840                    count++;
841                    module.setActionInstance(moduleAction);
842                    if (CmsLog.INIT.isInfoEnabled()) {
843                        CmsLog.INIT.info(
844                            Messages.get().getBundle().key(
845                                Messages.INIT_INITIALIZE_MOD_CLASS_1,
846                                moduleAction.getClass().getName()));
847                    }
848                    try {
849                        // create a copy of the adminCms so that each module instance does have
850                        // it's own context, a shared context might introduce side - effects
851                        CmsObject adminCmsCopy = OpenCms.initCmsObject(cms);
852                        // initialize the module
853                        moduleAction.initialize(adminCmsCopy, configurationManager, module);
854                    } catch (Throwable t) {
855                        LOG.error(
856                            Messages.get().getBundle().key(
857                                Messages.LOG_INSTANCE_INIT_ERR_1,
858                                moduleAction.getClass().getName()),
859                            t);
860                    }
861                }
862            }
863        }
864
865        // initialize the export points
866        initModuleExportPoints();
867        m_importExportRepository.initialize(cms);
868
869        if (CmsLog.INIT.isInfoEnabled()) {
870            CmsLog.INIT.info(
871                Messages.get().getBundle().key(Messages.INIT_NUM_CLASSES_INITIALIZED_1, new Integer(count)));
872        }
873    }
874
875    /**
876     * Shuts down all module instance classes managed in this module manager.<p>
877     */
878    public synchronized void shutDown() {
879
880        int count = 0;
881        Iterator<String> it = getModuleNames().iterator();
882        while (it.hasNext()) {
883            String moduleName = it.next();
884            // get the module
885            CmsModule module = m_modules.get(moduleName);
886            if (module == null) {
887                continue;
888            }
889            // get the module action instance
890            I_CmsModuleAction moduleAction = module.getActionInstance();
891            if (moduleAction == null) {
892                continue;
893            }
894
895            count++;
896            if (CmsLog.INIT.isInfoEnabled()) {
897                CmsLog.INIT.info(
898                    Messages.get().getBundle().key(
899                        Messages.INIT_SHUTDOWN_MOD_CLASS_1,
900                        moduleAction.getClass().getName()));
901            }
902            try {
903                // shut down the module
904                moduleAction.shutDown(module);
905            } catch (Throwable t) {
906                LOG.error(
907                    Messages.get().getBundle().key(
908                        Messages.LOG_INSTANCE_SHUTDOWN_ERR_1,
909                        moduleAction.getClass().getName()),
910                    t);
911            }
912        }
913
914        if (CmsLog.INIT.isInfoEnabled()) {
915            CmsLog.INIT.info(
916                Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_NUM_MOD_CLASSES_1, new Integer(count)));
917        }
918
919        if (CmsLog.INIT.isInfoEnabled()) {
920            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName()));
921        }
922    }
923
924    /**
925     * Updates a already configured module with new values.<p>
926     *
927     * @param cms must be initialized with "Admin" permissions
928     * @param module the module to update
929     *
930     * @throws CmsRoleViolationException if the required module manager role permissions are not available
931     * @throws CmsConfigurationException if a module with this name is not available for updating
932     */
933    public synchronized void updateModule(CmsObject cms, CmsModule module)
934    throws CmsRoleViolationException, CmsConfigurationException {
935
936        // check for module manager role permissions
937        OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
938
939        CmsModule oldModule = m_modules.get(module.getName());
940
941        if (oldModule == null) {
942            // module is not currently configured, no update possible
943            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_OLD_MOD_ERR_1, module.getName()));
944        }
945
946        if (LOG.isInfoEnabled()) {
947            LOG.info(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_1, module.getName()));
948        }
949
950        // indicate that the version number was recently updated
951        module.getVersion().setUpdated(true);
952
953        // initialize (freeze) the module
954        module.initialize(cms);
955
956        // replace old version of module with new version
957        m_modules.put(module.getName(), module);
958
959        try {
960            I_CmsModuleAction moduleAction = oldModule.getActionInstance();
961            // handle module action instance if initialized
962            if (moduleAction != null) {
963                moduleAction.moduleUpdate(module);
964                // set the old action instance
965                // the new action instance will be used after a system restart
966                module.setActionInstance(moduleAction);
967            }
968        } catch (Throwable t) {
969            LOG.error(Messages.get().getBundle().key(Messages.LOG_INSTANCE_UPDATE_ERR_1, module.getName()), t);
970        }
971
972        // initialize the export points
973        initModuleExportPoints();
974
975        // update the configuration
976        updateModuleConfiguration();
977    }
978
979    /**
980     * Initializes the list of export points from all configured modules.<p>
981     */
982    private synchronized void initModuleExportPoints() {
983
984        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
985        Iterator<CmsModule> i = m_modules.values().iterator();
986        while (i.hasNext()) {
987            CmsModule module = i.next();
988            List<CmsExportPoint> moduleExportPoints = module.getExportPoints();
989            for (int j = 0; j < moduleExportPoints.size(); j++) {
990                CmsExportPoint point = moduleExportPoints.get(j);
991                if (exportPoints.contains(point)) {
992                    if (LOG.isWarnEnabled()) {
993                        LOG.warn(
994                            Messages.get().getBundle().key(
995                                Messages.LOG_DUPLICATE_EXPORT_POINT_2,
996                                point,
997                                module.getName()));
998                    }
999                } else {
1000                    exportPoints.add(point);
1001                    if (LOG.isDebugEnabled()) {
1002                        LOG.debug(
1003                            Messages.get().getBundle().key(Messages.LOG_ADD_EXPORT_POINT_2, point, module.getName()));
1004                    }
1005                }
1006            }
1007        }
1008        m_moduleExportPoints = Collections.unmodifiableSet(exportPoints);
1009    }
1010
1011    /**
1012     * Updates the module configuration.<p>
1013     */
1014    private void updateModuleConfiguration() {
1015
1016        OpenCms.writeConfiguration(CmsModuleConfiguration.class);
1017    }
1018}