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.workflow;
029
030import org.opencms.ade.publish.CmsPublishRelationFinder.ResourceMap;
031import org.opencms.ade.publish.Messages;
032import org.opencms.ade.publish.shared.CmsPublishOptions;
033import org.opencms.ade.publish.shared.CmsPublishResource;
034import org.opencms.ade.publish.shared.CmsPublishResourceInfo;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProject;
037import org.opencms.file.CmsResource;
038import org.opencms.file.CmsResourceFilter;
039import org.opencms.file.CmsUser;
040import org.opencms.file.types.CmsResourceTypeXmlContent;
041import org.opencms.gwt.CmsIconUtil;
042import org.opencms.gwt.CmsVfsService;
043import org.opencms.gwt.shared.CmsPermissionInfo;
044import org.opencms.lock.CmsLock;
045import org.opencms.lock.CmsLockFilter;
046import org.opencms.main.CmsException;
047import org.opencms.main.CmsLog;
048import org.opencms.main.OpenCms;
049import org.opencms.security.CmsOrganizationalUnit;
050import org.opencms.security.CmsPermissionSet;
051import org.opencms.ui.components.CmsResourceIcon;
052import org.opencms.util.CmsStringUtil;
053import org.opencms.util.CmsUUID;
054import org.opencms.workplace.explorer.CmsResourceUtil;
055
056import java.util.ArrayList;
057import java.util.Arrays;
058import java.util.Collections;
059import java.util.Comparator;
060import java.util.Date;
061import java.util.HashMap;
062import java.util.HashSet;
063import java.util.List;
064import java.util.Locale;
065import java.util.Map;
066import java.util.Set;
067
068import org.apache.commons.logging.Log;
069
070import com.google.common.base.Predicate;
071import com.google.common.collect.ComparisonChain;
072import com.google.common.collect.Lists;
073import com.google.common.collect.Maps;
074
075/**
076 * Default formatter class for publish  resources.<p>
077 */
078public class CmsDefaultPublishResourceFormatter implements I_CmsPublishResourceFormatter {
079
080    /**
081     * Excludes resources which have already been published.<p>
082     */
083    public class AlreadyPublishedValidator implements I_PublishResourceValidator {
084
085        /**
086         * @see org.opencms.workflow.CmsDefaultPublishResourceFormatter.I_PublishResourceValidator#findInvalidResources(java.util.Set)
087         */
088        public Set<CmsResource> findInvalidResources(Set<CmsResource> resources) {
089
090            Set<CmsResource> result = new HashSet<CmsResource>();
091            for (CmsResource resource : resources) {
092                if (resource.getState().isUnchanged()) {
093                    result.add(resource);
094                }
095            }
096            return result;
097        }
098
099        /**
100         * @see org.opencms.workflow.CmsDefaultPublishResourceFormatter.I_PublishResourceValidator#getInfoForResource(org.opencms.file.CmsResource)
101         */
102        public CmsPublishResourceInfo getInfoForResource(CmsResource resource) throws CmsException {
103
104            String info;
105            CmsPublishResourceInfo.Type infoType;
106            CmsPublishResourceInfo infoObj;
107            String publishUser = null;
108            try {
109                String userName = m_cms.readUser(resource.getUserLastModified()).getName();
110                publishUser = getOuAwareName(m_cms, userName);
111            } catch (Exception e) {
112                publishUser = "" + resource.getUserLastModified();
113                LOG.error(e.getLocalizedMessage(), e);
114            }
115
116            Date publishDate = new Date(resource.getDateLastModified());
117            info = Messages.get().getBundle(getLocale()).key(
118                Messages.GUI_RESOURCE_PUBLISHED_BY_2,
119                publishUser,
120                publishDate);
121            infoType = CmsPublishResourceInfo.Type.PUBLISHED;
122            infoObj = new CmsPublishResourceInfo(info, infoType);
123            return infoObj;
124        }
125    }
126
127    /**
128     * Validator which checks if resources are locked by someone else.<p>
129     */
130    public class BlockingLockedValidator implements I_PublishResourceValidator {
131
132        /**
133         * @see org.opencms.workflow.CmsDefaultPublishResourceFormatter.I_PublishResourceValidator#findInvalidResources(java.util.Set)
134         */
135        @SuppressWarnings("synthetic-access")
136        public Set<CmsResource> findInvalidResources(Set<CmsResource> resources) {
137
138            CmsUser user = m_cms.getRequestContext().getCurrentUser();
139            CmsLockFilter blockingFilter = CmsLockFilter.FILTER_ALL;
140            blockingFilter = blockingFilter.filterNotLockableByUser(user);
141            Set<CmsResource> result = new HashSet<CmsResource>();
142
143            for (CmsResource resource : resources) {
144                try {
145                    List<CmsResource> blockingLocked = m_cms.getLockedResourcesWithCache(
146                        resource,
147                        blockingFilter,
148                        m_lockedResourceCache);
149                    for (CmsResource res : blockingLocked) {
150                        result.add(res);
151                    }
152                } catch (Exception e) {
153                    // error reading the resource list, should usually never happen
154                    if (LOG.isErrorEnabled()) {
155                        LOG.error(e.getLocalizedMessage(), e);
156                    }
157                }
158            }
159            return result;
160        }
161
162        /**
163         * @see org.opencms.workflow.CmsDefaultPublishResourceFormatter.I_PublishResourceValidator#getInfoForResource(org.opencms.file.CmsResource)
164         */
165        public CmsPublishResourceInfo getInfoForResource(CmsResource resource) throws CmsException {
166
167            String info;
168            CmsPublishResourceInfo.Type infoType;
169            CmsPublishResourceInfo infoObj;
170            CmsLock lock = m_cms.getLock(resource);
171            info = Messages.get().getBundle(getLocale()).key(
172                Messages.GUI_RESOURCE_LOCKED_BY_2,
173                getOuAwareName(m_cms, m_cms.readUser(lock.getUserId()).getName()),
174                getOuAwareName(m_cms, lock.getProject().getName()));
175            infoType = CmsPublishResourceInfo.Type.LOCKED;
176            infoObj = new CmsPublishResourceInfo(info, infoType);
177            return infoObj;
178        }
179    }
180
181    /**
182     * Compares publish resources by their sort date.<p>
183     */
184    public static class DefaultComparator implements Comparator<CmsPublishResource> {
185
186        /**
187         * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
188         */
189        public int compare(CmsPublishResource first, CmsPublishResource second) {
190
191            return ComparisonChain.start().compare(-first.getSortDate(), -second.getSortDate()).result();
192        }
193    }
194
195    /**
196     * Validator which can exclude some resources from publishing and supplies a status object for the excluded resources.<p>
197     */
198    public static interface I_PublishResourceValidator {
199
200        /**
201         * Finds the resources which should be excluded.<p>
202         *
203         * @param input the set of input resources
204         *
205         * @return the excluded resources
206         */
207        Set<CmsResource> findInvalidResources(Set<CmsResource> input);
208
209        /**
210         * Gets the status information for an excluded resource.<p>
211         *
212         * @param resource the resource for which to get the status
213         * @return the status for the resource
214         * @throws CmsException if something goes wrong
215         */
216        CmsPublishResourceInfo getInfoForResource(CmsResource resource) throws CmsException;
217    }
218
219    /**
220     * Validator which excludes resources for which the user has no publish permissions.<p>
221     */
222    public class NoPermissionsValidator implements I_PublishResourceValidator {
223
224        /**
225         * @see org.opencms.workflow.CmsDefaultPublishResourceFormatter.I_PublishResourceValidator#findInvalidResources(java.util.Set)
226         */
227        @SuppressWarnings("synthetic-access")
228        public Set<CmsResource> findInvalidResources(Set<CmsResource> resources) {
229
230            Set<CmsResource> result = new HashSet<CmsResource>();
231            Set<CmsUUID> projectIds = new HashSet<CmsUUID>();
232            try {
233                for (CmsProject project : OpenCms.getOrgUnitManager().getAllManageableProjects(m_cms, "", true)) {
234                    projectIds.add(project.getUuid());
235                }
236            } catch (CmsException e) {
237                // should never happen
238                LOG.error(e.getLocalizedMessage(), e);
239            }
240            for (CmsResource resource : resources) {
241                try {
242                    if (!projectIds.contains(resource.getProjectLastModified())
243                        && !m_cms.hasPermissions(resource, CmsPermissionSet.ACCESS_DIRECT_PUBLISH)) {
244                        result.add(resource);
245                    }
246                } catch (Exception e) {
247                    // error reading the permissions, should usually never happen
248                    if (LOG.isErrorEnabled()) {
249                        LOG.error(e.getLocalizedMessage(), e);
250                    }
251                }
252            }
253            return result;
254        }
255
256        /**
257         * @see org.opencms.workflow.CmsDefaultPublishResourceFormatter.I_PublishResourceValidator#getInfoForResource(org.opencms.file.CmsResource)
258         */
259        public CmsPublishResourceInfo getInfoForResource(CmsResource resource) {
260
261            String info;
262            CmsPublishResourceInfo.Type infoType;
263            CmsPublishResourceInfo infoObj;
264            info = Messages.get().getBundle(getLocale()).key(Messages.GUI_RESOURCE_NOT_ENOUGH_PERMISSIONS_0);
265            infoType = CmsPublishResourceInfo.Type.PERMISSIONS;
266            infoObj = new CmsPublishResourceInfo(info, infoType);
267            return infoObj;
268        }
269    }
270
271    /**
272     * Predicate which checks whether the current user has publish permissions for a resource.<p>
273     */
274    public class PublishPermissionFilter implements Predicate<CmsResource> {
275
276        /**
277         * @see com.google.common.base.Predicate#apply(java.lang.Object)
278         */
279        @SuppressWarnings("synthetic-access")
280        public boolean apply(CmsResource input) {
281
282            try {
283                return m_cms.hasPermissions(
284                    input,
285                    CmsPermissionSet.ACCESS_DIRECT_PUBLISH,
286                    false,
287                    CmsResourceFilter.ALL);
288            } catch (CmsException e) {
289                LOG.error(e.getLocalizedMessage(), e);
290                return true;
291            }
292        }
293
294    }
295
296    /** The logger for this class. */
297    private static final Log LOG = CmsLog.getLog(CmsDefaultPublishResourceFormatter.class);
298
299    /** The publish options. */
300    protected CmsPublishOptions m_options;
301
302    /** The CMS context for this class. */
303    CmsObject m_cms;
304
305    /** Cache for locked resources. */
306    private Map<String, CmsResource> m_lockedResourceCache = new HashMap<String, CmsResource>();
307
308    /** The publish resources. */
309    private List<CmsPublishResource> m_publishResources;
310
311    /** The publish resources by id. */
312    private Map<CmsUUID, CmsResource> m_resources = new HashMap<CmsUUID, CmsResource>();
313
314    /**
315     * Constructor.<p>
316     *
317     *
318     * @param cms the CMS context to use
319     */
320    public CmsDefaultPublishResourceFormatter(CmsObject cms) {
321
322        m_cms = cms;
323    }
324
325    /**
326     * Returns the simple name if the ou is the same as the current user's ou.<p>
327     *
328     * @param cms the CMS context
329     * @param name the fully qualified name to check
330     *
331     * @return the simple name if the ou is the same as the current user's ou
332     */
333    public static String getOuAwareName(CmsObject cms, String name) {
334
335        String ou = CmsOrganizationalUnit.getParentFqn(name);
336        if (ou.equals(cms.getRequestContext().getCurrentUser().getOuFqn())) {
337            return CmsOrganizationalUnit.getSimpleName(name);
338        }
339        return CmsOrganizationalUnit.SEPARATOR + name;
340    }
341
342    /**
343     * @see org.opencms.workflow.I_CmsPublishResourceFormatter#getPublishResources()
344     */
345    public List<CmsPublishResource> getPublishResources() {
346
347        sortResult(m_publishResources);
348        return m_publishResources;
349    }
350
351    /**
352     * @see org.opencms.workflow.I_CmsPublishResourceFormatter#initialize(org.opencms.ade.publish.shared.CmsPublishOptions, org.opencms.ade.publish.CmsPublishRelationFinder.ResourceMap)
353     */
354    public void initialize(CmsPublishOptions options, ResourceMap resources) throws CmsException {
355
356        m_options = options;
357        Predicate<CmsResource> resourceMapFilter = getResourceMapFilter();
358        if (resourceMapFilter != null) {
359            resources = resources.filter(resourceMapFilter);
360        }
361        for (CmsResource parentRes : resources.keySet()) {
362            m_resources.put(parentRes.getStructureId(), parentRes);
363            for (CmsResource childRes : resources.get(parentRes)) {
364                m_resources.put(childRes.getStructureId(), childRes);
365            }
366        }
367        Map<CmsUUID, CmsPublishResourceInfo> warnings = computeWarnings();
368        m_publishResources = Lists.newArrayList();
369        for (CmsResource parentRes : resources.keySet()) {
370            CmsPublishResource parentPubRes = createPublishResource(parentRes);
371            parentPubRes.setInfo(warnings.get(parentRes.getStructureId()));
372            for (CmsResource childRes : resources.get(parentRes)) {
373                CmsPublishResource childPubRes = createPublishResource(childRes);
374                childPubRes.setInfo(warnings.get(childRes.getStructureId()));
375                parentPubRes.getRelated().add(childPubRes);
376            }
377            if ((m_options.getProjectId() == null) || m_options.getProjectId().isNullUUID()) {
378                parentPubRes.setRemovable(true);
379            }
380            m_publishResources.add(parentPubRes);
381        }
382    }
383
384    /**
385     * Creates the publish resource warnings.<p>
386     *
387     * @return a map from structure ids to the warnings for the corresponding resources
388     */
389    protected Map<CmsUUID, CmsPublishResourceInfo> computeWarnings() {
390
391        Map<CmsUUID, CmsPublishResourceInfo> warnings = Maps.newHashMap();
392        Set<CmsResource> resourcesWithoutErrors = new HashSet<CmsResource>(m_resources.values());
393
394        List<I_PublishResourceValidator> validators = getValidators();
395        List<Set<CmsResource>> excludedSetsForValidators = Lists.newArrayList();
396        for (int i = 0; i < validators.size(); i++) {
397            I_PublishResourceValidator validator = validators.get(i);
398            Set<CmsResource> excluded = validator.findInvalidResources(resourcesWithoutErrors);
399            resourcesWithoutErrors.removeAll(excluded);
400            excludedSetsForValidators.add(excluded);
401        }
402        for (CmsResource resource : m_resources.values()) {
403            CmsPublishResourceInfo info = null;
404            try {
405                for (int i = 0; i < validators.size(); i++) {
406                    if (excludedSetsForValidators.get(i).contains(resource)) {
407                        info = validators.get(i).getInfoForResource(resource);
408                        break;
409                    }
410                }
411            } catch (CmsException e) {
412                LOG.error(e.getLocalizedMessage(), e);
413            }
414            warnings.put(resource.getStructureId(), info);
415        }
416        return warnings;
417    }
418
419    /**
420     * Creates a publish resource bean from a resource.<p>
421     *
422     * @param resource the resource
423     * @return the publish resource bean
424     *
425     * @throws CmsException if something goes wrong
426     */
427    protected CmsPublishResource createPublishResource(CmsResource resource) throws CmsException {
428
429        CmsResourceUtil resUtil = new CmsResourceUtil(m_cms, resource);
430        CmsPermissionInfo permissionInfo = OpenCms.getADEManager().getPermissionInfo(m_cms, resource, null);
431
432        String typeName = CmsIconUtil.getDisplayType(m_cms, resource);
433        String detailTypeName = CmsResourceIcon.getDefaultFileOrDetailType(m_cms, resource);
434        CmsPublishResource pubResource = new CmsPublishResource(
435            resource.getStructureId(),
436            resUtil.getFullPath(),
437            resUtil.getTitle(),
438            typeName,
439            resource.getState(),
440            permissionInfo,
441            CmsResourceTypeXmlContent.isXmlContent(resource),
442            resource.getDateLastModified(),
443            resUtil.getUserLastModified(),
444            CmsVfsService.formatDateTime(m_cms, resource.getDateLastModified()),
445            false,
446            null,
447            new ArrayList<CmsPublishResource>());
448        pubResource.setBigIconClasses(CmsIconUtil.getIconClasses(typeName, resource.getName(), false));
449        if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(detailTypeName)) {
450            pubResource.setSmallIconClasses(CmsIconUtil.getIconClasses(detailTypeName, null, true));
451        }
452        return pubResource;
453    }
454
455    /**
456     * Gets the workplace locale for the currently used CMS context.<p>
457     *
458     * @return the workplace locale
459     */
460    protected Locale getLocale() {
461
462        return OpenCms.getWorkplaceManager().getWorkplaceLocale(m_cms);
463    }
464
465    /**
466     * Gets the resource map filter.<p>
467     *
468     * This can be used to remove resources which shouldn't be displayed.<p>
469     *
470     * @return a predicate whose
471     */
472    protected Predicate<CmsResource> getResourceMapFilter() {
473
474        return new PublishPermissionFilter();
475    }
476
477    /**
478     * Gets the list of publish resource validators.<p>
479     *
480     * @return the list of publish resource validators
481     */
482    protected List<I_PublishResourceValidator> getValidators() {
483
484        return Arrays.asList(
485            new AlreadyPublishedValidator(),
486            new NoPermissionsValidator(),
487            new BlockingLockedValidator());
488    }
489
490    /**
491     * Sorts the result publish resource list.<p>
492     *
493     * @param publishResources the list to sort
494     */
495    protected void sortResult(List<CmsPublishResource> publishResources) {
496
497        Collections.sort(publishResources, new DefaultComparator());
498        for (CmsPublishResource resource : publishResources) {
499            Collections.sort(resource.getRelated(), new DefaultComparator());
500        }
501    }
502
503}