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