001/* 002 * (C) Copyright 2006-2012 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 * <a href="mailto:tdelprat@nuxeo.com">Tiry</a> 018 */ 019 020package org.nuxeo.ecm.quota.size; 021 022import static org.nuxeo.ecm.core.api.LifeCycleConstants.DELETE_TRANSITION; 023import static org.nuxeo.ecm.core.api.LifeCycleConstants.UNDELETE_TRANSITION; 024import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.ABOUT_TO_REMOVE; 025import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.ABOUT_TO_REMOVE_VERSION; 026import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDIN; 027import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CHECKEDOUT; 028import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_CREATED_BY_COPY; 029import static org.nuxeo.ecm.core.api.event.DocumentEventTypes.DOCUMENT_MOVED; 030import static org.nuxeo.ecm.quota.size.QuotaAwareDocument.DOCUMENTS_SIZE_STATISTICS_FACET; 031import static org.nuxeo.ecm.quota.size.SizeUpdateEventContext.DOCUMENT_UPDATE_INITIAL_STATISTICS; 032 033import java.io.IOException; 034import java.util.ArrayList; 035import java.util.List; 036 037import org.apache.commons.logging.Log; 038import org.apache.commons.logging.LogFactory; 039import org.nuxeo.ecm.core.api.CoreSession; 040import org.nuxeo.ecm.core.api.DocumentModel; 041import org.nuxeo.ecm.core.api.DocumentRef; 042import org.nuxeo.ecm.core.api.IdRef; 043import org.nuxeo.ecm.core.event.Event; 044import org.nuxeo.ecm.core.event.EventBundle; 045import org.nuxeo.ecm.core.event.EventContext; 046import org.nuxeo.ecm.core.event.PostCommitEventListener; 047import org.nuxeo.ecm.core.event.impl.DocumentEventContext; 048import org.nuxeo.ecm.core.event.impl.ShallowDocumentModel; 049 050/** 051 * Asynchronous listener triggered by the {@link QuotaSyncListenerChecker} when Quota needs to be recomputed 052 * 053 * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a> 054 * @since 5.6 055 */ 056public class QuotaComputerProcessor implements PostCommitEventListener { 057 058 protected static final Log log = LogFactory.getLog(QuotaComputerProcessor.class); 059 060 @Override 061 public void handleEvent(EventBundle eventBundle) { 062 063 if (eventBundle.containsEventName(SizeUpdateEventContext.QUOTA_UPDATE_NEEDED)) { 064 065 for (Event event : eventBundle) { 066 if (event.getName().equals(SizeUpdateEventContext.QUOTA_UPDATE_NEEDED)) { 067 EventContext ctx = event.getContext(); 068 069 if (ctx instanceof DocumentEventContext) { 070 SizeUpdateEventContext quotaCtx = SizeUpdateEventContext.unwrap((DocumentEventContext) ctx); 071 if (quotaCtx != null) { 072 processQuotaComputation(quotaCtx); 073 // double check 074 debugCheck(quotaCtx); 075 } 076 } 077 } 078 } 079 } 080 } 081 082 protected void debugCheck(SizeUpdateEventContext quotaCtx) { 083 String sourceEvent = quotaCtx.getSourceEvent(); 084 CoreSession session = quotaCtx.getCoreSession(); 085 DocumentModel sourceDocument = quotaCtx.getSourceDocument(); 086 087 if (session.exists(sourceDocument.getRef())) { 088 DocumentModel doc = session.getDocument(sourceDocument.getRef()); 089 if (log.isTraceEnabled()) { 090 if (doc.hasFacet(DOCUMENTS_SIZE_STATISTICS_FACET)) { 091 log.trace("Double Check Facet was added OK"); 092 } else { 093 log.trace("No facet !!!!"); 094 } 095 } 096 } else { 097 log.debug("Document " + sourceDocument.getRef() + " no longer exists (" + sourceEvent + ")"); 098 } 099 100 } 101 102 public void processQuotaComputation(SizeUpdateEventContext quotaCtx) { 103 String sourceEvent = quotaCtx.getSourceEvent(); 104 CoreSession session = quotaCtx.getCoreSession(); 105 DocumentModel sourceDocument = quotaCtx.getSourceDocument(); 106 107 if (sourceDocument instanceof ShallowDocumentModel) { 108 if (!(ABOUT_TO_REMOVE.equals(sourceEvent) || ABOUT_TO_REMOVE_VERSION.equals(sourceEvent))) { 109 log.error("Unable to reconnect Document " + sourceDocument.getPathAsString() + " on event " 110 + sourceEvent); 111 return; 112 } 113 } 114 List<DocumentModel> parents = new ArrayList<DocumentModel>(); 115 116 log.debug(sourceEvent + "/ compute Quota on " + sourceDocument.getPathAsString() + " and parents"); 117 118 if (ABOUT_TO_REMOVE.equals(sourceEvent) || ABOUT_TO_REMOVE_VERSION.equals(sourceEvent)) { 119 // use the store list of parentIds 120 for (String id : quotaCtx.getParentUUIds()) { 121 if (session.exists(new IdRef(id))) { 122 parents.add(session.getDocument(new IdRef(id))); 123 } 124 } 125 } else if (DOCUMENT_MOVED.equals(sourceEvent)) { 126 127 if (quotaCtx.getParentUUIds() != null && quotaCtx.getParentUUIds().size() > 0) { 128 // use the store list of parentIds 129 for (String id : quotaCtx.getParentUUIds()) { 130 if (session.exists(new IdRef(id))) { 131 parents.add(session.getDocument(new IdRef(id))); 132 } 133 } 134 } else { 135 parents.addAll(getParents(sourceDocument, session)); 136 } 137 } else { 138 // DELETE_TRANSITION 139 // UNDELETE_TRANSITION 140 // BEFORE_DOC_UPDATE 141 // DOCUMENT_CREATED 142 // DOCUMENT_CREATED_BY_COPY 143 // DOCUMENT_CHECKEDIN 144 // DOCUMENT_CHECKEDOUT 145 146 // several events in the bundle may impact the same doc, 147 // so it may have already been modified 148 sourceDocument = session.getDocument(sourceDocument.getRef()); 149 // TODO fix DocumentModel.refresh() to correctly take into account 150 // dynamic facets, then use this instead: 151 // sourceDocument.refresh(); 152 153 if (sourceDocument.getRef() == null) { 154 log.error("SourceDocument has no ref"); 155 } else { 156 parents.addAll(getParents(sourceDocument, session)); 157 } 158 159 QuotaAware quotaDoc = sourceDocument.getAdapter(QuotaAware.class); 160 // process Quota on target Document 161 if (!DOCUMENT_CREATED_BY_COPY.equals(sourceEvent)) { 162 if (quotaDoc == null) { 163 log.debug(" add Quota Facet on " + sourceDocument.getPathAsString()); 164 quotaDoc = QuotaAwareDocumentFactory.make(sourceDocument, false); 165 166 } else { 167 log.debug(" update Quota Facet on " + sourceDocument.getPathAsString()); 168 } 169 if (DOCUMENT_CHECKEDIN.equals(sourceEvent)) { 170 long versionSize = getVersionSizeFromCtx(quotaCtx); 171 quotaDoc.addVersionsSize(versionSize, false); 172 quotaDoc.addTotalSize(versionSize, true); 173 174 } else if (DOCUMENT_CHECKEDOUT.equals(sourceEvent)) { 175 // All quota computation are now handled on Checkin 176 } else if (DELETE_TRANSITION.equals(sourceEvent) || UNDELETE_TRANSITION.equals(sourceEvent)) { 177 quotaDoc.addTrashSize(quotaCtx.getBlobSize(), true); 178 } else if (DOCUMENT_UPDATE_INITIAL_STATISTICS.equals(sourceEvent)) { 179 quotaDoc.addInnerSize(quotaCtx.getBlobSize(), false); 180 quotaDoc.addTotalSize(quotaCtx.getVersionsSize(), false); 181 quotaDoc.addTrashSize(quotaCtx.getTrashSize(), false); 182 quotaDoc.addVersionsSize(quotaCtx.getVersionsSize(), true); 183 } else { 184 // BEFORE_DOC_UPDATE 185 // DOCUMENT_CREATED 186 quotaDoc.addInnerSize(quotaCtx.getBlobDelta(), true); 187 } 188 } else { 189 // When we copy some doc that are not folderish, we don't 190 // copy the versions so we can't rely on the copied quotaDocInfo 191 if (!sourceDocument.isFolder()) { 192 quotaDoc.resetInfos(false); 193 quotaDoc.setInnerSize(quotaCtx.getBlobSize(), true); 194 } 195 } 196 197 } 198 if (parents.size() > 0) { 199 if (DOCUMENT_CHECKEDIN.equals(sourceEvent)) { 200 long versionSize = getVersionSizeFromCtx(quotaCtx); 201 202 processOnParents(parents, versionSize, 0L, versionSize, true, false, true); 203 } else if (DOCUMENT_CHECKEDOUT.equals(sourceEvent)) { 204 // All quota computation are now handled on Checkin 205 } else if (DELETE_TRANSITION.equals(sourceEvent) || UNDELETE_TRANSITION.equals(sourceEvent)) { 206 processOnParents(parents, 0, quotaCtx.getBlobSize(), false, true); 207 } else if (ABOUT_TO_REMOVE_VERSION.equals(sourceEvent)) { 208 processOnParents(parents, quotaCtx.getBlobDelta(), 0L, quotaCtx.getBlobDelta(), true, false, true); 209 } else if (ABOUT_TO_REMOVE.equals(sourceEvent)) { 210 // when permanently deleting the doc clean the trash if the doc 211 // is in trash and all 212 // archived versions size 213 log.debug("Processing document about to be removed on parents. Total: " + quotaCtx.getBlobDelta() 214 + " , trash size: " + quotaCtx.getTrashSize() + " , versions size: " 215 + quotaCtx.getVersionsSize()); 216 processOnParents(parents, quotaCtx.getBlobDelta(), 217 quotaCtx.getBlobDelta()-quotaCtx.getVersionsSize(), 218 quotaCtx.getVersionsSize(), 219 true, quotaCtx.getProperties().get(SizeUpdateEventContext._UPDATE_TRASH_SIZE) != null 220 && (Boolean) quotaCtx.getProperties().get(SizeUpdateEventContext._UPDATE_TRASH_SIZE), 221 true); 222 } else if (DOCUMENT_MOVED.equals(sourceEvent)) { 223 // update versionsSize on source parents since all archived 224 // versions 225 // are also moved 226 processOnParents(parents, quotaCtx.getBlobDelta(), 0L, quotaCtx.getVersionsSize(), true, false, true); 227 } else if (DOCUMENT_UPDATE_INITIAL_STATISTICS.equals(sourceEvent)) { 228 QuotaAware quotaDoc = sourceDocument.getAdapter(QuotaAware.class); 229 if (quotaDoc.getInnerSize() > 0) { 230 processOnParents(parents, quotaCtx.getBlobSize() + quotaCtx.getVersionsSize(), 231 quotaCtx.getBlobSize(), 232 quotaCtx.getVersionsSize(), true, 233 quotaCtx.getProperties().get(SizeUpdateEventContext._UPDATE_TRASH_SIZE) != null 234 && (Boolean) quotaCtx.getProperties().get(SizeUpdateEventContext._UPDATE_TRASH_SIZE), 235 true); 236 } else { 237 log.debug("No inner size, parents not updated"); 238 } 239 } else if (DOCUMENT_CREATED_BY_COPY.equals(sourceEvent)) { 240 processOnParents(parents, quotaCtx.getBlobSize()); 241 } else { 242 processOnParents(parents, quotaCtx.getBlobDelta()); 243 } 244 } 245 } 246 247 /** 248 * @param quotaCtx 249 * @return 250 */ 251 private long getVersionSizeFromCtx(SizeUpdateEventContext quotaCtx) { 252 return quotaCtx.getBlobSize(); 253 } 254 255 protected void processOnParents(List<DocumentModel> parents, long delta) 256 { 257 processOnParents(parents, delta, 0L, 0L, true, false, false); 258 } 259 260 protected void processOnParents(List<DocumentModel> parents, long delta, long trash, boolean total, boolean trashOp) 261 { 262 processOnParents(parents, delta, trash, 0L, total, trashOp, false); 263 } 264 265 protected void processOnParents(List<DocumentModel> parents, long deltaTotal, long trashSize, long deltaVersions, 266 boolean total, boolean trashOp, boolean versionsOp) { 267 for (DocumentModel parent : parents) { 268 // process Quota on target Document 269 QuotaAware quotaDoc = parent.getAdapter(QuotaAware.class); 270 boolean toSave = false; 271 if (quotaDoc == null) { 272 log.debug(" add Quota Facet on parent " + parent.getPathAsString()); 273 quotaDoc = QuotaAwareDocumentFactory.make(parent, false); 274 toSave = true; 275 } else { 276 if (log.isDebugEnabled()) { 277 log.debug(" update Quota Facet on parent " + parent.getPathAsString() + " (" + quotaDoc.getQuotaInfo() + ")"); 278 } 279 } 280 if (total) { 281 quotaDoc.addTotalSize(deltaTotal, false); 282 toSave = true; 283 } 284 if (trashOp) { 285 quotaDoc.addTrashSize(trashSize, false); 286 toSave = true; 287 } 288 if (versionsOp) { 289 quotaDoc.addVersionsSize(deltaVersions, false); 290 toSave = true; 291 } 292 if (toSave) { 293 quotaDoc.save(true); 294 } 295 try { 296 quotaDoc.invalidateTotalSizeCache(); 297 } catch (IOException e) { 298 log.error(e.getMessage() + ": unable to invalidate cache " + QuotaAware.QUOTA_TOTALSIZE_CACHE_NAME + " for " + quotaDoc.getDoc().getId()); 299 } 300 if (log.isDebugEnabled()) { 301 log.debug(" ==> " + parent.getPathAsString() + " (" + quotaDoc.getQuotaInfo() + ")"); 302 } 303 } 304 } 305 306 protected List<DocumentModel> getParents(DocumentModel sourceDocument, CoreSession session) { 307 List<DocumentModel> parents = new ArrayList<DocumentModel>(); 308 // use getParentDocumentRefs instead of getParentDocuments , beacuse 309 // getParentDocuments doesn't fetch the root document 310 DocumentRef[] parentRefs = session.getParentDocumentRefs(sourceDocument.getRef()); 311 for (DocumentRef documentRef : parentRefs) { 312 parents.add(session.getDocument(documentRef)); 313 } 314 return parents; 315 } 316}