001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (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.gwt;
029
030import org.opencms.ade.containerpage.CmsRelationTargetListBean;
031import org.opencms.file.CmsFile;
032import org.opencms.file.CmsObject;
033import org.opencms.file.CmsProperty;
034import org.opencms.file.CmsPropertyDefinition;
035import org.opencms.file.CmsResource;
036import org.opencms.file.CmsResourceFilter;
037import org.opencms.file.CmsUser;
038import org.opencms.file.CmsVfsResourceNotFoundException;
039import org.opencms.file.types.CmsResourceTypeJsp;
040import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
041import org.opencms.file.types.CmsResourceTypeXmlContent;
042import org.opencms.file.types.I_CmsResourceType;
043import org.opencms.gwt.shared.CmsListInfoBean;
044import org.opencms.gwt.shared.CmsPermissionInfo;
045import org.opencms.gwt.shared.CmsResourceStatusBean;
046import org.opencms.gwt.shared.CmsResourceStatusRelationBean;
047import org.opencms.gwt.shared.CmsResourceStatusTabId;
048import org.opencms.i18n.CmsLocaleManager;
049import org.opencms.i18n.CmsMessageContainer;
050import org.opencms.jsp.CmsJspTagContainer;
051import org.opencms.lock.CmsLock;
052import org.opencms.main.CmsException;
053import org.opencms.main.CmsLog;
054import org.opencms.main.OpenCms;
055import org.opencms.relations.CmsRelation;
056import org.opencms.relations.CmsRelationFilter;
057import org.opencms.relations.CmsRelationType;
058import org.opencms.relations.I_CmsLinkParseable;
059import org.opencms.search.galleries.CmsGallerySearch;
060import org.opencms.search.galleries.CmsGallerySearchResult;
061import org.opencms.security.CmsRole;
062import org.opencms.site.CmsSite;
063import org.opencms.util.CmsStringUtil;
064import org.opencms.util.CmsUUID;
065import org.opencms.workplace.CmsWorkplace;
066import org.opencms.workplace.explorer.CmsResourceUtil;
067import org.opencms.xml.content.CmsXmlContent;
068import org.opencms.xml.content.CmsXmlContentFactory;
069
070import java.util.ArrayList;
071import java.util.Arrays;
072import java.util.Collections;
073import java.util.Comparator;
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.Set;
081
082import org.apache.commons.logging.Log;
083
084import com.google.common.base.Optional;
085import com.google.common.collect.ComparisonChain;
086import com.google.common.collect.Maps;
087import com.google.common.collect.Sets;
088
089/**
090 * Helper class to generate all the data which is necessary for the resource status dialog(s).<p>
091 */
092public class CmsDefaultResourceStatusProvider {
093
094    /** The log instance for this class. */
095    private static final Log LOG = CmsLog.getLog(CmsDefaultResourceStatusProvider.class);
096
097    /**
098     * Gets the relation targets for a resource.<p>
099     *
100     * @param cms the current CMS context
101     * @param source the structure id of the resource for which we want the relation targets
102     * @param additionalIds the structure ids of additional resources to include with the relation targets
103     * @param cancelIfChanged if this is true, this method will stop immediately if it finds a changed resource among the relation targets
104     *
105     * @return a bean containing a list of relation targets
106     *
107     * @throws CmsException if something goes wrong
108     */
109    public static CmsRelationTargetListBean getContainerpageRelationTargets(
110        CmsObject cms,
111        CmsUUID source,
112        List<CmsUUID> additionalIds,
113        boolean cancelIfChanged)
114    throws CmsException {
115
116        CmsRelationTargetListBean result = new CmsRelationTargetListBean();
117        CmsResource content = cms.readResource(source, CmsResourceFilter.ALL);
118        boolean isContainerPage = CmsResourceTypeXmlContainerPage.isContainerPage(content);
119        if (additionalIds != null) {
120            for (CmsUUID structureId : additionalIds) {
121                try {
122                    CmsResource res = cms.readResource(structureId, CmsResourceFilter.ALL);
123                    result.add(res);
124                    if (res.getState().isChanged() && cancelIfChanged) {
125                        return result;
126                    }
127                } catch (CmsException e) {
128                    LOG.error(e.getLocalizedMessage(), e);
129                }
130            }
131        }
132        List<CmsRelation> relations = cms.readRelations(CmsRelationFilter.relationsFromStructureId(source));
133        for (CmsRelation relation : relations) {
134            if (relation.getType() == CmsRelationType.XSD) {
135                continue;
136            }
137            try {
138                CmsResource target = relation.getTarget(cms, CmsResourceFilter.ALL);
139                I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(target);
140                if (isContainerPage && (type instanceof CmsResourceTypeJsp)) {
141                    // ignore formatters for container pages, as the normal user probably doesn't want to deal with them
142                    continue;
143                }
144                result.add(target);
145                if (target.getState().isChanged() && cancelIfChanged) {
146                    return result;
147                }
148            } catch (CmsException e) {
149                LOG.debug(e.getLocalizedMessage(), e);
150            }
151        }
152        return result;
153    }
154
155    /**
156     * Collects all the data to display in the resource status dialog.<p>
157     *
158     * @param cms the current CMS context
159     * @param structureId the structure id of the resource for which we want the information
160     * @param contentLocale the content locale
161     * @param includeTargets true if relation targets should be included
162     * @param detailContentId the structure id of the detail content if present
163     * @param additionalStructureIds structure ids of additional resources to include with the relation targets
164     *
165     * @return the resource status information
166     * @throws CmsException if something goes wrong
167     */
168    public CmsResourceStatusBean getResourceStatus(
169        CmsObject cms,
170        CmsUUID structureId,
171        String contentLocale,
172        boolean includeTargets,
173        CmsUUID detailContentId,
174        List<CmsUUID> additionalStructureIds)
175    throws CmsException {
176
177        Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
178        cms.getRequestContext().setLocale(locale);
179        CmsResource resource = cms.readResource(structureId, CmsResourceFilter.ALL);
180        String localizedTitle = null;
181        Locale realLocale = null;
182        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(contentLocale)) {
183            realLocale = CmsLocaleManager.getLocale(contentLocale);
184            CmsGallerySearchResult result = CmsGallerySearch.searchById(cms, structureId, realLocale);
185            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(result.getTitle())) {
186                localizedTitle = result.getTitle();
187            }
188        }
189        CmsResourceUtil resourceUtil = new CmsResourceUtil(cms, resource);
190        List<CmsProperty> properties = cms.readPropertyObjects(resource, false);
191        CmsResourceStatusBean result = new CmsResourceStatusBean();
192        result.setDateCreated(CmsVfsService.formatDateTime(cms, resource.getDateCreated()));
193        long dateExpired = resource.getDateExpired();
194        if (dateExpired != CmsResource.DATE_EXPIRED_DEFAULT) {
195            result.setDateExpired(CmsVfsService.formatDateTime(cms, dateExpired));
196        }
197        result.setDateLastModified(CmsVfsService.formatDateTime(cms, resource.getDateLastModified()));
198        long dateReleased = resource.getDateReleased();
199        if (dateReleased != CmsResource.DATE_RELEASED_DEFAULT) {
200            result.setDateReleased(CmsVfsService.formatDateTime(cms, dateReleased));
201        }
202        String lastProject = resourceUtil.getLockedInProjectName();
203        if ("".equals(lastProject)) {
204            lastProject = null;
205        }
206        result.setLastProject(lastProject);
207
208        result.setListInfo(CmsVfsService.getPageInfo(cms, resource));
209        CmsLock lock = cms.getLock(resource);
210        CmsUser lockOwner = null;
211        if (!lock.isUnlocked()) {
212            lockOwner = cms.readUser(lock.getUserId());
213            result.setLockState(
214                org.opencms.workplace.list.Messages.get().getBundle(locale).key(
215                    org.opencms.workplace.list.Messages.GUI_EXPLORER_LIST_ACTION_LOCK_NAME_2,
216                    lockOwner.getName(),
217                    lastProject));
218        } else {
219            result.setLockState(
220                org.opencms.workplace.list.Messages.get().getBundle(locale).key(
221                    org.opencms.workplace.list.Messages.GUI_EXPLORER_LIST_ACTION_UNLOCK_NAME_0));
222        }
223
224        CmsProperty navText = CmsProperty.get(CmsPropertyDefinition.PROPERTY_NAVTEXT, properties);
225        if (navText != null) {
226            result.setNavText(navText.getValue());
227        }
228        result.setPermissions(resourceUtil.getPermissionString());
229        result.setSize(resource.getLength());
230        result.setStateBean(resource.getState());
231        CmsProperty title = CmsProperty.get(CmsPropertyDefinition.PROPERTY_TITLE, properties);
232        if (localizedTitle != null) {
233            result.setTitle(localizedTitle);
234            result.getListInfo().setTitle(localizedTitle);
235        } else if (title != null) {
236            result.setTitle(title.getValue());
237        }
238        result.setUserCreated(resourceUtil.getUserCreated());
239        result.setUserLastModified(resourceUtil.getUserLastModified());
240
241        I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(resource);
242        result.setResourceType(resType.getTypeName());
243
244        result.setStructureId(resource.getStructureId());
245        if (resType instanceof CmsResourceTypeXmlContent) {
246            CmsFile file = cms.readFile(resource);
247            CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
248            List<Locale> locales = content.getLocales();
249            List<String> localeStrings = new ArrayList<String>();
250            for (Locale l : locales) {
251                localeStrings.add(l.toString());
252            }
253            result.setLocales(localeStrings);
254        }
255        Map<String, String> additionalAttributes = new LinkedHashMap<String, String>();
256        additionalAttributes.put(
257            Messages.get().getBundle(locale).key(Messages.GUI_STATUS_PERMALINK_0),
258            OpenCms.getLinkManager().getPermalink(cms, cms.getSitePath(resource), detailContentId));
259
260        if (OpenCms.getRoleManager().hasRole(cms, CmsRole.ADMINISTRATOR)) {
261            additionalAttributes.put(
262                Messages.get().getBundle(locale).key(Messages.GUI_STATUS_STRUCTURE_ID_0),
263                resource.getStructureId().toString());
264            additionalAttributes.put(
265                Messages.get().getBundle(locale).key(Messages.GUI_STATUS_RESOURCE_ID_0),
266                resource.getResourceId().toString());
267        }
268        result.setAdditionalAttributes(additionalAttributes);
269
270        List<CmsRelation> relations = cms.readRelations(
271            CmsRelationFilter.relationsToStructureId(resource.getStructureId()));
272        Map<CmsUUID, CmsResource> relationSources = new HashMap<CmsUUID, CmsResource>();
273
274        if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
275            // People may link to the folder of a container page instead of the page itself
276            try {
277                CmsResource parent = cms.readParentFolder(resource.getStructureId());
278                List<CmsRelation> parentRelations = cms.readRelations(
279                    CmsRelationFilter.relationsToStructureId(parent.getStructureId()));
280                relations.addAll(parentRelations);
281            } catch (CmsException e) {
282                LOG.error(e.getLocalizedMessage(), e);
283            }
284        }
285
286        // find all distinct relation sources
287        for (CmsRelation relation : relations) {
288            try {
289                CmsResource currentSource = relation.getSource(cms, CmsResourceFilter.ALL);
290                relationSources.put(currentSource.getStructureId(), currentSource);
291            } catch (CmsException e) {
292                LOG.error(e.getLocalizedMessage(), e);
293            }
294        }
295
296        for (CmsResource relationResource : relationSources.values()) {
297            try {
298                CmsPermissionInfo permissionInfo = OpenCms.getADEManager().getPermissionInfo(
299                    cms,
300                    relationResource,
301                    resource.getRootPath());
302                if (permissionInfo.hasViewPermission()) {
303                    CmsResourceStatusRelationBean relationBean = createRelationBean(
304                        cms,
305                        contentLocale,
306                        relationResource,
307                        permissionInfo);
308                    CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(relationResource.getRootPath());
309                    if ((site != null)
310                        && !CmsStringUtil.isPrefixPath(
311                            cms.getRequestContext().getSiteRoot(),
312                            relationResource.getRootPath())) {
313                        String siteTitle = site.getTitle();
314                        if (siteTitle == null) {
315                            siteTitle = site.getUrl();
316                        } else {
317                            siteTitle = CmsWorkplace.substituteSiteTitleStatic(
318                                siteTitle,
319                                OpenCms.getWorkplaceManager().getWorkplaceLocale(cms));
320                        }
321                        relationBean.setSiteRoot(site.getSiteRoot());
322                        result.getOtherSiteRelationSources().add(relationBean);
323                        relationBean.getInfoBean().setTitle(
324                            "[" + siteTitle + "] " + relationBean.getInfoBean().getTitle());
325                    } else {
326                        result.getRelationSources().add(relationBean);
327                    }
328
329                }
330            } catch (CmsVfsResourceNotFoundException notfound) {
331                LOG.error(notfound.getLocalizedMessage(), notfound);
332                continue;
333            }
334        }
335        sortOtherSiteRelations(cms, result);
336        if (includeTargets) {
337            result.getRelationTargets().addAll(getTargets(cms, contentLocale, resource, additionalStructureIds));
338            if ((detailContentId != null) && (realLocale != null)) {
339                // try to add detail only contents
340                try {
341                    Optional<CmsResource> detailOnlyPage = CmsJspTagContainer.getDetailOnlyPage(
342                        cms,
343                        cms.readResource(detailContentId, CmsResourceFilter.ALL),
344                        CmsJspTagContainer.getDetailContainerLocale(cms, realLocale.toString(), resource));
345                    if (detailOnlyPage.isPresent()) {
346                        result.getRelationTargets().addAll(
347                            getTargets(
348                                cms,
349                                contentLocale,
350                                detailOnlyPage.get(),
351                                Arrays.asList(detailOnlyPage.get().getStructureId())));
352                    }
353
354                } catch (CmsException e) {
355                    LOG.error(e.getLocalizedMessage(), e);
356                }
357            }
358            Iterator<CmsResourceStatusRelationBean> iter = result.getRelationTargets().iterator();
359            // Remove duplicates
360            Set<CmsUUID> visitedIds = Sets.newHashSet();
361            while (iter.hasNext()) {
362                CmsResourceStatusRelationBean bean = iter.next();
363                if (visitedIds.contains(bean.getStructureId())) {
364                    iter.remove();
365                }
366                visitedIds.add(bean.getStructureId());
367            }
368        }
369        result.getSiblings().addAll(getSiblings(cms, contentLocale, resource));
370        LinkedHashMap<CmsResourceStatusTabId, String> tabMap = new LinkedHashMap<CmsResourceStatusTabId, String>();
371        Map<CmsResourceStatusTabId, CmsMessageContainer> tabs;
372        CmsResourceStatusTabId startTab = CmsResourceStatusTabId.tabRelationsFrom;
373        if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
374            tabs = CmsResourceStatusConstants.STATUS_TABS_CONTAINER_PAGE;
375        } else if (OpenCms.getResourceManager().getResourceType(resource) instanceof I_CmsLinkParseable) {
376            tabs = CmsResourceStatusConstants.STATUS_TABS_CONTENT;
377        } else {
378            tabs = CmsResourceStatusConstants.STATUS_TABS_OTHER;
379            startTab = CmsResourceStatusTabId.tabStatus;
380        }
381        for (Map.Entry<CmsResourceStatusTabId, CmsMessageContainer> entry : tabs.entrySet()) {
382            tabMap.put(entry.getKey(), entry.getValue().key(locale));
383        }
384
385        result.setTabs(tabMap);
386        result.setStartTab(startTab);
387        return result;
388    }
389
390    /**
391     * Sorts relation beans from other sites by site order.<p>
392     *
393     * @param cms the current CMS context
394     * @param resStatus the bean in which to sort the relation beans
395     */
396    public void sortOtherSiteRelations(CmsObject cms, CmsResourceStatusBean resStatus) {
397
398        final List<CmsSite> sites = OpenCms.getSiteManager().getAvailableSites(
399            cms,
400            false,
401            false,
402            cms.getRequestContext().getOuFqn());
403        Collections.sort(resStatus.getOtherSiteRelationSources(), new Comparator<CmsResourceStatusRelationBean>() {
404
405            private Map<String, Integer> m_rankCache = Maps.newHashMap();
406
407            public int compare(CmsResourceStatusRelationBean o1, CmsResourceStatusRelationBean o2) {
408
409                return ComparisonChain.start().compare(rank(o1), rank(o2)).compare(
410                    o1.getSitePath(),
411                    o2.getSitePath()).result();
412
413            }
414
415            public int rank(CmsResourceStatusRelationBean r) {
416
417                if (m_rankCache.containsKey(r.getSiteRoot())) {
418                    return m_rankCache.get(r.getSiteRoot()).intValue();
419                }
420
421                int j = 0;
422                int result = Integer.MAX_VALUE;
423                for (CmsSite site : sites) {
424                    if (site.getSiteRoot().equals(r.getSiteRoot())) {
425                        result = j;
426                        break;
427                    }
428                    j += 1;
429                }
430
431                m_rankCache.put(r.getSiteRoot(), new Integer(result));
432                return result;
433            }
434        });
435    }
436
437    /**
438     * Gets beans which represents the siblings of a resource.<p>
439     *
440     * @param cms the CMS ccontext
441     * @param locale the locale
442     * @param resource the resource
443     * @return the list of sibling beans
444     *
445     * @throws CmsException if something goes wrong
446     */
447    protected List<CmsResourceStatusRelationBean> getSiblings(CmsObject cms, String locale, CmsResource resource)
448    throws CmsException {
449
450        List<CmsResourceStatusRelationBean> result = new ArrayList<CmsResourceStatusRelationBean>();
451        for (CmsResource sibling : cms.readSiblings(resource, CmsResourceFilter.ALL)) {
452            if (sibling.getStructureId().equals(resource.getStructureId())) {
453                continue;
454            }
455            try {
456                CmsPermissionInfo permissionInfo = OpenCms.getADEManager().getPermissionInfo(
457                    cms,
458                    sibling,
459                    resource.getRootPath());
460                if (permissionInfo.hasViewPermission()) {
461                    CmsResourceStatusRelationBean relationBean = createRelationBean(
462                        cms,
463                        locale,
464                        sibling,
465                        permissionInfo);
466                    result.add(relationBean);
467                }
468            } catch (CmsException e) {
469                LOG.error(e.getLocalizedMessage(), e);
470            }
471        }
472        return result;
473    }
474
475    /**
476     * Gets the list of relation targets for a resource.<p>
477     *
478     * @param cms the current CMS context
479     * @param locale the locale
480     * @param resource the resource for which we want the relation targets
481     * @param additionalStructureIds structure ids of additional resources to include with the relation target
482     *
483     * @return the list of relation beans for the relation targets
484     *
485     * @throws CmsException if something goes wrong
486     */
487    protected List<CmsResourceStatusRelationBean> getTargets(
488        CmsObject cms,
489        String locale,
490        CmsResource resource,
491        List<CmsUUID> additionalStructureIds)
492    throws CmsException {
493
494        CmsRelationTargetListBean listBean = getContainerpageRelationTargets(
495            cms,
496            resource.getStructureId(),
497            additionalStructureIds,
498            false);
499        List<CmsResourceStatusRelationBean> result = new ArrayList<CmsResourceStatusRelationBean>();
500        for (CmsResource target : listBean.getResources()) {
501            try {
502                CmsPermissionInfo permissionInfo = OpenCms.getADEManager().getPermissionInfo(
503                    cms,
504                    target,
505                    resource.getRootPath());
506                if (permissionInfo.hasViewPermission()) {
507                    CmsResourceStatusRelationBean relationBean = createRelationBean(
508                        cms,
509                        locale,
510                        target,
511                        permissionInfo);
512                    result.add(relationBean);
513                }
514            } catch (CmsException e) {
515                LOG.error(e.getLocalizedMessage(), e);
516            }
517        }
518        return result;
519
520    }
521
522    /**
523     * Creates a bean for a single resource which is part of a relation list.<p>
524     *
525     * @param cms the current CMS context
526     * @param locale the locale
527     * @param relationResource the resource
528     * @param permissionInfo the permission info
529     *
530     * @return the status bean for the resource
531     *
532     * @throws CmsException if something goes wrong
533     */
534    CmsResourceStatusRelationBean createRelationBean(
535        CmsObject cms,
536        String locale,
537        CmsResource relationResource,
538        CmsPermissionInfo permissionInfo)
539    throws CmsException {
540
541        CmsListInfoBean sourceBean = CmsVfsService.getPageInfo(cms, relationResource);
542        sourceBean.setMarkChangedState(true);
543        sourceBean.setResourceState(relationResource.getState());
544
545        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(locale)) {
546            Locale realLocale = CmsLocaleManager.getLocale(locale);
547            CmsGallerySearchResult result = CmsGallerySearch.searchById(
548                cms,
549                relationResource.getStructureId(),
550                realLocale);
551            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(result.getTitle())) {
552                sourceBean.setTitle(result.getTitle());
553            }
554        }
555        String link = null;
556        try {
557            link = OpenCms.getLinkManager().substituteLink(cms, relationResource);
558        } catch (Exception e) {
559            LOG.warn(e.getLocalizedMessage(), e);
560        }
561        CmsResourceStatusRelationBean relationBean = new CmsResourceStatusRelationBean(
562            sourceBean,
563            link,
564            relationResource.getStructureId(),
565            permissionInfo);
566        if (CmsResourceTypeXmlContent.isXmlContent(relationResource)) {
567            relationBean.setIsXmlContent(true);
568        }
569        String sitePath = cms.getSitePath(relationResource);
570        relationBean.setSitePath(sitePath);
571        return relationBean;
572    }
573
574}