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.solr;
033
034import org.opencms.file.CmsObject;
035import org.opencms.file.CmsResource;
036import org.opencms.main.CmsLog;
037import org.opencms.main.OpenCms;
038import org.opencms.relations.CmsCategory;
039import org.opencms.search.CmsSearchUtil;
040import org.opencms.search.I_CmsSearchDocument;
041import org.opencms.search.documents.CmsDocumentDependency;
042import org.opencms.search.fields.CmsSearchField;
043import org.opencms.util.CmsStringUtil;
044import org.opencms.util.CmsUUID;
045
046import java.nio.ByteBuffer;
047import java.text.ParseException;
048import java.util.ArrayList;
049import java.util.Collection;
050import java.util.Collections;
051import java.util.Date;
052import java.util.List;
053import java.util.Locale;
054
055import org.apache.commons.logging.Log;
056import org.apache.solr.common.SolrDocument;
057import org.apache.solr.common.SolrException;
058import org.apache.solr.common.SolrInputDocument;
059import org.apache.solr.schema.DatePointField;
060import org.apache.solr.schema.FieldType;
061import org.apache.solr.schema.IndexSchema;
062import org.apache.solr.schema.SchemaField;
063
064/**
065 * A search document implementation for Solr indexes.<p>
066 *
067 * @since 8.5.0
068 */
069public class CmsSolrDocument implements I_CmsSearchDocument {
070
071    /** The log object for this class. */
072    private static final Log LOG = CmsLog.getLog(CmsSolrDocument.class);
073
074    /** The Solr document. */
075    private SolrInputDocument m_doc;
076
077    /** Holds the score for this document. */
078    private float m_score;
079
080    /**
081     * Public constructor to create a encapsulate a Solr document.<p>
082     *
083     * @param doc the Solr document
084     */
085    public CmsSolrDocument(SolrDocument doc) {
086
087        this();
088        m_doc = CmsSearchUtil.toSolrInputDocument(doc);
089    }
090
091    /**
092     * Public constructor to create a encapsulate a Solr document.<p>
093     *
094     * @param doc the Solr document
095     */
096    public CmsSolrDocument(SolrInputDocument doc) {
097
098        this();
099        m_doc = doc;
100    }
101
102    /**
103     * Private constructor.<p>
104     */
105    private CmsSolrDocument() {
106
107    }
108
109    /**
110     * @see org.opencms.search.I_CmsSearchDocument#addCategoryField(java.util.List)
111     */
112    public void addCategoryField(List<CmsCategory> categories) {
113
114        if ((categories != null) && (categories.size() > 0)) {
115            for (CmsCategory category : categories) {
116                m_doc.addField(CmsSearchField.FIELD_CATEGORY, category.getPath());
117            }
118        }
119    }
120
121    /**
122     * @see org.opencms.search.I_CmsSearchDocument#addContentField(byte[])
123     */
124    public void addContentField(byte[] data) {
125
126        m_doc.setField(CmsSearchField.FIELD_CONTENT_BLOB, ByteBuffer.wrap(data));
127    }
128
129    /**
130     * @see org.opencms.search.I_CmsSearchDocument#addContentLocales(java.util.Collection)
131     */
132    public void addContentLocales(Collection<Locale> locales) {
133
134        if ((locales != null) && !locales.isEmpty()) {
135            for (Locale locale : locales) {
136                m_doc.addField(CmsSearchField.FIELD_CONTENT_LOCALES, locale.toString());
137            }
138        }
139    }
140
141    /**
142     * @see org.opencms.search.I_CmsSearchDocument#addDateField(java.lang.String, long, boolean)
143     */
144    public void addDateField(String name, long time, boolean analyzed) {
145
146        String val = CmsSearchUtil.getDateAsIso8601(time);
147        m_doc.addField(name, val);
148        if (analyzed) {
149            m_doc.addField(name + CmsSearchField.FIELD_DATE_LOOKUP_SUFFIX, val);
150        }
151    }
152
153    /**
154     * Adds the given document dependency to this document.<p>
155     *
156     * @param cms the current CmsObject
157     * @param resDeps the dependency
158     */
159    public void addDocumentDependency(CmsObject cms, CmsDocumentDependency resDeps) {
160
161        if (resDeps != null) {
162            m_doc.addField(CmsSearchField.FIELD_DEPENDENCY_TYPE, resDeps.getType());
163            if ((resDeps.getMainDocument() != null) && (resDeps.getType() != null)) {
164                m_doc.addField(
165                    CmsSearchField.FIELD_PREFIX_DEPENDENCY + resDeps.getType().toString(),
166                    resDeps.getMainDocument().toDependencyString(cms));
167            }
168            for (CmsDocumentDependency dep : resDeps.getVariants()) {
169                m_doc.addField(
170                    CmsSearchField.FIELD_PREFIX_DEPENDENCY + dep.getType().toString(),
171                    dep.toDependencyString(cms));
172            }
173            for (CmsDocumentDependency dep : resDeps.getAttachments()) {
174                m_doc.addField(
175                    CmsSearchField.FIELD_PREFIX_DEPENDENCY + dep.getType().toString(),
176                    dep.toDependencyString(cms));
177            }
178        }
179    }
180
181    /**
182     * @see org.opencms.search.I_CmsSearchDocument#addFileSizeField(int)
183     */
184    public void addFileSizeField(int length) {
185
186        if (OpenCms.getSearchManager().getSolrServerConfiguration().getSolrSchema().hasExplicitField(
187            CmsSearchField.FIELD_SIZE)) {
188            m_doc.addField(CmsSearchField.FIELD_SIZE, Integer.valueOf(length));
189        }
190    }
191
192    /**
193     * Adds a multi-valued field.<p>
194     *
195     * @param fieldName the field name to put the values in
196     * @param values the values to put in the field
197     */
198    public void addMultiValuedField(String fieldName, List<String> values) {
199
200        if ((values != null) && (values.size() > 0)) {
201            for (String value : values) {
202                m_doc.addField(fieldName, value);
203            }
204        }
205    }
206
207    /**
208     * @see org.opencms.search.I_CmsSearchDocument#addPathField(java.lang.String)
209     */
210    public void addPathField(String rootPath) {
211
212        String folderName = CmsResource.getFolderPath(rootPath);
213        for (int i = 0; i < folderName.length(); i++) {
214            char c = folderName.charAt(i);
215            if (c == '/') {
216                m_doc.addField(CmsSearchField.FIELD_PARENT_FOLDERS, folderName.substring(0, i + 1));
217            }
218        }
219    }
220
221    /**
222     * @see org.opencms.search.I_CmsSearchDocument#addResourceLocales(java.util.Collection)
223     */
224    public void addResourceLocales(Collection<Locale> locales) {
225
226        if ((locales != null) && !locales.isEmpty()) {
227            for (Locale locale : locales) {
228                m_doc.addField(CmsSearchField.FIELD_RESOURCE_LOCALES, locale.toString());
229            }
230        }
231    }
232
233    /**
234     * @see org.opencms.search.I_CmsSearchDocument#addRootPathField(java.lang.String)
235     */
236    public void addRootPathField(String rootPath) {
237
238        m_doc.addField(CmsSearchField.FIELD_PATH, rootPath);
239    }
240
241    /**
242     * @see org.opencms.search.I_CmsSearchDocument#addSearchField(org.opencms.search.fields.CmsSearchField, java.lang.String)
243     */
244    public void addSearchField(CmsSearchField sfield, String value) {
245
246        CmsSolrField field = (CmsSolrField)sfield;
247        List<String> fieldsToAdd = new ArrayList<String>(Collections.singletonList(field.getName()));
248        if ((field.getCopyFields() != null) && !field.getCopyFields().isEmpty()) {
249            fieldsToAdd.addAll(field.getCopyFields());
250        }
251        IndexSchema schema = OpenCms.getSearchManager().getSolrServerConfiguration().getSolrSchema();
252        for (String fieldName : fieldsToAdd) {
253            try {
254                List<String> splitedValues = new ArrayList<String>();
255                boolean multi = false;
256                boolean overrideValue = false;
257
258                try {
259                    SchemaField f = schema.getField(fieldName);
260                    if ((f != null) && (!field.getName().startsWith(CmsSearchField.FIELD_CONTENT))) {
261                        multi = f.multiValued();
262                        overrideValue = !multi;
263                    }
264                } catch (@SuppressWarnings("unused") SolrException e) {
265                    LOG.warn(Messages.get().getBundle().key(Messages.LOG_SOLR_FIELD_NOT_FOUND_1, field.toString()));
266                }
267                if (multi) {
268                    splitedValues = CmsStringUtil.splitAsList(value.toString(), "\n");
269                } else {
270                    splitedValues.add(value);
271                }
272                for (String val : splitedValues) {
273                    if (CmsStringUtil.isNotEmptyOrWhitespaceOnly(val)) {
274                        try {
275                            FieldType fieldType = schema.getFieldType(fieldName);
276                            if (fieldType instanceof DatePointField) {
277                                //sometime,the val is already Iso8601 formated
278                                if (!val.contains("Z")) {
279                                    val = CmsSearchUtil.getDateAsIso8601(Long.valueOf(val).longValue());
280                                }
281                            }
282                        } catch (SolrException e) {
283                            LOG.debug(e.getMessage(), e);
284                            throw new RuntimeException(e);
285                        }
286                        if (fieldName.endsWith(CmsSearchField.FIELD_EXCERPT)) {
287                            // TODO: make the length and the area configurable
288                            val = CmsStringUtil.trimToSize(val, 1000, 50, "");
289                        }
290                        if (overrideValue) {
291                            m_doc.setField(fieldName, val);
292                        } else {
293                            m_doc.addField(fieldName, val);
294                        }
295                    }
296                }
297            } catch (SolrException e) {
298                LOG.error(e.getMessage(), e);
299            } catch (@SuppressWarnings("unused") RuntimeException e) {
300                // noop
301            }
302        }
303    }
304
305    /**
306     * @see org.opencms.search.I_CmsSearchDocument#addSuffixField(java.lang.String)
307     */
308    public void addSuffixField(String suffix) {
309
310        m_doc.addField(CmsSearchField.FIELD_SUFFIX, suffix);
311    }
312
313    /**
314     * @see org.opencms.search.I_CmsSearchDocument#addTypeField(java.lang.String)
315     */
316    public void addTypeField(String type) {
317
318        m_doc.addField(CmsSearchField.FIELD_TYPE, type);
319    }
320
321    /**
322     * @see org.opencms.search.I_CmsSearchDocument#getContentBlob()
323     */
324    public byte[] getContentBlob() {
325
326        Object o = m_doc.getFieldValue(CmsSearchField.FIELD_CONTENT_BLOB);
327        if (o != null) {
328            if (o instanceof byte[]) {
329                return (byte[])o;
330            }
331            return o.toString().getBytes();
332        }
333        return null;
334    }
335
336    /**
337     * @see org.opencms.search.I_CmsSearchDocument#getDocument()
338     */
339    public Object getDocument() {
340
341        return m_doc;
342    }
343
344    /**
345     * @see org.opencms.search.I_CmsSearchDocument#getFieldNames()
346     */
347    public List<String> getFieldNames() {
348
349        return new ArrayList<String>(m_doc.getFieldNames());
350    }
351
352    /**
353     * @see org.opencms.search.I_CmsSearchDocument#getFieldValueAsDate(java.lang.String)
354     */
355    public Date getFieldValueAsDate(String fieldName) {
356
357        Object o = m_doc.getFieldValue(fieldName);
358        if (o instanceof Date) {
359            return (Date)o;
360        }
361        if (o != null) {
362            try {
363                return CmsSearchUtil.parseDate(o.toString());
364            } catch (ParseException e) {
365                // ignore: not a valid date format
366                LOG.debug(e.getLocalizedMessage(), e);
367            }
368        }
369        return null;
370    }
371
372    /**
373     * @see org.opencms.search.I_CmsSearchDocument#getFieldValueAsString(java.lang.String)
374     */
375    public String getFieldValueAsString(String fieldName) {
376
377        List<String> values = getMultivaluedFieldAsStringList(fieldName);
378        if ((values != null) && !values.isEmpty()) {
379            return CmsStringUtil.listAsString(values, "\n");
380        } else {
381            Object o = m_doc.getFieldValue(fieldName);
382            if (o != null) {
383                return o.toString();
384            }
385        }
386        return null;
387    }
388
389    /**
390     * @see org.opencms.search.I_CmsSearchDocument#getMultivaluedFieldAsStringList(java.lang.String)
391     */
392    public List<String> getMultivaluedFieldAsStringList(String fieldName) {
393
394        List<String> result = new ArrayList<String>();
395        Collection<Object> coll = m_doc.getFieldValues(fieldName);
396        if (coll != null) {
397            for (Object o : coll) {
398                if (o != null) {
399                    result.add(o.toString());
400                }
401            }
402            return result;
403        }
404        return null;
405    }
406
407    /**
408     * @see org.opencms.search.I_CmsSearchDocument#getPath()
409     */
410    public String getPath() {
411
412        return getFieldValueAsString(CmsSearchField.FIELD_PATH);
413    }
414
415    /**
416     * @see org.opencms.search.I_CmsSearchDocument#getScore()
417     */
418    public float getScore() {
419
420        Float score = (Float)getSolrDocument().getFirstValue(CmsSearchField.FIELD_SCORE);
421        if (score != null) {
422            m_score = score.floatValue();
423            return m_score;
424        }
425        return 0F;
426    }
427
428    /**
429     * Returns the Solr document.<p>
430     *
431     * @return the Solr document
432     */
433    public SolrDocument getSolrDocument() {
434
435        return CmsSearchUtil.toSolrDocument(m_doc);
436    }
437
438    /**
439     * @see org.opencms.search.I_CmsSearchDocument#getType()
440     */
441    public String getType() {
442
443        return getFieldValueAsString(CmsSearchField.FIELD_TYPE);
444    }
445
446    /**
447     * Sets the id of this document.<p>
448     *
449     * @param structureId the structure id to use
450     */
451    public void setId(CmsUUID structureId) {
452
453        m_doc.addField(CmsSearchField.FIELD_ID, structureId.toString());
454
455    }
456
457    /**
458     * @see org.opencms.search.I_CmsSearchDocument#setScore(float)
459     */
460    public void setScore(float score) {
461
462        m_score = score;
463    }
464
465    /**
466     * @see java.lang.Object#toString()
467     */
468    @Override
469    public String toString() {
470
471        return getFieldValueAsString(CmsSearchField.FIELD_PATH);
472    }
473}