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}