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; 036import static org.nuxeo.ecm.core.api.trash.TrashService.DOCUMENT_TRASHED; 037import static org.nuxeo.ecm.core.api.trash.TrashService.DOCUMENT_UNTRASHED; 038 039import java.util.Map; 040 041import org.apache.commons.logging.Log; 042import org.apache.commons.logging.LogFactory; 043import org.nuxeo.ecm.core.api.AbstractSession; 044import org.nuxeo.ecm.core.api.CoreSession; 045import org.nuxeo.ecm.core.api.DocumentModel; 046import org.nuxeo.ecm.core.api.DocumentModelList; 047import org.nuxeo.ecm.core.api.IdRef; 048import org.nuxeo.ecm.core.api.LifeCycleConstants; 049import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 050import org.nuxeo.elasticsearch.ElasticSearchConstants; 051import org.nuxeo.elasticsearch.commands.IndexingCommand.Type; 052import org.nuxeo.runtime.api.Framework; 053 054/** 055 * Contains logic to stack ElasticSearch commands depending on Document events This class is mainly here to make testing 056 * easier 057 */ 058public abstract class IndexingCommandsStacker { 059 060 protected static final Log log = LogFactory.getLog(IndexingCommandsStacker.class); 061 062 protected abstract Map<String, IndexingCommands> getAllCommands(); 063 064 protected abstract boolean isSyncIndexingByDefault(); 065 066 protected IndexingCommands getCommands(DocumentModel doc) { 067 return getAllCommands().get(getDocKey(doc)); 068 } 069 070 public void stackCommand(DocumentEventContext docCtx, String eventId) { 071 DocumentModel doc = docCtx.getSourceDocument(); 072 if (doc == null) { 073 return; 074 } 075 if ("/".equals(doc.getPathAsString())) { 076 if (log.isDebugEnabled()) { 077 log.debug("Skip indexing command for root document"); 078 } 079 if (eventId.equals(DOCUMENT_SECURITY_UPDATED)) { 080 if (log.isDebugEnabled()) { 081 log.debug("Indexing root document children to update their permissions"); 082 } 083 DocumentModelList children = doc.getCoreSession().getChildren(doc.getRef()); 084 children.forEach(child -> stackCommand(child, docCtx, eventId)); 085 } 086 } else { 087 stackCommand(doc, docCtx, eventId); 088 } 089 } 090 091 protected void stackCommand(DocumentModel doc, DocumentEventContext docCtx, String eventId) { 092 Boolean block = (Boolean) docCtx.getProperty(ElasticSearchConstants.DISABLE_AUTO_INDEXING); 093 if (block != null && block) { 094 if (log.isDebugEnabled()) { 095 log.debug("Indexing is disable, skip indexing command for doc " + doc); 096 } 097 return; 098 } 099 boolean sync = isSynchronous(docCtx, doc); 100 stackCommand(doc, eventId, sync); 101 } 102 103 protected boolean isSynchronous(DocumentEventContext docCtx, DocumentModel doc) { 104 // 1. look at event context 105 Boolean sync = (Boolean) docCtx.getProperty(ElasticSearchConstants.ES_SYNC_INDEXING_FLAG); 106 if (sync != null) { 107 return sync; 108 } 109 // 2. look at document context 110 sync = (Boolean) doc.getContextData(ElasticSearchConstants.ES_SYNC_INDEXING_FLAG); 111 if (sync != null) { 112 return sync; 113 } 114 // 3. get the default 115 sync = isSyncIndexingByDefault(); 116 return sync; 117 } 118 119 protected void stackCommand(DocumentModel doc, String eventId, boolean sync) { 120 IndexingCommands cmds = getOrCreateCommands(doc); 121 Type type; 122 boolean recurse = false; 123 switch (eventId) { 124 case DOCUMENT_CREATED: 125 case DOCUMENT_IMPORTED: 126 type = Type.INSERT; 127 break; 128 case DOCUMENT_CREATED_BY_COPY: 129 type = Type.INSERT; 130 recurse = isFolderish(doc); 131 break; 132 case BEFORE_DOC_UPDATE: 133 case DOCUMENT_CHECKEDOUT: 134 case BINARYTEXT_UPDATED: 135 case DOCUMENT_TAG_UPDATED: 136 case DOCUMENT_PROXY_UPDATED: 137 case LifeCycleConstants.TRANSITION_EVENT: 138 case DOCUMENT_TRASHED: 139 case DOCUMENT_UNTRASHED: 140 case DOCUMENT_RESTORED: 141 if (doc.isProxy() && !doc.isImmutable()) { 142 stackCommand(doc.getCoreSession().getDocument(new IdRef(doc.getSourceId())), BEFORE_DOC_UPDATE, false); 143 } 144 type = Type.UPDATE; 145 break; 146 case DOCUMENT_CHECKEDIN: 147 if (indexIsLatestVersion()) { 148 CoreSession session = doc.getCoreSession(); 149 if (session != null) { 150 // The previous doc version with isLastestVersion and isLatestMajorVersion need to be updated 151 // Here we have no way to get this exact doc version so we reindex all versions 152 for (DocumentModel version : doc.getCoreSession().getVersions(doc.getRef())) { 153 stackCommand(version, BEFORE_DOC_UPDATE, false); 154 } 155 } 156 } 157 type = Type.UPDATE; 158 break; 159 case DOCUMENT_MOVED: 160 type = Type.UPDATE; 161 recurse = isFolderish(doc); 162 break; 163 case DOCUMENT_REMOVED: 164 type = Type.DELETE; 165 recurse = isFolderish(doc); 166 break; 167 case DOCUMENT_SECURITY_UPDATED: 168 type = Type.UPDATE_SECURITY; 169 recurse = isFolderish(doc); 170 break; 171 case DOCUMENT_CHILDREN_ORDER_CHANGED: 172 type = Type.UPDATE_DIRECT_CHILDREN; 173 recurse = true; 174 break; 175 default: 176 return; 177 } 178 if (sync && recurse) { 179 // split into 2 commands one sync and an async recurse 180 cmds.add(type, true, false); 181 cmds.add(type, false, true); 182 } else { 183 cmds.add(type, sync, recurse); 184 } 185 } 186 187 private boolean indexIsLatestVersion() { 188 return !Framework.isBooleanPropertyTrue(AbstractSession.DISABLED_ISLATESTVERSION_PROPERTY); 189 } 190 191 private boolean isFolderish(DocumentModel doc) { 192 return doc.isFolder() && !doc.isVersion(); 193 } 194 195 protected IndexingCommands getOrCreateCommands(DocumentModel doc) { 196 IndexingCommands cmds = getCommands(doc); 197 if (cmds == null) { 198 cmds = new IndexingCommands(doc); 199 getAllCommands().put(getDocKey(doc), cmds); 200 } 201 return cmds; 202 } 203 204 protected String getDocKey(DocumentModel doc) { 205 // Don't merge commands with different session, so we work only on attached doc 206 return doc.getId() + "#" + doc.getSessionId(); 207 } 208 209}