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