001/*
002 * (C) Copyright 2014 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 */
020
021package org.nuxeo.elasticsearch.commands;
022
023import org.apache.commons.logging.Log;
024import org.apache.commons.logging.LogFactory;
025import org.nuxeo.ecm.core.api.CoreSession;
026import org.nuxeo.ecm.core.api.DocumentModel;
027import org.nuxeo.ecm.core.api.LifeCycleConstants;
028import org.nuxeo.ecm.core.event.impl.DocumentEventContext;
029import org.nuxeo.elasticsearch.ElasticSearchConstants;
030import org.nuxeo.elasticsearch.commands.IndexingCommand.Type;
031import java.util.Map;
032
033import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.BEFORE_DOC_UPDATE;
034import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.BINARYTEXT_UPDATED;
035import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDIN;
036import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDOUT;
037import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHILDREN_ORDER_CHANGED;
038import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED;
039import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED_BY_COPY;
040import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_MOVED;
041import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_PROXY_UPDATED;
042import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_REMOVED;
043import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_SECURITY_UPDATED;
044import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_TAG_UPDATED;
045
046/**
047 * Contains logic to stack ElasticSearch commands depending on Document events This class is mainly here to make testing
048 * easier
049 */
050public abstract class IndexingCommandsStacker {
051
052    protected static final Log log = LogFactory.getLog(IndexingCommandsStacker.class);
053
054    protected abstract Map<String, IndexingCommands> getAllCommands();
055
056    protected abstract boolean isSyncIndexingByDefault();
057
058    protected IndexingCommands getCommands(DocumentModel doc) {
059        return getAllCommands().get(getDocKey(doc));
060    }
061
062    public void stackCommand(DocumentEventContext docCtx, String eventId) {
063        DocumentModel doc = docCtx.getSourceDocument();
064        if (doc == null) {
065            return;
066        }
067        Boolean block = (Boolean) docCtx.getProperty(ElasticSearchConstants.DISABLE_AUTO_INDEXING);
068        if (block != null && block) {
069            if (log.isDebugEnabled()) {
070                log.debug("Indexing is disable, skip indexing command for doc " + doc);
071            }
072            return;
073        }
074        boolean sync = isSynchronous(docCtx, doc);
075        stackCommand(doc, eventId, sync);
076    }
077
078    protected boolean isSynchronous(DocumentEventContext docCtx, DocumentModel doc) {
079        // 1. look at event context
080        Boolean sync = (Boolean) docCtx.getProperty(ElasticSearchConstants.ES_SYNC_INDEXING_FLAG);
081        if (sync != null) {
082            return sync;
083        }
084        // 2. look at document context
085        sync = (Boolean) doc.getContextData().get(ElasticSearchConstants.ES_SYNC_INDEXING_FLAG);
086        if (sync != null) {
087            return sync;
088        }
089        // 3. get the default
090        sync = isSyncIndexingByDefault();
091        return sync;
092    }
093
094    protected void stackCommand(DocumentModel doc, String eventId, boolean sync) {
095        IndexingCommands cmds = getOrCreateCommands(doc);
096        Type type;
097        boolean recurse = false;
098        switch (eventId) {
099            case DOCUMENT_CREATED:
100                type = Type.INSERT;
101                break;
102            case DOCUMENT_CREATED_BY_COPY:
103                type = Type.INSERT;
104                recurse = isFolderish(doc);
105                break;
106            case BEFORE_DOC_UPDATE:
107            case DOCUMENT_CHECKEDOUT:
108            case BINARYTEXT_UPDATED:
109            case DOCUMENT_TAG_UPDATED:
110            case DOCUMENT_PROXY_UPDATED:
111            case LifeCycleConstants.TRANSITION_EVENT:
112                type = Type.UPDATE;
113                break;
114            case DOCUMENT_CHECKEDIN:
115                CoreSession session = doc.getCoreSession();
116                if (session != null) {
117                    // The previous doc version with isLastestVersion and isLatestMajorVersion need to be updated
118                    // Here we have no way to get this exact doc version so we reindex all versions
119                    for (DocumentModel version : doc.getCoreSession().getVersions(doc.getRef())) {
120                        stackCommand(version, BEFORE_DOC_UPDATE, false);
121                    }
122                }
123                type = Type.UPDATE;
124                break;
125            case DOCUMENT_MOVED:
126                type = Type.UPDATE;
127                recurse = isFolderish(doc);
128                break;
129            case DOCUMENT_REMOVED:
130                type = Type.DELETE;
131                recurse = isFolderish(doc);
132                break;
133            case DOCUMENT_SECURITY_UPDATED:
134                type = Type.UPDATE_SECURITY;
135                recurse = isFolderish(doc);
136                break;
137            case DOCUMENT_CHILDREN_ORDER_CHANGED:
138                type = Type.UPDATE_DIRECT_CHILDREN;
139                recurse = true;
140                break;
141            default:
142                return;
143        }
144        if (sync && recurse) {
145            // split into 2 commands one sync and an async recurse
146            cmds.add(type, true, false);
147            cmds.add(type, false, true);
148        } else {
149            cmds.add(type, sync, recurse);
150        }
151    }
152
153    private boolean isFolderish(DocumentModel doc) {
154        return doc.isFolder() && ! doc.isVersion();
155    }
156
157    protected IndexingCommands getOrCreateCommands(DocumentModel doc) {
158        IndexingCommands cmds = getCommands(doc);
159        if (cmds == null) {
160            cmds = new IndexingCommands(doc);
161            getAllCommands().put(getDocKey(doc), cmds);
162        }
163        return cmds;
164    }
165
166    protected String getDocKey(DocumentModel doc) {
167        // Don't merge commands with different session, so we work only on attached doc
168        return doc.getId() + "#" + doc.getSessionId();
169    }
170
171}