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.types;
029
030import org.opencms.configuration.CmsConfigurationException;
031import org.opencms.db.CmsSecurityManager;
032import org.opencms.file.CmsFile;
033import org.opencms.file.CmsObject;
034import org.opencms.file.CmsProperty;
035import org.opencms.file.CmsPropertyDefinition;
036import org.opencms.file.CmsResource;
037import org.opencms.file.CmsResource.CmsResourceDeleteMode;
038import org.opencms.file.CmsResource.CmsResourceUndoMode;
039import org.opencms.i18n.CmsEncoder;
040import org.opencms.i18n.CmsLocaleManager;
041import org.opencms.json.JSONObject;
042import org.opencms.jsp.util.CmsJspLinkMacroResolver;
043import org.opencms.loader.CmsJspLoader;
044import org.opencms.main.CmsException;
045import org.opencms.main.CmsIllegalArgumentException;
046import org.opencms.main.CmsLog;
047import org.opencms.main.OpenCms;
048import org.opencms.relations.CmsLink;
049import org.opencms.util.CmsStringUtil;
050import org.opencms.xml.containerpage.CmsFormatterBean;
051import org.opencms.xml.containerpage.CmsFormatterConfiguration;
052import org.opencms.xml.containerpage.I_CmsFormatterBean;
053
054import java.io.UnsupportedEncodingException;
055import java.util.ArrayList;
056import java.util.Collections;
057import java.util.HashSet;
058import java.util.List;
059import java.util.Set;
060
061import org.apache.commons.logging.Log;
062
063/**
064 * Resource type descriptor for the type "jsp".<p>
065 *
066 * Ensures that some required file properties are attached to new JSPs.<p>
067 *
068 * The value for the encoding properties of a new JSP usually is the
069 * system default encoding, but this can be overwritten by
070 * a configuration parameters set in <code>opencms-vfs.xml</code>.<p>
071 *
072 * @since 6.0.0
073 */
074public class CmsResourceTypeJsp extends A_CmsResourceTypeLinkParseable {
075
076    /** Key for formatter setting container maximum width. */
077    public static final String FORMATTER_SETTING_MAX_WIDTH = "maxwidth";
078
079    /** Key for formatter setting container minimum width. */
080    public static final String FORMATTER_SETTING_MIN_WIDTH = "minwidth";
081
082    /** Key for formatter setting container type. */
083    public static final String FORMATTER_SETTING_TYPE = "type";
084
085    /** The type id of the containerpage_template resource type. */
086    private static final int CONTAINERPAGE_TEMPLATE_TYPE_ID = 21;
087
088    /** The type name of the containerpage_template resource type. */
089    private static final String CONTAINERPAGE_TEMPLATE_TYPE_NAME = "containerpage_template";
090
091    /** The type id of the JSP resource type. */
092    private static final int JSP_RESOURCE_TYPE_ID = 4;
093
094    /** Static reference to the log. */
095    private static final Log LOG = CmsLog.getLog(org.opencms.file.types.CmsResourceTypeJsp.class);
096
097    /** The registered JSP resource type id's.    */
098    private static List<Integer> m_jspResourceTypeIds = new ArrayList<Integer>();
099
100    /** The name of this resource type. */
101    private static final String RESOURCE_TYPE_NAME = "jsp";
102
103    /** The serial version id. */
104    private static final long serialVersionUID = 6852747481533451911L;
105
106    /** JSP Loader instance. */
107    protected transient CmsJspLoader m_jspLoader;
108
109    /**
110     * Returns the type id of the containerpage_template resource type.<p>
111     *
112     * @return the type id of the containerpage_template resource type
113     */
114    public static int getContainerPageTemplateTypeId() {
115
116        return CONTAINERPAGE_TEMPLATE_TYPE_ID;
117    }
118
119    /**
120     * Returns the type name of the containerpage_template resource type.<p>
121     *
122     * @return the type name of the containerpage_template resource type
123     */
124    public static String getContainerPageTemplateTypeName() {
125
126        return CONTAINERPAGE_TEMPLATE_TYPE_NAME;
127    }
128
129    /**
130     * Returns the registered JSP resource type id's.<p>
131     *
132     * @return the resource type id's
133     */
134    public static List<Integer> getJspResourceTypeIds() {
135
136        return m_jspResourceTypeIds;
137    }
138
139    /**
140     * Returns the type id of the (default)JSP resource type.<p>
141     *
142     * @return the type id of this (default)JSP resource type
143     */
144    public static int getJSPTypeId() {
145
146        return JSP_RESOURCE_TYPE_ID;
147    }
148
149    /**
150     * Returns the static type name of this (default) resource type.<p>
151     *
152     * @return the static type name of this (default) resource type
153     */
154    public static String getStaticTypeName() {
155
156        return RESOURCE_TYPE_NAME;
157    }
158
159    /**
160     * Returns <code>true</code> in case the given resource is a JSP.<p>
161     *
162     * Internally this checks if the given resource type has an id that is registered as a JSP resource type.<p>
163     *
164     * @param resource the resource to check
165     *
166     * @return <code>true</code> in case the given resource is a JSP
167     *
168     * @since 8.0.0
169     */
170    public static boolean isJsp(CmsResource resource) {
171
172        return resource == null ? false : isJspTypeId(resource.getTypeId());
173    }
174
175    /**
176     * Returns <code>true</code> in case the given resource type id is a JSP type.<p>
177     *
178     * Internally this checks if the given resource type id is registered as a JSP resource type.<p>
179     *
180     * @param typeId the resource type id to check
181     *
182     * @return <code>true</code> in case the given resource type id is a JSP type
183     *
184     * @since 8.0.0
185     */
186    public static boolean isJspTypeId(int typeId) {
187
188        return m_jspResourceTypeIds.contains(Integer.valueOf(typeId));
189    }
190
191    /**
192     * @see org.opencms.file.types.A_CmsResourceType#chtype(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, int)
193     */
194    @Override
195    public void chtype(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, int type)
196    throws CmsException {
197
198        Set<String> references = getReferencingStrongLinks(cms, resource);
199        super.chtype(cms, securityManager, resource, type);
200        removeReferencingFromCache(references);
201    }
202
203    /**
204     * @see org.opencms.file.types.A_CmsResourceType#deleteResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, org.opencms.file.CmsResource.CmsResourceDeleteMode)
205     */
206    @Override
207    public void deleteResource(
208        CmsObject cms,
209        CmsSecurityManager securityManager,
210        CmsResource resource,
211        CmsResourceDeleteMode siblingMode)
212    throws CmsException {
213
214        Set<String> references = getReferencingStrongLinks(cms, resource);
215        super.deleteResource(cms, securityManager, resource, siblingMode);
216        removeReferencingFromCache(references);
217    }
218
219    /**
220     * @see org.opencms.file.types.A_CmsResourceType#getFormattersForResource(org.opencms.file.CmsObject, org.opencms.file.CmsResource)
221     */
222    @Override
223    public CmsFormatterConfiguration getFormattersForResource(CmsObject cms, CmsResource res) {
224
225        // by default JSPs may be rendered within any container with itself as formatter
226        String containerType = CmsFormatterBean.WILDCARD_TYPE;
227        int minWidth = 1;
228        int maxWidth = Integer.MAX_VALUE;
229        try {
230            // check template property to override default settings
231            String formatterSetting = cms.readPropertyObject(
232                res,
233                CmsPropertyDefinition.PROPERTY_TEMPLATE,
234                false).getValue();
235            if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(formatterSetting)) {
236                JSONObject setting = new JSONObject(formatterSetting);
237                if (setting.has(FORMATTER_SETTING_TYPE)) {
238                    containerType = setting.getString(FORMATTER_SETTING_TYPE);
239                }
240                if (setting.has(FORMATTER_SETTING_MIN_WIDTH)) {
241                    minWidth = setting.getInt(FORMATTER_SETTING_MIN_WIDTH);
242                }
243                if (setting.has(FORMATTER_SETTING_MAX_WIDTH)) {
244                    maxWidth = setting.getInt(FORMATTER_SETTING_MAX_WIDTH);
245                }
246            }
247        } catch (Exception e) {
248            LOG.error(
249                Messages.get().getBundle().key(
250                    Messages.ERR_PARSING_FORMATTER_SETTINGS_FROM_PROPERTY_2,
251                    res.getName(),
252                    CmsPropertyDefinition.PROPERTY_TEMPLATE),
253                e);
254        }
255        CmsFormatterBean selfFormatter = new CmsFormatterBean(
256            containerType,
257            res.getRootPath(),
258            res.getStructureId(),
259            minWidth,
260            maxWidth,
261            true,
262            false,
263            res.getRootPath());
264
265        return CmsFormatterConfiguration.create(cms, Collections.<I_CmsFormatterBean> singletonList(selfFormatter));
266    }
267
268    /**
269     * @see org.opencms.file.types.I_CmsResourceType#getLoaderId()
270     */
271    @Override
272    public int getLoaderId() {
273
274        return CmsJspLoader.RESOURCE_LOADER_ID;
275    }
276
277    /**
278     * @see org.opencms.file.types.A_CmsResourceType#initConfiguration(java.lang.String, java.lang.String, String)
279     */
280    @Override
281    public void initConfiguration(String name, String id, String className) throws CmsConfigurationException {
282
283        super.initConfiguration(name, id, className);
284        // set static members with values from the configuration
285        addTypeId(m_typeId);
286    }
287
288    /**
289     * @see org.opencms.file.types.A_CmsResourceType#initialize(org.opencms.file.CmsObject)
290     */
291    @Override
292    public void initialize(CmsObject cms) {
293
294        super.initialize(cms);
295        try {
296            m_jspLoader = (CmsJspLoader)OpenCms.getResourceManager().getLoader(CmsJspLoader.RESOURCE_LOADER_ID);
297        } catch (ArrayIndexOutOfBoundsException e) {
298            // ignore, loader not configured
299        }
300    }
301
302    /**
303     * @see org.opencms.file.types.A_CmsResourceType#moveResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, java.lang.String)
304     */
305    @Override
306    public void moveResource(
307        CmsObject cms,
308        CmsSecurityManager securityManager,
309        CmsResource resource,
310        String destination)
311    throws CmsException, CmsIllegalArgumentException {
312
313        Set<String> references = getReferencingStrongLinks(cms, resource);
314        super.moveResource(cms, securityManager, resource, destination);
315        removeReferencingFromCache(references);
316    }
317
318    /**
319     * @see org.opencms.relations.I_CmsLinkParseable#parseLinks(org.opencms.file.CmsObject, org.opencms.file.CmsFile)
320     */
321    public List<CmsLink> parseLinks(CmsObject cms, CmsFile file) {
322
323        CmsJspLinkMacroResolver macroResolver = new CmsJspLinkMacroResolver(cms, file.getRootPath(), false);
324        String encoding = CmsLocaleManager.getResourceEncoding(cms, file);
325        String content = CmsEncoder.createString(file.getContents(), encoding);
326        macroResolver.resolveMacros(content); // ignore return value
327        return macroResolver.getLinks();
328    }
329
330    /**
331     * @see org.opencms.file.types.A_CmsResourceType#replaceResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, int, byte[], java.util.List)
332     */
333    @Override
334    public void replaceResource(
335        CmsObject cms,
336        CmsSecurityManager securityManager,
337        CmsResource resource,
338        int type,
339        byte[] content,
340        List<CmsProperty> properties)
341    throws CmsException {
342
343        Set<String> references = getReferencingStrongLinks(cms, resource);
344        super.replaceResource(cms, securityManager, resource, type, content, properties);
345        removeReferencingFromCache(references);
346    }
347
348    /**
349     * @see org.opencms.file.types.A_CmsResourceType#restoreResource(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, int)
350     */
351    @Override
352    public void restoreResource(CmsObject cms, CmsSecurityManager securityManager, CmsResource resource, int version)
353    throws CmsException {
354
355        Set<String> references = getReferencingStrongLinks(cms, resource);
356        super.restoreResource(cms, securityManager, resource, version);
357        removeReferencingFromCache(references);
358    }
359
360    /**
361     * @see org.opencms.file.types.A_CmsResourceType#setDateExpired(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, long, boolean)
362     */
363    @Override
364    public void setDateExpired(
365        CmsObject cms,
366        CmsSecurityManager securityManager,
367        CmsResource resource,
368        long dateExpired,
369        boolean recursive)
370    throws CmsException {
371
372        Set<String> references = getReferencingStrongLinks(cms, resource);
373        super.setDateExpired(cms, securityManager, resource, dateExpired, recursive);
374        removeReferencingFromCache(references);
375    }
376
377    /**
378     * @see org.opencms.file.types.A_CmsResourceType#setDateLastModified(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, long, boolean)
379     */
380    @Override
381    public void setDateLastModified(
382        CmsObject cms,
383        CmsSecurityManager securityManager,
384        CmsResource resource,
385        long dateLastModified,
386        boolean recursive)
387    throws CmsException {
388
389        Set<String> references = getReferencingStrongLinks(cms, resource);
390        super.setDateLastModified(cms, securityManager, resource, dateLastModified, recursive);
391        removeReferencingFromCache(references);
392    }
393
394    /**
395     * @see org.opencms.file.types.A_CmsResourceType#setDateReleased(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, long, boolean)
396     */
397    @Override
398    public void setDateReleased(
399        CmsObject cms,
400        CmsSecurityManager securityManager,
401        CmsResource resource,
402        long dateReleased,
403        boolean recursive)
404    throws CmsException {
405
406        Set<String> references = getReferencingStrongLinks(cms, resource);
407        super.setDateReleased(cms, securityManager, resource, dateReleased, recursive);
408        removeReferencingFromCache(references);
409    }
410
411    /**
412     * @see org.opencms.file.types.A_CmsResourceType#undoChanges(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsResource, org.opencms.file.CmsResource.CmsResourceUndoMode)
413     */
414    @Override
415    public void undoChanges(
416        CmsObject cms,
417        CmsSecurityManager securityManager,
418        CmsResource resource,
419        CmsResourceUndoMode mode)
420    throws CmsException {
421
422        Set<String> references = getReferencingStrongLinks(cms, resource);
423        super.undoChanges(cms, securityManager, resource, mode);
424        if (m_jspLoader != null) {
425            // we need to remove the JSP explicitly because undoing the changes also
426            // resets the last modification date, so the automatic mechanism for updating
427            // JSPs based on modification dates doesn't work.
428            m_jspLoader.removeOfflineJspFromRepository(resource);
429        }
430        removeReferencingFromCache(references);
431    }
432
433    /**
434     * @see org.opencms.file.types.A_CmsResourceType#writeFile(org.opencms.file.CmsObject, org.opencms.db.CmsSecurityManager, org.opencms.file.CmsFile)
435     */
436    @Override
437    public CmsFile writeFile(CmsObject cms, CmsSecurityManager securityManager, CmsFile resource) throws CmsException {
438
439        // actualize the link paths and/or ids
440        CmsJspLinkMacroResolver macroResolver = new CmsJspLinkMacroResolver(cms, resource.getRootPath(), false);
441        String encoding = CmsLocaleManager.getResourceEncoding(cms, resource);
442        String content = CmsEncoder.createString(resource.getContents(), encoding);
443        content = macroResolver.resolveMacros(content);
444        try {
445            resource.setContents(content.getBytes(encoding));
446        } catch (UnsupportedEncodingException e) {
447            // this should usually never happen since the encoding is already used before
448            resource.setContents(content.getBytes());
449        }
450        // write the content with the 'right' links
451        Set<String> references = getReferencingStrongLinks(cms, resource);
452        CmsFile file = super.writeFile(cms, securityManager, resource);
453        removeReferencingFromCache(references);
454        return file;
455    }
456
457    /**
458     * Returns a set of root paths of files that are including the given resource using the 'link.strong' macro.<p>
459     *
460     * @param cms the current cms context
461     * @param resource the resource to check
462     *
463     * @return the set of referencing paths
464     *
465     * @throws CmsException if something goes wrong
466     */
467    protected Set<String> getReferencingStrongLinks(CmsObject cms, CmsResource resource) throws CmsException {
468
469        Set<String> references = new HashSet<String>();
470        if (m_jspLoader == null) {
471            return references;
472        }
473        m_jspLoader.getReferencingStrongLinks(cms, resource, references);
474        return references;
475    }
476
477    /**
478     * Removes the referencing resources from the cache.<p>
479     *
480     * @param references the references to remove
481     */
482    protected void removeReferencingFromCache(Set<String> references) {
483
484        if (m_jspLoader != null) {
485            m_jspLoader.removeFromCache(references, false);
486        }
487    }
488
489    /**
490     * Adds another resource type id to the registered JSP resource type id's.<p>
491     *
492     * @param typeId the resource type id to add
493     */
494    private void addTypeId(int typeId) {
495
496        m_jspResourceTypeIds.add(Integer.valueOf(typeId));
497    }
498}