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