001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.resourcetypes;
029
030import org.opencms.ade.configuration.formatters.CmsFormatterConfigurationCache;
031import org.opencms.file.CmsFile;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsProject;
034import org.opencms.file.CmsProperty;
035import org.opencms.file.CmsPropertyDefinition;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsUser;
038import org.opencms.file.types.CmsResourceTypeFolder;
039import org.opencms.file.types.CmsResourceTypeXmlContent;
040import org.opencms.file.types.I_CmsResourceType;
041import org.opencms.i18n.CmsLocaleManager;
042import org.opencms.lock.CmsLock;
043import org.opencms.lock.CmsLockUtil;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsLog;
046import org.opencms.main.OpenCms;
047import org.opencms.module.CmsModule;
048import org.opencms.report.CmsLogReport;
049import org.opencms.ui.A_CmsUI;
050import org.opencms.ui.CmsVaadinUtils;
051import org.opencms.ui.apps.Messages;
052import org.opencms.ui.apps.modules.CmsModuleApp;
053import org.opencms.ui.apps.modules.edit.CmsEditModuleForm;
054import org.opencms.ui.components.CmsBasicDialog;
055import org.opencms.ui.components.CmsResourceInfo;
056import org.opencms.ui.components.fileselect.CmsPathSelectField;
057import org.opencms.util.CmsFileUtil;
058import org.opencms.util.CmsMacroResolver;
059import org.opencms.util.CmsStringUtil;
060import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
061import org.opencms.xml.CmsXmlUtils;
062import org.opencms.xml.content.CmsVfsBundleLoaderXml;
063import org.opencms.xml.content.CmsXmlContent;
064import org.opencms.xml.content.CmsXmlContentFactory;
065import org.opencms.xml.types.I_CmsXmlContentValue;
066
067import java.io.UnsupportedEncodingException;
068import java.nio.charset.Charset;
069import java.util.ArrayList;
070import java.util.Arrays;
071import java.util.List;
072import java.util.Locale;
073import java.util.Map;
074import java.util.Map.Entry;
075import java.util.TreeMap;
076
077import org.apache.commons.logging.Log;
078
079import org.dom4j.Element;
080
081import com.google.common.collect.Lists;
082import com.vaadin.ui.Button;
083import com.vaadin.ui.Window;
084import com.vaadin.v7.data.Validator;
085import com.vaadin.v7.ui.TextArea;
086import com.vaadin.v7.ui.TextField;
087
088/**
089 * Dialog to edit or create resourcetypes.<p>
090 */
091@SuppressWarnings("deprecation")
092public class CmsNewResourceTypeDialog extends CmsBasicDialog {
093
094    /**
095     * XPath elements.
096     */
097    public static class XMLPath {
098
099        /**Module Config Constant.  */
100        private static final String CONFIG_RESOURCETYPE = "ResourceType";
101
102        /**Module Config Constant.  */
103        private static final String CONFIG_RESOURCETYPE_TYPENAME = "/TypeName";
104
105        /**Module Config Constant.  */
106        private static final String CONFIG_RESOURCETYPE_NAMEPATTERN = "/NamePattern";
107
108        /**
109         * Adds the count to the path, e.g., <code>"Title" + num(2)</code> results in <code>"Title[2]"</code>.
110         * @param i the count to add to the XPath
111         * @return the argument wrapped in square brackets.
112         */
113        public static String num(int i) {
114
115            return "[" + i + "]";
116        }
117    }
118
119    /**
120     * Validator for the bundle resource field.<p>
121     */
122    class BundleValidator implements Validator {
123
124        /**Vaadin serial id. */
125        private static final long serialVersionUID = 7872665683495080792L;
126
127        /**
128         * @see com.vaadin.v7.data.Validator#validate(java.lang.Object)
129         */
130        public void validate(Object value) throws InvalidValueException {
131
132            if (!m_cms.existsResource((String)value)) {
133                throw new InvalidValueException(
134                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_RESORUCE_0));
135            }
136
137            try {
138                CmsResource res = m_cms.readResource((String)value);
139                if (!OpenCms.getResourceManager().getResourceType(res).equals(
140                    OpenCms.getResourceManager().getResourceType("propertyvfsbundle"))) {
141                    throw new InvalidValueException(
142                        CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_RESORUCE_NO_VFSBUNDLE_0));
143
144                }
145            } catch (CmsException e) {
146                LOG.error("Unable to read resource", e);
147            }
148
149        }
150
151    }
152
153    /**
154     * Validator for the title field.<p>
155     */
156
157    class IDValidator implements Validator {
158
159        /**vaadin serial id.*/
160        private static final long serialVersionUID = 7878441125879949490L;
161
162        /**
163         * @see com.vaadin.v7.data.Validator#validate(java.lang.Object)
164         */
165        public void validate(Object value) throws InvalidValueException {
166
167            if (((String)value).isEmpty()) {
168                throw new InvalidValueException(
169                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_ID_0));
170            }
171            int id = Integer.parseInt((String)value);
172            if (!CmsResourceTypeApp.isResourceTypeIdFree(id)) {
173                throw new InvalidValueException(
174                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_ID_0));
175            }
176
177        }
178
179    }
180
181    /**
182     * Validator for the title field.<p>
183     */
184    class NameValidator implements Validator {
185
186        /**vaadin serial id.*/
187        private static final long serialVersionUID = 7878441125879949490L;
188
189        /**
190         * @see com.vaadin.v7.data.Validator#validate(java.lang.Object)
191         */
192        public void validate(Object value) throws InvalidValueException {
193
194            if (((String)value).isEmpty()) {
195                throw new InvalidValueException(
196                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_NAME_0));
197            }
198            if (!CmsResourceTypeApp.isResourceTypeNameFree((String)value)) {
199                throw new InvalidValueException(
200                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_NAME_0));
201            }
202
203            // Even though technically more names might work as resource type names, this GUI tool uses them as parts of OpenCms paths,
204            // so they have to be valid file names
205            try {
206                CmsResource.checkResourceName((String)value);
207            } catch (Exception e) {
208                throw new InvalidValueException(
209                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_NAME_0));
210            }
211        }
212
213    }
214
215    /**
216     * Validator for the title field.<p>
217     */
218    class ResourceValidator implements Validator {
219
220        /**vaadin serial id.*/
221        private static final long serialVersionUID = 7878441125879949490L;
222
223        /**
224         * @see com.vaadin.v7.data.Validator#validate(java.lang.Object)
225         */
226        public void validate(Object value) throws InvalidValueException {
227
228            if ((value == null) || ((String)value).isEmpty()) {
229                return;
230            }
231
232            String resource = (String)value;
233            if (!resource.startsWith("/system/")) {
234                throw new InvalidValueException(
235                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_RESORUCE_0));
236            }
237            if (CmsResource.getName(resource).equals(SCHEMA) || CmsResource.getName(resource).equals(FORMATTER)) {
238                if (!m_cms.existsResource(CmsResource.getParentFolder(resource))) {
239                    throw new InvalidValueException(
240                        CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_RESORUCE_0));
241                }
242                return;
243            }
244            if (!m_cms.existsResource((String)value)) {
245                throw new InvalidValueException(
246                    CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_INVALID_RESORUCE_0));
247            }
248
249        }
250
251    }
252
253    /**Serial id.*/
254    private static final long serialVersionUID = 8775619886579477116L;
255
256    /** Message bundle folder name. */
257    protected static final String PATH_I18N = "i18n";
258
259    /** Formatter folder name.*/
260    private static final String FORMATTER = "formatters/";
261
262    /**Message key schema. */
263    private static final String MESSAGE_KEY_FORMATTER = "type.%s.%s";
264
265    /**key: name.*/
266    private static final String MESSAGE_KEY_FORMATTER_NAME = "name";
267
268    /**key: description. */
269    private static final String MESSAGE_KEY_FORMATTER_DESCRIPTION = "description";
270
271    /**key: title. */
272    private static final String MESSAGE_KEY_FORMATTER_TITLE = "title";
273
274    /**key: formatter. */
275    private static final String MESSAGE_KEY_FORMATTER_FORMATTER = "formatter";
276
277    /** Logger instance for this class. */
278    protected static final Log LOG = CmsLog.getLog(CmsNewResourceTypeDialog.class);
279
280    /**Sample path. */
281    private static final String PATH_SAMPLE = "/system/modules/org.opencms.base/copyresources/";
282
283    /** Message properties file encoding. */
284    private static final String PROPERTIES_ENCODING = "ISO-8859-1";
285
286    /** Sample formatter. */
287    private static final String SAMPLE_FORMATTER = PATH_SAMPLE + "sample-formatter.jsp";
288
289    /** Message bundle file name suffix. */
290    private static final String SUFFIX_BUNDLE_FILE = ".messages";
291
292    /** Sample schema. */
293    private static final String SAMPLE_SCHEMA = PATH_SAMPLE + "sample-schema.xsd";
294
295    /**Sample schema element type. */
296    private static final String SAMPLE_TYPE_SCHEMA_ELEMENT = "SampleType";
297
298    /** Schema folder name.*/
299    private static final String SCHEMA = "schemas/";
300
301    /** Default small icon.*/
302    static final String ICON_SMALL_DEFAULT = "oc-icon-16-default";
303
304    /** Default big icon.*/
305    static final String ICON_BIG_DEFAULT = "oc-icon-24-default";
306
307    /**CmsObject. */
308    CmsObject m_cms;
309
310    /** vaadin component.*/
311    private Button m_cancel;
312
313    /** vaadin component.*/
314    private Button m_ok;
315
316    /**vaadin component. */
317    private CmsPathSelectField m_parentFormatter;
318
319    /** vaadin component.*/
320    private CmsPathSelectField m_parentSchema;
321
322    /** vaadin component.*/
323    private TextArea m_typeDescription;
324
325    /** vaadin component.*/
326    private TextField m_typeID;
327
328    /** vaadin component.*/
329    private TextField m_typeName;
330
331    /** vaadin component.*/
332    private TextField m_typeShortName;
333
334    /** vaadin component.*/
335    private TextField m_typeXPathName;
336
337    /** Module of the new resource type.*/
338    private CmsModule m_module;
339
340    /** Vaadin component.*/
341    private CmsPathSelectField m_bundle;
342
343    /** Vaadin component.*/
344    private CmsPathSelectField m_config;
345
346    /**
347     * Public cosntructor.<p>
348     *
349     * @param window window
350     * @param app app
351     */
352    public CmsNewResourceTypeDialog(final Window window, CmsResourceTypeApp app) {
353
354        init(window, app);
355    }
356
357    /**
358     * Get parent folder of messages.<p>
359     *
360     * @param moduleName name of module
361     * @return path name
362     */
363    protected static String getMessageParentFolder(String moduleName) {
364
365        CmsModule module = OpenCms.getModuleManager().getModule(moduleName);
366        for (String resource : module.getResources()) {
367            if (resource.contains(PATH_I18N + "/" + module.getName())) {
368                return resource.substring(0, resource.indexOf(PATH_I18N));
369            }
370        }
371        return "/system/modules/" + module.getName();
372    }
373
374    /**
375     * Tries to find the module folder under /system/modules for a given module.<p>
376     *
377     * @param moduleName the name of the module
378     *
379     * @return the module folder, or null if this module doesn't have one
380     */
381    private static String getModuleFolder(String moduleName) {
382
383        if (moduleName == null) {
384            return null;
385        }
386        CmsModule module = OpenCms.getModuleManager().getModule(moduleName);
387        if (module != null) {
388            for (String resource : module.getResources()) {
389                if (CmsStringUtil.comparePaths("/system/modules/" + moduleName, resource)) {
390                    return resource;
391                }
392            }
393        }
394        return null;
395    }
396
397    /**
398     * Creates the parents of nested XML elements if necessary.
399     * @param xmlContent the XML content that is edited.
400     * @param xmlPath the path of the (nested) element, for which the parents should be created
401     * @param l the locale for which the XML content is edited.
402     */
403    protected void createParentXmlElements(CmsXmlContent xmlContent, String xmlPath, Locale l) {
404
405        if (CmsXmlUtils.isDeepXpath(xmlPath)) {
406            String parentPath = CmsXmlUtils.removeLastXpathElement(xmlPath);
407            if (null == xmlContent.getValue(parentPath, l)) {
408                createParentXmlElements(xmlContent, parentPath, l);
409                xmlContent.addValue(m_cms, parentPath, l, CmsXmlUtils.getXpathIndexInt(parentPath) - 1);
410            }
411        }
412
413    }
414
415    /**
416     * Opens the module select dialog.<p>
417     *
418     * @param window window
419     */
420    protected void openModuleSelect(Window window) {
421
422        window.setContent(new CmsMoveResourceTypeDialog(this));
423        window.center();
424    }
425
426    /**
427     * Sets the module name.<p>
428     *
429     * @param moduleName to be set
430     * @param dialog dialog
431     */
432    protected void setModule(String moduleName, CmsBasicDialog dialog) {
433
434        Window window = CmsVaadinUtils.getWindow(dialog);
435        window.setContent(this);
436        m_module = OpenCms.getModuleManager().getModule(moduleName).clone();
437        CmsResourceInfo resInfo = new CmsResourceInfo(
438            m_module.getName(),
439            m_module.getNiceName(),
440            CmsModuleApp.Icons.RESINFO_ICON);
441        displayResourceInfoDirectly(Arrays.asList(resInfo));
442        fillFields();
443    }
444
445    /**
446     * Submit the entered data.<p>
447     *
448     * @param window window
449     * @param app app
450     */
451    protected void submit(Window window, CmsResourceTypeApp app) {
452
453        if (isValid()) {
454            createResourceType();
455            try {
456                OpenCms.getModuleManager().updateModule(m_cms, m_module);
457                OpenCms.getResourceManager().initialize(m_cms);
458                OpenCms.getWorkplaceManager().addExplorerTypeSettings(m_module);
459                // re-initialize the workplace
460                OpenCms.getWorkplaceManager().initialize(m_cms);
461            } catch (CmsException e) {
462                LOG.error("Unable to save resource type", e);
463            }
464            window.close();
465            app.reload();
466        }
467    }
468
469    /**
470     * Adds the given messages to the workplace properties file.<p>
471     *
472     * @param messages the messages
473     * @param propertiesFile the properties file
474     * @param forcePropertyFileEncoding flag, indicating if encoding {@link #PROPERTIES_ENCODING} should be forced
475     *
476     * @throws CmsException if writing the properties fails
477     * @throws UnsupportedEncodingException in case of encoding issues
478     */
479    private void addMessagesToPropertiesFile(
480        Map<String, String> messages,
481        CmsFile propertiesFile,
482        boolean forcePropertyFileEncoding)
483    throws CmsException, UnsupportedEncodingException {
484
485        lockTemporary(propertiesFile);
486        String encoding = forcePropertyFileEncoding
487        ? PROPERTIES_ENCODING
488        : CmsFileUtil.getEncoding(m_cms, propertiesFile);
489        StringBuffer contentBuffer = new StringBuffer();
490        contentBuffer.append(new String(propertiesFile.getContents(), encoding).trim());
491        for (Entry<String, String> entry : messages.entrySet()) {
492            contentBuffer.append("\n");
493            contentBuffer.append(entry.getKey());
494            contentBuffer.append("=");
495            contentBuffer.append(entry.getValue());
496        }
497        contentBuffer.append("\n");
498        propertiesFile.setContents(contentBuffer.toString().getBytes(encoding));
499        m_cms.writeFile(propertiesFile);
500    }
501
502    /**
503     * Adds the given messages to the vfs message bundle.<p>
504     *
505     * @param messages the messages
506     * @param vfsBundleFile the bundle file
507     *
508     * @throws CmsException if something goes wrong writing the file
509     */
510    private void addMessagesToVfsBundle(Map<String, String> messages, CmsFile vfsBundleFile) throws CmsException {
511
512        lockTemporary(vfsBundleFile);
513        CmsObject cms = m_cms;
514        CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, vfsBundleFile);
515        Locale locale = CmsLocaleManager.getDefaultLocale();
516        if (!content.hasLocale(locale)) {
517            content.addLocale(cms, locale);
518        }
519        Element root = content.getLocaleNode(locale);
520        for (Entry<String, String> entry : messages.entrySet()) {
521            Element message = root.addElement(CmsVfsBundleLoaderXml.N_MESSAGE);
522            Element key = message.addElement(CmsVfsBundleLoaderXml.N_KEY);
523            key.setText(entry.getKey());
524            Element value = message.addElement(CmsVfsBundleLoaderXml.N_VALUE);
525            value.setText(entry.getValue());
526        }
527        content.initDocument();
528        vfsBundleFile.setContents(content.marshal());
529        cms.writeFile(vfsBundleFile);
530    }
531
532    /**
533     * Adds the given resource to the module if necessary.<p>
534     *
535     * @param resourcePath to be added
536     * @param module module
537     */
538    private void addResourceToModule(String resourcePath, CmsModule module) {
539
540        List<String> currentRessources = Lists.newArrayList(module.getResources());
541        for (String resource : currentRessources) {
542            if (resourcePath.startsWith(resource)) {
543                return;
544            }
545        }
546        currentRessources.add(resourcePath);
547        module.setResources(currentRessources);
548    }
549
550    /**
551     * Adds the explorer type messages to the modules workplace bundle.<p>
552     *
553     * @param setting the explorer type settings
554     * @param moduleFolder the module folder name
555     *
556     * @throws CmsException if writing the bundle fails
557     * @throws UnsupportedEncodingException in case of encoding issues
558     */
559    private void addTypeMessages(CmsExplorerTypeSettings setting, String moduleFolder)
560    throws CmsException, UnsupportedEncodingException {
561
562        Map<String, String> messages = new TreeMap<String, String>();
563
564        // check if any messages to set
565        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_typeName.getValue())) {
566            String key = String.format(MESSAGE_KEY_FORMATTER, m_typeShortName.getValue(), MESSAGE_KEY_FORMATTER_NAME);
567            messages.put(key, m_typeName.getValue());
568            setting.setKey(key);
569        }
570        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_typeDescription.getValue())) {
571            String key = String.format(
572                MESSAGE_KEY_FORMATTER,
573                m_typeShortName.getValue(),
574                MESSAGE_KEY_FORMATTER_DESCRIPTION);
575            messages.put(key, m_typeDescription.getValue());
576            setting.setInfo(key);
577        }
578        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(m_typeName.getValue())) {
579            String key = String.format(MESSAGE_KEY_FORMATTER, m_typeShortName.getValue(), MESSAGE_KEY_FORMATTER_TITLE);
580            messages.put(key, m_typeName.getValue());
581            String key2 = String.format(
582                MESSAGE_KEY_FORMATTER,
583                m_typeShortName.getValue(),
584                MESSAGE_KEY_FORMATTER_FORMATTER);
585            messages.put(key2, m_typeName.getValue());
586            setting.setTitleKey(key);
587        }
588
589        if (!messages.isEmpty()) {
590            addMessagesToPropertiesFile(messages, m_cms.readFile(m_bundle.getValue()), false);
591        }
592    }
593
594    /**
595     *
596     * Adjustes formatter config.<p>
597     *
598     * @param formatterPath path of formatter
599     * @param formatterConfigPath config path
600     * @throws CmsException exception
601     */
602
603    private void adjustFormatterConfig(String formatterPath, String formatterConfigPath) throws CmsException {
604
605        CmsResource config = m_cms.readResource(formatterConfigPath);
606
607        CmsFile file = m_cms.readFile(config);
608
609        CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(m_cms, file);
610        Locale l = new Locale("en");
611        if (!xmlContent.hasLocale(l)) {
612            xmlContent.addLocale(m_cms, l);
613        }
614        CmsResource formatter = m_cms.readResource(formatterPath);
615
616        I_CmsXmlContentValue v = xmlContent.getValue("Jsp", l);
617        v.setStringValue(m_cms, formatter.getRootPath());
618
619        String xmlPath = "NiceName";
620        v = xmlContent.getValue(xmlPath, l);
621        v.setStringValue(m_cms, m_typeName.getValue());
622
623        xmlPath = "Type";
624        v = xmlContent.getValue(xmlPath, l);
625        v.setStringValue(m_cms, m_typeShortName.getValue());
626
627        xmlPath = "AutoEnabled";
628        v = xmlContent.getValue(xmlPath, l);
629        v.setStringValue(m_cms, "true");
630
631        xmlPath = "Match/Width/Width";
632        createParentXmlElements(xmlContent, xmlPath, l);
633        v = xmlContent.getValue(xmlPath, l);
634        v.setStringValue(m_cms, "-1");
635
636        file.setContents(xmlContent.marshal());
637        CmsLockUtil.ensureLock(m_cms, file);
638        m_cms.writeFile(file);
639        CmsLockUtil.tryUnlock(m_cms, file);
640
641    }
642
643    /**
644     * Adjustes module config.<p>
645     */
646    private void adjustModuleConfig() {
647
648        Locale l = CmsLocaleManager.getLocale("en");
649        try {
650            CmsResource config = m_cms.readResource(m_config.getValue());
651            CmsFile configFile = m_cms.readFile(config);
652            CmsXmlContent xmlContent = CmsXmlContentFactory.unmarshal(m_cms, configFile);
653            int number = xmlContent.getIndexCount(XMLPath.CONFIG_RESOURCETYPE, l) + 1;
654            createParentXmlElements(
655                xmlContent,
656                XMLPath.CONFIG_RESOURCETYPE + XMLPath.num(number) + XMLPath.CONFIG_RESOURCETYPE_TYPENAME,
657                l);
658            I_CmsXmlContentValue v = xmlContent.getValue(
659                XMLPath.CONFIG_RESOURCETYPE + XMLPath.num(number) + XMLPath.CONFIG_RESOURCETYPE_TYPENAME,
660                l);
661            v.setStringValue(m_cms, m_typeShortName.getValue());
662            v = xmlContent.addValue(
663                m_cms,
664                XMLPath.CONFIG_RESOURCETYPE + XMLPath.num(number) + XMLPath.CONFIG_RESOURCETYPE_NAMEPATTERN,
665                l,
666                CmsXmlUtils.getXpathIndexInt(
667                    XMLPath.CONFIG_RESOURCETYPE + XMLPath.num(number) + XMLPath.CONFIG_RESOURCETYPE_NAMEPATTERN) - 1);
668            v.setStringValue(m_cms, getNamePattern());
669            configFile.setContents(xmlContent.marshal());
670
671            CmsLockUtil.ensureLock(m_cms, configFile);
672            m_cms.writeFile(configFile);
673            CmsLockUtil.tryUnlock(m_cms, configFile);
674        } catch (CmsException e) {
675            LOG.error("Can't read module config resource", e);
676        }
677    }
678
679    /**
680     * Adjustes schema.<p>
681     *
682     * @param schemaPath path to schema resource
683     * @param newElementString new Element name
684     */
685    private void adjustSchema(String schemaPath, String newElementString) {
686
687        newElementString = newElementString.substring(0, 1).toUpperCase() + newElementString.substring(1);
688        try {
689            CmsFile file = m_cms.readFile(schemaPath);
690
691            CmsMacroResolver macroResolver = new CmsMacroResolver();
692            macroResolver.setKeepEmptyMacros(true);
693
694            macroResolver.addMacro(SAMPLE_TYPE_SCHEMA_ELEMENT, newElementString);
695            String bundleName = m_bundle.getValue();
696
697            bundleName = bundleName.split("/")[bundleName.split("/").length - 1];
698            if (bundleName.contains("_")) {
699                bundleName = bundleName.split("_")[0];
700            }
701            macroResolver.addMacro("ResourceBundle", bundleName);
702            macroResolver.addMacro("typeName", m_typeShortName.getValue());
703            String encoding = m_cms.readPropertyObject(
704                file,
705                CmsPropertyDefinition.PROPERTY_CONTENT_ENCODING,
706                true).getValue(OpenCms.getSystemInfo().getDefaultEncoding());
707            String newContent = macroResolver.resolveMacros(new String(file.getContents(), encoding));
708            // update the content
709            try {
710                file.setContents(newContent.getBytes(encoding));
711            } catch (UnsupportedEncodingException e) {
712                try {
713                    file.setContents(newContent.getBytes(Charset.defaultCharset().toString()));
714                } catch (UnsupportedEncodingException e1) {
715                    file.setContents(newContent.getBytes());
716                }
717            }
718            // write the target file
719            CmsLockUtil.ensureLock(m_cms, file);
720            m_cms.writeFile(file);
721            CmsLockUtil.tryUnlock(m_cms, file);
722        } catch (
723
724        CmsException e) {
725            LOG.error("Unable to read schema definition", e);
726        } catch (UnsupportedEncodingException e) {
727            LOG.error("Unable to fetch encoding", e);
728        }
729    }
730
731    /**
732     * Creates the new resource type.<p>
733     */
734    private void createResourceType() {
735
736        CmsProject currentProject = m_cms.getRequestContext().getCurrentProject();
737        try {
738
739            CmsProject workProject = m_cms.createProject(
740                "Add_resource_type_project",
741                "Add resource type project",
742                OpenCms.getDefaultUsers().getGroupAdministrators(),
743                OpenCms.getDefaultUsers().getGroupAdministrators(),
744                CmsProject.PROJECT_TYPE_TEMPORARY);
745            m_cms.getRequestContext().setCurrentProject(workProject);
746
747            if (!m_cms.existsResource(m_parentSchema.getValue())) {
748
749                I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(
750                    CmsResourceTypeFolder.RESOURCE_TYPE_NAME);
751                CmsResource res = m_cms.createResource(m_parentSchema.getValue(), type);
752                m_cms.unlockResource(res);
753
754            }
755            if (!m_cms.existsResource(m_parentFormatter.getValue())) {
756
757                I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(
758                    CmsResourceTypeFolder.RESOURCE_TYPE_NAME);
759                CmsResource res = m_cms.createResource(m_parentFormatter.getValue(), type);
760                m_cms.unlockResource(res);
761
762            }
763
764            String formatterPath = m_parentFormatter.getValue() + m_typeShortName.getValue() + ".jsp";
765            String formatterConfigPath = m_parentFormatter.getValue() + m_typeShortName.getValue() + ".xml";
766            String schemaPath = m_parentSchema.getValue() + m_typeShortName.getValue() + ".xsd";
767            if (!m_cms.existsResource(formatterPath)) {
768                m_cms.copyResource(SAMPLE_FORMATTER, formatterPath);
769            }
770            if (!m_cms.existsResource(schemaPath)) {
771                m_cms.copyResource(SAMPLE_SCHEMA, schemaPath);
772            }
773
774            if (!m_cms.existsResource(formatterConfigPath)) {
775                I_CmsResourceType configType = OpenCms.getResourceManager().getResourceType(
776                    CmsFormatterConfigurationCache.TYPE_FORMATTER_CONFIG);
777                List<CmsProperty> props = new ArrayList<CmsProperty>();
778                props.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_LOCALE, "en", null));
779                props.add(new CmsProperty(CmsPropertyDefinition.PROPERTY_AVAILABLE_LOCALES, "en", null));
780
781                m_cms.createResource(formatterConfigPath, configType, null, props);
782            }
783            CmsLockUtil.tryUnlock(m_cms, m_cms.readResource(formatterPath));
784            CmsLockUtil.tryUnlock(m_cms, m_cms.readResource(formatterConfigPath));
785            CmsLockUtil.tryUnlock(m_cms, m_cms.readResource(schemaPath));
786            addResourceToModule(formatterPath, m_module);
787            addResourceToModule(formatterConfigPath, m_module);
788            addResourceToModule(schemaPath, m_module);
789            adjustFormatterConfig(formatterPath, formatterConfigPath);
790            adjustSchema(schemaPath, m_typeXPathName.getValue());
791            adjustModuleConfig();
792
793            List<I_CmsResourceType> types = new ArrayList<I_CmsResourceType>(m_module.getResourceTypes());
794            // create the new resource type
795            CmsResourceTypeXmlContent type = new CmsResourceTypeXmlContent();
796            type.addConfigurationParameter(CmsResourceTypeXmlContent.CONFIGURATION_SCHEMA, schemaPath);
797            type.setAdditionalModuleResourceType(true);
798            type.setModuleName(m_module.getName());
799            type.initConfiguration(
800                m_typeShortName.getValue(),
801                m_typeID.getValue(),
802                CmsResourceTypeXmlContent.class.getName());
803            types.add(type);
804            m_module.setResourceTypes(types);
805
806            List<CmsExplorerTypeSettings> settings = new ArrayList<CmsExplorerTypeSettings>(
807                m_module.getExplorerTypes());
808            // create the matching explorer type
809            CmsExplorerTypeSettings setting = new CmsExplorerTypeSettings();
810            setting.setTypeAttributes(
811                m_typeShortName.getValue(),
812                m_typeName.getValue(), //ToDo nicename
813                null,
814                null,
815                ICON_SMALL_DEFAULT,
816                ICON_BIG_DEFAULT,
817                CmsResourceTypeXmlContent.getStaticTypeName(),
818                null,
819                "false",
820                null,
821                null);
822            setting.setAutoSetNavigation("false");
823            setting.setAutoSetTitle("false");
824            setting.setNewResourceOrder("10");
825            setting.setAddititionalModuleExplorerType(true);
826            addTypeMessages(setting, getMessageParentFolder(m_module.getName()));
827            settings.add(setting);
828            m_module.setExplorerTypes(settings);
829            m_cms.unlockProject(workProject.getUuid());
830            OpenCms.getPublishManager().publishProject(
831                m_cms,
832                new CmsLogReport(m_cms.getRequestContext().getLocale(), getClass()));
833            OpenCms.getPublishManager().waitWhileRunning();
834        } catch (Exception e) {
835            LOG.error("Unable to create resource type", e);
836        } finally {
837            m_cms.getRequestContext().setCurrentProject(currentProject);
838        }
839    }
840
841    /**
842     * Fills the fields.<p>
843     */
844    private void fillFields() {
845
846        String folderName = getModuleFolder(m_module.getName());
847        if (folderName != null) {
848            if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_parentFormatter.getValue())) {
849                m_parentFormatter.setValue(folderName + FORMATTER);
850            }
851            if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_parentSchema.getValue())) {
852                m_parentSchema.setValue(folderName + SCHEMA);
853            }
854        }
855        if (m_cms.existsResource(folderName + CmsEditModuleForm.CONFIG_FILE)) {
856            m_config.setValue(folderName + CmsEditModuleForm.CONFIG_FILE);
857        }
858        m_parentFormatter.removeAllValidators();
859        m_parentSchema.removeAllValidators();
860        m_config.removeAllValidators();
861        m_config.addValidator(new ResourceValidator());
862        m_bundle.addValidator(new BundleValidator());
863        m_parentFormatter.addValidator(new ResourceValidator());
864        m_parentSchema.addValidator(new ResourceValidator());
865        CmsResource bundle = getMessageBundle();
866        if (bundle != null) {
867            m_bundle.setValue(bundle.getRootPath());
868
869        }
870    }
871
872    /**
873     * Returns the property string for all available locales.<p>
874     *
875     * @return String to use in properties
876     */
877    private String getAvailableLocalString() {
878
879        String res = "";
880
881        for (Locale locale : OpenCms.getLocaleManager().getAvailableLocales()) {
882            res += locale.toString() + ",";
883        }
884        if (res.endsWith(",")) {
885            res = res.substring(0, res.length() - 1);
886        }
887        return res;
888    }
889
890    /**
891     * Returns a random valid resource type id.<p>
892     *
893     * @return valid id
894     */
895    private int getFreeId() {
896
897        int tryID = (int)(10000 + (Math.random() * 90000));
898        while (!CmsResourceTypeApp.isResourceTypeIdFree(tryID)) {
899            tryID = (int)(10000 + (Math.random() * 90000));
900        }
901        return tryID;
902    }
903
904    /**
905     * Gets the message bundle.<p>
906     * @return Message bundle resource
907     */
908    private CmsResource getMessageBundle() {
909
910        OpenCms.getLocaleManager();
911        String localString = CmsLocaleManager.getDefaultLocale().toString();
912        List<String> moduleResource = m_module.getResources();
913        for (String resourcePath : moduleResource) {
914            if (resourcePath.contains(PATH_I18N) && resourcePath.endsWith(localString)) {
915                try {
916                    return m_cms.readResource(resourcePath);
917                } catch (CmsException e) {
918                    LOG.error("Can not read message bundle", e);
919                }
920            }
921        }
922        String moduleFolder = getModuleFolder(m_module.getName());
923        if (CmsStringUtil.isEmptyOrWhitespaceOnly(moduleFolder)) {
924            return null;
925        }
926        String bundlePath = CmsStringUtil.joinPaths(
927            moduleFolder,
928            PATH_I18N,
929            m_module.getName() + SUFFIX_BUNDLE_FILE + "_" + localString);
930        if (m_cms.existsResource(bundlePath)) {
931            try {
932                return m_cms.readResource(bundlePath);
933            } catch (CmsException e) {
934                LOG.error("No bundle found for module", e);
935            }
936        }
937        return null;
938    }
939
940    /**
941     * Creates a name pattern for the resource type.<p>
942     *
943     * @return String name-pattern
944     */
945    private String getNamePattern() {
946
947        String niceName = m_typeShortName.getValue();
948        if (m_typeShortName.getValue().contains("-")) {
949            int maxLength = 0;
950            String[] nameParts = niceName.split("-");
951            for (int i = 0; i < nameParts.length; i++) {
952                if (nameParts[i].length() > maxLength) {
953                    maxLength = nameParts[i].length();
954                    niceName = nameParts[i];
955                }
956            }
957        }
958        return niceName + "_%(number).xml";
959    }
960
961    /**
962     * Initializes the form.<p>
963     *
964     * @param window Window
965     * @param app app
966     */
967    private void init(Window window, CmsResourceTypeApp app) {
968
969        CmsObject rootCms = null;
970        try {
971            m_cms = OpenCms.initCmsObject(A_CmsUI.getCmsObject());
972            rootCms = OpenCms.initCmsObject(m_cms);
973            rootCms.getRequestContext().setSiteRoot("");
974        } catch (CmsException e1) {
975            m_cms = A_CmsUI.getCmsObject();
976            rootCms = m_cms;
977        }
978        CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null);
979
980        m_typeID.addValidator(new IDValidator());
981        m_typeShortName.addValidator(new NameValidator());
982
983        m_parentSchema.setCmsObject(rootCms);
984        m_parentFormatter.setCmsObject(rootCms);
985        m_bundle.setCmsObject(rootCms);
986        m_config.setCmsObject(rootCms);
987
988        m_parentSchema.setRequired(true);
989        m_parentFormatter.setRequired(true);
990        m_typeID.setRequired(true);
991        m_typeDescription.setRequired(true);
992        m_typeShortName.setRequired(true);
993        m_typeXPathName.setRequired(true);
994
995        m_bundle.setRequired(true);
996
997        m_typeName.setRequired(true);
998
999        m_parentSchema.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1000        m_parentFormatter.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1001        m_typeID.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1002        m_typeDescription.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1003        m_typeShortName.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1004        m_typeXPathName.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1005        m_bundle.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1006
1007        m_typeName.setRequiredError(CmsVaadinUtils.getMessageText(Messages.GUI_RESOURCETYPE_EDIT_NOT_EMPTY_0));
1008
1009        m_typeID.setValue(String.valueOf(getFreeId()));
1010        m_ok.addClickListener(e -> submit(window, app));
1011        m_cancel.addClickListener(e -> window.close());
1012
1013    }
1014
1015    /**
1016     * Checks if form is valid.<p>
1017     *
1018     * @return true if form is valid
1019     */
1020    private boolean isValid() {
1021
1022        return m_typeID.isValid()
1023            && m_typeShortName.isValid()
1024            && m_parentFormatter.isValid()
1025            && m_parentSchema.isValid()
1026            && m_bundle.isValid()
1027            && m_config.isValid()
1028            && m_typeDescription.isValid()
1029            && m_typeName.isValid()
1030            && m_typeXPathName.isValid();
1031    }
1032
1033    /**
1034     * Locks the given resource temporarily.<p>
1035     *
1036     * @param resource the resource to lock
1037     *
1038     * @throws CmsException if locking fails
1039     */
1040    private void lockTemporary(CmsResource resource) throws CmsException {
1041
1042        CmsUser user = m_cms.getRequestContext().getCurrentUser();
1043        CmsLock lock = m_cms.getLock(resource);
1044        if (!lock.isOwnedBy(user)) {
1045            m_cms.lockResourceTemporary(resource);
1046        } else if (!lock.isOwnedInProjectBy(user, m_cms.getRequestContext().getCurrentProject())) {
1047            m_cms.changeLock(resource);
1048        }
1049    }
1050}