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