001/*
002 * (C) Copyright 2014-2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Thierry Delprat
018 *     Benoit Delbosc
019 */
020package org.nuxeo.elasticsearch.commands;
021
022import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.BEFORE_DOC_UPDATE;
023import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.BINARYTEXT_UPDATED;
024import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDIN;
025import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDOUT;
026import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHILDREN_ORDER_CHANGED;
027import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED;
028import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED_BY_COPY;
029import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_IMPORTED;
030import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_MOVED;
031import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_PROXY_UPDATED;
032import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_REMOVED;
033import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_RESTORED;
034import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_SECURITY_UPDATED;
035import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_TAG_UPDATED;
036
037import java.util.Map;
038
039import org.apache.commons.logging.Log;
040import org.apache.commons.logging.LogFactory;
041import org.nuxeo.ecm.core.api.AbstractSession;
042import org.nuxeo.ecm.core.api.CoreSession;
043import org.nuxeo.ecm.core.api.DocumentModel;
044import org.nuxeo.ecm.core.api.DocumentModelList;
045import org.nuxeo.ecm.core.api.IdRef;
046import org.nuxeo.ecm.core.api.LifeCycleConstants;
047import org.nuxeo.ecm.core.api.security.SecurityConstants;
048import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
049import org.nuxeo.elasticsearch.ElasticSearchConstants;
050import org.nuxeo.elasticsearch.commands.IndexingCommand.Type;
051import org.nuxeo.runtime.api.Framework;
052
053/**
054 * Contains logic to stack ElasticSearch commands depending on Document events This class is mainly here to make testing
055 * easier
056 */
057public abstract class IndexingCommandsStacker {
058
059    protected static final Log log = LogFactory.getLog(IndexingCommandsStacker.class);
060
061    protected abstract Map<String, IndexingCommands> getAllCommands();
062
063    protected abstract boolean isSyncIndexingByDefault();
064
065    protected IndexingCommands getCommands(DocumentModel doc) {
066        return getAllCommands().get(getDocKey(doc));
067    }
068
069    public void stackCommand(DocumentEventContext docCtx, String eventId) {
070        DocumentModel doc = docCtx.getSourceDocument();
071        if (doc == null) {
072            return;
073        }
074        if ("/".equals(doc.getPathAsString())) {
075            if (log.isDebugEnabled()) {
076                log.debug("Skip indexing command for root document");
077            }
078            if (eventId.equals(DOCUMENT_SECURITY_UPDATED)) {
079                if (log.isDebugEnabled()) {
080                    log.debug("Indexing root document children to update their permissions");
081                }
082                DocumentModelList children = doc.getCoreSession().getChildren(doc.getRef());
083                children.forEach(child -> stackCommand(child, docCtx, eventId));
084            }
085        } else {
086            stackCommand(doc, docCtx, eventId);
087        }
088    }
089
090    protected void stackCommand(DocumentModel doc, DocumentEventContext docCtx, String eventId) {
091        Boolean block = (Boolean) docCtx.getProperty(ElasticSearchConstants.DISABLE_AUTO_INDEXING);
092        if (block != null && block) {
093            if (log.isDebugEnabled()) {
094                log.debug("Indexing is disable, skip indexing command for doc " + doc);
095            }
096            return;
097        }
098        boolean sync = isSynchronous(docCtx, doc);
099        stackCommand(doc, eventId, sync);
100    }
101
102    protected boolean isSynchronous(DocumentEventContext docCtx, DocumentModel doc) {
103        // 1. look at event context
104        Boolean sync = (Boolean) docCtx.getProperty(ElasticSearchConstants.ES_SYNC_INDEXING_FLAG);
105        if (sync != null) {
106            return sync;
107        }
108        // 2. look at document context
109        sync = (Boolean) doc.getContextData(ElasticSearchConstants.ES_SYNC_INDEXING_FLAG);
110        if (sync != null) {
111            return sync;
112        }
113        // 3. get the default
114        sync = isSyncIndexingByDefault();
115        return sync;
116    }
117
118    protected void stackCommand(DocumentModel doc, String eventId, boolean sync) {
119        IndexingCommands cmds = getOrCreateCommands(doc);
120        Type type;
121        boolean recurse = false;
122        switch (eventId) {
123        case DOCUMENT_CREATED:
124        case DOCUMENT_IMPORTED:
125            type = Type.INSERT;
126            break;
127        case DOCUMENT_CREATED_BY_COPY:
128            type = Type.INSERT;
129            recurse = isFolderish(doc);
130            break;
131        case BEFORE_DOC_UPDATE:
132        case DOCUMENT_CHECKEDOUT:
133        case BINARYTEXT_UPDATED:
134        case DOCUMENT_TAG_UPDATED:
135        case DOCUMENT_PROXY_UPDATED:
136        case LifeCycleConstants.TRANSITION_EVENT:
137        case DOCUMENT_RESTORED:
138            if (doc.isProxy() && !doc.isImmutable()) {
139                stackCommand(doc.getCoreSession().getDocument(new IdRef(doc.getSourceId())), BEFORE_DOC_UPDATE, false);
140            }
141            type = Type.UPDATE;
142            break;
143        case DOCUMENT_CHECKEDIN:
144            if (indexIsLatestVersion()) {
145                CoreSession session = doc.getCoreSession();
146                if (session != null) {
147                    // The previous doc version with isLastestVersion and isLatestMajorVersion need to be updated
148                    // Here we have no way to get this exact doc version so we reindex all versions
149                    for (DocumentModel version : doc.getCoreSession().getVersions(doc.getRef())) {
150                        stackCommand(version, BEFORE_DOC_UPDATE, false);
151                    }
152                }
153            }
154            type = Type.UPDATE;
155            break;
156        case DOCUMENT_MOVED:
157            type = Type.UPDATE;
158            recurse = isFolderish(doc);
159            break;
160        case DOCUMENT_REMOVED:
161            type = Type.DELETE;
162            recurse = isFolderish(doc);
163            break;
164        case DOCUMENT_SECURITY_UPDATED:
165            type = Type.UPDATE_SECURITY;
166            recurse = isFolderish(doc);
167            break;
168        case DOCUMENT_CHILDREN_ORDER_CHANGED:
169            type = Type.UPDATE_DIRECT_CHILDREN;
170            recurse = true;
171            break;
172        default:
173            return;
174        }
175        if (sync && recurse) {
176            // split into 2 commands one sync and an async recurse
177            cmds.add(type, true, false);
178            cmds.add(type, false, true);
179        } else {
180            cmds.add(type, sync, recurse);
181        }
182    }
183
184    private boolean indexIsLatestVersion() {
185        return !Framework.isBooleanPropertyTrue(AbstractSession.DISABLED_ISLATESTVERSION_PROPERTY);
186    }
187
188    private boolean isFolderish(DocumentModel doc) {
189        return doc.isFolder() && !doc.isVersion();
190    }
191
192    protected IndexingCommands getOrCreateCommands(DocumentModel doc) {
193        IndexingCommands cmds = getCommands(doc);
194        if (cmds == null) {
195            cmds = new IndexingCommands(doc);
196            getAllCommands().put(getDocKey(doc), cmds);
197        }
198        return cmds;
199    }
200
201    protected String getDocKey(DocumentModel doc) {
202        // Don't merge commands with different session, so we work only on attached doc
203        return doc.getId() + "#" + doc.getSessionId();
204    }
205
206}