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