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.db.CmsPublishedResource;
035import org.opencms.main.CmsLog;
036import org.opencms.main.OpenCms;
037import org.opencms.search.I_CmsSearchDocument;
038import org.opencms.search.fields.CmsSearchField;
039
040import java.io.IOException;
041import java.util.List;
042
043import org.apache.commons.logging.Log;
044import org.apache.solr.client.solrj.SolrClient;
045import org.apache.solr.client.solrj.SolrServerException;
046import org.apache.solr.common.SolrException;
047import org.apache.solr.common.SolrInputDocument;
048
049/**
050 * Implements the index writer for the Solr server used by OpenCms.<p>
051 *
052 * @since 8.5.0
053 */
054public class CmsSolrIndexWriter implements I_CmsSolrIndexWriter {
055
056    /** The log object for this class. */
057    protected static final Log LOG = CmsLog.getLog(CmsSolrIndexWriter.class);
058
059    /** The time to wait before a commit is sent to the Solr index.  */
060    private int m_commitMs = new Long(
061        OpenCms.getSearchManager().getSolrServerConfiguration().getSolrCommitMs()).intValue();
062
063    /** The Solr index. */
064    private CmsSolrIndex m_index;
065
066    /** The Solr client. */
067    private SolrClient m_server;
068
069    /**
070     * Constructor to create a Solr index writer.<p>
071     *
072     * @param client the client to use
073     */
074    public CmsSolrIndexWriter(SolrClient client) {
075
076        this(client, null);
077    }
078
079    /**
080     * Creates a new index writer based on the provided standard Lucene IndexWriter for the
081     * provided OpenCms search index instance.<p>
082     *
083     * The OpenCms search instance is currently used only for improved logging of the
084     * index operations.<p>
085     *
086     * @param client the standard Lucene IndexWriter to use as delegate
087     * @param index the OpenCms search index instance this writer to supposed to write to
088     */
089    public CmsSolrIndexWriter(SolrClient client, CmsSolrIndex index) {
090
091        m_index = index;
092        m_server = client;
093        if (m_index != null) {
094            LOG.info(
095                Messages.get().getBundle().key(
096                    Messages.LOG_SOLR_WRITER_CREATE_2,
097                    m_index.getName(),
098                    m_index.getPath()));
099        }
100    }
101
102    /**
103     * @see org.opencms.search.I_CmsIndexWriter#close()
104     */
105    public void close() {
106
107        // nothing to do here
108    }
109
110    /**
111     * @see org.opencms.search.I_CmsIndexWriter#commit()
112     */
113    public void commit() throws IOException {
114
115        if ((m_server != null) && (m_index != null)) {
116            try {
117                LOG.info(
118                    Messages.get().getBundle().key(
119                        Messages.LOG_SOLR_WRITER_COMMIT_2,
120                        m_index.getName(),
121                        m_index.getPath()));
122                m_server.commit();
123            } catch (SolrServerException e) {
124                throw new IOException(e.getLocalizedMessage(), e);
125            }
126        }
127    }
128
129    /**
130     * @see org.opencms.search.solr.I_CmsSolrIndexWriter#deleteAllDocuments()
131     */
132    public void deleteAllDocuments() throws IOException {
133
134        if ((m_server != null) && (m_index != null)) {
135            try {
136                LOG.info(
137                    Messages.get().getBundle().key(
138                        Messages.LOG_SOLR_WRITER_DELETE_ALL_2,
139                        m_index.getName(),
140                        m_index.getPath()));
141                m_server.deleteByQuery("*:*", m_commitMs);
142            } catch (SolrServerException e) {
143                throw new IOException(e.getLocalizedMessage(), e);
144            }
145        }
146    }
147
148    /**
149     * @see org.opencms.search.I_CmsIndexWriter#deleteDocument(org.opencms.db.CmsPublishedResource)
150     */
151    public void deleteDocument(CmsPublishedResource resource) throws IOException {
152
153        if ((m_server != null) && (m_index != null)) {
154            try {
155                LOG.info(
156                    Messages.get().getBundle().key(
157                        Messages.LOG_SOLR_WRITER_DOC_DELETE_3,
158                        resource.getRootPath(),
159                        m_index.getName(),
160                        m_index.getPath()));
161                m_server.deleteByQuery("id:" + resource.getStructureId().toString(), m_commitMs);
162            } catch (SolrServerException e) {
163                throw new IOException(e.getLocalizedMessage(), e);
164            } catch (SolrException e) {
165                throw new IOException(e.getLocalizedMessage(), e);
166            }
167        }
168    }
169
170    /**
171     * @see org.opencms.search.I_CmsIndexWriter#optimize()
172     */
173    public void optimize() {
174
175        // optimization is not recommended
176        // should be configured within solrconfig.xml
177    }
178
179    /**
180     * @see org.opencms.search.I_CmsIndexWriter#updateDocument(java.lang.String, org.opencms.search.I_CmsSearchDocument)
181     */
182    public void updateDocument(String rootPath, I_CmsSearchDocument document) throws IOException {
183
184        if ((m_server != null) && (m_index != null)) {
185
186            if (document.getDocument() != null) {
187                try {
188                    m_server.deleteByQuery("path:\"" + rootPath + "\"", m_commitMs);
189                } catch (Exception e1) {
190                    LOG.error(e1.getLocalizedMessage(), e1);
191                }
192                try {
193                    LOG.info(
194                        Messages.get().getBundle().key(
195                            Messages.LOG_SOLR_WRITER_DOC_UPDATE_3,
196                            rootPath,
197                            m_index.getName(),
198                            m_index.getPath()));
199                    addDocumentInstances(document);
200                } catch (SolrServerException e) {
201                    throw new IOException(e.getLocalizedMessage(), e);
202                }
203            }
204        }
205    }
206
207    /**
208     * Adds Solr documents to the index for the {@link I_CmsSearchDocument}.
209     * Documents for serial dates are added for each occurrence once with the date of the respective occurrence.
210     * @param document the document for the indexed resource
211     * @throws SolrServerException thrown if adding the document to the index fails
212     * @throws IOException thrown if adding the document to the index fails
213     */
214    private void addDocumentInstances(I_CmsSearchDocument document) throws SolrServerException, IOException {
215
216        List<String> serialDates = document.getMultivaluedFieldAsStringList(CmsSearchField.FIELD_SERIESDATES);
217        SolrInputDocument inputDoc = (SolrInputDocument)document.getDocument();
218        String id = inputDoc.getFieldValue(CmsSearchField.FIELD_ID).toString();
219        if (null != serialDates) {
220            // NOTE: We can assume the following to arrays have the same length as serialDates.
221            List<String> serialDatesEnd = document.getMultivaluedFieldAsStringList(
222                CmsSearchField.FIELD_SERIESDATES_END);
223            List<String> serialDatesCurrentTill = document.getMultivaluedFieldAsStringList(
224                CmsSearchField.FIELD_SERIESDATES_CURRENT_TILL);
225            for (int i = 0; i < serialDates.size(); i++) {
226                String date = serialDates.get(i);
227                String endDate = serialDatesEnd.get(i);
228                String currentTillDate = serialDatesCurrentTill.get(i);
229                inputDoc.setField(CmsSearchField.FIELD_INSTANCEDATE + CmsSearchField.FIELD_POSTFIX_DATE, date);
230                inputDoc.setField(CmsSearchField.FIELD_INSTANCEDATE_END + CmsSearchField.FIELD_POSTFIX_DATE, endDate);
231                inputDoc.setField(
232                    CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL + CmsSearchField.FIELD_POSTFIX_DATE,
233                    currentTillDate);
234                for (String locale : document.getMultivaluedFieldAsStringList(CmsSearchField.FIELD_CONTENT_LOCALES)) {
235                    inputDoc.setField(
236                        CmsSearchField.FIELD_INSTANCEDATE + "_" + locale + CmsSearchField.FIELD_POSTFIX_DATE,
237                        date);
238                    inputDoc.setField(
239                        CmsSearchField.FIELD_INSTANCEDATE_END + "_" + locale + CmsSearchField.FIELD_POSTFIX_DATE,
240                        endDate);
241                    inputDoc.setField(
242                        CmsSearchField.FIELD_INSTANCEDATE_CURRENT_TILL
243                            + "_"
244                            + locale
245                            + CmsSearchField.FIELD_POSTFIX_DATE,
246                        currentTillDate);
247                }
248                String newId = id + String.format("-%04d", Integer.valueOf(i + 1));
249                inputDoc.setField(CmsSearchField.FIELD_SOLR_ID, newId);
250                //remove fields that should not be part of the index, but were used to transport extra-information on date series
251                inputDoc.removeField(CmsSearchField.FIELD_SERIESDATES_END);
252                inputDoc.removeField(CmsSearchField.FIELD_SERIESDATES_CURRENT_TILL);
253                m_server.add(inputDoc, m_commitMs);
254            }
255        } else {
256            inputDoc.setField(CmsSearchField.FIELD_SOLR_ID, id);
257            m_server.add(inputDoc, m_commitMs);
258        }
259
260    }
261}