001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (c) Alkacon Software GmbH & Co. KG (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.search.galleries;
029
030import org.opencms.file.CmsObject;
031import org.opencms.file.CmsProperty;
032import org.opencms.file.CmsPropertyDefinition;
033import org.opencms.file.CmsResource;
034import org.opencms.file.CmsResourceFilter;
035import org.opencms.file.types.CmsResourceTypeFunctionConfig;
036import org.opencms.file.types.CmsResourceTypeXmlContainerPage;
037import org.opencms.i18n.CmsLocaleManager;
038import org.opencms.loader.CmsLoaderException;
039import org.opencms.main.CmsException;
040import org.opencms.main.CmsLog;
041import org.opencms.main.OpenCms;
042import org.opencms.search.I_CmsSearchDocument;
043import org.opencms.search.fields.CmsSearchField;
044import org.opencms.search.fields.CmsSearchFieldConfiguration;
045import org.opencms.util.CmsStringUtil;
046import org.opencms.util.CmsUUID;
047
048import java.util.ArrayList;
049import java.util.Arrays;
050import java.util.Date;
051import java.util.List;
052import java.util.Locale;
053import java.util.Map;
054
055import org.apache.commons.logging.Log;
056
057/**
058 * Contains a single search result from the gallery search index.<p>
059 *
060 * @since 8.0.0
061 */
062/**
063 *
064 */
065public class CmsGallerySearchResult implements Comparable<CmsGallerySearchResult>, Cloneable {
066
067    /** The logger instance for this class. */
068    public static final Log LOG = CmsLog.getLog(CmsGallerySearchResult.class);
069
070    /** List of Solr fields that are read during the initialization of the search result. */
071    private static String[] m_requiredSolrFields;
072
073    /** The supported container types of this search result. */
074    protected List<String> m_containerTypes;
075
076    /** The creation date of this search result. */
077    protected Date m_dateCreated;
078
079    /** The expiration date of this search result. */
080    protected Date m_dateExpired;
081
082    /** The last modification date of this search result. */
083    protected Date m_dateLastModified;
084
085    /** The release date of this search result. */
086    protected Date m_dateReleased;
087
088    /** The description of this search result. */
089    protected String m_description;
090
091    /** The excerpt of this search result. */
092    protected String m_excerpt;
093
094    /** The length of the search result. */
095    protected int m_length;
096
097    /** The locales in which the content is available. */
098    protected List<String> m_locales;
099
100    /** The resource path of this search result. */
101    protected String m_path;
102
103    /** The resource type of the search result. */
104    protected String m_resourceType;
105
106    /** The score of this search result. */
107    protected int m_score;
108
109    /** The state of the search result. */
110    protected int m_state;
111
112    /** The structure UUID of the resource. */
113    protected String m_structureId;
114
115    /** The title of this search result. */
116    protected String m_title;
117
118    /** The user who created the search result resource. */
119    protected String m_userCreated;
120
121    /** The user who last modified the search result resource. */
122    protected String m_userLastModified;
123
124    /**
125     * Creates a fake gallery search result by reading the necessary data from a VFS resource.<p>
126     *
127     * @param cms the current CMS context
128     * @param res the resource from which the data should be read
129     * @param title optional value that can be used to override the title (if null, the title is not overridden)
130     */
131    public CmsGallerySearchResult(CmsObject cms, CmsResource res, String title) {
132
133        m_title = title;
134        try {
135            Map<String, String> props = CmsProperty.toMap(
136                cms.readPropertyObjects(res, CmsResourceTypeXmlContainerPage.isContainerPage(res)));
137            if (m_title == null) {
138                m_title = props.get(CmsPropertyDefinition.PROPERTY_TITLE);
139            }
140            m_description = props.get(CmsPropertyDefinition.PROPERTY_DESCRIPTION);
141        } catch (CmsException e) {
142            LOG.error(e.getLocalizedMessage(), e);
143        }
144        if (m_description == null) {
145            m_description = "";
146        }
147        if (m_title == null) {
148            m_title = res.getName();
149        }
150        m_dateCreated = new Date(res.getDateCreated());
151        m_dateExpired = new Date(res.getDateExpired());
152        m_dateLastModified = new Date(res.getDateLastModified());
153        m_dateReleased = new Date(res.getDateReleased());
154        m_length = res.getLength();
155        m_locales = null;
156        m_path = res.getRootPath();
157        try {
158            m_resourceType = OpenCms.getResourceManager().getResourceType(res.getTypeId()).getTypeName();
159        } catch (CmsLoaderException e) {
160            LOG.warn(e.getLocalizedMessage(), e);
161        }
162        m_state = res.getState().getState();
163        m_structureId = res.getStructureId().toString();
164        try {
165            m_userCreated = cms.readUser(res.getUserCreated()).getFullName();
166        } catch (CmsException e) {
167            LOG.warn(e.getLocalizedMessage(), e);
168        }
169        try {
170            m_userLastModified = cms.readUser(res.getUserLastModified()).getFullName();
171        } catch (CmsException e) {
172            LOG.warn(e.getLocalizedMessage(), e);
173        }
174    }
175
176    /**
177     * Creates a new gallery search result.
178     *
179     * @param cms the current CMS context (used for reading information missing from the index)
180     * @param doc the I_CmsSearchResult document to extract information from.
181     * @param score the score of this search result
182     * @param locale the locale to create the result for
183     */
184    public CmsGallerySearchResult(I_CmsSearchDocument doc, CmsObject cms, int score, Locale locale) {
185
186        if (null == doc) {
187            throw new IllegalArgumentException();
188        }
189
190        if (doc.getType().equals(CmsResourceTypeFunctionConfig.TYPE_NAME)) {
191            locale = OpenCms.getWorkplaceManager().getWorkplaceLocale(cms);
192        }
193
194        m_score = score; //(int)doc.getScore();
195
196        m_path = doc.getFieldValueAsString(CmsSearchField.FIELD_PATH);
197
198        if (null == locale) {
199            OpenCms.getLocaleManager();
200            locale = CmsLocaleManager.getDefaultLocale();
201        }
202
203        // For title and description, try various fields and use the first which is not empty
204
205        String mainTitleField = CmsSearchFieldConfiguration.getLocaleExtendedName(
206            CmsSearchField.FIELD_TITLE_UNSTORED,
207            locale.toString()) + "_s";
208        String localizedTitlePropertyField = CmsSearchFieldConfiguration.getLocaleExtendedName(
209            CmsPropertyDefinition.PROPERTY_TITLE,
210            locale.toString()) + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT + "_s";
211        String titlePropertyField = CmsPropertyDefinition.PROPERTY_TITLE
212            + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT
213            + "_s";
214
215        for (String fieldName : Arrays.asList(mainTitleField, localizedTitlePropertyField, titlePropertyField)) {
216            m_title = doc.getFieldValueAsString(fieldName);
217            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_title)) {
218                break;
219            }
220        }
221
222        String mainDescField = CmsSearchFieldConfiguration.getLocaleExtendedName(
223            CmsSearchField.FIELD_DESCRIPTION,
224            locale.toString()) + "_s";
225        String localizedDescPropertyField = CmsSearchFieldConfiguration.getLocaleExtendedName(
226            CmsPropertyDefinition.PROPERTY_DESCRIPTION,
227            locale.toString()) + CmsSearchField.FIELD_DYNAMIC_PROPERTIES + "_s";
228        String descPropertyField = CmsPropertyDefinition.PROPERTY_DESCRIPTION
229            + CmsSearchField.FIELD_DYNAMIC_PROPERTIES
230            + "_s";
231
232        for (String fieldName : Arrays.asList(mainDescField, localizedDescPropertyField, descPropertyField)) {
233            m_description = doc.getFieldValueAsString(fieldName);
234            if (!CmsStringUtil.isEmptyOrWhitespaceOnly(m_description)) {
235                break;
236            }
237        }
238
239        m_resourceType = doc.getFieldValueAsString(CmsSearchField.FIELD_TYPE);
240
241        m_dateCreated = doc.getFieldValueAsDate(CmsSearchField.FIELD_DATE_CREATED);
242
243        m_dateLastModified = doc.getFieldValueAsDate(CmsSearchField.FIELD_DATE_LASTMODIFIED);
244
245        m_dateExpired = doc.getFieldValueAsDate(CmsSearchField.FIELD_DATE_EXPIRED);
246
247        m_dateReleased = doc.getFieldValueAsDate(CmsSearchField.FIELD_DATE_RELEASED);
248
249        m_length = 0;
250        final String s_length = doc.getFieldValueAsString(CmsSearchField.FIELD_SIZE);
251        if (null != s_length) {
252            try {
253                m_length = Integer.parseInt(s_length);
254            } catch (NumberFormatException exc) {
255                // NOOP, default is 0
256            }
257        }
258
259        m_state = 0;
260        final String s_state = doc.getFieldValueAsString(CmsSearchField.FIELD_STATE);
261        if (s_state != null) {
262            try {
263                m_state = Integer.parseInt(s_state);
264            } catch (NumberFormatException exc) {
265                // NOOP, default is 0
266            }
267        }
268
269        m_userCreated = doc.getFieldValueAsString(CmsSearchField.FIELD_USER_CREATED);
270
271        m_structureId = doc.getFieldValueAsString(CmsSearchField.FIELD_ID);
272
273        m_userLastModified = doc.getFieldValueAsString(CmsSearchField.FIELD_USER_LAST_MODIFIED);
274
275        final String s_locales = doc.getFieldValueAsString(CmsSearchField.FIELD_RESOURCE_LOCALES);
276        if (null != s_locales) {
277            m_locales = CmsStringUtil.splitAsList(s_locales, ' ');
278        } else {
279            m_locales = new ArrayList<String>(0);
280        }
281
282        if ((null != cms) && (null != m_structureId)) {
283            initializeMissingFieldsFromVfs(cms, new CmsUUID(m_structureId));
284        }
285    }
286
287    /**
288     * Returns the list of Solr fields a search result must have to initialize the gallery search result correctly.
289     * @return the list of Solr fields.
290     */
291    public static final String[] getRequiredSolrFields() {
292
293        if (null == m_requiredSolrFields) {
294            List<Locale> locales = OpenCms.getLocaleManager().getAvailableLocales();
295            m_requiredSolrFields = new String[14 + (locales.size() * 6)];
296            int count = 0;
297            m_requiredSolrFields[count++] = CmsSearchField.FIELD_PATH;
298            m_requiredSolrFields[count++] = CmsSearchField.FIELD_TYPE;
299            m_requiredSolrFields[count++] = CmsSearchField.FIELD_DATE_CREATED;
300            m_requiredSolrFields[count++] = CmsSearchField.FIELD_DATE_LASTMODIFIED;
301            m_requiredSolrFields[count++] = CmsSearchField.FIELD_DATE_EXPIRED;
302            m_requiredSolrFields[count++] = CmsSearchField.FIELD_DATE_RELEASED;
303            m_requiredSolrFields[count++] = CmsSearchField.FIELD_SIZE;
304            m_requiredSolrFields[count++] = CmsSearchField.FIELD_STATE;
305            m_requiredSolrFields[count++] = CmsSearchField.FIELD_USER_CREATED;
306            m_requiredSolrFields[count++] = CmsSearchField.FIELD_ID;
307            m_requiredSolrFields[count++] = CmsSearchField.FIELD_USER_LAST_MODIFIED;
308            m_requiredSolrFields[count++] = CmsSearchField.FIELD_RESOURCE_LOCALES;
309            for (Locale locale : locales) {
310                m_requiredSolrFields[count++] = CmsSearchFieldConfiguration.getLocaleExtendedName(
311                    CmsSearchField.FIELD_TITLE_UNSTORED,
312                    locale.toString()) + "_s";
313                m_requiredSolrFields[count++] = CmsSearchFieldConfiguration.getLocaleExtendedName(
314                    CmsPropertyDefinition.PROPERTY_TITLE,
315                    locale.toString()) + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT + "_s";
316                m_requiredSolrFields[count++] = CmsPropertyDefinition.PROPERTY_TITLE
317                    + CmsSearchField.FIELD_DYNAMIC_PROPERTIES_DIRECT
318                    + "_s";
319                m_requiredSolrFields[count++] = CmsSearchFieldConfiguration.getLocaleExtendedName(
320                    CmsSearchField.FIELD_DESCRIPTION,
321                    locale.toString()) + "_s";
322                m_requiredSolrFields[count++] = CmsSearchFieldConfiguration.getLocaleExtendedName(
323                    CmsPropertyDefinition.PROPERTY_DESCRIPTION,
324                    locale.toString()) + CmsSearchField.FIELD_DYNAMIC_PROPERTIES + "_s";
325                m_requiredSolrFields[count++] = CmsPropertyDefinition.PROPERTY_DESCRIPTION
326                    + CmsSearchField.FIELD_DYNAMIC_PROPERTIES
327                    + "_s";
328            }
329        }
330        return m_requiredSolrFields;
331    }
332
333    /**
334     * Compares two search results based on the score of the result.<p>
335     *
336     * @param other the result to compare this result with
337     * @return the comparison result
338     *
339     * @see java.lang.Comparable#compareTo(java.lang.Object)
340     */
341    public int compareTo(CmsGallerySearchResult other) {
342
343        if (other == this) {
344            return 0;
345        }
346        return other.m_score - m_score;
347    }
348
349    /**
350     * @see java.lang.Object#equals(java.lang.Object)
351     */
352    @Override
353    public boolean equals(Object obj) {
354
355        if (obj == this) {
356            return true;
357        }
358        if (obj instanceof CmsGallerySearchResult) {
359            CmsGallerySearchResult other = (CmsGallerySearchResult)obj;
360            return m_path.equals(other.m_path);
361        }
362        return false;
363    }
364
365    /**
366     * Returns the containers supported by this resource.<p>
367     *
368     * @return the containers supported by this resource
369     */
370    public List<String> getContainerTypes() {
371
372        return m_containerTypes;
373    }
374
375    /**
376     * Returns the date created.<p>
377     *
378     * @return the date created
379     */
380    public Date getDateCreated() {
381
382        return m_dateCreated;
383    }
384
385    /**
386     * Returns the date the resource expires.<p>
387     *
388     * @return the date the resource expires
389     *
390     * @see org.opencms.file.CmsResource#getDateExpired()
391     */
392    public Date getDateExpired() {
393
394        return m_dateExpired;
395    }
396
397    /**
398     * Returns the date last modified.<p>
399     *
400     * @return the date last modified
401     */
402    public Date getDateLastModified() {
403
404        return m_dateLastModified;
405    }
406
407    /**
408     * Returns the date the resource is released.<p>
409     *
410     * @return the date the resource is released
411     *
412     * @see  org.opencms.file.CmsResource#getDateReleased()
413     */
414    public Date getDateReleased() {
415
416        return m_dateReleased;
417    }
418
419    /**
420     * Returns the description.<p>
421     *
422     * @return the description
423     */
424    public String getDescription() {
425
426        return m_description;
427    }
428
429    /**
430     * Returns the excerpt.<p>
431     *
432     * @return the excerpt
433     */
434    public String getExcerpt() {
435
436        return m_excerpt;
437    }
438
439    /**
440     * Returns the length of the resource.<p>
441     *
442     * @return the length of the resource
443     *
444     * @see org.opencms.file.CmsResource#getLength()
445     */
446    public int getLength() {
447
448        return m_length;
449    }
450
451    /**
452     * Returns the list of locales this search result is available for.<p>
453     *
454     * @return the list of locales this search result is available for
455     */
456    public List<String> getLocales() {
457
458        return m_locales;
459    }
460
461    /**
462     * Returns the resource root path.<p>
463     *
464     * @return the resource root path
465     *
466     * @see org.opencms.file.CmsResource#getRootPath()
467     */
468    public String getPath() {
469
470        return m_path;
471    }
472
473    /**
474     * Returns the resource type of the search result document.<p>
475     *
476     * @return the resource type of the search result document
477     *
478     * @see org.opencms.loader.CmsResourceManager#getResourceType(String)
479     */
480    public String getResourceType() {
481
482        return m_resourceType;
483    }
484
485    /**
486     * Returns the Lucene search score for this result.<p>
487     *
488     * @return the Lucene search score for this result
489     */
490    public int getScore() {
491
492        return m_score;
493    }
494
495    /**
496     * Returns the state of the resource.<p>
497     *
498     * @return the state of the resource
499     *
500     * @see org.opencms.file.CmsResource#getState()
501     */
502    public int getState() {
503
504        return m_state;
505    }
506
507    /**
508     * Returns the structure id of the resource.<p>
509     *
510     * @return the structure id of the resource
511     */
512    public String getStructureId() {
513
514        return m_structureId;
515    }
516
517    /**
518     * Returns the title of the resource.<p>
519     *
520     * @return the title of the resource
521     */
522    public String getTitle() {
523
524        return m_title;
525    }
526
527    /**
528     * Returns the name of the user who created the resource.<p>
529     *
530     * @return the name of the user who created the resource
531     *
532     * @see org.opencms.file.CmsResource#getUserCreated()
533     */
534    public String getUserCreated() {
535
536        return m_userCreated;
537    }
538
539    /**
540     * Returns the name of the user who last modified the resource.<p>
541     *
542     * @return the name of the user who last modified the resource
543     *
544     * @see org.opencms.file.CmsResource#getUserLastModified()
545     */
546    public String getUserLastModified() {
547
548        return m_userLastModified;
549    }
550
551    /**
552     * @see java.lang.Object#hashCode()
553     */
554    @Override
555    public int hashCode() {
556
557        return m_path.hashCode();
558    }
559
560    /**
561     * Returns if the related resource is released and not expired.<p>
562     *
563     * @param cms the cms context
564     *
565     * @return <code>true</code> if the related resource is released and not expired
566     */
567    public boolean isReleaseAndNotExpired(CmsObject cms) {
568
569        long time = cms.getRequestContext().getRequestTime();
570        return (time == CmsResource.DATE_RELEASED_EXPIRED_IGNORE)
571            || ((time > m_dateReleased.getTime()) && (time < m_dateExpired.getTime()));
572    }
573
574    /**
575     * Returns a shallow copy of this result, with a changed title.
576     *
577     * @param title the new title
578     *
579     * @return the shallow copy with the changed title
580     */
581    public CmsGallerySearchResult withTitle(String title) {
582
583        try {
584            CmsGallerySearchResult res = (CmsGallerySearchResult)clone();
585            res.m_title = title;
586            return res;
587        } catch (CloneNotSupportedException e) {
588            // shouldn't happen
589            LOG.error(e.getLocalizedMessage(), e);
590            return null;
591        }
592    }
593
594    /**
595     * Initializes missing fields by reading the information from the VFS.<p>
596     *
597     * @param cms the current CMS context
598     * @param structureId the current structure id
599     */
600    protected void initializeMissingFieldsFromVfs(CmsObject cms, CmsUUID structureId) {
601
602        if (structureId == null) {
603            return;
604        }
605        if ((m_title != null) && (m_description != null)) {
606            return;
607        }
608
609        try {
610            CmsResource res = cms.readResource(structureId, CmsResourceFilter.ALL);
611            if (m_description == null) {
612                CmsProperty descProp = cms.readPropertyObject(
613                    res,
614                    CmsPropertyDefinition.PROPERTY_DESCRIPTION,
615                    CmsResourceTypeXmlContainerPage.isContainerPage(res));
616                m_description = descProp.getValue();
617                if (m_description == null) {
618                    m_description = "";
619                }
620            }
621
622            if (m_title == null) {
623                CmsProperty titleProp = cms.readPropertyObject(
624                    res,
625                    CmsPropertyDefinition.PROPERTY_TITLE,
626                    CmsResourceTypeXmlContainerPage.isContainerPage(res));
627                m_title = titleProp.getValue();
628                if (CmsStringUtil.isEmptyOrWhitespaceOnly(m_title)) {
629                    m_title = res.getName();
630                }
631            }
632        } catch (CmsException e) {
633            LOG.error(e.getLocalizedMessage(), e);
634            return;
635        }
636    }
637}