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 GmbH & Co. KG, 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.file.collectors;
029
030import org.opencms.ade.contenteditor.shared.CmsEditorConstants;
031import org.opencms.ade.publish.CmsCollectorPublishListHelper;
032import org.opencms.file.CmsDataAccessException;
033import org.opencms.file.CmsFile;
034import org.opencms.file.CmsObject;
035import org.opencms.file.CmsRequestContext;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsResourceFilter;
038import org.opencms.gwt.shared.CmsGwtConstants;
039import org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo;
040import org.opencms.main.CmsException;
041import org.opencms.main.CmsIllegalArgumentException;
042import org.opencms.main.CmsLog;
043import org.opencms.main.OpenCms;
044import org.opencms.util.CmsPair;
045import org.opencms.util.CmsStringUtil;
046import org.opencms.xml.content.CmsXmlContent;
047import org.opencms.xml.content.CmsXmlContentFactory;
048
049import java.util.List;
050import java.util.Locale;
051import java.util.Set;
052
053import org.apache.commons.lang3.StringUtils;
054import org.apache.commons.lang3.math.NumberUtils;
055import org.apache.commons.logging.Log;
056
057/**
058 * Provides some helpful base implementations for resource collector classes.<p>
059 *
060 * @since 6.0.0
061 */
062public abstract class A_CmsResourceCollector implements I_CmsResourceCollector {
063
064    /** The template file separator string for creating a new resource in direct edit mode,
065     *  can be used to append an explicit template file name in {@link #getCreateParam(CmsObject, String, String)}. */
066    public static final String SEPARATOR_TEMPLATEFILE = "::";
067
068    /** Logger instance for this class. */
069    private static final Log LOG = CmsLog.getLog(A_CmsResourceCollector.class);
070
071    /** The collector order of this collector. */
072    protected int m_order;
073
074    /** The name of the configured default collector. */
075    private String m_defaultCollectorName;
076
077    /** The default collector parameters. */
078    private String m_defaultCollectorParam;
079
080    /** The hash code of this collector. */
081    private int m_hashcode;
082
083    /**
084     * Constructor to initialize some default values.<p>
085     */
086    public A_CmsResourceCollector() {
087
088        m_hashcode = getClass().getName().hashCode();
089    }
090
091    /**
092     * Creates a new content collector resource.<p>
093     *
094     * @param cms the cms context
095     * @param newLink the new resource link
096     * @param locale the content locale
097     * @param referenceResource the reference resource
098     * @param modelFile the model file
099     * @param mode the optional creation mode (can be null)
100     * @param postCreateHandler optional class name of an {@link I_CmsCollectorPostCreateHandler} which is invoked after the content has been created.
101     *      The fully qualified class name can be followed by a "|" symbol and a handler specific configuration string.
102     * @return the new file name
103     *
104     * @throws CmsException if something goes wrong
105     */
106    public static String createResourceForCollector(
107        CmsObject cms,
108        String newLink,
109        Locale locale,
110        String referenceResource,
111        String modelFile,
112        String mode,
113        String postCreateHandler)
114    throws CmsException {
115
116        // get the collector used to create the new content
117        int pos = newLink.indexOf('|');
118        String collectorName = newLink.substring(0, pos);
119        String collectorParams = newLink.substring(pos + 1);
120
121        String param;
122        String templateFileName;
123
124        pos = collectorParams.indexOf(A_CmsResourceCollector.SEPARATOR_TEMPLATEFILE);
125        if (pos != -1) {
126            // found an explicit template file name to use for the new resource, use it
127            param = collectorParams.substring(0, pos);
128            templateFileName = collectorParams.substring(pos + A_CmsResourceCollector.SEPARATOR_TEMPLATEFILE.length());
129        } else {
130            // no template file name was specified, use given resource name as template file
131            param = collectorParams;
132            templateFileName = referenceResource;
133        }
134
135        // get the collector used for calculating the next file name
136        I_CmsResourceCollector collector = OpenCms.getResourceManager().getContentCollector(collectorName);
137        String newFileName = "";
138        // one resource serves as a "template" for the new resource
139        CmsResource templateResource = cms.readResource(templateFileName, CmsResourceFilter.IGNORE_EXPIRATION);
140        CmsXmlContent newContent = null;
141        int typeId;
142        CmsObject cloneCms = OpenCms.initCmsObject(cms);
143        cloneCms.getRequestContext().setRequestTime(CmsResource.DATE_RELEASED_EXPIRED_IGNORE);
144        // the reference resource may be a folder in case of creating for an empty collector list
145        if (!templateResource.isFolder()) {
146            typeId = templateResource.getTypeId();
147            CmsFile templateFile = cms.readFile(templateResource);
148            CmsXmlContent template = CmsXmlContentFactory.unmarshal(cloneCms, templateFile);
149            // now create a new XML content based on the templates content definition
150            newContent = CmsXmlContentFactory.createDocument(
151                cms,
152                locale,
153                template.getEncoding(),
154                template.getContentDefinition());
155        } else {
156            typeId = collector.getCreateTypeId(cloneCms, collectorName, collectorParams);
157        }
158        // IMPORTANT: calculation of the name MUST be done here so the file name is ensured to be valid
159        newFileName = collector.getCreateLink(cms, collectorName, param);
160
161        boolean isCopy = StringUtils.equalsIgnoreCase(mode, CmsEditorConstants.MODE_COPY);
162        if (isCopy) {
163            modelFile = referenceResource;
164        }
165        boolean useModelFile = false;
166        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(modelFile)) {
167            cms.getRequestContext().setAttribute(CmsRequestContext.ATTRIBUTE_MODEL, modelFile);
168            useModelFile = true;
169        }
170        // now create the resource, fill it with the marshalled XML and write it back to the VFS
171        cms.createResource(newFileName, typeId);
172        // re-read the created resource
173        CmsFile newFile = cms.readFile(newFileName, CmsResourceFilter.ALL);
174        if (!useModelFile && (newContent != null)) {
175            newFile.setContents(newContent.marshal());
176            // write the file with the updated content
177            cloneCms.writeFile(newFile);
178        }
179        CmsPair<String, String> handlerParameter = I_CmsCollectorPostCreateHandler.splitClassAndConfig(
180            postCreateHandler);
181        I_CmsCollectorPostCreateHandler handler = getPostCreateHandler(handlerParameter.getFirst());
182        handler.onCreate(cms, newFile, isCopy, handlerParameter.getSecond());
183        return newFileName;
184
185    }
186
187    /**
188     * Instantiates a post-create handler given a class name (which may actually be null).<p>
189     *
190     * If the given name is null or does not refer to a valid post-create handler class, a default implementation
191     * will be returned.<p>
192     *
193     * @param className the class name of the post-create handler class
194     *
195     * @return a post-create handler instance
196     */
197    public static I_CmsCollectorPostCreateHandler getPostCreateHandler(String className) {
198
199        if (CmsStringUtil.isEmptyOrWhitespaceOnly(className)) {
200            return new CmsDefaultPostCreateHandler();
201        }
202        try {
203            Class<?> handlerClass = Class.forName(className);
204            if (I_CmsCollectorPostCreateHandler.class.isAssignableFrom(handlerClass)) {
205                I_CmsCollectorPostCreateHandler handler = (I_CmsCollectorPostCreateHandler)handlerClass.newInstance();
206                return handler;
207            } else {
208                LOG.error("Post-create handler class does not implement I_CmsPostCreateHandler: '" + className + "'");
209                return new CmsDefaultPostCreateHandler();
210            }
211        } catch (Exception e) {
212            LOG.error("Problem using post-create handler: '" + className + "'," + e.getLocalizedMessage(), e);
213            return new CmsDefaultPostCreateHandler();
214        }
215    }
216
217    /**
218     * @see java.lang.Comparable#compareTo(java.lang.Object)
219     */
220    public int compareTo(I_CmsResourceCollector obj) {
221
222        if (obj == this) {
223            return 0;
224        }
225        return getOrder() - obj.getOrder();
226    }
227
228    /**
229     * Two collectors are considered to be equal if they are sharing the same
230     * implementation class.<p>
231     *
232     * @see java.lang.Object#equals(java.lang.Object)
233     */
234    @Override
235    public boolean equals(Object obj) {
236
237        if (obj == this) {
238            return true;
239        }
240        if (obj instanceof I_CmsResourceCollector) {
241            return getClass().getName().equals(obj.getClass().getName());
242        }
243        return false;
244    }
245
246    /**
247     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateLink(org.opencms.file.CmsObject)
248     */
249    public String getCreateLink(CmsObject cms) throws CmsException, CmsDataAccessException {
250
251        checkParams();
252        return getCreateLink(cms, getDefaultCollectorName(), getDefaultCollectorParam());
253    }
254
255    /**
256     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateParam(org.opencms.file.CmsObject)
257     */
258    public String getCreateParam(CmsObject cms) throws CmsDataAccessException {
259
260        checkParams();
261        return getCreateParam(cms, getDefaultCollectorName(), getDefaultCollectorParam());
262    }
263
264    /**
265     * @see org.opencms.file.collectors.I_CmsResourceCollector#getCreateTypeId(org.opencms.file.CmsObject, java.lang.String, java.lang.String)
266     */
267    @SuppressWarnings("unused")
268    public int getCreateTypeId(CmsObject cms, String collectorName, String param) throws CmsException {
269
270        // overwrite to allow creation of new items
271        return -1;
272    }
273
274    /**
275     * @see org.opencms.file.collectors.I_CmsResourceCollector#getDefaultCollectorName()
276     */
277    public String getDefaultCollectorName() {
278
279        return m_defaultCollectorName;
280    }
281
282    /**
283     * @see org.opencms.file.collectors.I_CmsResourceCollector#getDefaultCollectorParam()
284     */
285    public String getDefaultCollectorParam() {
286
287        return m_defaultCollectorParam;
288    }
289
290    /**
291     * @see org.opencms.file.collectors.I_CmsResourceCollector#getOrder()
292     */
293    public int getOrder() {
294
295        return m_order;
296    }
297
298    /**
299     * @see org.opencms.file.collectors.I_CmsCollectorPublishListProvider#getPublishResources(org.opencms.file.CmsObject, org.opencms.gwt.shared.I_CmsContentLoadCollectorInfo)
300     */
301    public Set<CmsResource> getPublishResources(final CmsObject cms, final I_CmsContentLoadCollectorInfo info)
302    throws CmsException {
303
304        int collectorLimit = NumberUtils.toInt(
305            OpenCms.getADEManager().getParameters(cms).get(CmsGwtConstants.COLLECTOR_PUBLISH_LIST_LIMIT),
306            DEFAULT_LIMIT);
307        CmsCollectorPublishListHelper helper = new CmsCollectorPublishListHelper(cms, info, collectorLimit);
308        return helper.getPublishListFiles();
309
310    }
311
312    /**
313     * @see org.opencms.file.collectors.I_CmsResourceCollector#getResults(org.opencms.file.CmsObject)
314     */
315    public List<CmsResource> getResults(CmsObject cms) throws CmsDataAccessException, CmsException {
316
317        checkParams();
318        return getResults(cms, getDefaultCollectorName(), getDefaultCollectorParam());
319    }
320
321    /**
322     * @see java.lang.Object#hashCode()
323     */
324    @Override
325    public int hashCode() {
326
327        return m_hashcode;
328    }
329
330    /**
331     * @see org.opencms.file.collectors.I_CmsResourceCollector#setDefaultCollectorName(java.lang.String)
332     */
333    public void setDefaultCollectorName(String collectorName) {
334
335        m_defaultCollectorName = collectorName;
336    }
337
338    /**
339     * @see org.opencms.file.collectors.I_CmsResourceCollector#setDefaultCollectorParam(java.lang.String)
340     */
341    public void setDefaultCollectorParam(String param) {
342
343        m_defaultCollectorParam = param;
344    }
345
346    /**
347     * @see org.opencms.file.collectors.I_CmsResourceCollector#setOrder(int)
348     */
349    public void setOrder(int order) {
350
351        m_order = order;
352    }
353
354    /**
355     * Checks if the required parameters have been set.<p>
356     *
357     * @see #setDefaultCollectorName(String)
358     * @see #setDefaultCollectorParam(String)
359     */
360    protected void checkParams() {
361
362        if ((m_defaultCollectorName == null) || (m_defaultCollectorParam == null)) {
363            throw new CmsIllegalArgumentException(
364                Messages.get().container(
365                    Messages.ERR_COLLECTOR_DEFAULTS_INVALID_2,
366                    m_defaultCollectorName,
367                    m_defaultCollectorParam));
368        }
369    }
370
371    /**
372     * Returns the link to create a new XML content item in the folder pointed to by the parameter.<p>
373     *
374     * @param cms the current CmsObject
375     * @param data the collector data to use
376     *
377     * @return the link to create a new XML content item in the folder
378     *
379     * @throws CmsException if something goes wrong
380     *
381     * @since 7.0.2
382     */
383    protected String getCreateInFolder(CmsObject cms, CmsCollectorData data) throws CmsException {
384
385        return OpenCms.getResourceManager().getNameGenerator().getNewFileName(cms, data.getFileName(), 4);
386    }
387
388    /**
389     * Returns the link to create a new XML content item in the folder pointed to by the parameter.<p>
390     *
391     * @param cms the current CmsObject
392     * @param param the folder name to use
393     *
394     * @return the link to create a new XML content item in the folder
395     *
396     * @throws CmsException if something goes wrong
397     */
398    protected String getCreateInFolder(CmsObject cms, String param) throws CmsException {
399
400        return getCreateInFolder(cms, new CmsCollectorData(param));
401    }
402
403    /**
404     * Shrinks a List to fit a maximum size.<p>
405     *
406     * @param result a List
407     * @param maxSize the maximum size of the List
408     *
409     * @return the reduced list
410     */
411    protected List<CmsResource> shrinkToFit(List<CmsResource> result, int maxSize) {
412
413        if ((maxSize > 0) && (result.size() > maxSize)) {
414            // cut off all items > count
415            result = result.subList(0, maxSize);
416        }
417
418        return result;
419    }
420
421    /**
422     * Shrinks a List to fit a maximum size.<p>
423     *
424     * @param result a List
425     * @param maxSize the maximum size of the List
426     * @param explicitNumResults the value of the numResults parameter given to the getResults method (this overrides maxSize if it is positive)
427     *
428     * @return the reduced list
429     */
430    protected List<CmsResource> shrinkToFit(List<CmsResource> result, int maxSize, int explicitNumResults) {
431
432        return shrinkToFit(result, explicitNumResults > 0 ? explicitNumResults : maxSize);
433    }
434}