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