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.util;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsPropertyDefinition;
033import org.opencms.file.CmsRequestContext;
034import org.opencms.file.CmsResource;
035import org.opencms.file.CmsResourceFilter;
036import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
037import org.opencms.file.types.I_CmsResourceType;
038import org.opencms.gwt.CmsIconUtil;
039import org.opencms.gwt.CmsPropertyEditorHelper;
040import org.opencms.gwt.CmsTemplateFinder;
041import org.opencms.gwt.shared.CmsListInfoBean;
042import org.opencms.gwt.shared.property.CmsPropertiesBean;
043import org.opencms.gwt.shared.property.CmsPropertyChangeSet;
044import org.opencms.gwt.shared.property.CmsPropertyModification;
045import org.opencms.i18n.CmsMessages;
046import org.opencms.main.CmsException;
047import org.opencms.main.CmsLog;
048import org.opencms.main.OpenCms;
049import org.opencms.util.CmsMacroResolver;
050import org.opencms.util.CmsStringUtil;
051import org.opencms.util.CmsUUID;
052import org.opencms.xml.content.CmsXmlContentProperty;
053import org.opencms.xml.content.CmsXmlContentPropertyHelper;
054
055import java.util.ArrayList;
056import java.util.LinkedHashMap;
057import java.util.List;
058import java.util.Locale;
059import java.util.Map;
060
061import org.apache.commons.logging.Log;
062
063import com.google.common.collect.Lists;
064
065/**
066 * Helper class for creating a new resource using the New dialog.<p>
067 */
068public class CmsNewResourceBuilder {
069
070    /**
071     * Interface for callbacks which should be notified when this helper has created a resource.<p>
072     */
073    public static interface I_Callback {
074
075        /**
076         * Error handler.<p>
077         *
078         * @param e the exception which was thrown
079         */
080        void onError(Exception e);
081
082        /**
083         * This should be called after the resource is fully created and its properties have been set.<p>
084         *
085         * @param builder the resource builder
086         */
087        void onResourceCreated(CmsNewResourceBuilder builder);
088    }
089
090    /**
091     * Property helper subclass which is responsible for loading the initial property data to display in the property
092     * dialog for a resource to be created in the New dialog.<p>
093     */
094    public class PropertyEditorHelper extends CmsPropertyEditorHelper {
095
096        /**
097         * Creates a new instance.<p>
098         *
099         * @param cms the CMS cntext
100         */
101        public PropertyEditorHelper(CmsObject cms) {
102
103            super(cms);
104        }
105
106        /**
107         * Loads the data needed for editing the properties of a resource.<p>
108         *
109         * @param id the structure id of the resource (ignored)
110         *
111         * @return the data needed for editing the properties
112         *
113         * @throws CmsException if something goes wrong
114         */
115        @SuppressWarnings("synthetic-access")
116        @Override
117        public CmsPropertiesBean loadPropertyData(CmsUUID id) throws CmsException {
118
119            CmsObject cms = m_cms;
120            String originalSiteRoot = cms.getRequestContext().getSiteRoot();
121            CmsPropertiesBean result = new CmsPropertiesBean();
122
123            result.setReadOnly(false);
124            I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(m_type);
125            List<CmsProperty> typeDefaultProperties = type.getConfiguredDefaultProperties();
126            result.setFolder(type.isFolder());
127            result.setContainerPage(m_type.equals(CmsResourceTypeXmlContainerPage.getStaticTypeName()));
128            String sitePath = OpenCms.getResourceManager().getNameGenerator().getNewFileName(
129                m_cms,
130                m_pathWithPattern,
131                5,
132                m_explorerNameGeneration);
133            String rootPath = m_cms.getRequestContext().addSiteRoot(sitePath);
134            Map<String, CmsXmlContentProperty> propertyConfig;
135            Map<String, CmsXmlContentProperty> defaultProperties = getDefaultPropertiesForType(m_type);
136            Map<String, CmsXmlContentProperty> mergedConfig = OpenCms.getADEManager().lookupConfiguration(
137                cms,
138                rootPath).getPropertyConfiguration(defaultProperties);
139            propertyConfig = mergedConfig;
140
141            // Resolve macros in the property configuration
142            propertyConfig = CmsXmlContentPropertyHelper.resolveMacrosInProperties(
143                propertyConfig,
144                CmsMacroResolver.newWorkplaceLocaleResolver(cms));
145            CmsPropertyEditorHelper.updateWysiwygConfig(propertyConfig, cms, null);
146
147            result.setPropertyDefinitions(new LinkedHashMap<String, CmsXmlContentProperty>(propertyConfig));
148            try {
149                cms.getRequestContext().setSiteRoot("");
150                String parentPath = CmsResource.getParentFolder(rootPath);
151                CmsResource parent = cms.readResource(parentPath, CmsResourceFilter.IGNORE_EXPIRATION);
152                List<CmsProperty> parentProperties = cms.readPropertyObjects(parent, true);
153                List<CmsProperty> ownProperties = typeDefaultProperties;
154                result.setOwnProperties(convertProperties(ownProperties));
155                result.setInheritedProperties(convertProperties(parentProperties));
156                result.setPageInfo(getPageInfo(sitePath));
157                List<CmsPropertyDefinition> propDefs = cms.readAllPropertyDefinitions();
158                List<String> propNames = new ArrayList<String>();
159                for (CmsPropertyDefinition propDef : propDefs) {
160                    if (CmsStringUtil.isEmpty(propDef.getName())) {
161                        LOG.warn("Empty property definition name: " + propDef);
162                        continue;
163                    }
164                    propNames.add(propDef.getName());
165                }
166                CmsTemplateFinder templateFinder = new CmsTemplateFinder(cms);
167                result.setTemplates(templateFinder.getTemplates());
168                result.setAllProperties(propNames);
169                result.setStructureId(id);
170                result.setSitePath(sitePath);
171                return result;
172            } finally {
173                cms.getRequestContext().setSiteRoot(originalSiteRoot);
174            }
175        }
176
177        /**
178         * Gets the page info bean.<p>
179         *
180         * @param sitePath the site path
181         * @return the page info bean
182         */
183        private CmsListInfoBean getPageInfo(String sitePath) {
184
185            CmsListInfoBean listInfo = new CmsListInfoBean();
186            listInfo.setResourceState(CmsResource.STATE_NEW);
187            listInfo.setTitle(CmsResource.getName(sitePath));
188            listInfo.setSubTitle(sitePath);
189
190            String key = OpenCms.getWorkplaceManager().getExplorerTypeSetting(m_type).getKey();
191            Locale currentLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms);
192            CmsMessages messages = OpenCms.getWorkplaceManager().getMessages(currentLocale);
193            String resTypeNiceName = messages.key(key);
194            listInfo.addAdditionalInfo(
195                messages.key(org.opencms.workplace.commons.Messages.GUI_LABEL_TYPE_0),
196                resTypeNiceName);
197            listInfo.setBigIconClasses(CmsIconUtil.getIconClasses(m_type, sitePath, false));
198            listInfo.setResourceType(m_type);
199            return listInfo;
200        }
201    }
202
203    /** The logger instance for this class. */
204    private static final Log LOG = CmsLog.getLog(CmsNewResourceBuilder.class);
205
206    /** The CMS context. */
207    CmsObject m_cms;
208
209    /** The resource type name. */
210    String m_type;
211
212    /** The list of registered callbacks. */
213    private List<I_Callback> m_callbacks = Lists.newArrayList();
214
215    /** The created resource (null until this helper has finished creating the resource). */
216    private CmsResource m_createdResource;
217
218    /** True if explorer name generation is enabled. */
219    private boolean m_explorerNameGeneration;
220
221    /** The model resource. */
222    private CmsResource m_modelResource;
223
224    /** The path with name pattern at which the resource should be created. */
225    private String m_pathWithPattern;
226
227    /** The property changes to save (may be null). */
228    private CmsPropertyChangeSet m_propChanges;
229
230    /**
231     * Creates a new instance.<p>
232     *
233     * @param cms the CMS context
234     * @throws CmsException if something goes wrong
235     */
236    public CmsNewResourceBuilder(CmsObject cms)
237    throws CmsException {
238
239        m_cms = OpenCms.initCmsObject(cms);
240    }
241
242    /**
243     * Adds a callback to be notified when the resource is created.<p>
244     *
245     * @param callback the callback
246     */
247    public void addCallback(I_Callback callback) {
248
249        m_callbacks.add(callback);
250    }
251
252    /**
253     * Triggers the resource creation.<p>
254     *
255     * @return the created resource
256     *
257     * @throws CmsException if something goes wrong
258     */
259    public CmsResource createResource() throws CmsException {
260
261        CmsRequestContext context = m_cms.getRequestContext();
262        if (m_modelResource != null) {
263            context.setAttribute(CmsRequestContext.ATTRIBUTE_MODEL, m_modelResource.getRootPath());
264        }
265
266        String name = null;
267        for (CmsPropertyModification propMod : m_propChanges.getChanges()) {
268            if (propMod.isFileNameProperty()) {
269                name = propMod.getValue();
270            }
271        }
272        String path = null;
273        if (CmsStringUtil.isEmptyOrWhitespaceOnly(name)) {
274            path = OpenCms.getResourceManager().getNameGenerator().getNewFileName(
275                m_cms,
276                m_pathWithPattern,
277                5,
278                m_explorerNameGeneration);
279        } else {
280            String parent = CmsResource.getParentFolder(m_pathWithPattern);
281            path = CmsStringUtil.joinPaths(parent, name.trim());
282        }
283        Locale contentLocale = OpenCms.getLocaleManager().getDefaultLocale(m_cms, CmsResource.getFolderPath(path));
284        context.setAttribute(CmsRequestContext.ATTRIBUTE_NEW_RESOURCE_LOCALE, contentLocale);
285        CmsResource res = m_cms.createResource(
286            path,
287            OpenCms.getResourceManager().getResourceType(m_type),
288            null,
289            new ArrayList<CmsProperty>());
290        if (m_propChanges != null) {
291            CmsPropertyEditorHelper helper = new CmsPropertyEditorHelper(m_cms);
292            helper.overrideStructureId(res.getStructureId());
293            helper.saveProperties(m_propChanges);
294        }
295        // Path or other metadata may have changed
296        m_createdResource = m_cms.readResource(res.getStructureId(), CmsResourceFilter.IGNORE_EXPIRATION);
297        try {
298            m_cms.unlockResource(m_createdResource);
299        } catch (CmsException e) {
300            LOG.info(e.getLocalizedMessage(), e);
301        }
302        for (I_Callback callback : m_callbacks) {
303            callback.onResourceCreated(this);
304        }
305        return m_createdResource;
306    }
307
308    /**
309     * Gets the created resource.<p>
310     *
311     * This will null before the resource creation process.
312     *
313     * @return the created resource
314     */
315    public CmsResource getCreatedResource() {
316
317        return m_createdResource;
318    }
319
320    /**
321     * Loads the property data with which the property dialog for the new resource should be initialized.<p>
322     *
323     * @return the properties bean
324     */
325    public CmsPropertiesBean getPropertyData() {
326
327        CmsPropertyEditorHelper helper = new PropertyEditorHelper(m_cms);
328        try {
329            CmsPropertiesBean data = helper.loadPropertyData(CmsUUID.getNullUUID());
330            return data;
331        } catch (Exception e) {
332            throw new RuntimeException(e);
333        }
334    }
335
336    /**
337     * Creates a resource, but doesn't throw any exceptions.<p>
338     *
339     * Exceptions will be passed to the onError method of registered callbacks.<p>
340     *
341     * @return the created resource
342     */
343    public CmsResource safeCreateResource() {
344
345        try {
346            return createResource();
347        } catch (Exception e) {
348            for (I_Callback callback : m_callbacks) {
349                callback.onError(e);
350            }
351            return null;
352        }
353    }
354
355    /**
356     * Sets the Explorer name generation mode.<p>
357     *
358     * @param explorerNameGenerationMode the explorer name generation mode
359     */
360    public void setExplorerNameGeneration(boolean explorerNameGenerationMode) {
361
362        m_explorerNameGeneration = explorerNameGenerationMode;
363    }
364
365    /**
366     * Sets the locale.<p>
367     *
368     * @param locale the locale
369     */
370    public void setLocale(Locale locale) {
371
372        m_cms.getRequestContext().setLocale(locale);
373
374    }
375
376    /**
377     * Sets the model resource.<p>
378     *
379     * @param modelResource the model resource
380     */
381    public void setModel(CmsResource modelResource) {
382
383        m_modelResource = modelResource;
384    }
385
386    /**
387     * Sets the creation path containing a number pattern.<p>
388     *
389     * @param destination the creation path
390     */
391    public void setPatternPath(String destination) {
392
393        m_pathWithPattern = destination;
394    }
395
396    /**
397     * Sets the property changes.<p>
398     *
399     * @param propertyChanges the property changes
400     */
401    public void setPropertyChanges(CmsPropertyChangeSet propertyChanges) {
402
403        m_propChanges = propertyChanges;
404    }
405
406    /**
407     * Sets the site root of the CMS context.<p>
408     *
409     * @param siteRoot the site root
410     */
411    public void setSiteRoot(String siteRoot) {
412
413        m_cms.getRequestContext().setSiteRoot(siteRoot);
414    }
415
416    /**
417     * Sets the resource type name.<p>
418     *
419     * @param type the resource type name
420     */
421    public void setType(String type) {
422
423        m_type = type;
424    }
425}