001/*
002 * File   : $Source$
003 * Date   : $Date$
004 * Version: $Revision$
005 *
006 * This library is part of OpenCms -
007 * the Open Source Content Management System
008 *
009 * Copyright (C) 2002 - 2009 Alkacon Software (http://www.alkacon.com)
010 *
011 * This library is free software; you can redistribute it and/or
012 * modify it under the terms of the GNU Lesser General Public
013 * License as published by the Free Software Foundation; either
014 * version 2.1 of the License, or (at your option) any later version.
015 *
016 * This library is distributed in the hope that it will be useful,
017 * but WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 * Lesser General Public License for more details.
020 *
021 * For further information about Alkacon Software, please see the
022 * company website: http://www.alkacon.com
023 *
024 * For further information about OpenCms, please see the
025 * project website: http://www.opencms.org
026 *
027 * You should have received a copy of the GNU Lesser General Public
028 * License along with this library; if not, write to the Free Software
029 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
030 */
031
032package org.opencms.search.documents;
033
034import org.opencms.db.CmsPublishedResource;
035import org.opencms.db.CmsResourceState;
036import org.opencms.file.CmsObject;
037import org.opencms.file.CmsProperty;
038import org.opencms.file.CmsPropertyDefinition;
039import org.opencms.file.CmsResource;
040import org.opencms.file.CmsResourceFilter;
041import org.opencms.i18n.CmsLocaleManager;
042import org.opencms.json.JSONArray;
043import org.opencms.json.JSONException;
044import org.opencms.json.JSONObject;
045import org.opencms.main.CmsException;
046import org.opencms.main.CmsLog;
047import org.opencms.main.OpenCms;
048import org.opencms.util.CmsStringUtil;
049
050import java.util.ArrayList;
051import java.util.HashMap;
052import java.util.Iterator;
053import java.util.List;
054import java.util.Locale;
055import java.util.Map;
056import java.util.regex.Matcher;
057
058import org.apache.commons.logging.Log;
059
060/**
061 * Provides the dependency information about one search result document,
062 * used to generate the list of document search results.<p>
063 *
064 * @since 8.5.0
065 */
066public final class CmsDocumentDependency {
067
068    /**
069     * Defines the possible dependency types.<p>
070     */
071    public static enum DependencyType {
072
073        /** A attachment dependency. */
074        attachment,
075
076        /** The main document dependency. */
077        document,
078
079        /** A variant dependency. */
080        variant
081    }
082
083    /** Prefix for context attributes. */
084    private static final String ATTR_DOC_DEPENDENCY = "CmsDocumentDependency.";
085
086    /** Field name in JSON. */
087    private static final String JSON_ATTACHMENTS = "attachments";
088
089    /** Field name in JSON. */
090    private static final String JSON_DATE_CREATED = "dateCreated";
091
092    /** Field name in JSON. */
093    private static final String JSON_DATE_MODIFIED = "dateModified";
094
095    /** Field name in JSON. */
096    private static final String JSON_LANGUAGES = "languages";
097
098    /** Field name in JSON. */
099    private static final String JSON_LOCALE = "locale";
100
101    /** Field name in JSON. */
102    private static final String JSON_MAIN = "main";
103
104    /** Field name in JSON. */
105    private static final String JSON_PATH = "path";
106
107    /** Field name in JSON. */
108    private static final String JSON_TITLE = "title";
109
110    /** Field name in JSON. */
111    private static final String JSON_UUID = "uuid";
112
113    /** The log object for this class. */
114    private static final Log LOG = CmsLog.getLog(CmsDocumentDependency.class);
115
116    /** The attachment number. */
117    private Integer m_attachmentNumber;
118
119    /** The document attachments . */
120    private List<CmsDocumentDependency> m_attachments = new ArrayList<CmsDocumentDependency>();
121
122    /** The list of resources the main resource depends on, including the main resource itself. */
123    private List<CmsDocumentDependency> m_dependencies = new ArrayList<CmsDocumentDependency>();
124
125    /** The file name of the document without attachment or locale suffixes. */
126    private String m_documentName;
127
128    /** The file suffix of the document. */
129    private String m_documentSuffix;
130
131    /** The locale of this document container. */
132    private Locale m_locale;
133
134    /** Signals whether this document exists with different locale file name extensions.  */
135    private boolean m_localeFileName;
136
137    /** The main document in case this document is an attachment. */
138    private CmsDocumentDependency m_mainDocument;
139
140    /** The VFS resource for which the dependencies are calculated. */
141    private CmsPublishedResource m_resource;
142
143    /** Stores the root path of this dependency. */
144    private String m_rootPath;
145
146    /** The dependency type for this dependency. */
147    private DependencyType m_type;
148
149    /** The language version dependencies. */
150    private List<CmsDocumentDependency> m_variants = new ArrayList<CmsDocumentDependency>();
151
152    /**
153     * Creates a new dependency container for the given VFS resource.<p>
154     *
155     * @param res the VFS resource for which the dependencies are calculated
156     */
157    private CmsDocumentDependency(CmsPublishedResource res) {
158
159        this(res, res.getRootPath());
160    }
161
162    /**
163     * Creates a new dependency container for the given VFS resource.<p>
164     *
165     * Additional constructor with extra root path, to be used only for test cases.<p>
166     *
167     * @param resource the VFS resource for which the dependencies are calculated
168     * @param rootPath the root path to use
169     */
170    private CmsDocumentDependency(CmsPublishedResource resource, String rootPath) {
171
172        m_resource = resource;
173        m_rootPath = rootPath;
174        String docName = CmsResource.getName(rootPath);
175
176        // check if an attachment number is present
177        Matcher matcher = CmsStringUtil.PATTERN_NUMBER_SUFFIX.matcher(docName);
178        if (matcher.find()) {
179            docName = matcher.group(1);
180            try {
181                Integer partNumber = Integer.valueOf(Integer.parseInt(matcher.group(2)));
182                setAttachmentNumber(partNumber);
183            } catch (NumberFormatException e) {
184                // ignore, can happen if the second group of the matcher is larger than Integer.MAX_VALUE
185            }
186        }
187
188        Locale locale = null;
189        matcher = CmsStringUtil.PATTERN_LOCALE_SUFFIX.matcher(docName);
190        if (matcher.find()) {
191            docName = matcher.group(1);
192            String suffix = matcher.group(2);
193            String langString = suffix.substring(0, 2);
194            locale = suffix.length() == 5 ? new Locale(langString, suffix.substring(3, 5)) : new Locale(langString);
195            m_localeFileName = true;
196        }
197        if (locale == null) {
198            locale = CmsLocaleManager.getDefaultLocale();
199        }
200        if ((locale == null) || !OpenCms.getLocaleManager().getAvailableLocales().contains(locale)) {
201            // check if the determined locale is available in the opencms-system.xml
202            // use the default locale as fall-back
203            // locale = CmsLocaleManager.getDefaultLocale();
204        }
205        setLocale(locale);
206
207        // we must remove file suffixes like "plapla.doc", ".pdf" etc. because attachments can have different types
208        int index = rootPath.lastIndexOf('.');
209        if (index != -1) {
210            // store the suffix for comparison to decide if this is a main document or a variation later
211            String suffix = rootPath.substring(index);
212            setDocumentSuffix(suffix);
213            if ((docName.lastIndexOf(suffix) == (docName.length() - suffix.length())) && docName.contains(suffix)) {
214                docName = docName.substring(0, docName.length() - suffix.length());
215            }
216        }
217        setDocumentName(CmsResource.getFolderPath(rootPath) + docName);
218    }
219
220    /**
221     * Creates a new dependency container for the given VFS resource.<p>
222     *
223     * @param res the VFS resource for which the dependencies are calculated
224     */
225    private CmsDocumentDependency(CmsResource res) {
226
227        this(new CmsPublishedResource(res, -1, CmsResourceState.STATE_CHANGED));
228    }
229
230    /**
231     * Creates a dependency object from a String representation.<p>
232     *
233     * @param input the String representation
234     * @param rootPath the root path of the base document of which the dependencies are encoded
235     *
236     * @return the dependency object created from a String representation
237     */
238    public static CmsDocumentDependency fromDependencyString(String input, String rootPath) {
239
240        CmsDocumentDependency result = new CmsDocumentDependency(null, rootPath);
241        if (input != null) {
242
243            try {
244                if (input.startsWith("{")) {
245                    JSONObject jsonDoc = new JSONObject(input);
246                    result.fromJSON(jsonDoc, rootPath);
247
248                    // main document
249                    if (jsonDoc.has(JSON_MAIN)) {
250
251                        JSONObject jsonMain = jsonDoc.getJSONObject(JSON_MAIN);
252
253                        CmsDocumentDependency main = new CmsDocumentDependency(null, jsonMain.getString(JSON_PATH));
254                        main.fromJSON(jsonMain, rootPath);
255                        result.setMainDocument(main);
256                    }
257                } else {
258                    // special handling for news
259                    String[] docs = CmsStringUtil.splitAsArray(input, '|');
260                    for (int i = 0; i < docs.length; i++) {
261                        String doc = docs[i];
262
263                        String lang = doc.substring(0, 2);
264                        Locale loc = new Locale(lang);
265
266                        if (i == 0) {
267                            result.setLocale(loc);
268                        } else {
269                            String rp = doc.substring(3);
270                            CmsDocumentDependency dep = new CmsDocumentDependency(null, rp);
271                            if (!loc.equals(result.getLocale())) {
272                                dep.setLocale(new Locale(lang));
273                                result.addVariant(dep);
274                            }
275                        }
276                    }
277                }
278            } catch (Exception ex) {
279                if (LOG.isErrorEnabled()) {
280                    LOG.error(ex.getLocalizedMessage(), ex);
281                }
282            }
283        }
284        return result;
285    }
286
287    /**
288     * Returns the locale (language) of the given resource based on the resource root path.<p>
289     *
290     * @param rootPath the resource name to check for the locale information
291     *
292     * @return the locale of the given resource based on the resource root path
293     */
294    public static Locale getLocale(String rootPath) {
295
296        return (new CmsDocumentDependency(null, rootPath)).getLocale();
297    }
298
299    /**
300     * Loads or creates a dependency object for the given parameters.<p>
301     *
302     * @param cms the current OpenCms user context
303     * @param pubRes the published resource to get the dependency object for
304     *
305     * @return a dependency object for the given parameters
306     */
307    public static CmsDocumentDependency load(CmsObject cms, CmsPublishedResource pubRes) {
308
309        CmsDocumentDependency result = readFromContext(cms, pubRes.getRootPath());
310        if (result == null) {
311            result = new CmsDocumentDependency(pubRes);
312            result.readDependencies(cms);
313        }
314        return result;
315    }
316
317    /**
318     * Loads or creates a dependency object for the given parameters.<p>
319     *
320     * @param cms the current OpenCms user context
321     * @param res the VFS resource to get the dependency object for
322     *
323     * @return a dependency object for the given parameters
324     */
325    public static CmsDocumentDependency load(CmsObject cms, CmsResource res) {
326
327        CmsDocumentDependency result = readFromContext(cms, res.getRootPath());
328        if (result == null) {
329            result = new CmsDocumentDependency(res);
330            result.readDependencies(cms);
331        }
332        return result;
333    }
334
335    /**
336     * Loads or creates a dependency object for the given parameters.<p>
337     *
338     * @param cms the current OpenCms user context
339     * @param res the VFS resource to get the dependency object for
340     * @param resources the resource folder data to check for dependencies
341     *
342     * @return a dependency object for the given parameters
343     */
344    public static CmsDocumentDependency load(CmsObject cms, CmsResource res, List<CmsResource> resources) {
345
346        CmsDocumentDependency result = readFromContext(cms, res.getRootPath());
347        if (result == null) {
348            result = new CmsDocumentDependency(res);
349            result.readDependencies(cms, resources);
350        }
351        return result;
352    }
353
354    /**
355     * Creates a dependency object for the given root path, to be used only for test cases.<p>
356     *
357     * @param rootPath the root path to create the dependency object for
358     *
359     * @return a dependency object for the given parameters
360     */
361    protected static CmsDocumentDependency loadForTest(String rootPath) {
362
363        return new CmsDocumentDependency(null, rootPath);
364    }
365
366    /**
367     * Removes the dependency object for a published resource from the OpenCms
368     * runtime context.<p>
369     *
370     * <b>Please note:</b> This must be used with caution since the information
371     * may be required to generate documents for several configured indexes. It
372     * must be ensured that this is called only when all indexes have been
373     * updated.<p>
374     *
375     * @param cms the current OpenCms user context
376     * @param pubRes the published resource info
377     *
378     * @see #storeInContext(CmsObject)
379     */
380    protected static void removeFromContext(CmsObject cms, CmsPublishedResource pubRes) {
381
382        cms.getRequestContext().removeAttribute(getAttributeKey(pubRes.getRootPath()));
383    }
384
385    /**
386     * Generates a context attribute name for a root path.
387     *
388     * @param rootPath the root path
389     *
390     * @return the Attr_Doc_Deps + rootPapth
391     */
392    private static String getAttributeKey(String rootPath) {
393
394        return ATTR_DOC_DEPENDENCY + rootPath;
395    }
396
397    /**
398     * Reads the dependency object for the given root path in the OpenCms runtime context.<p>
399     *
400     * @param cms the current OpenCms user context
401     * @param rootPath the root path to look up the dependency object for
402     *
403     * @return the deps
404     */
405    private static CmsDocumentDependency readFromContext(CmsObject cms, String rootPath) {
406
407        return (CmsDocumentDependency)cms.getRequestContext().getAttribute(getAttributeKey(rootPath));
408    }
409
410    /**
411     * Adds another document attachment dependency to this document.<p>
412     *
413     * @param dep the document attachment dependency to add
414     */
415    public void addAttachment(CmsDocumentDependency dep) {
416
417        // don't add attachment if this is a language version of an already existing attachment
418        boolean exist = false;
419        for (CmsDocumentDependency att : m_attachments) {
420            if (att.getAttachmentNumber() == dep.getAttachmentNumber()) {
421
422                if (m_locale.equals(dep.getLocale())) {
423                    // if dependency has same locale as main document it is added as attachment
424                    // and gets the old attachment as a language-version with all previous language-versions
425                    for (CmsDocumentDependency langAtt : att.getVariants()) {
426                        dep.addVariant(langAtt);
427                    }
428                    dep.addVariant(att);
429                    m_attachments.remove(att);
430                } else {
431                    exist = true;
432                    att.addVariant(dep);
433                }
434                break;
435            }
436        }
437
438        if (!exist) {
439            dep.setType(DependencyType.attachment);
440            m_attachments.add(dep);
441        } else {
442            dep.setType(DependencyType.variant);
443        }
444        addDependency(dep);
445    }
446
447    /**
448     * Adds another document dependency to this document.<p>
449     *
450     * @param dep the document dependency to add
451     */
452    public void addDependency(CmsDocumentDependency dep) {
453
454        m_dependencies.add(dep);
455    }
456
457    /**
458     * Adds another language version document dependency to this document.<p>
459     *
460     * @param dep the language version document dependency to add
461     */
462    public void addVariant(CmsDocumentDependency dep) {
463
464        // check if already exists
465        for (CmsDocumentDependency lang : m_variants) {
466            if (lang.getLocale().equals(dep.getLocale())) {
467                return;
468            }
469        }
470        dep.setType(DependencyType.variant);
471        m_variants.add(dep);
472        addDependency(dep);
473    }
474
475    /**
476     * @see java.lang.Object#equals(java.lang.Object)
477     */
478    @Override
479    public boolean equals(Object obj) {
480
481        if (obj == this) {
482            return true;
483        }
484        if (obj instanceof CmsDocumentDependency) {
485            CmsDocumentDependency other = (CmsDocumentDependency)obj;
486            return m_resource.getRootPath().equals(other.getResource().getRootPath())
487                && m_locale.equals(other.m_locale);
488        }
489        return false;
490    }
491
492    /**
493     * Read the information out of the given JSON object to fill
494     * the values of the document.<p>
495     *
496     * @param json the JSON object with the information about this document
497     * @param rootPath the current path the home division
498     */
499    public void fromJSON(JSONObject json, String rootPath) {
500
501        try {
502            // language versions
503            if (json.has(JSON_LANGUAGES)) {
504                JSONArray jsonLanguages = json.getJSONArray(JSON_LANGUAGES);
505                for (int i = 0; i < jsonLanguages.length(); i++) {
506                    JSONObject jsonLang = (JSONObject)jsonLanguages.get(i);
507
508                    CmsDocumentDependency lang = new CmsDocumentDependency(null, jsonLang.getString(JSON_PATH));
509                    lang.fromJSON(jsonLang, rootPath);
510                    addVariant(lang);
511                }
512            }
513
514            // attachments
515            if (json.has(JSON_ATTACHMENTS)) {
516                JSONArray jsonAttachments = json.getJSONArray(JSON_ATTACHMENTS);
517                for (int i = 0; i < jsonAttachments.length(); i++) {
518                    try {
519                        JSONObject jsonAttachment = (JSONObject)jsonAttachments.get(i);
520
521                        CmsDocumentDependency att = new CmsDocumentDependency(
522                            null,
523                            jsonAttachment.getString(JSON_PATH));
524                        att.fromJSON(jsonAttachment, rootPath);
525
526                        // language versions of attachment
527                        if (jsonAttachment.has(JSON_LANGUAGES)) {
528                            JSONArray jsonAttLanguages = jsonAttachment.getJSONArray(JSON_LANGUAGES);
529                            for (int j = 0; j < jsonAttLanguages.length(); j++) {
530                                JSONObject jsonAttLanguage = (JSONObject)jsonAttLanguages.get(j);
531
532                                CmsDocumentDependency attLang = new CmsDocumentDependency(
533                                    null,
534                                    jsonAttLanguage.getString(JSON_PATH));
535                                attLang.fromJSON(jsonAttLanguage, rootPath);
536                                att.addVariant(attLang);
537                            }
538                        }
539                        addAttachment(att);
540                    } catch (Exception e) {
541                        LOG.error(e.getLocalizedMessage(), e);
542                    }
543                }
544            }
545        } catch (Exception ex) {
546            if (LOG.isErrorEnabled()) {
547                LOG.error(ex.getLocalizedMessage(), ex);
548            }
549        }
550    }
551
552    /**
553     * Returns the attachment number.<p>
554     *
555     * @return the attachment number
556     */
557    public int getAttachmentNumber() {
558
559        if (m_attachmentNumber != null) {
560            return m_attachmentNumber.intValue();
561        }
562        return 0;
563    }
564
565    /**
566     * Returns the attachments.<p>
567     *
568     * @return the attachments
569     */
570    public List<CmsDocumentDependency> getAttachments() {
571
572        return m_attachments;
573    }
574
575    /**
576     * Returns the list of resources the main resource depends on, including the main resource itself.<p>
577     *
578     * @return the list of resources the main resource depends on, including the main resource itself
579     */
580    public List<CmsDocumentDependency> getDependencies() {
581
582        return m_dependencies;
583    }
584
585    /**
586     * Returns the file name of the document without attachment or locale suffixes.<p>
587     *
588     * @return the file name of the document without attachment or locale suffixes
589     */
590    public String getDocumentName() {
591
592        return m_documentName;
593    }
594
595    /**
596     * Returns the suffix of the document.<p>
597     *
598     * @return the suffix of the document
599     */
600    public String getDocumentSuffix() {
601
602        return m_documentSuffix;
603    }
604
605    /**
606     * Returns the locale of this document container.<p>
607     *
608     * @return the locale of this document container
609     */
610    public Locale getLocale() {
611
612        return m_locale;
613    }
614
615    /**
616     * Returns the main document in case this document is an attachment.<p>
617     *
618     * @return the main document in case this document is an attachment
619     */
620    public CmsDocumentDependency getMainDocument() {
621
622        return m_mainDocument;
623    }
624
625    /**
626     * Returns the VFS resource for which the dependencies are calculated.<p>
627     *
628     * @return the VFS resource for which the dependencies are calculated
629     */
630    public CmsPublishedResource getResource() {
631
632        return m_resource;
633    }
634
635    /**
636     * Returns the root path of this dependency.<p>
637     *
638     * @return the root path
639     */
640    public String getRootPath() {
641
642        if (m_resource != null) {
643            return m_resource.getRootPath();
644        } else {
645            return m_rootPath;
646        }
647    }
648
649    /**
650     * Returns the type.<p>
651     *
652     * @return the type
653     */
654    public DependencyType getType() {
655
656        return m_type;
657    }
658
659    /**
660     * Returns the variants.<p>
661     *
662     * @return the variants
663     */
664    public List<CmsDocumentDependency> getVariants() {
665
666        return m_variants;
667    }
668
669    /**
670     * @see java.lang.Object#hashCode()
671     */
672    @Override
673    public int hashCode() {
674
675        return getResource().hashCode();
676    }
677
678    /**
679     * Returns the locale file name flag.<p>
680     *
681     * @return the locale file name flag
682     */
683    public boolean hasLocaleFileName() {
684
685        return m_localeFileName;
686    }
687
688    /**
689     * Returns true if this document is an attachment, i.e. an attachment number is provided.<p>
690     *
691     * @return true if this document is an attachment, otherwise false
692     */
693    public boolean isAttachment() {
694
695        return m_attachmentNumber != null;
696    }
697
698    /**
699     * Reads all dependencies that exist for this main resource in the OpenCms VFS.<p>
700     *
701     * To be used when incremental updating an index.<p>
702     *
703     * @param cms the current users OpenCms context
704     */
705    public void readDependencies(CmsObject cms) {
706
707        try {
708            // read all resources in the parent folder of the published resource
709            List<CmsResource> folderContent = cms.getResourcesInFolder(
710                CmsResource.getParentFolder(cms.getRequestContext().removeSiteRoot(getResource().getRootPath())),
711                CmsResourceFilter.DEFAULT);
712            // now calculate the dependencies form the folder content that has been read
713            readDependencies(cms, folderContent);
714        } catch (CmsException e) {
715            LOG.warn("Unable to read dependencies for " + getResource().getRootPath(), e);
716        }
717    }
718
719    /**
720     * Reads all dependencies that exist for this main resource in provided list of resources.<p>
721     *
722     * @param cms the current users OpenCms context
723     * @param folderContent the contents of the folder to check the dependencies for
724     */
725    public void readDependencies(CmsObject cms, List<CmsResource> folderContent) {
726
727        Map<Integer, CmsDocumentDependency> attachments = new HashMap<Integer, CmsDocumentDependency>();
728        if (isAttachment()) {
729            attachments.put(Integer.valueOf(getAttachmentNumber()), this);
730        }
731
732        // iterate all resources in the folder to check if this is a language version
733        Iterator<CmsResource> i = folderContent.iterator();
734        while (i.hasNext()) {
735            CmsResource r = i.next();
736            // only add files and don't add the resource itself again
737            if (r.isFile() && !getResource().getRootPath().equals(r.getRootPath())) {
738                CmsPublishedResource pubRes = new CmsPublishedResource(
739                    r,
740                    getResource().getPublishTag(),
741                    CmsResourceState.STATE_CHANGED);
742                CmsDocumentDependency dep = new CmsDocumentDependency(pubRes);
743                if (getDocumentName().equals(dep.getDocumentName())) {
744                    if (isAttachment()) {
745                        // this document is an attachment
746                        if (dep.isAttachment()) {
747                            if ((getAttachmentNumber() == dep.getAttachmentNumber()) && dep.hasLocaleFileName()) {
748                                // the dependency must be a language version of this attachment document
749                                addVariant(dep);
750                            }
751                            if (getAttachmentNumber() == dep.getAttachmentNumber()) {
752                                dep.setMainDocument(dep);
753                            } else {
754                                Integer attNum = Integer.valueOf(dep.getAttachmentNumber());
755                                CmsDocumentDependency att = attachments.get(attNum);
756                                if (att != null) {
757                                    att.addVariant(dep);
758                                } else {
759                                    attachments.put(attNum, dep);
760                                }
761                            }
762                        } else {
763                            // this is the main document of the dependency
764                            setMainDocument(dep);
765                        }
766                    } else {
767                        // this document is a main document
768                        if (dep.isAttachment()) {
769                            // add this dependency as an attachment
770                            addAttachment(dep);
771                        } else if (CmsStringUtil.isEqual(getDocumentSuffix(), dep.getDocumentSuffix())) {
772                            // if this is no attachment, and the file suffix is equal,
773                            // this must be a language version of the main document
774                            addVariant(dep);
775                        }
776                        // if the file suffix is NOT equal, this is a new main document
777                        setMainDocument(this);
778                    }
779                }
780            }
781        }
782
783        if (m_mainDocument != null) {
784            for (CmsDocumentDependency att : attachments.values()) {
785                m_mainDocument.addAttachment(att);
786            }
787            // add the main document as dependency for this attachment
788            addDependency(m_mainDocument);
789            for (CmsDocumentDependency var : getVariants()) {
790                String mainFileName = getMainDocument().getDocumentName();
791                if (getMainDocument().getDocumentSuffix() != null) {
792                    mainFileName += getMainDocument().getDocumentSuffix();
793                }
794                if (mainFileName.equals(var.getResource().getRootPath())) {
795                    setType(DependencyType.variant);
796                    break;
797                } else {
798                    setType(DependencyType.document);
799                    break;
800                }
801            }
802        }
803    }
804
805    /**
806     * Sets the attachment number.<p>
807     *
808     * @param attachmentNumber the attachment number
809     */
810    public void setAttachmentNumber(Integer attachmentNumber) {
811
812        m_attachmentNumber = attachmentNumber;
813    }
814
815    /**
816     * Sets the file name of the document without attachment or locale suffixes.<p>
817     *
818     * @param documentName the file name of the document without attachment or locale suffixes
819     */
820    public void setDocumentName(String documentName) {
821
822        m_documentName = documentName;
823    }
824
825    /**
826     * Sets the suffix (.pdf, .doc etc.) of the document.<p>
827     *
828     * @param documentSuffix the suffix to set
829     */
830    public void setDocumentSuffix(String documentSuffix) {
831
832        m_documentSuffix = documentSuffix;
833    }
834
835    /**
836     * Sets the locale of this document container.<p>
837     *
838     * @param locale the locale of this document container
839     */
840    public void setLocale(Locale locale) {
841
842        m_locale = locale;
843    }
844
845    /**
846     * Sets the main document in case this document is an attachment.<p>
847     *
848     * @param mainDocument the main document to set
849     */
850    public void setMainDocument(CmsDocumentDependency mainDocument) {
851
852        if (m_mainDocument == null) {
853            // we currently have no main document at all
854            m_mainDocument = mainDocument;
855        } else {
856            // check if we find a better match for the main document locale
857            if (mainDocument.getLocale().equals(getLocale())) {
858                // mainDocument.addVariant(m_mainDocument);
859                // main document locale is the "best" one
860                m_mainDocument = mainDocument;
861            } else {
862                // check if the new document is a "better" one
863                List<Locale> locales = OpenCms.getLocaleManager().getDefaultLocales();
864                int pos1 = locales.indexOf(m_mainDocument.getLocale());
865                if (pos1 > 0) {
866                    int pos2 = locales.indexOf(mainDocument.getLocale());
867                    if (pos2 < pos1) {
868                        mainDocument.addVariant(m_mainDocument);
869                        // locale is closer to the default
870                        m_mainDocument = mainDocument;
871                    }
872                } else {
873                    m_mainDocument.addVariant(mainDocument);
874                }
875            }
876        }
877    }
878
879    /**
880     * Sets the type for this dependency.<p>
881     *
882     * @param type the type to set
883     */
884    public void setType(DependencyType type) {
885
886        m_type = type;
887    }
888
889    /**
890     * Stores this dependency object for a published resource in the OpenCms runtime context.<p>
891     *
892     * This done to optimize indexing speed. When the index update information is calculated,
893     * all dependencies for a resource must be calculated also. The same information is later needed when
894     * the Lucene document is created, for example in order to store the list of other available languages.<p>
895     *
896     * @param cms the current OpenCms user context
897     */
898    public void storeInContext(CmsObject cms) {
899
900        cms.getRequestContext().setAttribute(getAttributeKey(getResource().getRootPath()), this);
901    }
902
903    /**
904     * Creates the String representation of this dependency object.<p>
905     *
906     * @param cms the current OpenCms user context
907     *
908     * @return the String representation of this dependency object
909     */
910    public String toDependencyString(CmsObject cms) {
911
912        JSONObject jsonDoc = toJSON(cms, true);
913
914        try {
915            if ((!isAttachment()) && (m_attachments != null)) {
916                // iterate through all attachments
917                JSONArray jsonAttachments = new JSONArray();
918                for (CmsDocumentDependency att : m_attachments) {
919                    JSONObject jsonAttachment = att.toJSON(cms, true);
920                    jsonAttachments.put(jsonAttachment);
921                }
922                jsonDoc.put(JSON_ATTACHMENTS, jsonAttachments);
923            } else if (isAttachment()) {
924                CmsDocumentDependency main = getMainDocument();
925                if (main != null) {
926                    JSONObject jsonMain = main.toJSON(cms, true);
927                    // iterate through all attachments of the main document
928                    List<CmsDocumentDependency> attachments = main.getAttachments();
929                    if (attachments != null) {
930                        JSONArray jsonAttachments = new JSONArray();
931                        for (CmsDocumentDependency att : attachments) {
932                            JSONObject jsonAttachment = att.toJSON(cms, true);
933                            jsonAttachments.put(jsonAttachment);
934                        }
935                        jsonMain.put(JSON_ATTACHMENTS, jsonAttachments);
936                    }
937                    jsonDoc.put(JSON_MAIN, jsonMain);
938                }
939            }
940            return jsonDoc.toString();
941        } catch (JSONException e) {
942            if (LOG.isErrorEnabled()) {
943                LOG.error(e.getLocalizedMessage(), e);
944            }
945        }
946        return null;
947    }
948
949    /**
950     * Returns a JSON object describing this dependency document.<p>
951     *
952     * @param cms the current cms object
953     * @param includeLang flag if language versions should be included
954     *
955     * @return a JSON object describing this dependency document
956     */
957    public JSONObject toJSON(CmsObject cms, boolean includeLang) {
958
959        try {
960            CmsObject clone = OpenCms.initCmsObject(cms);
961            clone.getRequestContext().setSiteRoot("");
962            JSONObject jsonAttachment = new JSONObject();
963            CmsResource res = clone.readResource(m_rootPath, CmsResourceFilter.IGNORE_EXPIRATION);
964            Map<String, String> props = CmsProperty.toMap(clone.readPropertyObjects(res, false));
965            // id and path
966            jsonAttachment.put(JSON_UUID, res.getStructureId());
967            jsonAttachment.put(JSON_PATH, res.getRootPath());
968            // title
969            jsonAttachment.put(JSON_TITLE, props.get(CmsPropertyDefinition.PROPERTY_TITLE));
970            // date created
971            jsonAttachment.put(JSON_DATE_CREATED, res.getDateCreated());
972            // date created
973            jsonAttachment.put(JSON_LOCALE, m_locale);
974            // date modified
975            jsonAttachment.put(JSON_DATE_MODIFIED, res.getDateLastModified());
976
977            if (includeLang) {
978                // get all language versions of the document
979                List<CmsDocumentDependency> langs = getVariants();
980                if (langs != null) {
981                    JSONArray jsonLanguages = new JSONArray();
982                    for (CmsDocumentDependency lang : langs) {
983
984                        JSONObject jsonLanguage = lang.toJSON(cms, false);
985                        jsonLanguages.put(jsonLanguage);
986                    }
987                    jsonAttachment.put(JSON_LANGUAGES, jsonLanguages);
988                }
989            }
990            return jsonAttachment;
991        } catch (Exception ex) {
992            LOG.error(ex.getLocalizedMessage(), ex);
993        }
994        return null;
995    }
996
997    /**
998     * @see java.lang.Object#toString()
999     */
1000    @Override
1001    public String toString() {
1002
1003        if (m_resource != null) {
1004            return m_resource.toString();
1005        } else {
1006            return m_rootPath;
1007        }
1008    }
1009}