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