/* * Copyright (C) 2005-2007 Alfresco Software Limited. * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * This program 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 General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * As a special exception to the terms and conditions of version 2.0 of * the GPL, you may redistribute this Program in connection with Free/Libre * and Open Source Software ("FLOSS") applications as described in Alfresco's * FLOSS exception. You should have recieved a copy of the text describing * the FLOSS exception, and it is also available here: * http://www.alfresco.com/legal/licensing" */ package org.alfresco.repo.audit.hibernate; import java.io.BufferedInputStream; import java.io.IOException; import java.io.InputStream; import java.io.Serializable; import java.sql.SQLException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.audit.AuditComponentImpl; import org.alfresco.repo.audit.AuditConfiguration; import org.alfresco.repo.audit.AuditDAO; import org.alfresco.repo.audit.AuditState; import org.alfresco.repo.content.ContentContext; import org.alfresco.repo.content.ContentStore; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.transaction.TransactionalDao; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.service.cmr.audit.AuditInfo; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.datatype.Duration; import org.alfresco.service.transaction.TransactionService; import org.alfresco.util.EqualsHelper; import org.alfresco.util.GUID; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.HibernateException; import org.hibernate.Query; import org.hibernate.Session; import org.hibernate.mapping.Column; import org.springframework.orm.hibernate3.HibernateCallback; import org.springframework.orm.hibernate3.HibernateTemplate; import org.springframework.orm.hibernate3.LocalSessionFactoryBean; import org.springframework.orm.hibernate3.support.HibernateDaoSupport; /** * Assumes mimetype and encoding sent to the content store (we are not saving this anywhere) * * @author Andy Hind */ public class HibernateAuditDAO extends HibernateDaoSupport implements AuditDAO, TransactionalDao { /** * Logging */ private static Log s_logger = LogFactory.getLog(HibernateAuditDAO.class); private static final String QUERY_LAST_AUDIT_DATE = "audit.GetLatestAuditDate"; private static final String QUERY_AUDIT_DATE = "audit.GetAuditDate"; private static final String QUERY_LAST_AUDIT_CONFIG = "audit.GetLatestAuditConfig"; private static final String QUERY_AUDIT_APP_SOURCE = "audit.GetAuditSourceByApplication"; private static final String QUERY_AUDIT_METHOD_SOURCE = "audit.GetAuditSourceByApplicationServiceMethod"; private static final String QUERY_AUDIT_APP_SOURCE_APP = "application"; private static final String QUERY_AUDIT_APP_SOURCE_SER = "service"; private static final String QUERY_AUDIT_APP_SOURCE_MET = "method"; private static final String QUERY_AUDIT_TRAIL = "audit.GetAuditTrailForNode"; private static final String QUERY_AUDIT_PROTOCOL = "protocol"; private static final String QUERY_AUDIT_STORE_ID = "store_id"; private static final String QUERY_AUDIT_NODE_ID = "node_id"; private static final String QUERY_AUDIT_NODE_REF = "nodeRef"; private static final String QUERY_AUDIT_DATE_PARAM = "date"; /** a uuid identifying this unique instance */ private String uuid; private ContentStore contentStore; private LocalSessionFactoryBean localSessionFactory; public HibernateAuditDAO() { super(); this.uuid = GUID.generate(); } public ContentStore getContentStore() { return contentStore; } public void setContentStore(ContentStore contentStore) { this.contentStore = contentStore; } public void setLocalSessionFactory(LocalSessionFactoryBean localSessionFactory) { this.localSessionFactory = localSessionFactory; } public void audit(AuditState auditInfo) { if (auditInfo.getUserIdentifier() == null) { auditInfo.setUserIdentifier(AuthenticationUtil.getSystemUserName()); } if (AuthenticationUtil.getCurrentUserName() == null) { AuthenticationUtil.setSystemUserAsCurrentUser(); try { audit0(auditInfo); } finally { AuthenticationUtil.clearCurrentSecurityContext(); } } else { audit0(auditInfo); } } private void audit0(AuditState auditInfo) { // Find/Build the configuraton entry AuditConfig auditConfig = getAuditConfig(auditInfo); // Find/Build any dates AuditDate auditDate = getAuditDate(auditInfo); // Find/Build the source AuditSource auditSource = getAuditSource(auditInfo); // Build the new audit fact information AuditFactImpl auditFact = new AuditFactImpl(); auditFact.setAuditConfig(auditConfig); auditFact.setAuditDate(auditDate); auditFact.setAuditSource(auditSource); // Properties Serializable[] args = auditInfo.getMethodArguments(); if (args != null) { switch (args.length) { default: case 5: auditFact.setArg5(getStringOrNull(args[4], getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "arg5"))); case 4: auditFact.setArg4(getStringOrNull(args[3], getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "arg4"))); case 3: auditFact.setArg3(getStringOrNull(args[2], getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "arg3"))); case 2: auditFact.setArg2(getStringOrNull(args[1], getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "arg2"))); case 1: auditFact.setArg1(getStringOrNull(args[0], getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "arg1"))); case 0: } } auditFact.setClientInetAddress(getStringOrNull(auditInfo.getClientAddress(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "clientInetAddress"))); auditFact.setDate(auditInfo.getDate()); auditFact.setException(getStringOrNull((auditInfo.getThrowable() == null ? null : auditInfo.getThrowable().getMessage()), getColumnLength( "org.alfresco.repo.audit.hibernate.AuditFactImpl", "exception"))); auditFact.setFail(auditInfo.isFail()); auditFact.setFiltered(auditInfo.isFiltered()); auditFact.setHostInetAddress(getStringOrNull(auditInfo.getHostAddress(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "hostInetAddress"))); auditFact.setMessage(getStringOrNull(auditInfo.getMessage(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "message"))); auditFact.setNodeUUID(getStringOrNull(auditInfo.getKeyGUID(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "nodeUUID"))); auditFact.setPath(getStringOrNull(auditInfo.getPath(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "path"))); auditFact.setReturnValue(getStringOrNull(auditInfo.getReturnObject(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "returnValue"))); // auditFact.setSerialisedURL() auditFact.setSessionId(getStringOrNull(auditInfo.getSessionId(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "sessionId"))); if (auditInfo.getKeyStore() != null) { auditFact.setStoreId(getStringOrNull(auditInfo.getKeyStore().getIdentifier(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "storeId"))); auditFact.setStoreProtocol(getStringOrNull(auditInfo.getKeyStore().getProtocol(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "storeProtocol"))); } auditFact.setTransactionId(getStringOrNull(auditInfo.getTxId(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "transactionId"))); auditFact.setUserId(getStringOrNull(auditInfo.getUserIdentifier(), getColumnLength("org.alfresco.repo.audit.hibernate.AuditFactImpl", "userId"))); // Save getSession().save(auditFact); } private int getColumnLength(String entityName, String propertyName) { int length = -1; Iterator it = localSessionFactory.getConfiguration().getClassMapping(entityName).getProperty(propertyName).getValue().getColumnIterator(); if (it.hasNext()) { Column col = (Column) it.next(); length = col.getLength(); } if (s_logger.isDebugEnabled()) { s_logger.debug(entityName + " " + propertyName + " is of length " + length); } return length; } private String getStringOrNull(Object o, int size) { if (o == null) { return null; } else { try { String answer = o.toString(); if ((size > -1) && (answer.length() > size)) { answer = answer.substring(0, size); } return answer; } catch (Throwable t) { String answer = "Throwable in toString implementation for " + o.getClass() + " was " + t.getMessage(); if ((size > -1) && (answer.length() > size)) { answer = answer.substring(0, size); } return answer; } } } private AuditSource getAuditSource(final AuditState auditInfo) { AuditSource auditSourceImpl; if ((auditInfo.getAuditService() != null) && (auditInfo.getAuditService().length() > 0) && (auditInfo.getAuditMethod() != null) && (auditInfo.getAuditMethod().length() > 0)) { auditSourceImpl = queryApplicationSource(auditInfo.getAuditApplication(), auditInfo.getAuditService(), auditInfo.getAuditMethod()); if (auditSourceImpl == null) { auditSourceImpl = new AuditSourceImpl(); auditSourceImpl.setApplication(auditInfo.getAuditApplication()); auditSourceImpl.setService(auditInfo.getAuditService()); auditSourceImpl.setMethod(auditInfo.getAuditMethod()); Long id = (Long) getSession().save(auditSourceImpl); } } else { auditSourceImpl = queryApplicationSource(auditInfo.getAuditApplication()); if (auditSourceImpl == null) { auditSourceImpl = new AuditSourceImpl(); auditSourceImpl.setApplication(auditInfo.getAuditApplication()); Long id = (Long) getSession().save(auditSourceImpl); } } return auditSourceImpl; } private AuditDate getAuditDate(final AuditState auditInfo) { Calendar cal = GregorianCalendar.getInstance(); cal.setTime(auditInfo.getDate()); cal.set(Calendar.MILLISECOND, 0); cal.set(Calendar.SECOND, 0); cal.set(Calendar.MINUTE, 0); cal.set(Calendar.HOUR_OF_DAY, 0); Date required = cal.getTime(); AuditDate auditDate; auditDate = queryLatestDate(required); if (auditDate == null) { auditDate = queryLatestDate(); if (auditDate == null) { // The first entry ever so we just make it auditDate = new AuditDateImpl(auditInfo.getDate()); Long id = (Long) getSession().save(auditDate); } else { if (required.compareTo(auditDate.getDate()) < 0) { auditDate = new AuditDateImpl(required); Long id = (Long) getSession().save(auditDate); } else if (required.compareTo(auditDate.getDate()) == 0) { // no action } else { while (!required.equals(auditDate.getDate())) { Date nextDate = Duration.add(auditDate.getDate(), new Duration("P1D")); auditDate = new AuditDateImpl(nextDate); Long id = (Long) getSession().save(auditDate); } } } } else { // no action } return auditDate; } private AuditConfig getAuditConfig(final AuditState auditInfo) { AuditConfig auditConfig; auditConfig = queryLatestConfig(getSession()); if (auditConfig == null) { auditConfig = createNewAuditConfigImpl(auditInfo); } else { InputStream current = new BufferedInputStream(auditInfo.getAuditConfiguration().getInputStream()); ContentReader reader = contentStore.getReader(auditConfig.getConfigURL()); reader.setMimetype(MimetypeMap.MIMETYPE_XML); reader.setEncoding("UTF-8"); InputStream last = new BufferedInputStream(reader.getContentInputStream()); int currentValue = -2; int lastValue = -2; try { while ((currentValue != -1) && (lastValue != -1) && (currentValue == lastValue)) { currentValue = current.read(); lastValue = last.read(); } } catch (IOException e) { throw new AlfrescoRuntimeException("Failed to read and validate current audit configuration against the last", e); } if (currentValue != lastValue) { // Files are different - require a new entry auditConfig = createNewAuditConfigImpl(auditInfo); } else { // No change } } return auditConfig; } private AuditConfig createNewAuditConfigImpl(final AuditState auditInfo) { AuditConfigImpl auditConfig = new AuditConfigImpl(); InputStream is = new BufferedInputStream(auditInfo.getAuditConfiguration().getInputStream()); ContentWriter writer = contentStore.getWriter(ContentStore.NEW_CONTENT_CONTEXT); writer.setMimetype(MimetypeMap.MIMETYPE_XML); writer.setEncoding("UTF-8"); writer.putContent(is); String contentUrl = writer.getContentUrl(); auditConfig.setConfigURL(contentUrl); getSession().save(auditConfig); return auditConfig; } /** * Checks equality by type and uuid */ public boolean equals(Object obj) { if (obj == null) { return false; } else if (!(obj instanceof HibernateAuditDAO)) { return false; } HibernateAuditDAO that = (HibernateAuditDAO) obj; return this.uuid.equals(that.uuid); } /** * @see #uuid */ public int hashCode() { return uuid.hashCode(); } /** * Does this Session contain any changes which must be synchronized with the store? * * @return true => changes are pending */ public boolean isDirty() { // create a callback for the task HibernateCallback callback = new HibernateCallback() { public Object doInHibernate(Session session) { return session.isDirty(); } }; // execute the callback return ((Boolean) getHibernateTemplate().execute(callback)).booleanValue(); } /** * Just flushes the session */ public void flush() { getSession().flush(); } /** * NO-OP */ public void beforeCommit() { } static class SourceKey { String application; String service; String method; SourceKey(String application, String service, String method) { this.application = application; this.service = service; this.method = method; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (!(this instanceof SourceKey)) { return false; } SourceKey other = (SourceKey) o; return EqualsHelper.nullSafeEquals(this.application, other.application) && EqualsHelper.nullSafeEquals(this.service, other.service) && EqualsHelper.nullSafeEquals(this.method, other.method); } @Override public int hashCode() { int hash = application.hashCode(); if (service != null) { hash = (hash * 37) + service.hashCode(); } if (method != null) { hash = (hash * 37) + method.hashCode(); } return hash; } } public List getAuditTrail(NodeRef nodeRef) { if (nodeRef == null) { return Collections. emptyList(); } List internalTrail = queryAuditTrail(nodeRef); ArrayList answer = new ArrayList(internalTrail.size()); for (AuditFact auditFact : internalTrail) { AuditInfo info = new AuditInfoImpl(auditFact); answer.add(info); } return answer; } /** * Helper method to get the latest audit config */ public static AuditConfig queryLatestConfig(Session session) { Query query = session.getNamedQuery(QUERY_LAST_AUDIT_CONFIG); return (AuditConfig) query.uniqueResult(); } /** * Helper method to get the latest audit date */ public AuditDate queryLatestDate() { Query query = getSession().getNamedQuery(QUERY_LAST_AUDIT_DATE); return (AuditDate) query.uniqueResult(); } /** * Helper method to get all the audit entries for a node. */ @SuppressWarnings("unchecked") public List queryAuditTrail(NodeRef nodeRef) { Query query = getSession().getNamedQuery(QUERY_AUDIT_TRAIL); query.setParameter(QUERY_AUDIT_PROTOCOL, nodeRef.getStoreRef().getProtocol()); query.setParameter(QUERY_AUDIT_STORE_ID, nodeRef.getStoreRef().getIdentifier()); query.setParameter(QUERY_AUDIT_NODE_ID, nodeRef.getId()); query.setParameter(QUERY_AUDIT_NODE_REF, "%" + nodeRef.toString() + "%"); return (List) query.list(); } /** * Helper method to get the application source * * @param application * @return */ public AuditSource queryApplicationSource(String application) { Query query = getSession().getNamedQuery(QUERY_AUDIT_APP_SOURCE); query.setParameter(QUERY_AUDIT_APP_SOURCE_APP, application); return (AuditSource) query.uniqueResult(); } /** * Helper method to get the application source * * @param application * @return */ public AuditSource queryApplicationSource(String application, String service, String method) { Query query = getSession().getNamedQuery(HibernateAuditDAO.QUERY_AUDIT_METHOD_SOURCE); query.setParameter(QUERY_AUDIT_APP_SOURCE_APP, application); query.setParameter(QUERY_AUDIT_APP_SOURCE_SER, service); query.setParameter(QUERY_AUDIT_APP_SOURCE_MET, method); return (AuditSource) query.uniqueResult(); } /** * Helper method to get the latest audit date */ public AuditDate queryLatestDate(Date date) { Query query = getSession().getNamedQuery(HibernateAuditDAO.QUERY_AUDIT_DATE); query.setParameter(QUERY_AUDIT_DATE_PARAM, date); return (AuditDate) query.uniqueResult(); } }