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.ui.apps.modules.edit;
029
030import org.opencms.ade.configuration.CmsADEManager;
031import org.opencms.ade.galleries.CmsSiteSelectorOptionBuilder;
032import org.opencms.ade.galleries.shared.CmsSiteSelectorOption;
033import org.opencms.db.CmsExportPoint;
034import org.opencms.db.CmsUserSettings;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsResource;
037import org.opencms.file.types.CmsResourceTypeFolder;
038import org.opencms.file.types.I_CmsResourceType;
039import org.opencms.i18n.CmsLocaleManager;
040import org.opencms.i18n.CmsVfsBundleManager;
041import org.opencms.lock.CmsLockException;
042import org.opencms.main.CmsException;
043import org.opencms.main.CmsLog;
044import org.opencms.main.OpenCms;
045import org.opencms.module.CmsModule;
046import org.opencms.module.CmsModuleDependency;
047import org.opencms.module.CmsModuleVersion;
048import org.opencms.site.CmsSite;
049import org.opencms.site.CmsSiteManagerImpl;
050import org.opencms.ui.A_CmsUI;
051import org.opencms.ui.CmsVaadinUtils;
052import org.opencms.ui.apps.Messages;
053import org.opencms.ui.apps.modules.CmsModuleApp;
054import org.opencms.ui.components.CmsAutoItemCreatingComboBox;
055import org.opencms.ui.components.CmsBasicDialog;
056import org.opencms.ui.components.CmsErrorDialog;
057import org.opencms.ui.components.CmsRemovableFormRow;
058import org.opencms.ui.components.CmsResourceInfo;
059import org.opencms.ui.components.editablegroup.CmsEditableGroup;
060import org.opencms.ui.components.editablegroup.I_CmsEditableGroupRow;
061import org.opencms.ui.util.CmsComponentField;
062import org.opencms.ui.util.CmsNullToEmptyConverter;
063import org.opencms.util.CmsFileUtil;
064import org.opencms.util.CmsStringUtil;
065import org.opencms.workplace.CmsWorkplace;
066
067import java.util.Arrays;
068import java.util.HashSet;
069import java.util.List;
070import java.util.Map;
071import java.util.Set;
072import java.util.StringTokenizer;
073import java.util.TreeMap;
074
075import org.apache.commons.logging.Log;
076
077import com.google.common.base.Predicate;
078import com.google.common.base.Supplier;
079import com.google.common.collect.Lists;
080import com.google.common.collect.Maps;
081import com.vaadin.ui.AbstractComponentContainer;
082import com.vaadin.ui.Button;
083import com.vaadin.ui.Button.ClickEvent;
084import com.vaadin.ui.Button.ClickListener;
085import com.vaadin.ui.Component;
086import com.vaadin.ui.FormLayout;
087import com.vaadin.ui.TabSheet;
088import com.vaadin.v7.data.Item;
089import com.vaadin.v7.data.Property.ValueChangeEvent;
090import com.vaadin.v7.data.Property.ValueChangeListener;
091import com.vaadin.v7.data.Validator;
092import com.vaadin.v7.data.fieldgroup.BeanFieldGroup;
093import com.vaadin.v7.data.fieldgroup.FieldGroup;
094import com.vaadin.v7.data.fieldgroup.FieldGroup.CommitException;
095import com.vaadin.v7.data.util.IndexedContainer;
096import com.vaadin.v7.ui.AbstractField;
097import com.vaadin.v7.ui.CheckBox;
098import com.vaadin.v7.ui.Field;
099import com.vaadin.v7.ui.TextArea;
100import com.vaadin.v7.ui.TextField;
101import com.vaadin.v7.ui.VerticalLayout;
102
103/**
104 * Form for editing a module.<p>
105 */
106public class CmsEditModuleForm extends CmsBasicDialog {
107
108    /** CSS class. */
109    public static final String COMPLEX_ROW = "o-module-complex-row";
110
111    /** Dummy site root used to identify the 'none' select option in the module site selector. */
112    public static final String ID_EMPTY_SITE = "!empty";
113
114    /** Classes folder within the module. */
115    public static final String PATH_CLASSES = "classes/";
116
117    /** Elements folder within the module. */
118    public static final String PATH_ELEMENTS = "elements/";
119
120    /** The formatters folder within the module. */
121    public static final String PATH_FORMATTERS = "formatters/";
122
123    /** Message bundle file name suffix. */
124    private static final String SUFFIX_BUNDLE_FILE = ".messages";
125
126    /** Lib folder within the module. */
127    public static final String PATH_LIB = "lib/";
128
129    /** Resources folder within the module. */
130    public static final String PATH_RESOURCES = "resources/";
131
132    /** Schemas folder within the module. */
133    public static final String PATH_SCHEMAS = "schemas/";
134
135    /** Template folder within the module. */
136    public static final String PATH_TEMPLATES = "templates/";
137
138    /** Logger instance for this class. */
139    private static final Log LOG = CmsLog.getLog(CmsEditModuleForm.class);
140
141    /** The name of the caption property for the module site selector. */
142    private static final String PROPERTY_SITE_NAME = "name";
143
144    /** Serial version id. */
145    private static final long serialVersionUID = 1L;
146
147    /**I18n path. */
148    private static final String PATH_i18n = "i18n/";
149
150    public static final String CONFIG_FILE = ".config";
151
152    /** Text box for the action class. */
153    private TextField m_actionClass;
154
155    /** The text box for the author email address. */
156    private TextField m_authorEmail;
157
158    /** Text box for the author name. */
159    private TextField m_authorName;
160
161    /** Check box to enable / disable version autoincrement mode. */
162    private CheckBox m_autoIncrement;
163
164    /** The cancel button. */
165    private Button m_cancel;
166
167    /** Layout containing the module dependency widgets. */
168    private FormLayout m_dependencies;
169
170    /** Group for editing lists of dependencies. */
171    private CmsEditableGroup m_dependencyGroup;
172
173    /** Text box for the description. */
174    private TextArea m_description;
175
176    /** Parent layout for the excluded resources. */
177    private FormLayout m_excludedResources;
178
179    /** The group for the excluded module resource fields. */
180    private CmsEditableGroup m_excludedResourcesGroup;
181
182    /** Group for editing list of export points. */
183    private CmsEditableGroup m_exportPointGroup;
184
185    /** Parent layout for export point widgets. */
186    private VerticalLayout m_exportPoints;
187
188    /** The field group. */
189    private BeanFieldGroup<CmsModule> m_fieldGroup = new BeanFieldGroup<CmsModule>(CmsModule.class);
190
191    /** Check box for creating the classes folder. */
192    private CheckBox m_folderClasses;
193
194    /** Check box for creating the elmments folder. */
195    private CheckBox m_folderI18N;
196
197    /** Check box for creating the formatters folder. */
198    private CheckBox m_folderFormatters;
199
200    /** Check box for creating the lib folder. */
201    private CheckBox m_folderLib;
202
203    /** Check box for crreating the module folder. */
204    private CheckBox m_folderModule;
205
206    /** Check box for creating the resources folder. */
207    private CheckBox m_folderResources;
208
209    /** Check box for creating the schemas folder. */
210    private CheckBox m_folderSchemas;
211
212    /** Check box for creating the templates folder. */
213    private CheckBox m_folderTemplates;
214
215    /** Text box for the group. */
216    private TextField m_group;
217
218    /** Check box to enable / disable fixed import site. */
219    private CheckBox m_hasImportSite;
220
221    /** Text area for the import script. */
222    private TextArea m_importScript;
223
224    /** Select box for the module site. */
225    private CmsAutoItemCreatingComboBox m_importSite;
226
227    /** Contains the widget used to display the module site information. */
228    private CmsComponentField<CmsResourceInfo> m_info = new CmsComponentField<CmsResourceInfo>();
229
230    /** The module being edited. */
231    private CmsModule m_module;
232
233    /** The layout containing the module resources. */
234    private FormLayout m_moduleResources;
235
236    /** The group for the module resource fields. */
237    private CmsEditableGroup m_moduleResourcesGroup;
238
239    /** Text box for the module name. */
240    private TextField m_name;
241
242    /** True if this dialog instance was opened for a new module (rather than an existing module). */
243    private boolean m_new;
244
245    /** Text box for the nice module name. */
246    private TextField m_niceName;
247
248    /** The OK button. */
249    private Button m_ok;
250
251    /** The original module instance passed into the constructor. */
252    private CmsModule m_oldModuleInstance;
253
254    /** Group for editing lists of parameters. */
255    private CmsEditableGroup m_parameterGroup;
256
257    /** Parent layout for module parameter widgets. */
258    private FormLayout m_parameters;
259
260    /** Check box for the 'reduced metadata' export mode. */
261    private CheckBox m_reducedMetadata;
262
263    /** The tab layout. */
264    private TabSheet m_tabs;
265
266    /** The callback to call after editing the module. */
267    private Runnable m_updateCallback;
268
269    /** Text box for the version. */
270    private TextField m_version;
271
272    /**
273     * Creates a new instance.<p>
274     *
275     * @param module the module to edit
276     * @param newModule true if the module is a new one, false for editing an existing module
277     * @param updateCallback the update callback
278     */
279    @SuppressWarnings("unchecked")
280    public CmsEditModuleForm(CmsModule module, boolean newModule, Runnable updateCallback) {
281
282        m_oldModuleInstance = module;
283        m_module = (CmsModule)(module.clone());
284        String site = m_module.getSite();
285        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(site)) {
286            site = site.trim();
287            if (!site.equals("/")) {
288                site = CmsFileUtil.removeTrailingSeparator(site);
289                m_module.setSite(site);
290            }
291        }
292        m_new = newModule;
293        m_updateCallback = updateCallback;
294        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null);
295        IndexedContainer importSitesModel = getModuleSiteContainer(
296            A_CmsUI.getCmsObject(),
297            PROPERTY_SITE_NAME,
298            m_module.getSite());
299        m_importSite.setContainerDataSource(importSitesModel);
300        m_importSite.setNullSelectionItemId(ID_EMPTY_SITE);
301        m_importSite.setItemCaptionPropertyId(PROPERTY_SITE_NAME);
302        m_importSite.setNewValueHandler(new CmsSiteSelectorNewValueHandler(PROPERTY_SITE_NAME));
303        if (m_new) {
304            m_module.setCreateModuleFolder(true);
305            m_module.setCreateI18NFolder(true);
306        }
307        m_fieldGroup.setItemDataSource(m_module);
308        m_fieldGroup.bind(m_name, "name");
309        m_fieldGroup.bind(m_niceName, "niceName");
310        m_fieldGroup.bind(m_description, "description");
311        m_fieldGroup.bind(m_version, "versionStr");
312        m_fieldGroup.bind(m_group, "group");
313        m_fieldGroup.bind(m_actionClass, "actionClass");
314        m_fieldGroup.bind(m_importScript, "importScript");
315        m_fieldGroup.bind(m_importSite, "site");
316        m_fieldGroup.bind(m_hasImportSite, "hasImportSite");
317        m_fieldGroup.bind(m_authorName, "authorName");
318        m_fieldGroup.bind(m_authorEmail, "authorEmail");
319        m_fieldGroup.bind(m_reducedMetadata, "reducedExportMode");
320        m_fieldGroup.bind(m_folderModule, "createModuleFolder");
321        m_fieldGroup.bind(m_folderClasses, "createClassesFolder");
322        m_fieldGroup.bind(m_folderI18N, "createI18NFolder");
323        m_fieldGroup.bind(m_folderFormatters, "createFormattersFolder");
324        m_fieldGroup.bind(m_folderLib, "createLibFolder");
325        m_fieldGroup.bind(m_folderResources, "createResourcesFolder");
326        m_fieldGroup.bind(m_folderSchemas, "createSchemasFolder");
327        m_fieldGroup.bind(m_autoIncrement, "autoIncrement");
328        if (m_new) {
329            m_reducedMetadata.setValue(Boolean.TRUE);
330            m_name.addValidator(new Validator() {
331
332                private static final long serialVersionUID = 1L;
333
334                public void validate(Object value) throws InvalidValueException {
335
336                    if (OpenCms.getModuleManager().hasModule((String)value)) {
337                        throw new InvalidValueException(
338                            CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_MODULE_ALREADY_EXISTS_0));
339                    }
340                    if (!CmsStringUtil.isValidJavaClassName((String)value)) {
341                        throw new InvalidValueException(
342                            CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_INVALID_MODULE_NAME_0));
343                    }
344                }
345
346            });
347
348        }
349        m_version.addValidator(new Validator() {
350
351            private static final long serialVersionUID = 1L;
352
353            public void validate(Object value) throws InvalidValueException {
354
355                try {
356                    @SuppressWarnings("unused")
357                    CmsModuleVersion ver = new CmsModuleVersion("" + value);
358                } catch (Exception e) {
359                    throw new InvalidValueException(
360                        CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_INVALID_MODULE_VERSION_0));
361                }
362            }
363        });
364        m_fieldGroup.bind(m_folderTemplates, "createTemplateFolder");
365        for (AbstractField<String> field : new AbstractField[] {
366            m_name,
367            m_niceName,
368            m_group,
369            m_importScript,
370            m_actionClass}) {
371            field.setConverter(new CmsNullToEmptyConverter());
372        }
373
374        if (!newModule) {
375            for (AbstractField<?> field : new AbstractField[] {
376                m_folderModule,
377                m_folderClasses,
378                m_folderI18N,
379                m_folderFormatters,
380                m_folderLib,
381                m_folderResources,
382                m_folderSchemas,
383                m_folderTemplates}) {
384                field.setVisible(false);
385            }
386            m_name.setEnabled(false);
387        }
388
389        Supplier<Component> moduleResourceFieldFactory = new Supplier<Component>() {
390
391            public Component get() {
392
393                return createModuleResourceField(null);
394            }
395        };
396        String addResourceButtonText = CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_ADD_RESOURCE_0);
397        m_moduleResourcesGroup = new CmsEditableGroup(
398            m_moduleResources,
399            moduleResourceFieldFactory,
400            addResourceButtonText);
401        m_excludedResourcesGroup = new CmsEditableGroup(
402            m_excludedResources,
403            moduleResourceFieldFactory,
404            addResourceButtonText);
405        m_parameterGroup = new CmsEditableGroup(m_parameters, new Supplier<Component>() {
406
407            public Component get() {
408
409                TextField result = new TextField();
410                return result;
411            }
412        }, CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_ADD_PARAMETER_0));
413        m_exportPointGroup = new CmsEditableGroup(m_exportPoints, new Supplier<Component>() {
414
415            public Component get() {
416
417                return new CmsExportPointWidget("", "");
418            }
419        }, CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_ADD_EXPORT_POINT_0));
420
421        m_dependencyGroup = new CmsEditableGroup(m_dependencies, new Supplier<Component>() {
422
423            public Component get() {
424
425                CmsModuleDependencyWidget component = CmsModuleDependencyWidget.create(null);
426                return component;
427            }
428        }, CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_ADD_DEPENDENCY_0));
429
430        m_moduleResourcesGroup.init();
431        m_excludedResourcesGroup.init();
432        m_parameterGroup.init();
433        m_exportPointGroup.init();
434        m_dependencyGroup.init();
435        String resourceListError = CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_RESOURCE_LIST_ERROR_0);
436        m_moduleResourcesGroup.setErrorMessage(resourceListError);
437        m_excludedResourcesGroup.setErrorMessage(resourceListError);
438
439        Map<String, String> params = module.getParameters();
440        for (Map.Entry<String, String> entry : params.entrySet()) {
441            addParameter(entry.getKey() + "=" + entry.getValue());
442        }
443        for (CmsModuleDependency dependency : module.getDependencies()) {
444            addDependencyRow(dependency);
445        }
446
447        for (CmsExportPoint exportPoint : module.getExportPoints()) {
448            addExportPointRow(exportPoint.getUri(), exportPoint.getConfiguredDestination());
449        }
450        for (String moduleResource : module.getResources()) {
451            addModuleResource(moduleResource);
452        }
453
454        for (String excludedResource : module.getExcludeResources()) {
455            addExcludedResource(excludedResource);
456        }
457
458        m_cancel.addClickListener(new ClickListener() {
459
460            private static final long serialVersionUID = 1L;
461
462            public void buttonClick(ClickEvent event) {
463
464                CmsVaadinUtils.getWindow(CmsEditModuleForm.this).close();
465            }
466        });
467        m_ok.addClickListener(new ClickListener() {
468
469            private static final long serialVersionUID = 1L;
470
471            public void buttonClick(ClickEvent event) {
472
473                updateModule();
474            }
475        });
476        m_importSite.addValueChangeListener(new ValueChangeListener() {
477
478            private static final long serialVersionUID = 1L;
479
480            @SuppressWarnings("synthetic-access")
481            public void valueChange(ValueChangeEvent event) {
482
483                String siteRoot = (String)(event.getProperty().getValue());
484                updateSiteInfo(siteRoot);
485
486            }
487        });
488
489        m_info.set(new CmsResourceInfo("", "", ""));
490        m_info.get().getResourceIcon().initContent(null, CmsModuleApp.Icons.RESINFO_ICON, null, false, false);
491        updateSiteInfo(module.getSite());
492        displayResourceInfoDirectly(Arrays.asList(m_info.get()));
493    }
494
495    /**
496     * Builds the container used for the module site selector.<p>
497     *
498     * @param cms the CMS context
499     * @param captionPropertyName the name of the property used to store captions
500     * @param prevValue the value previously set in the module
501     *
502     * @return the container with the available sites
503     */
504    public static IndexedContainer getModuleSiteContainer(CmsObject cms, String captionPropertyName, String prevValue) {
505
506        CmsSiteSelectorOptionBuilder optBuilder = new CmsSiteSelectorOptionBuilder(cms);
507        optBuilder.addNormalSites(true, (new CmsUserSettings(cms)).getStartFolder());
508        IndexedContainer availableSites = new IndexedContainer();
509        availableSites.addContainerProperty(captionPropertyName, String.class, null);
510        for (CmsSiteSelectorOption option : optBuilder.getOptions()) {
511            String siteRoot = option.getSiteRoot();
512            if (siteRoot.equals("")) {
513                siteRoot = "/";
514            }
515            Item siteItem = availableSites.addItem(siteRoot);
516            siteItem.getItemProperty(captionPropertyName).setValue(option.getMessage());
517        }
518        if (!availableSites.containsId(prevValue)) {
519            String caption = prevValue;
520            String siteId = prevValue;
521
522            if (CmsStringUtil.isEmptyOrWhitespaceOnly(prevValue)) {
523                caption = CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_MODULE_SITE_NONE_0);
524                siteId = ID_EMPTY_SITE;
525            }
526            availableSites.addItem(siteId).getItemProperty(captionPropertyName).setValue(caption);
527        }
528        return availableSites;
529    }
530
531    /**
532     * Adds another entry to the list of module dependencies in the dependencies tab.<p>
533     *
534     * @param dep the module dependency for which a new row should be added
535     */
536    public void addDependencyRow(CmsModuleDependency dep) {
537
538        CmsModuleDependencyWidget w = CmsModuleDependencyWidget.create(dep);
539        m_dependencyGroup.addRow(w);
540    }
541
542    /**
543     * Adds another entry to the list of export points in the export point tab.<p>
544     *
545     * @param src the export point source
546     * @param target the export point target
547     */
548    public void addExportPointRow(String src, String target) {
549
550        CmsExportPointWidget exportPointWidget = new CmsExportPointWidget(src, target);
551        m_exportPointGroup.addRow(exportPointWidget);
552        //        row.addStyleName(COMPLEX_ROW);
553        //        m_exportPoints.addComponent(row);
554    }
555
556    /**
557     * Writes the form data back to the module.<p>
558     */
559    public void updateModule() {
560
561        try {
562            m_fieldGroup.commit();
563            // validate 'dynamic' tabs here
564            TreeMap<String, String> params = Maps.newTreeMap();
565            m_parameterGroup.getRows();
566            for (I_CmsEditableGroupRow row : m_parameterGroup.getRows()) {
567                TextField paramField = (TextField)(row.getComponent());
568                String paramStr = paramField.getValue();
569                int eqPos = paramStr.indexOf("=");
570                if (eqPos >= 0) {
571                    String key = paramStr.substring(0, eqPos);
572                    String value = paramStr.substring(eqPos + 1);
573                    if (!CmsStringUtil.isEmpty(key)) {
574                        params.put(key, value);
575                    }
576                }
577            }
578            m_module.setParameters(params);
579
580            List<CmsExportPoint> exportPoints = Lists.newArrayList();
581            for (I_CmsEditableGroupRow row : m_exportPointGroup.getRows()) {
582                CmsExportPointWidget widget = (CmsExportPointWidget)(row.getComponent());
583                String source = widget.getUri().trim();
584                String target = widget.getDestination().trim();
585                if (CmsStringUtil.isEmpty(source) || CmsStringUtil.isEmpty(target)) {
586                    continue;
587                }
588                CmsExportPoint point = new CmsExportPoint(source, target);
589                exportPoints.add(point);
590            }
591            m_module.setExportPoints(exportPoints);
592
593            List<CmsModuleDependency> dependencies = Lists.newArrayList();
594            for (CmsModuleDependencyWidget widget : getFormRowChildren(
595                m_dependencies,
596                CmsModuleDependencyWidget.class)) {
597                String moduleName = widget.getModuleName();
598                String moduleVersion = widget.getModuleVersion();
599                try {
600                    CmsModuleDependency dep = new CmsModuleDependency(moduleName, new CmsModuleVersion(moduleVersion));
601                    dependencies.add(dep);
602                } catch (Exception e) {
603                    LOG.debug(e.getLocalizedMessage(), e);
604                }
605            }
606            m_module.setDependencies(dependencies);
607
608            List<String> moduleResources = Lists.newArrayList();
609            for (I_CmsEditableGroupRow row : m_moduleResourcesGroup.getRows()) {
610                CmsModuleResourceSelectField field = (CmsModuleResourceSelectField)(row.getComponent());
611                String moduleResource = field.getValue().trim();
612                if (!moduleResource.isEmpty()) {
613                    moduleResources.add(moduleResource);
614                }
615            }
616            m_module.setResources(moduleResources);
617
618            List<String> excludedResources = Lists.newArrayList();
619            for (I_CmsEditableGroupRow row : m_excludedResourcesGroup.getRows()) {
620                CmsModuleResourceSelectField field = (CmsModuleResourceSelectField)(row.getComponent());
621                String moduleResource = field.getValue().trim();
622                if (!moduleResource.isEmpty()) {
623                    excludedResources.add(moduleResource);
624                }
625            }
626            m_module.setExcludeResources(excludedResources);
627
628            if (!m_oldModuleInstance.isAutoIncrement() && m_module.isAutoIncrement()) {
629                m_module.setCheckpointTime(System.currentTimeMillis());
630            }
631
632            CmsObject cms = A_CmsUI.getCmsObject();
633            if (m_new) {
634                createModuleFolders(cms, m_module);
635                OpenCms.getModuleManager().addModule(cms, m_module);
636            } else {
637                OpenCms.getModuleManager().updateModule(cms, m_module);
638            }
639            CmsVaadinUtils.getWindow(this).close();
640            m_updateCallback.run();
641        } catch (CommitException e) {
642            if (e.getCause() instanceof FieldGroup.FieldGroupInvalidValueException) {
643                int minTabIdx = 999;
644                for (Field<?> field : e.getInvalidFields().keySet()) {
645                    int tabIdx = getTabIndex(field);
646                    if (tabIdx != -1) {
647                        minTabIdx = Math.min(tabIdx, minTabIdx);
648                    }
649                }
650                m_tabs.setSelectedTab(minTabIdx);
651            } else {
652                CmsErrorDialog.showErrorDialog(e);
653            }
654            return;
655        } catch (Exception e) {
656            CmsErrorDialog.showErrorDialog(e);
657        }
658
659    }
660
661    /**
662     * Adds a new module dependency widget.<p>
663     *
664     * @param moduleName the module name
665     * @param version the module version
666     */
667    void addDependency(String moduleName, String version) {
668
669        try {
670            m_dependencies.addComponent(
671                new CmsRemovableFormRow<CmsModuleDependencyWidget>(
672                    CmsModuleDependencyWidget.create(
673                        new CmsModuleDependency(moduleName, new CmsModuleVersion(version))),
674                    ""));
675        } catch (Exception e) {
676            CmsErrorDialog.showErrorDialog(e);
677        }
678    }
679
680    /**
681     * Adds a new resource selection widget to the list of module resources.<p>
682     *
683     * @param moduleResource the initial value for the new widget
684     */
685    void addExcludedResource(String moduleResource) {
686
687        CmsModuleResourceSelectField resField = createModuleResourceField(moduleResource);
688        if (resField != null) {
689            m_excludedResourcesGroup.addRow(resField);
690        }
691    }
692
693    /**
694     * Adds a new module resource row.<p>
695     *
696     * @param moduleResource the initial value for the module resource
697     */
698    void addModuleResource(String moduleResource) {
699
700        CmsModuleResourceSelectField resField = createModuleResourceField(moduleResource);
701        if (resField != null) {
702            m_moduleResourcesGroup.addRow(resField);
703        }
704    }
705
706    /**
707     * Add a given parameter to the form layout.<p>
708     *
709     * @param parameter parameter to add to form
710     */
711    void addParameter(String parameter) {
712
713        TextField textField = new TextField();
714        if (parameter != null) {
715            textField.setValue(parameter);
716        }
717        m_parameterGroup.addRow(textField);
718    }
719
720    /**
721     * Creates a module resource selection field.<p>
722     *
723     * @param moduleResource the initial content for the field
724     *
725     * @return the module resource selection field
726     */
727    CmsModuleResourceSelectField createModuleResourceField(String moduleResource) {
728
729        CmsModuleResourceSelectField resField = new CmsModuleResourceSelectField();
730        CmsObject moduleCms = null;
731        try {
732            moduleCms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
733            if (getSelectedSite() != null) {
734                moduleCms.getRequestContext().setSiteRoot(getSelectedSite());
735            }
736            resField.setCmsObject(moduleCms);
737            if (moduleResource != null) {
738                resField.setValue(moduleResource);
739            }
740            return resField;
741        } catch (CmsException e) {
742            LOG.error(e.getLocalizedMessage(), e);
743            return null;
744        }
745    }
746
747    /**
748     * Helper method to get the descendants of a container with a specific widget type.<p>
749     *
750     * @param container the container
751     * @param cls the class
752     *
753     * @return the list of results
754     */
755    <T extends Component> List<T> getFormRowChildren(AbstractComponentContainer container, final Class<T> cls) {
756
757        final List<T> result = Lists.newArrayList();
758        CmsVaadinUtils.visitDescendants(container, new Predicate<Component>() {
759
760            public boolean apply(Component comp) {
761
762                if (cls.isAssignableFrom(comp.getClass())) {
763                    result.add(cls.cast(comp));
764                }
765                return true;
766            }
767        });
768        return result;
769    }
770
771    /**
772     * Gets the site root currently selected in the module site combo box.<p>
773     *
774     * @return the currently selected module site
775     */
776    String getSelectedSite() {
777
778        return (String)(m_importSite.getValue());
779    }
780
781    /**
782     * Gets the tab index for the given component.<p>
783     *
784     * @param component a component
785     *
786     * @return the tab index
787     */
788    int getTabIndex(Component component) {
789
790        List<Component> tabs = Lists.newArrayList(m_tabs.iterator());
791        while (component != null) {
792            int pos = tabs.indexOf(component);
793            if (pos >= 0) {
794                return pos;
795            }
796            component = component.getParent();
797        }
798        return -1;
799    }
800
801    /**
802     * Creates all module folders that are selected in the input form.<p>
803     *
804     * @param module the module
805     *
806     * @return the updated module
807     *
808     * @throws CmsException if somehting goes wrong
809     */
810    private CmsModule createModuleFolders(CmsObject cms, CmsModule module) throws CmsException {
811
812        String modulePath = CmsWorkplace.VFS_PATH_MODULES + module.getName() + "/";
813        List<CmsExportPoint> exportPoints = module.getExportPoints();
814        List<String> resources = module.getResources();
815
816        // set the createModuleFolder flag if any other flag is set
817        if (module.isCreateClassesFolder()
818            || module.isCreateElementsFolder()
819            || module.isCreateI18NFolder()
820            || module.isCreateLibFolder()
821            || module.isCreateResourcesFolder()
822            || module.isCreateSchemasFolder()
823            || module.isCreateTemplateFolder()
824            || module.isCreateFormattersFolder()) {
825            module.setCreateModuleFolder(true);
826        }
827
828        Set<String> exportPointPaths = new HashSet<String>();
829        for (CmsExportPoint exportPoint : exportPoints) {
830            exportPointPaths.add(exportPoint.getUri());
831        }
832
833        // check if we have to create the module folder
834
835        I_CmsResourceType folderType = OpenCms.getResourceManager().getResourceType(
836            CmsResourceTypeFolder.getStaticTypeName());
837        I_CmsResourceType configType = OpenCms.getResourceManager().getResourceType(CmsADEManager.MODULE_CONFIG_TYPE);
838
839        if (module.isCreateModuleFolder()) {
840            CmsResource resource = cms.createResource(modulePath, folderType);
841            CmsResource configResource = cms.createResource(modulePath + CONFIG_FILE, configType);
842            try {
843                cms.unlockResource(resource);
844                cms.unlockResource(configResource);
845            } catch (CmsLockException locke) {
846                LOG.warn("Unbale to unlock resource", locke);
847            }
848            // add the module folder to the resource list
849            resources.add(modulePath);
850            module.setResources(resources);
851        }
852
853        // check if we have to create the template folder
854        if (module.isCreateTemplateFolder()) {
855            String path = modulePath + PATH_TEMPLATES;
856            CmsResource resource = cms.createResource(path, folderType);
857            try {
858                cms.unlockResource(resource);
859            } catch (CmsLockException locke) {
860                LOG.warn("Unbale to unlock resource", locke);
861            }
862        }
863
864        if (module.isCreateI18NFolder()) {
865            String path = modulePath + PATH_i18n;
866            CmsResource resource = cms.createResource(path, folderType);
867            CmsResource bundleResource = cms.createResource(
868                path + module.getName() + SUFFIX_BUNDLE_FILE + "_" + CmsLocaleManager.getDefaultLocale(),
869                OpenCms.getResourceManager().getResourceType(CmsVfsBundleManager.TYPE_PROPERTIES_BUNDLE),
870                null,
871                null);
872            cms.writeResource(bundleResource);
873            try {
874                cms.unlockResource(resource);
875                cms.unlockResource(bundleResource);
876            } catch (CmsLockException locke) {
877                LOG.warn("Unbale to unlock resource", locke);
878            }
879        }
880
881        // check if we have to create the elements folder
882        if (module.isCreateElementsFolder()) {
883            String path = modulePath + PATH_ELEMENTS;
884            CmsResource resource = cms.createResource(path, folderType);
885            try {
886                cms.unlockResource(resource);
887            } catch (CmsLockException locke) {
888                LOG.warn("Unbale to unlock resource", locke);
889            }
890        }
891
892        if (module.isCreateFormattersFolder()) {
893            String path = modulePath + PATH_FORMATTERS;
894            CmsResource resource = cms.createResource(path, folderType);
895            try {
896                cms.unlockResource(resource);
897            } catch (CmsLockException locke) {
898                LOG.warn("Unbale to unlock resource", locke);
899            }
900        }
901
902        // check if we have to create the schemas folder
903        if (module.isCreateSchemasFolder()) {
904            String path = modulePath + PATH_SCHEMAS;
905            CmsResource resource = cms.createResource(path, folderType);
906            try {
907                cms.unlockResource(resource);
908            } catch (CmsLockException locke) {
909                LOG.warn("Unbale to unlock resource", locke);
910            }
911        }
912
913        // check if we have to create the resources folder
914        if (module.isCreateResourcesFolder()) {
915            String path = modulePath + PATH_RESOURCES;
916            CmsResource resource = cms.createResource(path, folderType);
917            try {
918                cms.unlockResource(resource);
919            } catch (CmsLockException locke) {
920                LOG.warn("Unbale to unlock resource", locke);
921            }
922        }
923
924        // check if we have to create the lib folder
925        if (module.isCreateLibFolder()) {
926            String path = modulePath + PATH_LIB;
927            CmsResource resource = cms.createResource(path, folderType);
928            try {
929                cms.unlockResource(resource);
930            } catch (CmsLockException locke) {
931                LOG.warn("Unbale to unlock resource", locke);
932            }
933            if (!exportPointPaths.contains(path)) {
934                CmsExportPoint exp = new CmsExportPoint(path, "WEB-INF/lib/");
935                exportPoints.add(exp);
936            }
937            module.setExportPoints(exportPoints);
938        }
939
940        // check if we have to create the classes folder
941        if (module.isCreateClassesFolder()) {
942            String path = modulePath + PATH_CLASSES;
943            CmsResource resource = cms.createResource(path, folderType);
944            try {
945                cms.unlockResource(resource);
946            } catch (CmsLockException locke) {
947                LOG.warn("Unbale to unlock resource", locke);
948            }
949            if (!exportPointPaths.contains(path)) {
950                CmsExportPoint exp = new CmsExportPoint(path, "WEB-INF/classes/");
951                exportPoints.add(exp);
952                module.setExportPoints(exportPoints);
953            }
954
955            // now create all subfolders for the package structure
956            StringTokenizer tok = new StringTokenizer(m_module.getName(), ".");
957            while (tok.hasMoreTokens()) {
958                String folder = tok.nextToken();
959                path += folder + "/";
960                CmsResource resource2 = cms.createResource(path, folderType);
961                try {
962                    cms.unlockResource(resource2);
963                } catch (CmsLockException locke) {
964                    LOG.warn("Unbale to unlock resource", locke);
965                }
966            }
967        }
968        return module;
969    }
970
971    /**
972     * Updates the module site info display after the module site is changed.<p>
973     *
974     * @param siteRoot the new module site root
975     */
976    private void updateSiteInfo(final String siteRoot) {
977
978        String top = "";
979        String bottom = "";
980
981        if (m_new) {
982            top = CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_RESINFO_NEW_MODULE_0);
983        } else {
984            top = m_module.getName();
985        }
986
987        if (siteRoot == null) {
988            bottom = CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_MODULE_SITE_NOT_SET_0);
989        } else {
990            CmsSiteManagerImpl siteManager = OpenCms.getSiteManager();
991            CmsSite site = siteManager.getSiteForSiteRoot(siteRoot);
992            if (site != null) {
993                bottom = CmsVaadinUtils.getMessageText(
994                    Messages.GUI_MODULES_MODULE_SITE_1,
995                    site.getTitle() + " (" + siteRoot + ")");
996            } else if (siteRoot.equals("") || siteRoot.equals("/")) {
997                bottom = CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_MODULE_SITE_ROOT_FOLDER_0);
998            } else {
999                bottom = CmsVaadinUtils.getMessageText(Messages.GUI_MODULES_MODULE_SITE_1, siteRoot);
1000            }
1001        }
1002        m_info.get().getTopLine().setValue(top);
1003        m_info.get().getBottomLine().setValue(bottom);
1004
1005        for (Component c : Arrays.asList(m_moduleResources, m_excludedResources)) {
1006            CmsVaadinUtils.visitDescendants(c, new Predicate<Component>() {
1007
1008                public boolean apply(Component comp) {
1009
1010                    if (comp instanceof CmsModuleResourceSelectField) {
1011                        ((CmsModuleResourceSelectField)comp).updateSite(siteRoot);
1012                    }
1013                    return true;
1014                }
1015            });
1016        }
1017
1018    }
1019
1020}