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