diff --git a/source/java/org/alfresco/rest/api/Audit.java b/source/java/org/alfresco/rest/api/Audit.java index 791b7d2543..b95091f444 100644 --- a/source/java/org/alfresco/rest/api/Audit.java +++ b/source/java/org/alfresco/rest/api/Audit.java @@ -26,10 +26,13 @@ package org.alfresco.rest.api; import org.alfresco.rest.api.model.AuditApp; +import org.alfresco.rest.api.model.AuditEntry; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Parameters; +import com.sun.star.auth.InvalidArgumentException; + /** * Handles audit (applications & entries) * @@ -37,6 +40,13 @@ import org.alfresco.rest.framework.resource.parameters.Parameters; */ public interface Audit { + String PARAM_ID = "id"; + String PARAM_AUDIT_APP_ID = "auditApplicationId"; + String VALUES_VALUE = "valuesValue"; + String VALUES_KEY = "valuesKey"; + String CREATED_BY_USER = "createdByUser"; + String CREATED_AT = "createdAt"; + /** * Gets a single audit application by id * @@ -80,9 +90,9 @@ public interface Audit * if null then across all audit apps * @param parameters * @return Collection of audit entries + * @throws InvalidArgumentException */ - // CollectionWithPagingInfo listAuditEntries(String auditAppId, - // Parameters parameters); + CollectionWithPagingInfo listAuditEntries(String auditAppId, Parameters parameters); /** * Deletes a set of audit entries diff --git a/source/java/org/alfresco/rest/api/audit/AuditApplicationsAuditEntriesRelation.java b/source/java/org/alfresco/rest/api/audit/AuditApplicationsAuditEntriesRelation.java new file mode 100644 index 0000000000..a2d6b24020 --- /dev/null +++ b/source/java/org/alfresco/rest/api/audit/AuditApplicationsAuditEntriesRelation.java @@ -0,0 +1,63 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.rest.api.audit; + +import org.alfresco.rest.api.Audit; +import org.alfresco.rest.api.model.AuditEntry; +import org.alfresco.rest.framework.WebApiDescription; +import org.alfresco.rest.framework.resource.RelationshipResource; +import org.alfresco.rest.framework.resource.actions.interfaces.RelationshipResourceAction; +import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; +import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.util.ParameterCheck; +import org.springframework.beans.factory.InitializingBean; + +@RelationshipResource(name = "audit-entries", entityResource = AuditApplicationsEntityResource.class, title = "Audit Application Entries") +public class AuditApplicationsAuditEntriesRelation implements RelationshipResourceAction.Read, InitializingBean +{ + + private Audit audit; + + public void setAudit(Audit audit) + { + this.audit = audit; + } + + @Override + public void afterPropertiesSet() + { + ParameterCheck.mandatory("audit", this.audit); + } + + @WebApiDescription(title = "Returns audit entries for audit app id") + @Override + public CollectionWithPagingInfo readAll(String auditAppId, Parameters parameters) + { + + return audit.listAuditEntries(auditAppId, parameters); + } + +} diff --git a/source/java/org/alfresco/rest/api/impl/AuditImpl.java b/source/java/org/alfresco/rest/api/impl/AuditImpl.java index 371fa938b1..4379c8f12a 100644 --- a/source/java/org/alfresco/rest/api/impl/AuditImpl.java +++ b/source/java/org/alfresco/rest/api/impl/AuditImpl.java @@ -25,23 +25,40 @@ */ package org.alfresco.rest.api.impl; +import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.rest.antlr.WhereClauseParser; import org.alfresco.rest.api.Audit; import org.alfresco.rest.api.model.AuditApp; +import org.alfresco.rest.api.model.AuditEntry; +import org.alfresco.rest.api.model.UserInfo; import org.alfresco.rest.framework.core.exceptions.DisabledServiceException; +import org.alfresco.rest.framework.core.exceptions.InvalidArgumentException; import org.alfresco.rest.framework.core.exceptions.EntityNotFoundException; import org.alfresco.rest.framework.resource.parameters.CollectionWithPagingInfo; import org.alfresco.rest.framework.resource.parameters.Paging; import org.alfresco.rest.framework.resource.parameters.Parameters; +import org.alfresco.rest.framework.resource.parameters.SortColumn; +import org.alfresco.rest.framework.resource.parameters.where.Query; +import org.alfresco.rest.framework.resource.parameters.where.QueryHelper; +import org.alfresco.rest.workflow.api.impl.MapBasedQueryWalker; +import org.alfresco.service.cmr.audit.AuditQueryParameters; import org.alfresco.service.cmr.audit.AuditService; import org.alfresco.service.cmr.audit.AuditService.AuditApplication; +import org.alfresco.service.cmr.audit.AuditService.AuditQueryCallback; +import org.alfresco.util.Pair; /** * Handles audit (applications & entries) @@ -50,7 +67,23 @@ import org.alfresco.service.cmr.audit.AuditService.AuditApplication; */ public class AuditImpl implements Audit { + private final static String DISABLED = "Audit is disabled system-wide"; + private final static int MAX_ITEMS_AUDIT_ENTRIES = 100; + + // list of equals filter's auditEntry (via where clause) + private final static Set LIST_AUDIT_ENTRY_EQUALS_QUERY_PROPERTIES = new HashSet<>( + Arrays.asList(new String[] { CREATED_BY_USER, VALUES_KEY, VALUES_VALUE })); + + // map of sort parameters for the moment one createdAt + private final static Map SORT_PARAMS_TO_NAMES; + + static + { + Map aMap = new HashMap<>(1); + aMap.put(CREATED_AT, CREATED_AT); + SORT_PARAMS_TO_NAMES = Collections.unmodifiableMap(aMap); + } private AuditService auditService; @@ -140,6 +173,197 @@ public class AuditImpl implements Audit return CollectionWithPagingInfo.asPaged(paging, auditApps, hasMoreItems, totalItems); } + + @Override + public CollectionWithPagingInfo listAuditEntries(String auditAppId, Parameters parameters) + { + checkEnabled(); + + // adding orderBy property + Pair sortProp = getAuditEntrySortProp(parameters); + Boolean forward = true; + if ((sortProp != null) && (sortProp.getFirst().equals(CREATED_AT))) + forward = sortProp.getSecond(); + + // Parse where clause properties. + List entriesAfterWhereQuery = new ArrayList(); + Query q = parameters.getQuery(); + if (q != null) + { + // filtering via "where" clause + AuditEntryQueryWalker propertyWalker = new AuditEntryQueryWalker(); + QueryHelper.walk(q, propertyWalker); + entriesAfterWhereQuery = getQueryResultAuditEntries(auditAppId, propertyWalker, MAX_ITEMS_AUDIT_ENTRIES, forward); + } + + // paging + Paging paging = parameters.getPaging(); + + int skipCount = paging.getSkipCount(); + int maxItems = paging.getMaxItems(); + int max = skipCount + maxItems; // to detect hasMoreItems + int totalItems = entriesAfterWhereQuery.size(); + + if (skipCount >= totalItems) + { + List empty = Collections.emptyList(); + return CollectionWithPagingInfo.asPaged(paging, empty, false, totalItems); + } + else + { + int end = Math.min(max, totalItems); + boolean hasMoreItems = totalItems > end; + + entriesAfterWhereQuery = entriesAfterWhereQuery.subList(skipCount, end); + return CollectionWithPagingInfo.asPaged(paging, entriesAfterWhereQuery, hasMoreItems, totalItems); + } + } + + /** + * + * @param parameters + * @return + * @throws InvalidArgumentException + */ + private Pair getAuditEntrySortProp(Parameters parameters) + { + Pair sortProp = null; + List sortCols = parameters.getSorting(); + + if ((sortCols != null) && (sortCols.size() > 0)) + { + if (sortCols.size() > 1) + { + throw new InvalidArgumentException("Multiple sort fields not allowed."); + } + + SortColumn sortCol = sortCols.get(0); + + String sortPropName = SORT_PARAMS_TO_NAMES.get(sortCol.column); + if (sortPropName == null) + { + throw new InvalidArgumentException("Invalid sort field: " + sortCol.column); + } + + sortProp = new Pair<>(sortPropName, (sortCol.asc ? Boolean.TRUE : Boolean.FALSE)); + } + return sortProp; + } + + /** + * + * @author anechifor + * + */ + private static class AuditEntryQueryWalker extends MapBasedQueryWalker + { + private Long fromTime; + + private Long toTime; + + public AuditEntryQueryWalker() + { + super(LIST_AUDIT_ENTRY_EQUALS_QUERY_PROPERTIES, null); + } + + @Override + public void and() + { + // allow AND, e.g. isRoot=true AND zones in ('BLAH') + } + + @Override + public void between(String propertyName, String firstValue, String secondValue, boolean negated) + { + if (propertyName.equals(CREATED_AT)) + { + fromTime = new Long(firstValue); + toTime = new Long(secondValue); + } + } + + public Long getFromTime() + { + return fromTime; + } + + public Long getToTime() + { + return toTime; + } + + public String getCreatedByUser() + { + return getProperty(CREATED_BY_USER, WhereClauseParser.EQUALS, String.class); + } + + public String getValuesKey() + { + return getProperty(VALUES_KEY, WhereClauseParser.EQUALS, String.class); + } + + public String getValuesValue() + { + return getProperty(VALUES_VALUE, WhereClauseParser.EQUALS, String.class); + } + } + + /** + * + * @param auditAppId + * @param propertyWalker + * @param maxItem + * @param forward + * @return + */ + public List getQueryResultAuditEntries(String auditAppId, AuditEntryQueryWalker propertyWalker, int maxItem, Boolean forward) + { + + final List results = new ArrayList(); + + AuditApplication auditApplication = findAuditAppById(auditAppId); + if (auditApplication != null) + { + String auditApplicationName = auditApplication.getName(); + + // Execute the query + AuditQueryParameters params = new AuditQueryParameters(); + // used to orderBY by field createdAt + params.setForward(forward); + params.setApplicationName(auditApplicationName); + params.setUser(propertyWalker.getCreatedByUser()); + params.setFromTime(propertyWalker.getFromTime()); + params.setToTime(propertyWalker.getToTime()); + params.addSearchKey(propertyWalker.getValuesKey(), propertyWalker.getValuesValue()); + + // create the callback for auditQuery method + final AuditQueryCallback callback = new AuditQueryCallback() + { + public boolean valuesRequired() + { + return true; + } + + public boolean handleAuditEntryError(Long entryId, String errorMsg, Throwable error) + { + throw new AlfrescoRuntimeException("Failed to retrieve audit data.", error); + } + + public boolean handleAuditEntry(Long entryId, String applicationName, String user, long time, Map values) + { + AuditEntry auditEntry = new AuditEntry(entryId, new Long(auditAppId), new UserInfo(null, user, null), new Date(time), values); + results.add(auditEntry); + return true; + } + }; + + auditService.auditQuery(callback, params, maxItem); + } + + return results; + } + + @Override public AuditApp update(String auditAppId, AuditApp auditApp, Parameters parameters) { @@ -166,4 +390,5 @@ public class AuditImpl implements Audit return new AuditApp(auditApplication.getKey().substring(1), auditApplication.getName(), auditApp.getIsEnabled()); } + } diff --git a/source/java/org/alfresco/rest/api/model/AuditEntry.java b/source/java/org/alfresco/rest/api/model/AuditEntry.java new file mode 100644 index 0000000000..0a41e89e3c --- /dev/null +++ b/source/java/org/alfresco/rest/api/model/AuditEntry.java @@ -0,0 +1,105 @@ +/* + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * 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 . + * #L% + */ +package org.alfresco.rest.api.model; + +import java.io.Serializable; +import java.util.Date; +import java.util.Map; + +public class AuditEntry +{ + + private Long id; + private Long auditApplicationId; + protected UserInfo createdByUser; + protected Date createdAt; + protected Map values; + + public AuditEntry() + { + + } + + public AuditEntry(Long id, Long auditApplicationId, UserInfo createdByUser, Date createdAt, Map values2) + { + this.id = id; + this.auditApplicationId = auditApplicationId; + this.createdByUser = createdByUser; + this.createdAt = createdAt; + this.values = values2; + } + + public Long getId() + { + return id; + } + + public void setId(Long id) + { + this.id = id; + } + + public Long getAuditApplicationId() + { + return auditApplicationId; + } + + public void setAuditApplicationId(Long auditApplicationId) + { + this.auditApplicationId = auditApplicationId; + } + + public UserInfo getCreatedByUser() + { + return createdByUser; + } + + public void setCreatedByUser(UserInfo createdByUser) + { + this.createdByUser = createdByUser; + } + + public Date getCreatedAt() + { + return createdAt; + } + + public void setCreatedAt(Date createdAt) + { + this.createdAt = createdAt; + } + + public Map getValues() + { + return values; + } + + public void setValues(Map values) + { + this.values = values; + } + +}