001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.ade.galleries;
029
030import org.opencms.ade.configuration.CmsADEConfigData;
031import org.opencms.ade.galleries.shared.CmsImageInfoBean;
032import org.opencms.ade.galleries.shared.CmsPoint;
033import org.opencms.ade.galleries.shared.CmsResourceInfoBean;
034import org.opencms.ade.galleries.shared.rpc.I_CmsPreviewService;
035import org.opencms.file.CmsFile;
036import org.opencms.file.CmsObject;
037import org.opencms.file.CmsProperty;
038import org.opencms.file.CmsPropertyDefinition;
039import org.opencms.file.CmsResource;
040import org.opencms.file.CmsResourceFilter;
041import org.opencms.file.CmsVfsResourceNotFoundException;
042import org.opencms.file.history.I_CmsHistoryResource;
043import org.opencms.file.types.CmsResourceTypeImage;
044import org.opencms.file.types.CmsResourceTypeXmlContent;
045import org.opencms.file.types.I_CmsResourceType;
046import org.opencms.gwt.CmsGwtService;
047import org.opencms.gwt.CmsIconUtil;
048import org.opencms.gwt.CmsRpcException;
049import org.opencms.i18n.CmsLocaleManager;
050import org.opencms.jsp.util.CmsJspStandardContextBean;
051import org.opencms.loader.CmsImageScaler;
052import org.opencms.lock.CmsLock;
053import org.opencms.main.CmsException;
054import org.opencms.main.CmsLog;
055import org.opencms.main.CmsPermalinkResourceHandler;
056import org.opencms.main.OpenCms;
057import org.opencms.ui.components.CmsResourceIcon;
058import org.opencms.util.CmsMacroResolver;
059import org.opencms.util.CmsStringUtil;
060import org.opencms.workplace.CmsWorkplaceMessages;
061import org.opencms.workplace.explorer.CmsExplorerTypeSettings;
062import org.opencms.workplace.explorer.CmsResourceUtil;
063import org.opencms.xml.containerpage.CmsContainerBean;
064import org.opencms.xml.containerpage.CmsContainerElementBean;
065import org.opencms.xml.containerpage.CmsContainerPageBean;
066import org.opencms.xml.containerpage.CmsFormatterBean;
067import org.opencms.xml.containerpage.CmsFormatterConfiguration;
068import org.opencms.xml.containerpage.I_CmsFormatterBean;
069import org.opencms.xml.content.CmsXmlContentProperty;
070import org.opencms.xml.content.CmsXmlContentPropertyHelper;
071
072import java.util.Collections;
073import java.util.Date;
074import java.util.HashMap;
075import java.util.Iterator;
076import java.util.LinkedHashMap;
077import java.util.List;
078import java.util.Locale;
079import java.util.Map;
080import java.util.Map.Entry;
081import java.util.regex.Matcher;
082import java.util.regex.Pattern;
083
084import javax.servlet.http.HttpServletRequest;
085import javax.servlet.http.HttpServletResponse;
086
087import org.apache.commons.logging.Log;
088
089import com.google.common.collect.Lists;
090
091/**
092 * Handles all RPC services related to the gallery preview dialog.<p>
093 *
094 * @since 8.0.0
095 */
096public class CmsPreviewService extends CmsGwtService implements I_CmsPreviewService {
097
098    /** Regex used to parse the image.focalpoint property. */
099    public static final Pattern PATTERN_FOCAL_POINT = Pattern.compile(" *([0-9]+) *, *([0-9]+) *");
100
101    /** The logger instance for this class. */
102    private static final Log LOG = CmsLog.getLog(CmsPreviewService.class);
103
104    /** Serialization uid. */
105    private static final long serialVersionUID = -8175522641937277445L;
106
107    /**
108     * Renders the preview content for the given resource and locale.<p>
109     *
110     * @param request the current servlet request
111     * @param response the current servlet response
112     * @param cms the cms context
113     * @param resource the resource
114     * @param locale the content locale
115     *
116     * @return the rendered HTML preview content
117     */
118    public static String getPreviewContent(
119        HttpServletRequest request,
120        HttpServletResponse response,
121        CmsObject cms,
122        CmsResource resource,
123        Locale locale) {
124
125        try {
126            if (CmsResourceTypeXmlContent.isXmlContent(resource)) {
127                CmsADEConfigData adeConfig = OpenCms.getADEManager().lookupConfiguration(
128                    cms,
129                    cms.getRequestContext().getRootUri());
130
131                CmsFormatterConfiguration formatters = adeConfig.getFormatters(cms, resource);
132                I_CmsFormatterBean formatter = formatters.getPreviewFormatter();
133                if (formatter != null) {
134                    CmsObject tempCms = OpenCms.initCmsObject(cms);
135                    tempCms.getRequestContext().setLocale(locale);
136                    CmsResource formatterResource = tempCms.readResource(formatter.getJspStructureId());
137                    request.setAttribute(CmsJspStandardContextBean.ATTRIBUTE_CMS_OBJECT, tempCms);
138                    CmsJspStandardContextBean standardContext = CmsJspStandardContextBean.getInstance(request);
139
140                    Map<String, String> settings = new HashMap<>();
141                    for (Map.Entry<String, CmsXmlContentProperty> entry : formatter.getSettings(adeConfig).entrySet()) {
142                        CmsXmlContentProperty settingConfig = entry.getValue();
143                        String defaultValue = settingConfig.getDefault();
144                        if (defaultValue != null) {
145                            settings.put(entry.getKey(), settingConfig.getDefault());
146                        }
147                    }
148
149                    CmsContainerElementBean element = new CmsContainerElementBean(
150                        resource.getStructureId(),
151                        formatter.getJspStructureId(),
152                        settings,
153                        false);
154                    if ((resource instanceof I_CmsHistoryResource) && (resource instanceof CmsFile)) {
155                        element.setHistoryFile((CmsFile)resource);
156                    }
157                    element.initResource(tempCms);
158                    CmsContainerBean containerBean = new CmsContainerBean(
159                        "PREVIEW",
160                        CmsFormatterBean.PREVIEW_TYPE,
161                        null,
162                        true,
163                        1,
164                        Collections.<CmsContainerElementBean> emptyList());
165                    containerBean.setWidth(String.valueOf(CmsFormatterBean.PREVIEW_WIDTH));
166
167                    standardContext.setContainer(containerBean);
168                    standardContext.setElement(element);
169                    standardContext.setEdited(true);
170                    standardContext.setPage(
171                        new CmsContainerPageBean(Collections.<CmsContainerBean> singletonList(containerBean)));
172                    String encoding = response.getCharacterEncoding();
173                    return (new String(
174                        OpenCms.getResourceManager().getLoader(
175                            formatterResource).dump(tempCms, formatterResource, null, locale, request, response),
176                        encoding)).trim();
177                }
178            }
179        } catch (Exception e) {
180            LOG.warn(e.getLocalizedMessage(), e);
181        }
182        return null;
183    }
184
185    /**
186     * Reads the focal point from a resource.<p>
187     *
188     * @param cms  the CMS context to use
189     * @param resource the resource
190     * @return the focal point (or null, if the focal point property is not set or contains an invalid value)
191     *
192     * @throws CmsException if something goes wrong
193     */
194    public static CmsPoint readFocalPoint(CmsObject cms, CmsResource resource) throws CmsException {
195
196        CmsProperty focalPointProp = cms.readPropertyObject(
197            resource,
198            CmsPropertyDefinition.PROPERTY_IMAGE_FOCAL_POINT,
199            false);
200        CmsPoint focalPoint = null;
201        if (!focalPointProp.isNullProperty()) {
202            String focalPointVal = focalPointProp.getValue();
203            Matcher matcher = PATTERN_FOCAL_POINT.matcher(focalPointVal);
204            if (matcher.matches()) {
205                int fx = Integer.parseInt(matcher.group(1));
206                int fy = Integer.parseInt(matcher.group(2));
207                focalPoint = new CmsPoint(fx, fy);
208
209            }
210        }
211        return focalPoint;
212    }
213
214    /**
215     * @see org.opencms.ade.galleries.shared.rpc.I_CmsPreviewService#getImageInfo(java.lang.String, java.lang.String)
216     */
217    public CmsImageInfoBean getImageInfo(String resourcePath, String locale) throws CmsRpcException {
218
219        CmsObject cms = getCmsObject();
220        CmsImageInfoBean resInfo = new CmsImageInfoBean();
221        try {
222            int pos = resourcePath.indexOf("?");
223            String resName = resourcePath;
224            if (pos > -1) {
225                resName = resourcePath.substring(0, pos);
226            }
227            CmsResource resource = readResourceFromCurrentOrRootSite(cms, resName);
228            readResourceInfo(cms, resource, resInfo, locale);
229            resInfo.setViewLink(
230                CmsStringUtil.joinPaths(
231                    OpenCms.getSystemInfo().getOpenCmsContext(),
232                    CmsPermalinkResourceHandler.PERMALINK_HANDLER,
233                    resource.getStructureId().toString()));
234            resInfo.setHash(resource.getStructureId().hashCode());
235            CmsImageScaler scaler = new CmsImageScaler(cms, resource);
236            int height = -1;
237            int width = -1;
238            if (scaler.isValid()) {
239                height = scaler.getHeight();
240                width = scaler.getWidth();
241            }
242            CmsPoint focalPoint = readFocalPoint(cms, resource);
243            resInfo.setFocalPoint(focalPoint);
244
245            resInfo.setHeight(height);
246            resInfo.setWidth(width);
247            CmsProperty property = cms.readPropertyObject(resource, CmsPropertyDefinition.PROPERTY_COPYRIGHT, false);
248            if (!property.isNullProperty()) {
249                resInfo.setCopyright(property.getValue());
250            }
251        } catch (Exception e) {
252            error(e);
253        }
254        return resInfo;
255    }
256
257    /**
258     * @see org.opencms.ade.galleries.shared.rpc.I_CmsPreviewService#getResourceInfo(java.lang.String, java.lang.String)
259     */
260    public CmsResourceInfoBean getResourceInfo(String resourcePath, String locale) throws CmsRpcException {
261
262        CmsObject cms = getCmsObject();
263        CmsResourceInfoBean resInfo = new CmsResourceInfoBean();
264        try {
265            int pos = resourcePath.indexOf("?");
266            String resName = resourcePath;
267            if (pos > -1) {
268                resName = resourcePath.substring(0, pos);
269            }
270            CmsResource resource = readResourceFromCurrentOrRootSite(cms, resName);
271            readResourceInfo(cms, resource, resInfo, locale);
272        } catch (CmsException e) {
273            error(e);
274        }
275        return resInfo;
276    }
277
278    /**
279     * Retrieves the resource information and puts it into the provided resource info bean.<p>
280     *
281     * @param cms the initialized cms object
282     * @param resource the resource
283     * @param resInfo the resource info bean
284     * @param locale the content locale
285     *
286     * @throws CmsException if something goes wrong
287     */
288    public void readResourceInfo(CmsObject cms, CmsResource resource, CmsResourceInfoBean resInfo, String locale)
289    throws CmsException {
290
291        I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(resource.getTypeId());
292        Locale wpLocale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
293        resInfo.setTitle(resource.getName());
294        resInfo.setStructureId(resource.getStructureId());
295        resInfo.setDescription(CmsWorkplaceMessages.getResourceTypeName(wpLocale, type.getTypeName()));
296        resInfo.setResourcePath(cms.getSitePath(resource));
297        resInfo.setResourceType(type.getTypeName());
298        resInfo.setBigIconClasses(
299            CmsIconUtil.getIconClasses(CmsIconUtil.getDisplayType(cms, resource), resource.getName(), false));
300        // set the default file and detail type info
301        String detailType = CmsResourceIcon.getDefaultFileOrDetailType(cms, resource);
302        if (detailType != null) {
303            resInfo.setSmallIconClasses(CmsIconUtil.getIconClasses(detailType, null, true));
304        }
305        resInfo.setSize((resource.getLength() / 1024) + " kb");
306        resInfo.setLastModified(new Date(resource.getDateLastModified()));
307        resInfo.setNoEditReason(new CmsResourceUtil(cms, resource).getNoEditReason(wpLocale, true));
308        // reading default explorer-type properties
309        CmsExplorerTypeSettings setting = OpenCms.getWorkplaceManager().getExplorerTypeSetting(type.getTypeName());
310        List<String> properties;
311        String rootPathForConfig = cms.getRequestContext().getRootUri();
312        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(cms, rootPathForConfig);
313        Map<String, CmsXmlContentProperty> propConfig = config.getPropertyConfigurationAsMap();
314        CmsMacroResolver resolver = new CmsMacroResolver();
315        resolver.setCmsObject(cms);
316        resolver.setMessages(OpenCms.getWorkplaceManager().getMessages(wpLocale));
317        propConfig = CmsXmlContentPropertyHelper.resolveMacrosInProperties(propConfig, resolver);
318        Map<String, String> niceNames = new HashMap<>();
319        for (CmsXmlContentProperty propEntry : propConfig.values()) {
320            String niceName = propEntry.getNiceName();
321            if (niceName != null) {
322                niceNames.put(propEntry.getName(), niceName);
323            }
324        }
325        if (OpenCms.getResourceManager().matchResourceType(
326            CmsResourceTypeImage.getStaticTypeName(),
327            resource.getTypeId())) {
328            properties = Lists.newArrayList(
329                CmsPropertyDefinition.PROPERTY_TITLE,
330                CmsPropertyDefinition.PROPERTY_COPYRIGHT,
331                CmsPropertyDefinition.PROPERTY_DESCRIPTION);
332        } else {
333            properties = setting.getProperties();
334            String reference = setting.getReference();
335            while ((properties.size() == 0) && !CmsStringUtil.isEmptyOrWhitespaceOnly(reference)) {
336                // looking up properties from referenced explorer types if properties list is empty
337                setting = OpenCms.getWorkplaceManager().getExplorerTypeSetting(reference);
338                properties = setting.getProperties();
339                reference = setting.getReference();
340            }
341        }
342        Map<String, String> props = new LinkedHashMap<String, String>();
343        Iterator<String> propIt = properties.iterator();
344        while (propIt.hasNext()) {
345            String propertyName = propIt.next();
346            CmsProperty property = cms.readPropertyObject(resource, propertyName, false);
347            if (!property.isNullProperty()) {
348                props.put(property.getName(), property.getValue());
349            } else {
350                props.put(propertyName, null);
351            }
352        }
353        resInfo.setProperties(props);
354        resInfo.setPropertyNiceNames(niceNames);
355        resInfo.setPreviewContent(getPreviewContent(cms, resource, CmsLocaleManager.getLocale(locale)));
356    }
357
358    /**
359     * @see org.opencms.ade.galleries.shared.rpc.I_CmsPreviewService#updateImageProperties(java.lang.String, java.lang.String, java.util.Map)
360     */
361    public CmsImageInfoBean updateImageProperties(String resourcePath, String locale, Map<String, String> properties)
362    throws CmsRpcException {
363
364        try {
365            saveProperties(resourcePath, properties);
366        } catch (CmsException e) {
367            error(e);
368        }
369        return getImageInfo(resourcePath, locale);
370    }
371
372    /**
373     * @see org.opencms.ade.galleries.shared.rpc.I_CmsPreviewService#updateResourceProperties(java.lang.String, java.lang.String, java.util.Map)
374     */
375    public CmsResourceInfoBean updateResourceProperties(
376        String resourcePath,
377        String locale,
378        Map<String, String> properties)
379    throws CmsRpcException {
380
381        try {
382            saveProperties(resourcePath, properties);
383        } catch (CmsException e) {
384            error(e);
385        }
386        return getResourceInfo(resourcePath, locale);
387    }
388
389    /**
390     * Renders the preview content for the given resource and locale.<p>
391     *
392     * @param cms the cms context
393     * @param resource the resource
394     * @param locale the content locale
395     *
396     * @return the rendered HTML preview content
397     */
398    private String getPreviewContent(CmsObject cms, CmsResource resource, Locale locale) {
399
400        return getPreviewContent(getRequest(), getResponse(), cms, resource, locale);
401    }
402
403    /**
404     * Tries to read a resource either from the current site or from the root site.<p>
405     *
406     * @param cms the CMS context to use
407     * @param name the resource path
408     *
409     * @return the resource which was read
410     * @throws CmsException if something goes wrong
411     */
412    private CmsResource readResourceFromCurrentOrRootSite(CmsObject cms, String name) throws CmsException {
413
414        CmsResource resource = null;
415        try {
416            resource = cms.readResource(name, CmsResourceFilter.IGNORE_EXPIRATION);
417        } catch (CmsVfsResourceNotFoundException e) {
418            String originalSiteRoot = cms.getRequestContext().getSiteRoot();
419            try {
420                cms.getRequestContext().setSiteRoot("");
421                resource = cms.readResource(name, CmsResourceFilter.IGNORE_EXPIRATION);
422            } finally {
423                cms.getRequestContext().setSiteRoot(originalSiteRoot);
424            }
425
426        }
427        return resource;
428    }
429
430    /**
431     * Saves the given properties to the resource.<p>
432     *
433     * @param resourcePath the resource path
434     * @param properties the properties
435     *
436     * @throws CmsException if something goes wrong
437     */
438    private void saveProperties(String resourcePath, Map<String, String> properties) throws CmsException {
439
440        CmsResource resource;
441        CmsObject cms = getCmsObject();
442        int pos = resourcePath.indexOf("?");
443        String resName = resourcePath;
444        if (pos > -1) {
445            resName = resourcePath.substring(0, pos);
446        }
447        resource = cms.readResource(resName);
448
449        if (properties != null) {
450            for (Entry<String, String> entry : properties.entrySet()) {
451                String propertyName = entry.getKey();
452                String propertyValue = entry.getValue();
453                if (CmsStringUtil.isEmptyOrWhitespaceOnly(propertyValue)) {
454                    propertyValue = "";
455                }
456                try {
457                    CmsProperty currentProperty = cms.readPropertyObject(resource, propertyName, false);
458                    // detect if property is a null property or not
459                    if (currentProperty.isNullProperty()) {
460                        // create new property object and set key and value
461                        currentProperty = new CmsProperty();
462                        currentProperty.setName(propertyName);
463                        if (OpenCms.getWorkplaceManager().isDefaultPropertiesOnStructure()) {
464                            // set structure value
465                            currentProperty.setStructureValue(propertyValue);
466                            currentProperty.setResourceValue(null);
467                        } else {
468                            // set resource value
469                            currentProperty.setStructureValue(null);
470                            currentProperty.setResourceValue(propertyValue);
471                        }
472                    } else if (currentProperty.getStructureValue() != null) {
473                        // structure value has to be updated
474                        currentProperty.setStructureValue(propertyValue);
475                        currentProperty.setResourceValue(null);
476                    } else {
477                        // resource value has to be updated
478                        currentProperty.setStructureValue(null);
479                        currentProperty.setResourceValue(propertyValue);
480                    }
481                    CmsLock lock = cms.getLock(resource);
482                    if (lock.isUnlocked()) {
483                        // lock resource before operation
484                        cms.lockResource(resName);
485                    }
486                    // write the property to the resource
487                    cms.writePropertyObject(resName, currentProperty);
488                    // unlock the resource
489                    cms.unlockResource(resName);
490                } catch (CmsException e) {
491                    // writing the property failed, log error
492                    log(e.getLocalizedMessage());
493                }
494            }
495        }
496    }
497
498}