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.file.CmsResourceFilter;
038import org.opencms.file.CmsVfsResourceNotFoundException;
039import org.opencms.i18n.CmsMessageContainer;
040import org.opencms.importexport.CmsImportExportManager;
041import org.opencms.lock.CmsLock;
042import org.opencms.lock.CmsLockException;
043import org.opencms.lock.CmsLockFilter;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsIllegalArgumentException;
046import org.opencms.main.CmsIllegalStateException;
047import org.opencms.main.CmsLog;
048import org.opencms.main.CmsRuntimeException;
049import org.opencms.main.OpenCms;
050import org.opencms.report.I_CmsReport;
051import org.opencms.security.CmsRole;
052import org.opencms.security.CmsRoleViolationException;
053import org.opencms.security.CmsSecurityException;
054import org.opencms.util.CmsStringUtil;
055
056import java.io.File;
057import java.util.ArrayList;
058import java.util.Collections;
059import java.util.HashMap;
060import java.util.HashSet;
061import java.util.Hashtable;
062import java.util.Iterator;
063import java.util.List;
064import java.util.Map;
065import java.util.Set;
066
067import org.apache.commons.logging.Log;
068
069/**
070 * Manages the modules of an OpenCms installation.<p>
071 * 
072 * @since 6.0.0 
073 */
074public class CmsModuleManager {
075
076    /** Indicates dependency check for module deletion. */
077    public static final int DEPENDENCY_MODE_DELETE = 0;
078
079    /** Indicates dependency check for module import. */
080    public static final int DEPENDENCY_MODE_IMPORT = 1;
081
082    /** The log object for this class. */
083    private static final Log LOG = CmsLog.getLog(CmsModuleManager.class);
084
085    /** The list of module export points. */
086    private Set<CmsExportPoint> m_moduleExportPoints;
087
088    /** The map of configured modules. */
089    private Map<String, CmsModule> m_modules;
090
091    /**
092     * Basic constructor.<p>
093     * 
094     * @param configuredModules the list of configured modules 
095     */
096    public CmsModuleManager(List<CmsModule> configuredModules) {
097
098        if (CmsLog.INIT.isInfoEnabled()) {
099            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_MOD_MANAGER_CREATED_0));
100        }
101
102        m_modules = new Hashtable<String, CmsModule>();
103        for (int i = 0; i < configuredModules.size(); i++) {
104            CmsModule module = configuredModules.get(i);
105            m_modules.put(module.getName(), module);
106            if (CmsLog.INIT.isInfoEnabled()) {
107                CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_MOD_CONFIGURED_1, module.getName()));
108            }
109        }
110
111        if (CmsLog.INIT.isInfoEnabled()) {
112            CmsLog.INIT.info(Messages.get().getBundle().key(
113                Messages.INIT_NUM_MODS_CONFIGURED_1,
114                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(Messages.get().container(
321                Messages.ERR_MODULE_DEPENDENCY_CYCLE_1,
322                modules.toString()));
323        }
324        Collections.reverse(retList);
325        return retList;
326    }
327
328    /**
329     * Adds a new module to the module manager.<p>
330     * 
331     * @param cms must be initialized with "Admin" permissions 
332     * @param module the module to add
333     * 
334     * @throws CmsSecurityException if the required permissions are not available (i.e. no "Admin" CmsObject has been provided)
335     * @throws CmsConfigurationException if a module with this name is already configured 
336     */
337    public synchronized void addModule(CmsObject cms, CmsModule module)
338    throws CmsSecurityException, CmsConfigurationException {
339
340        // check the role permissions
341        OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
342
343        if (m_modules.containsKey(module.getName())) {
344            // module is currently configured, no create possible
345            throw new CmsConfigurationException(Messages.get().container(
346                Messages.ERR_MODULE_ALREADY_CONFIGURED_1,
347                module.getName()));
348
349        }
350
351        if (LOG.isInfoEnabled()) {
352            LOG.info(Messages.get().getBundle().key(Messages.LOG_CREATE_NEW_MOD_1, module.getName()));
353        }
354
355        // initialize the module
356        module.initialize(cms);
357
358        m_modules.put(module.getName(), module);
359
360        try {
361            I_CmsModuleAction moduleAction = module.getActionInstance();
362            // handle module action instance if initialized
363            if (moduleAction != null) {
364                moduleAction.moduleUpdate(module);
365            }
366        } catch (Throwable t) {
367            LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_ERR_1, module.getName()), t);
368        }
369
370        // initialize the export points
371        initModuleExportPoints();
372
373        // update the configuration
374        updateModuleConfiguration();
375    }
376
377    /**
378     * Checks if a modules dependencies are fulfilled.<p> 
379     * 
380     * The possible values for the <code>mode</code> parameter are:<dl>
381     * <dt>{@link #DEPENDENCY_MODE_DELETE}</dt>
382     *      <dd>Check for module deleting, i.e. are other modules dependent on the 
383     *          given module?</dd>
384     * <dt>{@link #DEPENDENCY_MODE_IMPORT}</dt>
385     *      <dd>Check for module importing, i.e. are all dependencies required by the given
386     *          module available?</dd></dl>
387     * 
388     * @param module the module to check the dependencies for
389     * @param mode the dependency check mode
390     * @return a list of dependencies that are not fulfilled, if empty all dependencies are fulfilled
391     */
392    public List<CmsModuleDependency> checkDependencies(CmsModule module, int mode) {
393
394        List<CmsModuleDependency> result = new ArrayList<CmsModuleDependency>();
395
396        if (mode == DEPENDENCY_MODE_DELETE) {
397            // delete mode, check if other modules depend on this module
398            Iterator<CmsModule> i = m_modules.values().iterator();
399            while (i.hasNext()) {
400                CmsModule otherModule = i.next();
401                CmsModuleDependency dependency = otherModule.checkDependency(module);
402                if (dependency != null) {
403                    // dependency found, add to list
404                    result.add(new CmsModuleDependency(otherModule.getName(), otherModule.getVersion()));
405                }
406            }
407
408        } else if (mode == DEPENDENCY_MODE_IMPORT) {
409            // import mode, check if all module dependencies are fulfilled            
410            Iterator<CmsModule> i = m_modules.values().iterator();
411            // add all dependencies that must be found
412            result.addAll(module.getDependencies());
413            while (i.hasNext() && (result.size() > 0)) {
414                CmsModule otherModule = i.next();
415                CmsModuleDependency dependency = module.checkDependency(otherModule);
416                if (dependency != null) {
417                    // dependency found, remove from list
418                    result.remove(dependency);
419                }
420            }
421        } else {
422            // invalid mode selected
423            throw new CmsRuntimeException(Messages.get().container(
424                Messages.ERR_CHECK_DEPENDENCY_INVALID_MODE_1,
425                new Integer(mode)));
426        }
427
428        return result;
429    }
430
431    /**
432     * Checks the module selection list for consistency, that means 
433     * that if a module is selected, all its dependencies are also selected.<p>
434     * 
435     * The module dependencies are get from the installed modules or
436     * from the module manifest.xml files found in the given FRS path.<p>
437     * 
438     * @param moduleNames a list of module names
439     * @param rfsAbsPath a RFS absolute path to search for modules, or <code>null</code> to use the installed modules
440     * @param forDeletion there are two modes, one for installation of modules, and one for deletion.
441     * 
442     * @throws CmsIllegalArgumentException if the module list is not consistent
443     * @throws CmsConfigurationException if something goes wrong
444     */
445    public void checkModuleSelectionList(List<String> moduleNames, String rfsAbsPath, boolean forDeletion)
446    throws CmsIllegalArgumentException, CmsConfigurationException {
447
448        Map<String, List<String>> moduleDependencies = buildDepsForAllModules(rfsAbsPath, forDeletion);
449        Iterator<String> itMods = moduleNames.iterator();
450        while (itMods.hasNext()) {
451            String moduleName = itMods.next();
452            List<String> dependencies = moduleDependencies.get(moduleName);
453            if (dependencies != null) {
454                List<String> depModules = new ArrayList<String>(dependencies);
455                depModules.removeAll(moduleNames);
456                if (!depModules.isEmpty()) {
457                    throw new CmsIllegalArgumentException(Messages.get().container(
458                        Messages.ERR_MODULE_SELECTION_INCONSISTENT_2,
459                        moduleName,
460                        depModules.toString()));
461                }
462            }
463        }
464    }
465
466    /**
467     * Deletes a module from the configuration.<p>
468     * 
469     * @param cms must be initialized with "Admin" permissions 
470     * @param moduleName the name of the module to delete
471     * @param replace indicates if the module is replaced (true) or finally deleted (false)
472     * @param report the report to print progress messages to
473     * 
474     * @throws CmsRoleViolationException if the required module manager role permissions are not available 
475     * @throws CmsConfigurationException if a module with this name is not available for deleting
476     * @throws CmsLockException if the module resources can not be locked
477     */
478    public synchronized void deleteModule(CmsObject cms, String moduleName, boolean replace, I_CmsReport report)
479    throws CmsRoleViolationException, CmsConfigurationException, CmsLockException {
480
481        // check for module manager role permissions
482        OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
483
484        if (!m_modules.containsKey(moduleName)) {
485            // module is not currently configured, no update possible
486            throw new CmsConfigurationException(Messages.get().container(
487                Messages.ERR_MODULE_NOT_CONFIGURED_1,
488                moduleName));
489        }
490
491        if (LOG.isInfoEnabled()) {
492            LOG.info(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_1, moduleName));
493        }
494
495        CmsModule module = m_modules.get(moduleName);
496
497        if (!replace) {
498            // module is deleted, not replaced
499
500            // perform dependency check
501            List<CmsModuleDependency> dependencies = checkDependencies(module, DEPENDENCY_MODE_DELETE);
502            if (!dependencies.isEmpty()) {
503                StringBuffer message = new StringBuffer();
504                Iterator<CmsModuleDependency> it = dependencies.iterator();
505                while (it.hasNext()) {
506                    message.append("  ").append(it.next().getName()).append("\r\n");
507                }
508                throw new CmsConfigurationException(Messages.get().container(
509                    Messages.ERR_MOD_DEPENDENCIES_2,
510                    moduleName,
511                    message.toString()));
512            }
513            try {
514                I_CmsModuleAction moduleAction = module.getActionInstance();
515                // handle module action instance if initialized
516                if (moduleAction != null) {
517                    moduleAction.moduleUninstall(module);
518                }
519            } catch (Throwable t) {
520                LOG.error(Messages.get().getBundle().key(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName), t);
521                report.println(
522                    Messages.get().container(Messages.LOG_MOD_UNINSTALL_ERR_1, moduleName),
523                    I_CmsReport.FORMAT_WARNING);
524            }
525        }
526
527        boolean removeResourceTypes = !module.getResourceTypes().isEmpty();
528        if (removeResourceTypes) {
529            // mark the resource manager to reinitialize if necessary
530            OpenCms.getWorkplaceManager().removeExplorerTypeSettings(module);
531        }
532
533        CmsProject previousProject = cms.getRequestContext().getCurrentProject();
534        // try to create a new offline project for deletion 
535        CmsProject deleteProject = null;
536        try {
537            // try to read a (leftover) module delete project
538            deleteProject = cms.readProject(Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
539                Messages.GUI_DELETE_MODULE_PROJECT_NAME_1,
540                new Object[] {moduleName}));
541        } catch (CmsException e) {
542            try {
543                // create a Project to delete the module
544                deleteProject = cms.createProject(
545                    Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
546                        Messages.GUI_DELETE_MODULE_PROJECT_NAME_1,
547                        new Object[] {moduleName}),
548                    Messages.get().getBundle(cms.getRequestContext().getLocale()).key(
549                        Messages.GUI_DELETE_MODULE_PROJECT_DESC_1,
550                        new Object[] {moduleName}),
551                    OpenCms.getDefaultUsers().getGroupAdministrators(),
552                    OpenCms.getDefaultUsers().getGroupAdministrators(),
553                    CmsProject.PROJECT_TYPE_TEMPORARY);
554            } catch (CmsException e1) {
555                throw new CmsConfigurationException(e1.getMessageContainer(), e1);
556            }
557        }
558
559        try {
560            cms.getRequestContext().setCurrentProject(deleteProject);
561
562            // check locks
563            List<String> lockedResources = new ArrayList<String>();
564            CmsLockFilter filter1 = CmsLockFilter.FILTER_ALL.filterNotLockableByUser(cms.getRequestContext().getCurrentUser());
565            CmsLockFilter filter2 = CmsLockFilter.FILTER_INHERITED;
566            List<String> moduleResources = module.getResources();
567            for (int iLock = 0; iLock < moduleResources.size(); iLock++) {
568                String resourceName = moduleResources.get(iLock);
569                try {
570                    lockedResources.addAll(cms.getLockedResources(resourceName, filter1));
571                    lockedResources.addAll(cms.getLockedResources(resourceName, filter2));
572                } catch (CmsException e) {
573                    // may happen if the resource has already been deleted
574                    if (LOG.isDebugEnabled()) {
575                        LOG.debug(e.getMessageContainer(), e);
576                    }
577                    report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING);
578                }
579            }
580            if (!lockedResources.isEmpty()) {
581                CmsMessageContainer msg = Messages.get().container(
582                    Messages.ERR_DELETE_MODULE_CHECK_LOCKS_2,
583                    moduleName,
584                    CmsStringUtil.collectionAsString(lockedResources, ","));
585                report.addError(msg.key(cms.getRequestContext().getLocale()));
586                report.println(msg);
587                cms.getRequestContext().setCurrentProject(previousProject);
588                try {
589                    cms.deleteProject(deleteProject.getUuid());
590                } catch (CmsException e1) {
591                    throw new CmsConfigurationException(e1.getMessageContainer(), e1);
592                }
593                throw new CmsLockException(msg);
594            }
595        } finally {
596            cms.getRequestContext().setCurrentProject(previousProject);
597        }
598
599        // now remove the module
600        module = m_modules.remove(moduleName);
601
602        try {
603            cms.getRequestContext().setCurrentProject(deleteProject);
604
605            // copy the module resources to the project
606            List<String> projectFiles = module.getResources();
607            for (int i = 0; i < projectFiles.size(); i++) {
608                String resourceName = projectFiles.get(i);
609                if (cms.existsResource(resourceName, CmsResourceFilter.ALL)) {
610                    try {
611                        cms.copyResourceToProject(resourceName);
612                    } catch (CmsException e) {
613                        // may happen if the resource has already been deleted
614                        if (LOG.isDebugEnabled()) {
615                            LOG.debug(Messages.get().getBundle().key(Messages.LOG_MOVE_RESOURCE_FAILED_1, resourceName));
616                        }
617                        report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING);
618                    }
619                }
620            }
621
622            report.print(Messages.get().container(Messages.RPT_DELETE_MODULE_BEGIN_0), I_CmsReport.FORMAT_HEADLINE);
623            report.println(
624                org.opencms.report.Messages.get().container(
625                    org.opencms.report.Messages.RPT_ARGUMENT_HTML_ITAG_1,
626                    moduleName),
627                I_CmsReport.FORMAT_HEADLINE);
628
629            // move through all module resources and delete them
630            for (int i = 0; i < module.getResources().size(); i++) {
631                String currentResource = null;
632                try {
633                    currentResource = module.getResources().get(i);
634                    if (LOG.isDebugEnabled()) {
635                        LOG.debug(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_RESOURCE_1, currentResource));
636                    }
637                    CmsResource resource = null;
638                    try {
639                        resource = cms.readResource(currentResource, CmsResourceFilter.ALL);
640                    } catch (CmsVfsResourceNotFoundException e) {
641                        // ignore
642                    }
643                    if (resource != null) {
644                        CmsLock lock = cms.getLock(currentResource);
645                        if (lock.isUnlocked()) {
646                            // lock the resource
647                            cms.lockResource(currentResource);
648                        } else if (lock.isLockableBy(cms.getRequestContext().getCurrentUser())) {
649                            // steal the resource
650                            cms.changeLock(currentResource);
651                        }
652                        if (!resource.getState().isDeleted()) {
653                            // delete the resource
654                            cms.deleteResource(currentResource, CmsResource.DELETE_PRESERVE_SIBLINGS);
655                        }
656                        // update the report
657                        report.print(Messages.get().container(Messages.RPT_DELETE_0), I_CmsReport.FORMAT_NOTE);
658                        report.println(org.opencms.report.Messages.get().container(
659                            org.opencms.report.Messages.RPT_ARGUMENT_1,
660                            currentResource));
661                        if (!resource.getState().isNew()) {
662                            // unlock the resource (so it gets deleted with next publish)
663                            cms.unlockResource(currentResource);
664                        }
665                    }
666                } catch (CmsException e) {
667                    // ignore the exception and delete the next resource
668                    LOG.error(Messages.get().getBundle().key(Messages.LOG_DEL_MOD_EXC_1, currentResource), e);
669                    report.println(e.getMessageContainer(), I_CmsReport.FORMAT_WARNING);
670                }
671            }
672
673            report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_BEGIN_0), I_CmsReport.FORMAT_HEADLINE);
674
675            // now unlock and publish the project
676            cms.unlockProject(deleteProject.getUuid());
677            OpenCms.getPublishManager().publishProject(cms, report);
678            OpenCms.getPublishManager().waitWhileRunning();
679
680            report.println(Messages.get().container(Messages.RPT_PUBLISH_PROJECT_END_0), I_CmsReport.FORMAT_HEADLINE);
681            report.println(Messages.get().container(Messages.RPT_DELETE_MODULE_END_0), I_CmsReport.FORMAT_HEADLINE);
682        } catch (CmsException e) {
683            throw new CmsConfigurationException(e.getMessageContainer(), e);
684        } finally {
685            cms.getRequestContext().setCurrentProject(previousProject);
686        }
687
688        // initialize the export points (removes export points from deleted module)
689        initModuleExportPoints();
690
691        // update the configuration
692        updateModuleConfiguration();
693
694        // reinit the manager is necessary
695        if (removeResourceTypes) {
696            OpenCms.getResourceManager().initialize(cms);
697        }
698    }
699
700    /**
701     * Returns a list of installed modules.<p>
702     * 
703     * @return a list of <code>{@link CmsModule}</code> objects
704     */
705    public List<CmsModule> getAllInstalledModules() {
706
707        return new ArrayList<CmsModule>(m_modules.values());
708    }
709
710    /**
711     * Returns the (immutable) list of configured module export points.<p>
712     * 
713     * @return the (immutable) list of configured module export points
714     * @see CmsExportPoint
715     */
716    public Set<CmsExportPoint> getExportPoints() {
717
718        return m_moduleExportPoints;
719    }
720
721    /**
722     * Returns the module with the given module name,
723     * or <code>null</code> if no module with the given name is configured.<p>
724     * 
725     * @param name the name of the module to return
726     * @return the module with the given module name
727     */
728    public CmsModule getModule(String name) {
729
730        return m_modules.get(name);
731    }
732
733    /**
734     * Returns the set of names of all the installed modules.<p>
735     * 
736     * @return the set of names of all the installed modules
737     */
738    public Set<String> getModuleNames() {
739
740        synchronized (m_modules) {
741            return new HashSet<String>(m_modules.keySet());
742        }
743    }
744
745    /**
746     * Checks if this module manager has a module with the given name installed.<p>
747     * 
748     * @param name the name of the module to check
749     * @return true if this module manager has a module with the given name installed
750     */
751    public boolean hasModule(String name) {
752
753        return m_modules.containsKey(name);
754    }
755
756    /**
757     * Initializes all module instance classes managed in this module manager.<p>
758     * 
759     * @param cms an initialized CmsObject with "manage modules" role permissions
760     * @param configurationManager the initialized OpenCms configuration manager
761     * 
762     * @throws CmsRoleViolationException if the provided OpenCms context does not have "manage modules" role permissions
763     */
764    public synchronized void initialize(CmsObject cms, CmsConfigurationManager configurationManager)
765    throws CmsRoleViolationException {
766
767        if (OpenCms.getRunLevel() > OpenCms.RUNLEVEL_1_CORE_OBJECT) {
768            // certain test cases won't have an OpenCms context
769            OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
770        }
771
772        Iterator<String> it;
773        int count = 0;
774        it = m_modules.keySet().iterator();
775        while (it.hasNext()) {
776            // get the module description
777            CmsModule module = m_modules.get(it.next());
778
779            if (module.getActionClass() != null) {
780                // create module instance class
781                I_CmsModuleAction moduleAction = module.getActionInstance();
782                if (module.getActionClass() != null) {
783                    try {
784                        moduleAction = (I_CmsModuleAction)Class.forName(module.getActionClass()).newInstance();
785                    } catch (Exception e) {
786                        CmsLog.INIT.info(
787                            Messages.get().getBundle().key(Messages.INIT_CREATE_INSTANCE_FAILED_1, module.getName()),
788                            e);
789                    }
790                }
791                if (moduleAction != null) {
792                    count++;
793                    module.setActionInstance(moduleAction);
794                    if (CmsLog.INIT.isInfoEnabled()) {
795                        CmsLog.INIT.info(Messages.get().getBundle().key(
796                            Messages.INIT_INITIALIZE_MOD_CLASS_1,
797                            moduleAction.getClass().getName()));
798                    }
799                    try {
800                        // create a copy of the adminCms so that each module instance does have 
801                        // it's own context, a shared context might introduce side - effects
802                        CmsObject adminCmsCopy = OpenCms.initCmsObject(cms);
803                        // initialize the module
804                        moduleAction.initialize(adminCmsCopy, configurationManager, module);
805                    } catch (Throwable t) {
806                        LOG.error(
807                            Messages.get().getBundle().key(
808                                Messages.LOG_INSTANCE_INIT_ERR_1,
809                                moduleAction.getClass().getName()),
810                            t);
811                    }
812                }
813            }
814        }
815
816        // initialize the export points
817        initModuleExportPoints();
818
819        if (CmsLog.INIT.isInfoEnabled()) {
820            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_NUM_CLASSES_INITIALIZED_1, new Integer(count)));
821        }
822    }
823
824    /**
825     * Shuts down all module instance classes managed in this module manager.<p>
826     */
827    public synchronized void shutDown() {
828
829        int count = 0;
830        Iterator<String> it = getModuleNames().iterator();
831        while (it.hasNext()) {
832            String moduleName = it.next();
833            // get the module
834            CmsModule module = m_modules.get(moduleName);
835            if (module == null) {
836                continue;
837            }
838            // get the module action instance            
839            I_CmsModuleAction moduleAction = module.getActionInstance();
840            if (moduleAction == null) {
841                continue;
842            }
843
844            count++;
845            if (CmsLog.INIT.isInfoEnabled()) {
846                CmsLog.INIT.info(Messages.get().getBundle().key(
847                    Messages.INIT_SHUTDOWN_MOD_CLASS_1,
848                    moduleAction.getClass().getName()));
849            }
850            try {
851                // shut down the module
852                moduleAction.shutDown(module);
853            } catch (Throwable t) {
854                LOG.error(
855                    Messages.get().getBundle().key(
856                        Messages.LOG_INSTANCE_SHUTDOWN_ERR_1,
857                        moduleAction.getClass().getName()),
858                    t);
859            }
860        }
861
862        if (CmsLog.INIT.isInfoEnabled()) {
863            CmsLog.INIT.info(Messages.get().getBundle().key(
864                Messages.INIT_SHUTDOWN_NUM_MOD_CLASSES_1,
865                new Integer(count)));
866        }
867
868        if (CmsLog.INIT.isInfoEnabled()) {
869            CmsLog.INIT.info(Messages.get().getBundle().key(Messages.INIT_SHUTDOWN_1, this.getClass().getName()));
870        }
871    }
872
873    /**
874     * Updates a already configured module with new values.<p>
875     * 
876     * @param cms must be initialized with "Admin" permissions 
877     * @param module the module to update
878     * 
879     * @throws CmsRoleViolationException if the required module manager role permissions are not available 
880     * @throws CmsConfigurationException if a module with this name is not available for updating 
881     */
882    public synchronized void updateModule(CmsObject cms, CmsModule module)
883    throws CmsRoleViolationException, CmsConfigurationException {
884
885        // check for module manager role permissions
886        OpenCms.getRoleManager().checkRole(cms, CmsRole.DATABASE_MANAGER);
887
888        CmsModule oldModule = m_modules.get(module.getName());
889
890        if (oldModule == null) {
891            // module is not currently configured, no update possible
892            throw new CmsConfigurationException(Messages.get().container(Messages.ERR_OLD_MOD_ERR_1, module.getName()));
893        }
894
895        if (LOG.isInfoEnabled()) {
896            LOG.info(Messages.get().getBundle().key(Messages.LOG_MOD_UPDATE_1, module.getName()));
897        }
898
899        // indicate that the version number was recently updated
900        module.getVersion().setUpdated(true);
901
902        // initialize (freeze) the module
903        module.initialize(cms);
904
905        // replace old version of module with new version
906        m_modules.put(module.getName(), module);
907
908        try {
909            I_CmsModuleAction moduleAction = oldModule.getActionInstance();
910            // handle module action instance if initialized
911            if (moduleAction != null) {
912                moduleAction.moduleUpdate(module);
913                // set the old action instance
914                // the new action instance will be used after a system restart
915                module.setActionInstance(moduleAction);
916            }
917        } catch (Throwable t) {
918            LOG.error(Messages.get().getBundle().key(Messages.LOG_INSTANCE_UPDATE_ERR_1, module.getName()), t);
919        }
920
921        // initialize the export points
922        initModuleExportPoints();
923
924        // update the configuration
925        updateModuleConfiguration();
926    }
927
928    /**
929     * Initializes the list of export points from all configured modules.<p>
930     */
931    private synchronized void initModuleExportPoints() {
932
933        Set<CmsExportPoint> exportPoints = new HashSet<CmsExportPoint>();
934        Iterator<CmsModule> i = m_modules.values().iterator();
935        while (i.hasNext()) {
936            CmsModule module = i.next();
937            List<CmsExportPoint> moduleExportPoints = module.getExportPoints();
938            for (int j = 0; j < moduleExportPoints.size(); j++) {
939                CmsExportPoint point = moduleExportPoints.get(j);
940                if (exportPoints.contains(point)) {
941                    if (LOG.isWarnEnabled()) {
942                        LOG.warn(Messages.get().getBundle().key(
943                            Messages.LOG_DUPLICATE_EXPORT_POINT_2,
944                            point,
945                            module.getName()));
946                    }
947                } else {
948                    exportPoints.add(point);
949                    if (LOG.isDebugEnabled()) {
950                        LOG.debug(Messages.get().getBundle().key(
951                            Messages.LOG_ADD_EXPORT_POINT_2,
952                            point,
953                            module.getName()));
954                    }
955                }
956            }
957        }
958        m_moduleExportPoints = Collections.unmodifiableSet(exportPoints);
959    }
960
961    /**
962     * Updates the module configuration.<p>
963     */
964    private void updateModuleConfiguration() {
965
966        OpenCms.writeConfiguration(CmsModuleConfiguration.class);
967    }
968}