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