001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (https://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: https://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: https://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.configuration.CmsADEConfigData;
031import org.opencms.ade.containerpage.CmsContainerpageService;
032import org.opencms.ade.containerpage.CmsDetailOnlyContainerUtil;
033import org.opencms.ade.containerpage.CmsRelationTargetListBean;
034import org.opencms.file.CmsFile;
035import org.opencms.file.CmsObject;
036import org.opencms.file.CmsProperty;
037import org.opencms.file.CmsPropertyDefinition;
038import org.opencms.file.CmsResource;
039import org.opencms.file.CmsResourceFilter;
040import org.opencms.file.CmsUser;
041import org.opencms.file.CmsVfsResourceNotFoundException;
042import org.opencms.file.types.CmsResourceTypeJsp;
043import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
044import org.opencms.file.types.CmsResourceTypeXmlContent;
045import org.opencms.file.types.I_CmsResourceType;
046import org.opencms.gwt.shared.CmsGwtConstants;
047import org.opencms.gwt.shared.CmsListInfoBean;
048import org.opencms.gwt.shared.CmsPermissionInfo;
049import org.opencms.gwt.shared.CmsResourceStatusBean;
050import org.opencms.gwt.shared.CmsResourceStatusRelationBean;
051import org.opencms.gwt.shared.CmsResourceStatusTabId;
052import org.opencms.i18n.CmsLocaleManager;
053import org.opencms.i18n.CmsMessageContainer;
054import org.opencms.jsp.CmsJspTagDisplay;
055import org.opencms.lock.CmsLock;
056import org.opencms.main.CmsException;
057import org.opencms.main.CmsLog;
058import org.opencms.main.OpenCms;
059import org.opencms.relations.CmsRelation;
060import org.opencms.relations.CmsRelationFilter;
061import org.opencms.relations.CmsRelationType;
062import org.opencms.relations.I_CmsLinkParseable;
063import org.opencms.search.galleries.CmsGallerySearch;
064import org.opencms.search.galleries.CmsGallerySearchResult;
065import org.opencms.security.CmsRole;
066import org.opencms.site.CmsSite;
067import org.opencms.util.CmsStringUtil;
068import org.opencms.util.CmsUUID;
069import org.opencms.workplace.CmsWorkplace;
070import org.opencms.workplace.explorer.CmsResourceUtil;
071import org.opencms.xml.containerpage.CmsContainerElementBean;
072import org.opencms.xml.containerpage.I_CmsFormatterBean;
073import org.opencms.xml.content.CmsXmlContent;
074import org.opencms.xml.content.CmsXmlContentFactory;
075
076import java.text.NumberFormat;
077import java.util.ArrayList;
078import java.util.Arrays;
079import java.util.Collections;
080import java.util.Comparator;
081import java.util.HashMap;
082import java.util.Iterator;
083import java.util.LinkedHashMap;
084import java.util.List;
085import java.util.Locale;
086import java.util.Map;
087import java.util.Set;
088
089import javax.servlet.http.HttpServletRequest;
090
091import org.apache.commons.logging.Log;
092
093import com.google.common.base.Optional;
094import com.google.common.collect.ComparisonChain;
095import com.google.common.collect.Maps;
096import com.google.common.collect.Sets;
097
098/**
099 * Helper class to generate all the data which is necessary for the resource status dialog(s).<p>
100 */
101public class CmsDefaultResourceStatusProvider {
102
103    /**
104     * Exception class for signalling that there are too many relations to process.
105     */
106    static class TooManyRelationsException extends Exception {
107
108        /** Serial version id. */
109        private static final long serialVersionUID = 1L;
110
111        /**
112         * Creates a new instance.
113         */
114        public TooManyRelationsException() {
115
116            super();
117        }
118    }
119
120    /** The detail container path pattern. */
121    private static final String DETAIL_CONTAINER_PATTERN = ".*\\/\\.detailContainers\\/.*";
122
123    /** The log instance for this class. */
124    private static final Log LOG = CmsLog.getLog(CmsDefaultResourceStatusProvider.class);
125
126    /** Maximum relation count that is still displayed. */
127    public static final long MAX_RELATIONS = 500;
128
129    /**
130     * Gets the relation targets for a resource.<p>
131     *
132     * @param cms the current CMS context
133     * @param source the structure id of the resource for which we want the relation targets
134     * @param additionalIds the structure ids of additional resources to include with the relation targets
135     * @param cancelIfChanged if this is true, this method will stop immediately if it finds a changed resource among the relation targets
136     *
137     * @return a bean containing a list of relation targets
138     *
139     * @throws CmsException if something goes wrong
140     */
141    public static CmsRelationTargetListBean getContainerpageRelationTargets(
142        CmsObject cms,
143        CmsUUID source,
144        List<CmsUUID> additionalIds,
145        boolean cancelIfChanged)
146    throws CmsException {
147
148        CmsRelationTargetListBean result = new CmsRelationTargetListBean();
149        CmsResource content = cms.readResource(source, CmsResourceFilter.ALL);
150        boolean isContainerPage = CmsResourceTypeXmlContainerPage.isContainerPage(content);
151        if (additionalIds != null) {
152            for (CmsUUID structureId : additionalIds) {
153                try {
154                    CmsResource res = cms.readResource(structureId, CmsResourceFilter.ALL);
155                    result.add(res);
156                    if (res.getState().isChanged() && cancelIfChanged) {
157                        return result;
158                    }
159                } catch (CmsException e) {
160                    LOG.error(e.getLocalizedMessage(), e);
161                }
162            }
163        }
164        List<CmsRelation> relations = cms.readRelations(CmsRelationFilter.relationsFromStructureId(source));
165        for (CmsRelation relation : relations) {
166            if (relation.getType() == CmsRelationType.XSD) {
167                continue;
168            }
169            try {
170                CmsResource target = relation.getTarget(cms, CmsResourceFilter.ALL);
171                I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(target);
172                if (isContainerPage && (type instanceof CmsResourceTypeJsp)) {
173                    // ignore formatters for container pages, as the normal user probably doesn't want to deal with them
174                    continue;
175                }
176                result.add(target);
177                if (target.getState().isChanged() && cancelIfChanged) {
178                    return result;
179                }
180            } catch (CmsException e) {
181                LOG.debug(e.getLocalizedMessage(), e);
182            }
183        }
184
185        return result;
186    }
187
188    /**
189     * Gets the relation targets for a resource.<p>
190     *
191     * @param cms the current CMS context
192     * @param source the structure id of the resource for which we want the relation targets
193     * @param additionalIds the structure ids of additional resources to include with the relation targets
194     * @param cancelIfChanged if this is true, this method will stop immediately if it finds a changed resource among the relation targets
195     *
196     * @return a bean containing a list of relation targets
197     *
198     * @throws CmsException if something goes wrong
199     * @throws TooManyRelationsException if too many relations are found
200     */
201    public static CmsRelationTargetListBean getContainerpageRelationTargetsLimited(
202        CmsObject cms,
203        CmsUUID source,
204        List<CmsUUID> additionalIds,
205        boolean cancelIfChanged)
206    throws CmsException, TooManyRelationsException {
207
208        CmsRelationTargetListBean result = new CmsRelationTargetListBean();
209        CmsResource content = cms.readResource(source, CmsResourceFilter.ALL);
210        boolean isContainerPage = CmsResourceTypeXmlContainerPage.isContainerPage(content);
211        if (additionalIds != null) {
212            for (CmsUUID structureId : additionalIds) {
213                try {
214                    CmsResource res = cms.readResource(structureId, CmsResourceFilter.ALL);
215                    result.add(res);
216                    if (res.getState().isChanged() && cancelIfChanged) {
217                        return result;
218                    }
219                } catch (CmsException e) {
220                    LOG.error(e.getLocalizedMessage(), e);
221                }
222            }
223        }
224        List<CmsRelation> relations = cms.readRelations(CmsRelationFilter.relationsFromStructureId(source));
225        if (relations.size() > MAX_RELATIONS) {
226            throw new TooManyRelationsException();
227
228        } else {
229            for (CmsRelation relation : relations) {
230                if (relation.getType() == CmsRelationType.XSD) {
231                    continue;
232                }
233                try {
234                    CmsResource target = relation.getTarget(cms, CmsResourceFilter.ALL);
235                    I_CmsResourceType type = OpenCms.getResourceManager().getResourceType(target);
236                    if (isContainerPage && (type instanceof CmsResourceTypeJsp)) {
237                        // ignore formatters for container pages, as the normal user probably doesn't want to deal with them
238                        continue;
239                    }
240                    result.add(target);
241                    if (target.getState().isChanged() && cancelIfChanged) {
242                        return result;
243                    }
244                } catch (CmsException e) {
245                    LOG.debug(e.getLocalizedMessage(), e);
246                }
247            }
248        }
249        return result;
250    }
251
252    /**
253     * Gets the additional information related to the formatter.
254     *
255     * @param cms the CMS context
256     * @param bean the formatter bean
257     * @return the additional information about the formatter
258     */
259    public static Map<String, String> getFormatterInfo(CmsObject cms, I_CmsFormatterBean bean) {
260
261        Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
262        Map<String, String> result = new LinkedHashMap<>();
263        if (bean != null) {
264            String label = org.opencms.ade.containerpage.Messages.get().getBundle(locale).key(
265                org.opencms.ade.containerpage.Messages.GUI_ADDINFO_FORMATTER_0);
266            result.put(label, bean.getJspRootPath());
267            String locationLabel = org.opencms.ade.containerpage.Messages.get().getBundle(locale).key(
268                org.opencms.ade.containerpage.Messages.GUI_ADDINFO_FORMATTER_LOCATION_0);
269
270            String location = bean.getLocation();
271            if (location != null) {
272                result.put(locationLabel, location);
273            }
274            String key = bean.getKeyOrId();
275            String keyLabel = org.opencms.ade.containerpage.Messages.get().getBundle(locale).key(
276                org.opencms.ade.containerpage.Messages.GUI_ADDINFO_FORMATTER_KEY_0);
277
278            if (key != null) {
279                result.put(keyLabel, key);
280            }
281
282        }
283        return result;
284
285    }
286
287    /**
288     * Collects all the data to display in the resource status dialog.<p>
289     *
290     * @param request the current request
291     * @param cms the current CMS context
292     * @param structureId the structure id of the resource for which we want the information
293     * @param contentLocale the content locale
294     * @param includeTargets true if relation targets should be included
295     * @param detailContentId the structure id of the detail content if present
296     * @param additionalStructureIds structure ids of additional resources to include with the relation targets
297     * @param context context parameters used for displaying additional infos
298     *
299     * @return the resource status information
300     * @throws CmsException if something goes wrong
301     */
302    public CmsResourceStatusBean getResourceStatus(
303        HttpServletRequest request,
304        CmsObject cms,
305        CmsUUID structureId,
306        String contentLocale,
307        boolean includeTargets,
308        CmsUUID detailContentId,
309        List<CmsUUID> additionalStructureIds,
310        Map<String, String> context)
311    throws CmsException {
312
313        Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
314        cms.getRequestContext().setLocale(locale);
315        CmsResource resource = cms.readResource(structureId, CmsResourceFilter.ALL);
316        String localizedTitle = null;
317        Locale realLocale = null;
318        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(contentLocale)) {
319            realLocale = CmsLocaleManager.getLocale(contentLocale);
320            CmsGallerySearchResult result = CmsGallerySearch.searchById(cms, structureId, realLocale);
321            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(result.getTitle())) {
322                localizedTitle = result.getTitle();
323            }
324        }
325        CmsResourceUtil resourceUtil = new CmsResourceUtil(cms, resource);
326        List<CmsProperty> properties = cms.readPropertyObjects(resource, false);
327        CmsResourceStatusBean result = new CmsResourceStatusBean();
328        result.setDateCreated(CmsVfsService.formatDateTime(cms, resource.getDateCreated()));
329        long dateExpired = resource.getDateExpired();
330        if (dateExpired != CmsResource.DATE_EXPIRED_DEFAULT) {
331            result.setDateExpired(CmsVfsService.formatDateTime(cms, dateExpired));
332        }
333        result.setDateLastModified(CmsVfsService.formatDateTime(cms, resource.getDateLastModified()));
334        long dateReleased = resource.getDateReleased();
335        if (dateReleased != CmsResource.DATE_RELEASED_DEFAULT) {
336            result.setDateReleased(CmsVfsService.formatDateTime(cms, dateReleased));
337        }
338        String lastProject = resourceUtil.getLockedInProjectName();
339        if ("".equals(lastProject)) {
340            lastProject = null;
341        }
342        result.setLastProject(lastProject);
343
344        result.setListInfo(CmsVfsService.getPageInfo(cms, resource));
345        Map<String, String> contextualAddInfos = createContextInfos(cms, request, resource, context);
346        for (Map.Entry<String, String> entry : contextualAddInfos.entrySet()) {
347            result.getListInfo().addAdditionalInfo(entry.getKey(), entry.getValue());
348        }
349
350        CmsLock lock = cms.getLock(resource);
351        CmsUser lockOwner = null;
352        if (!lock.isUnlocked()) {
353            lockOwner = cms.readUser(lock.getUserId());
354            result.setLockState(
355                org.opencms.workplace.list.Messages.get().getBundle(locale).key(
356                    org.opencms.workplace.list.Messages.GUI_EXPLORER_LIST_ACTION_LOCK_NAME_2,
357                    lockOwner.getName(),
358                    lastProject));
359        } else {
360            result.setLockState(
361                org.opencms.workplace.list.Messages.get().getBundle(locale).key(
362                    org.opencms.workplace.list.Messages.GUI_EXPLORER_LIST_ACTION_UNLOCK_NAME_0));
363        }
364
365        CmsProperty navText = CmsProperty.get(CmsPropertyDefinition.PROPERTY_NAVTEXT, properties);
366        if (navText != null) {
367            result.setNavText(navText.getValue());
368        }
369        result.setPermissions(resourceUtil.getPermissionString());
370        if (resource.isFile()) {
371            result.setSize(resource.getLength());
372            result.setFormattedSize(result.getSize() + " B");
373        } else {
374            long totalSize = OpenCms.getFolderSizeTracker(
375                cms.getRequestContext().getCurrentProject().isOnlineProject()).getTotalFolderSize(
376                    resource.getRootPath());
377            String formattedSize = formatFolderSize(totalSize, locale);
378            result.setSize(totalSize);
379            result.setFormattedSize(formattedSize);
380        }
381        result.setStateBean(resource.getState());
382        CmsProperty title = CmsProperty.get(CmsPropertyDefinition.PROPERTY_TITLE, properties);
383        if (localizedTitle != null) {
384            result.setTitle(localizedTitle);
385            result.getListInfo().setTitle(localizedTitle);
386        } else if (title != null) {
387            result.setTitle(title.getValue());
388        }
389        result.setUserCreated(resourceUtil.getUserCreated());
390        result.setUserLastModified(resourceUtil.getUserLastModified());
391
392        I_CmsResourceType resType = OpenCms.getResourceManager().getResourceType(resource);
393        result.setResourceType(resType.getTypeName());
394
395        result.setStructureId(resource.getStructureId());
396        if (resType instanceof CmsResourceTypeXmlContent) {
397            CmsFile file = cms.readFile(resource);
398            CmsXmlContent content = CmsXmlContentFactory.unmarshal(cms, file);
399            List<Locale> locales = content.getLocales();
400            List<String> localeStrings = new ArrayList<String>();
401            for (Locale l : locales) {
402                localeStrings.add(l.toString());
403            }
404            result.setLocales(localeStrings);
405        }
406        Map<String, String> additionalAttributes = new LinkedHashMap<String, String>();
407        additionalAttributes.put(
408            Messages.get().getBundle(locale).key(Messages.GUI_STATUS_PERMALINK_0),
409            OpenCms.getLinkManager().getPermalink(cms, cms.getSitePath(resource), detailContentId));
410
411        if (OpenCms.getRoleManager().hasRole(cms, CmsRole.ADMINISTRATOR)) {
412            additionalAttributes.put(
413                Messages.get().getBundle(locale).key(Messages.GUI_STATUS_STRUCTURE_ID_0),
414                resource.getStructureId().toString());
415            additionalAttributes.put(
416                Messages.get().getBundle(locale).key(Messages.GUI_STATUS_RESOURCE_ID_0),
417                resource.getResourceId().toString());
418        }
419
420        result.setAdditionalAttributes(additionalAttributes);
421
422        List<CmsRelation> relations = cms.readRelations(
423            CmsRelationFilter.relationsToStructureId(resource.getStructureId()));
424        Map<CmsUUID, CmsResource> relationSources = new HashMap<CmsUUID, CmsResource>();
425
426        if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
427            // People may link to the folder of a container page instead of the page itself
428            try {
429                CmsResource parent = cms.readParentFolder(resource.getStructureId());
430                List<CmsRelation> parentRelations = cms.readRelations(
431                    CmsRelationFilter.relationsToStructureId(parent.getStructureId()));
432                relations.addAll(parentRelations);
433            } catch (CmsException e) {
434                LOG.error(e.getLocalizedMessage(), e);
435            }
436        }
437
438        if (relations.size() > MAX_RELATIONS) {
439            String errorMessage = Messages.get().getBundle(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms)).key(
440                Messages.GUI_TOO_MANY_RELATIONS_1,
441                "" + MAX_RELATIONS);
442            result.setSourcesError(errorMessage);
443        } else {
444            // find all distinct relation sources
445            for (CmsRelation relation : relations) {
446                try {
447                    CmsResource currentSource = relation.getSource(cms, CmsResourceFilter.ALL);
448                    relationSources.put(currentSource.getStructureId(), currentSource);
449                } catch (CmsException e) {
450                    LOG.error(e.getLocalizedMessage(), e);
451                }
452            }
453
454            for (CmsResource relationResource : relationSources.values()) {
455                try {
456                    CmsPermissionInfo permissionInfo = OpenCms.getADEManager().getPermissionInfo(
457                        cms,
458                        relationResource,
459                        resource.getRootPath());
460                    if (permissionInfo.hasViewPermission()) {
461                        CmsResourceStatusRelationBean relationBean = createRelationBean(
462                            cms,
463                            contentLocale,
464                            relationResource,
465                            permissionInfo);
466                        CmsSite site = OpenCms.getSiteManager().getSiteForRootPath(relationResource.getRootPath());
467                        if ((site != null)
468                            && !CmsStringUtil.isPrefixPath(
469                                cms.getRequestContext().getSiteRoot(),
470                                relationResource.getRootPath())) {
471                            String siteTitle = site.getTitle();
472                            if (siteTitle == null) {
473                                siteTitle = site.getUrl();
474                            } else {
475                                siteTitle = CmsWorkplace.substituteSiteTitleStatic(
476                                    siteTitle,
477                                    OpenCms.getWorkplaceManager().getWorkplaceLocale(cms));
478                            }
479                            relationBean.setSiteRoot(site.getSiteRoot());
480                            result.getOtherSiteRelationSources().add(relationBean);
481                            relationBean.getInfoBean().setTitle(
482                                "[" + siteTitle + "] " + relationBean.getInfoBean().getTitle());
483                        } else {
484                            result.getRelationSources().add(relationBean);
485                        }
486
487                    }
488                } catch (CmsVfsResourceNotFoundException notfound) {
489                    LOG.error(notfound.getLocalizedMessage(), notfound);
490                    continue;
491                }
492            }
493            sortRelations(cms, result);
494        }
495        if (includeTargets) {
496            try {
497                result.getRelationTargets().addAll(getTargets(cms, contentLocale, resource, additionalStructureIds));
498                if ((detailContentId != null) && (realLocale != null)) {
499                    // try to add detail only contents
500                    try {
501                        CmsResource detailContent = cms.readResource(detailContentId, CmsResourceFilter.ALL);
502                        Optional<CmsResource> detailOnlyPage = CmsDetailOnlyContainerUtil.getDetailOnlyResource(
503                            cms,
504                            realLocale.toString(),
505                            detailContent,
506                            resource);
507                        if (detailOnlyPage.isPresent()) {
508                            result.getRelationTargets().addAll(
509                                getTargets(
510                                    cms,
511                                    contentLocale,
512                                    detailOnlyPage.get(),
513                                    Arrays.asList(detailOnlyPage.get().getStructureId())));
514                        }
515
516                    } catch (CmsException e) {
517                        LOG.error(e.getLocalizedMessage(), e);
518                    }
519                }
520                Iterator<CmsResourceStatusRelationBean> iter = result.getRelationTargets().iterator();
521                // Remove duplicates
522                Set<CmsUUID> visitedIds = Sets.newHashSet();
523                while (iter.hasNext()) {
524                    CmsResourceStatusRelationBean bean = iter.next();
525                    if (visitedIds.contains(bean.getStructureId())) {
526                        iter.remove();
527                    }
528                    visitedIds.add(bean.getStructureId());
529                }
530            } catch (TooManyRelationsException e) {
531                result.setTargetsError(
532                    Messages.get().getBundle(OpenCms.getWorkplaceManager().getWorkplaceLocale(cms)).key(
533                        Messages.GUI_TOO_MANY_RELATIONS_1,
534                        "" + MAX_RELATIONS));
535            }
536        }
537        result.getSiblings().addAll(getSiblings(cms, contentLocale, resource));
538        LinkedHashMap<CmsResourceStatusTabId, String> tabMap = new LinkedHashMap<CmsResourceStatusTabId, String>();
539        Map<CmsResourceStatusTabId, CmsMessageContainer> tabs;
540        CmsResourceStatusTabId startTab = CmsResourceStatusTabId.tabRelationsFrom;
541        if (CmsResourceTypeXmlContainerPage.isContainerPage(resource)) {
542            tabs = CmsResourceStatusConstants.STATUS_TABS_CONTAINER_PAGE;
543        } else if (OpenCms.getResourceManager().getResourceType(resource) instanceof I_CmsLinkParseable) {
544            tabs = CmsResourceStatusConstants.STATUS_TABS_CONTENT;
545        } else {
546            tabs = CmsResourceStatusConstants.STATUS_TABS_OTHER;
547            startTab = CmsResourceStatusTabId.tabStatus;
548        }
549        for (Map.Entry<CmsResourceStatusTabId, CmsMessageContainer> entry : tabs.entrySet()) {
550            tabMap.put(entry.getKey(), entry.getValue().key(locale));
551        }
552
553        result.setTabs(tabMap);
554        result.setStartTab(startTab);
555        return result;
556    }
557
558    /**
559     * Sorts relation beans from other sites by site order.<p>
560     *
561     * @param cms the current CMS context
562     * @param resStatus the bean in which to sort the relation beans
563     */
564    public void sortRelations(CmsObject cms, CmsResourceStatusBean resStatus) {
565
566        final List<CmsSite> sites = OpenCms.getSiteManager().getAvailableSites(
567            cms,
568            false,
569            false,
570            cms.getRequestContext().getOuFqn());
571        Collections.sort(resStatus.getOtherSiteRelationSources(), new Comparator<CmsResourceStatusRelationBean>() {
572
573            private Map<String, Integer> m_rankCache = Maps.newHashMap();
574
575            public int compare(CmsResourceStatusRelationBean o1, CmsResourceStatusRelationBean o2) {
576
577                return ComparisonChain.start().compare(rank(o1), rank(o2)).compare(
578                    o1.getSitePath(),
579                    o2.getSitePath()).result();
580
581            }
582
583            public int rank(CmsResourceStatusRelationBean r) {
584
585                if (m_rankCache.containsKey(r.getSiteRoot())) {
586                    return m_rankCache.get(r.getSiteRoot()).intValue();
587                }
588
589                int j = 0;
590                int result = Integer.MAX_VALUE;
591                for (CmsSite site : sites) {
592                    if (site.getSiteRoot().equals(r.getSiteRoot())) {
593                        result = j;
594                        break;
595                    }
596                    j += 1;
597                }
598
599                m_rankCache.put(r.getSiteRoot(), Integer.valueOf(result));
600                return result;
601            }
602        });
603        // sort relation sources by path, make sure all resources within .detailContainer folders show last
604        Collections.sort(resStatus.getRelationSources(), new Comparator<CmsResourceStatusRelationBean>() {
605
606            public int compare(CmsResourceStatusRelationBean arg0, CmsResourceStatusRelationBean arg1) {
607
608                if (arg0.getSitePath().matches(DETAIL_CONTAINER_PATTERN)) {
609                    if (!arg1.getSitePath().matches(DETAIL_CONTAINER_PATTERN)) {
610                        return 1;
611                    }
612                } else if (arg1.getSitePath().matches(DETAIL_CONTAINER_PATTERN)) {
613                    return -1;
614                }
615                return arg0.getSitePath().compareTo(arg1.getSitePath());
616            }
617
618        });
619    }
620
621    /**
622     * Gets beans which represents the siblings of a resource.<p>
623     *
624     * @param cms the CMS ccontext
625     * @param locale the locale
626     * @param resource the resource
627     * @return the list of sibling beans
628     *
629     * @throws CmsException if something goes wrong
630     */
631    protected List<CmsResourceStatusRelationBean> getSiblings(CmsObject cms, String locale, CmsResource resource)
632    throws CmsException {
633
634        List<CmsResourceStatusRelationBean> result = new ArrayList<CmsResourceStatusRelationBean>();
635        for (CmsResource sibling : cms.readSiblings(resource, CmsResourceFilter.ALL)) {
636            if (sibling.getStructureId().equals(resource.getStructureId())) {
637                continue;
638            }
639            try {
640                CmsPermissionInfo permissionInfo = OpenCms.getADEManager().getPermissionInfo(
641                    cms,
642                    sibling,
643                    resource.getRootPath());
644                if (permissionInfo.hasViewPermission()) {
645                    CmsResourceStatusRelationBean relationBean = createRelationBean(
646                        cms,
647                        locale,
648                        sibling,
649                        permissionInfo);
650                    result.add(relationBean);
651                }
652            } catch (CmsException e) {
653                LOG.error(e.getLocalizedMessage(), e);
654            }
655        }
656        return result;
657    }
658
659    /**
660     * Gets the list of relation targets for a resource.<p>
661     *
662     * @param cms the current CMS context
663     * @param locale the locale
664     * @param resource the resource for which we want the relation targets
665     * @param additionalStructureIds structure ids of additional resources to include with the relation target
666     *
667     * @return the list of relation beans for the relation targets
668     *
669     * @throws CmsException if something goes wrong
670     * @throws TooManyRelationsException if too many relations are found
671     */
672    protected List<CmsResourceStatusRelationBean> getTargets(
673        CmsObject cms,
674        String locale,
675        CmsResource resource,
676        List<CmsUUID> additionalStructureIds)
677    throws CmsException, TooManyRelationsException {
678
679        CmsRelationTargetListBean listBean = getContainerpageRelationTargetsLimited(
680            cms,
681            resource.getStructureId(),
682            additionalStructureIds,
683            false);
684        List<CmsResourceStatusRelationBean> result = new ArrayList<CmsResourceStatusRelationBean>();
685        for (CmsResource target : listBean.getResources()) {
686            try {
687                CmsPermissionInfo permissionInfo = OpenCms.getADEManager().getPermissionInfo(
688                    cms,
689                    target,
690                    resource.getRootPath());
691                if (permissionInfo.hasViewPermission()) {
692                    CmsResourceStatusRelationBean relationBean = createRelationBean(
693                        cms,
694                        locale,
695                        target,
696                        permissionInfo);
697                    result.add(relationBean);
698                }
699            } catch (CmsException e) {
700                LOG.error(e.getLocalizedMessage(), e);
701            }
702        }
703        return result;
704
705    }
706
707    /**
708     * Creates a bean for a single resource which is part of a relation list.<p>
709     *
710     * @param cms the current CMS context
711     * @param locale the locale
712     * @param relationResource the resource
713     * @param permissionInfo the permission info
714     *
715     * @return the status bean for the resource
716     *
717     * @throws CmsException if something goes wrong
718     */
719    CmsResourceStatusRelationBean createRelationBean(
720        CmsObject cms,
721        String locale,
722        CmsResource relationResource,
723        CmsPermissionInfo permissionInfo)
724    throws CmsException {
725
726        CmsListInfoBean sourceBean = CmsVfsService.getPageInfo(cms, relationResource);
727        sourceBean.setMarkChangedState(true);
728        sourceBean.setResourceState(relationResource.getState());
729
730        if (!CmsStringUtil.isEmptyOrWhitespaceOnly(locale)) {
731            Locale realLocale = CmsLocaleManager.getLocale(locale);
732            CmsGallerySearchResult result = CmsGallerySearch.searchById(
733                cms,
734                relationResource.getStructureId(),
735                realLocale);
736            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(result.getTitle())) {
737                sourceBean.setTitle(result.getTitle());
738            }
739        }
740        String link = null;
741        try {
742            link = OpenCms.getLinkManager().substituteLink(cms, relationResource);
743        } catch (Exception e) {
744            LOG.warn(e.getLocalizedMessage(), e);
745        }
746        CmsResourceStatusRelationBean relationBean = new CmsResourceStatusRelationBean(
747            sourceBean,
748            link,
749            relationResource.getStructureId(),
750            permissionInfo);
751        if (CmsResourceTypeXmlContent.isXmlContent(relationResource)) {
752            relationBean.setIsXmlContent(true);
753        }
754        String sitePath = cms.getSitePath(relationResource);
755        relationBean.setSitePath(sitePath);
756        return relationBean;
757    }
758
759    /**
760     * Creates the additional infos from the context parameters.<p>
761     *
762     * @param cms the current CMS object
763     * @param request the current request
764     * @param resource the resource for which to generate additional infos
765     * @param context the context parameters
766     * @return the additional infos (keys are labels)
767     *
768     */
769    private Map<String, String> createContextInfos(
770        CmsObject cms,
771        HttpServletRequest request,
772        CmsResource resource,
773        Map<String, String> context) {
774
775        CmsADEConfigData config = OpenCms.getADEManager().lookupConfiguration(
776            cms,
777            cms.getRequestContext().getRootUri());
778        Locale locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
779        Map<String, String> additionalAttributes = new LinkedHashMap<String, String>();
780        try {
781            try {
782                CmsRelationFilter filter = CmsRelationFilter.relationsFromStructureId(
783                    resource.getStructureId()).filterType(CmsRelationType.XSD);
784                String schema = null;
785                String label = org.opencms.ade.containerpage.Messages.get().getBundle(locale).key(
786                    org.opencms.ade.containerpage.Messages.GUI_ADDINFO_SCHEMA_0);
787
788                for (CmsRelation relation : cms.readRelations(filter)) {
789                    CmsResource target = relation.getTarget(cms, CmsResourceFilter.IGNORE_EXPIRATION);
790                    schema = target.getRootPath();
791                    break;
792                }
793                if (schema != null) {
794                    additionalAttributes.put(label, schema);
795                }
796            } catch (CmsException e) {
797                LOG.error(e.getLocalizedMessage(), e);
798            }
799
800            String elementId = context.get(CmsGwtConstants.ATTR_ELEMENT_ID);
801            String pageRootPath = context.get(CmsGwtConstants.ATTR_PAGE_ROOT_PATH);
802            String containr = context.get(CmsGwtConstants.ATTR_CONTAINER_ID);
803            // We need to handle the case of normal formatter element vs display formatter differently,
804            // because in the former case the jsp id is sometimes incorrect (i.e. inconsistent with the formatter configured in the settings)
805            if ((elementId != null) && (containr != null)) {
806                CmsContainerpageService pageService = new CmsContainerpageService();
807                pageService.setCms(cms);
808                pageService.setRequest(request);
809                CmsContainerElementBean elementBean = pageService.getCachedElement(elementId, pageRootPath);
810                for (Map.Entry<String, String> entry : elementBean.getSettings().entrySet()) {
811                    if (entry.getKey().contains(containr)) {
812                        String formatterId = entry.getValue();
813                        I_CmsFormatterBean formatter = config.findFormatter(formatterId);
814                        for (Map.Entry<String, String> entry1 : getFormatterInfo(cms, formatter).entrySet()) {
815                            additionalAttributes.put(entry1.getKey(), entry1.getValue());
816                        }
817                    }
818                }
819            } else if ((elementId != null) && (pageRootPath != null)) {
820                CmsContainerpageService pageService = new CmsContainerpageService();
821                pageService.setCms(cms);
822                pageService.setRequest(request);
823                CmsContainerElementBean elementBean = pageService.getCachedElement(elementId, pageRootPath);
824                String displayFormatterKey = elementBean.getSettings().get(CmsJspTagDisplay.DISPLAY_FORMATTER_SETTING);
825                boolean foundFormatterInfo = false;
826                if (displayFormatterKey != null) {
827                    I_CmsFormatterBean formatter = config.findFormatter(displayFormatterKey);
828                    if (formatter != null) {
829                        foundFormatterInfo = true;
830                        Map<String, String> formatterInfo = getFormatterInfo(cms, formatter);
831                        for (Map.Entry<String, String> infoEntry : formatterInfo.entrySet()) {
832                            additionalAttributes.put(infoEntry.getKey(), infoEntry.getValue());
833                        }
834                    }
835                }
836                if (!foundFormatterInfo) {
837                    CmsUUID formatterId = elementBean.getFormatterId();
838                    try {
839                        CmsResource formatterRes = cms.readResource(formatterId, CmsResourceFilter.IGNORE_EXPIRATION);
840                        String path = formatterRes.getRootPath();
841                        String label = org.opencms.ade.containerpage.Messages.get().getBundle(locale).key(
842                            org.opencms.ade.containerpage.Messages.GUI_ADDINFO_FORMATTER_0);
843                        additionalAttributes.put(label, path);
844                    } catch (CmsVfsResourceNotFoundException e) {
845                        // ignore
846                    } catch (CmsException e) {
847                        LOG.error(e.getLocalizedMessage(), e);
848                    }
849                }
850            }
851        } catch (Exception e) {
852            LOG.error(e.getLocalizedMessage(), e);
853        }
854
855        return additionalAttributes;
856    }
857
858    /**
859     * Formats the folder size.
860     *
861     * @param totalSize the size to format
862     * @param locale the locale to use for formatting
863     *
864     * @return the formatted folder size
865     */
866    private String formatFolderSize(long totalSize, Locale locale) {
867
868        long kilo = 1024l;
869        long mega = kilo * kilo;
870        long giga = kilo * mega;
871        NumberFormat format = NumberFormat.getInstance(locale);
872        format.setMinimumFractionDigits(2);
873        format.setMaximumFractionDigits(2);
874        if (totalSize >= giga) {
875            return format.format(((double)totalSize) / giga) + " GB";
876        }
877        if (totalSize >= mega) {
878            return format.format(((double)totalSize) / mega) + " MB";
879        }
880        if (totalSize >= kilo) {
881            return format.format(((double)totalSize) / kilo) + " KB";
882        }
883        return totalSize + " B";
884    }
885}