/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco * * Alfresco is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Alfresco is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ package org.alfresco.cmis.changelog; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.alfresco.cmis.CMISBaseObjectTypeIds; import org.alfresco.cmis.CMISCapabilityChanges; import org.alfresco.cmis.CMISChangeEvent; import org.alfresco.cmis.CMISChangeLog; import org.alfresco.cmis.CMISChangeLogService; import org.alfresco.cmis.CMISChangeType; import org.alfresco.cmis.CMISInvalidArgumentException; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.opencmis.CMISChangeLogDataExtractor; import org.alfresco.service.cmr.audit.AuditQueryParameters; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; import org.alfresco.service.cmr.repository.NodeRef; /** * ChangeLog Service Implementation * * @author Dmitry Velichkevich */ public class CMISChangeLogServiceImpl implements CMISChangeLogService { private static final String PATH_DELIMITER = "/"; private static final int DEFAULT_RETURN_SIZE = 100; private AuditService auditService; private String cmisAuditApplicationName; private List changesOnTypeCapability; /** * Set the AuditService. * * @param auditService AuditService */ public void setAuditService(AuditService auditService) { this.auditService = auditService; } /** * Set the Audit Application Name. * * @param cmisAuditApplicationName Audit Application Name */ public void setCmisAuditApplicationName(String cmisAuditApplicationName) { this.cmisAuditApplicationName = cmisAuditApplicationName; } /** * Set the ChangesOnTypeCapability. * * @param changesOnTypeCapability list of CMISBaseObjectTypeIds */ public void setChangesOnTypeCapability(List changesOnTypeCapability) { this.changesOnTypeCapability = changesOnTypeCapability; } /** * @see org.alfresco.cmis.CMISChangeLogService#getCapability() */ public CMISCapabilityChanges getCapability() { return (auditService.isAuditEnabled(cmisAuditApplicationName, PATH_DELIMITER + cmisAuditApplicationName)) ? (CMISCapabilityChanges.OBJECTIDSONLY) : (CMISCapabilityChanges.NONE); } /** * @throws CMISInvalidArgumentException * @see org.alfresco.cmis.CMISChangeLogService#getChangeLogEvents(java.lang.String, java.lang.Integer) */ public CMISChangeLog getChangeLogEvents(String changeLogToken, Integer maxItems) throws CMISInvalidArgumentException { if (!auditService.isAuditEnabled(cmisAuditApplicationName, ("/" + cmisAuditApplicationName))) { throw new AlfrescoRuntimeException("Auditing for " + cmisAuditApplicationName + " is disabled!"); } CMISChangeLogImpl result = new CMISChangeLogImpl(); final List changeEvents = result.getChangeEvents(); EntryIdCallback changeLogCollectingCallback = new EntryIdCallback(true) { @Override public boolean handleAuditEntry(Long entryId, String user, long time, Map values) { List changeLogEvents = convertValuesMapToChangeLogEvents(values, time); changeEvents.addAll(changeLogEvents); return super.handleAuditEntry(entryId, user, time, values); } }; Long from; try { from = changeLogToken != null ? Long.parseLong(changeLogToken) : null; } catch (NumberFormatException e) { throw new CMISInvalidArgumentException("Invalid change log token " + changeLogToken); } AuditQueryParameters params = new AuditQueryParameters(); params.setApplicationName(cmisAuditApplicationName); params.setForward(true); params.setFromId(from); // Query one past the last item, so that we know what the next ID is int maxAmount = ((null == maxItems) || (0 == maxItems)) ? (0) : (maxItems + 1); auditService.auditQuery(changeLogCollectingCallback, params, maxAmount); if ((0 != maxAmount) && (changeEvents.size() > maxItems)) { changeEvents.remove(changeEvents.size() - 1); result.setNextChangeToken(changeLogCollectingCallback.getEntryId().toString()); result.setHasMoreItems(true); } return result; } /** * @see org.alfresco.cmis.CMISChangeLogService#getChangesIncomplete() */ public boolean getChangesIncomplete() { return true; } /** * @see org.alfresco.cmis.CMISChangeLogService#getLastChangeLogToken() */ public String getLastChangeLogToken() { EntryIdCallback auditQueryCallback = new EntryIdCallback(false); AuditQueryParameters params = new AuditQueryParameters(); params.setApplicationName(cmisAuditApplicationName); params.setForward(false); auditService.auditQuery(auditQueryCallback, params, 1); return auditQueryCallback.getEntryId(); } /** * @see org.alfresco.cmis.CMISChangeLogService#getPreviousPageChangeLogToken(java.lang.String, java.lang.Integer) */ public String getPreviousPageChangeLogToken(String currentPageToken, Integer maxItems) { if (currentPageToken == null) { return null; } maxItems = maxItems == null ? DEFAULT_RETURN_SIZE : maxItems; EntryIdCallback auditQueryCallback = new EntryIdCallback(false); AuditQueryParameters params = new AuditQueryParameters(); params.setApplicationName(cmisAuditApplicationName); params.setForward(false); params.setToId(Long.parseLong(currentPageToken)); auditService.auditQuery(auditQueryCallback, params, maxItems); return auditQueryCallback.getEntryId(); } /** * @see org.alfresco.cmis.CMISChangeLogService#getLastPageChangeLogToken(java.lang.String, java.lang.Integer) */ public String getLastPageChangeLogToken(String currentPageToken, Integer maxItems) { maxItems = maxItems == null ? DEFAULT_RETURN_SIZE : maxItems; EntryIdCallback auditQueryCallback = new PageStartEntryIdCallback(maxItems); AuditQueryParameters params = new AuditQueryParameters(); params.setApplicationName(cmisAuditApplicationName); if (currentPageToken != null) { params.setFromId(Long.parseLong(currentPageToken)); } auditService.auditQuery(auditQueryCallback, params, -1); return auditQueryCallback.getEntryId(); } /** * @see org.alfresco.cmis.CMISChangeLogService#getChangesOnTypeCapability() */ public List getChangesOnTypeCapability() { if (null == changesOnTypeCapability) { changesOnTypeCapability = new LinkedList(); } return changesOnTypeCapability; } /** * Converts audit values map to list of CMISChangeEvents. * * @param values audit values map * @param time audit event time * @return list of CMISChangeEvent */ @SuppressWarnings("unchecked") private List convertValuesMapToChangeLogEvents(Map values, long time) { List result = new ArrayList(); if (values != null && values.size() > 0) { for (Entry entry : values.entrySet()) { if (entry.getKey() != null && entry.getValue() != null) { String path = entry.getKey(); CMISChangeType changeType = getCMISChangeType(path); if (changeType != null && entry.getValue() instanceof Map) { Map valueMap = (Map)entry.getValue(); result.add(new CMISChangeEventImpl(changeType, new Date(time), (NodeRef) valueMap .get(CMISChangeLogDataExtractor.KEY_NODE_REF), (String) valueMap .get(CMISChangeLogDataExtractor.KEY_OBJECT_ID))); } } } } return result; } /** * Gets CMISChangeType by audit path. * * @param auditPath audit path * @return CMISChangeType */ private CMISChangeType getCMISChangeType(String auditPath) { CMISChangeType result = null; if (auditPath != null) { if (auditPath.startsWith(PATH_DELIMITER)) { auditPath = auditPath.substring(PATH_DELIMITER.length()); } if (auditPath.startsWith(cmisAuditApplicationName)) { auditPath = auditPath.substring(cmisAuditApplicationName.length()); } if (auditPath.startsWith(PATH_DELIMITER)) { auditPath = auditPath.substring(PATH_DELIMITER.length()); } auditPath = auditPath.toLowerCase(); for (CMISChangeType changeType : CMISChangeType.values()) { if (auditPath.startsWith(changeType.getLabel())) { result = changeType; break; } } } return result; } private class EntryIdCallback implements AuditQueryCallback { private final boolean valuesRequired; private Long entryId; public EntryIdCallback(boolean valuesRequired) { this.valuesRequired = valuesRequired; } public String getEntryId() { return entryId == null ? null : entryId.toString(); } public boolean valuesRequired() { return this.valuesRequired; } public final boolean handleAuditEntry(Long entryId, String applicationName, String user, long time, Map values) { if (applicationName.equals(CMISChangeLogServiceImpl.this.cmisAuditApplicationName)) { return handleAuditEntry(entryId, user, time, values); } return true; } public boolean handleAuditEntry(Long entryId, String user, long time, Map values) { this.entryId = entryId; return true; } public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) { throw new AlfrescoRuntimeException(errorMsg, error); } }; private class PageStartEntryIdCallback extends EntryIdCallback { private final int pageSize; private int indexWithinPage; public PageStartEntryIdCallback(int pageSize) { super(false); this.pageSize = pageSize; this.indexWithinPage = -1; } @Override public boolean handleAuditEntry(Long entryId, String user, long time, Map values) { if (++this.indexWithinPage == this.pageSize) { this.indexWithinPage = 0; return super.handleAuditEntry(entryId, user, time, values); } return true; } } }