Java tutorial
/* * Copyright (c) 2011 by the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.powertac.common.state; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import org.apache.commons.beanutils.PropertyUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.aspectj.lang.JoinPoint; import org.aspectj.lang.Signature; import org.aspectj.lang.annotation.AfterReturning; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.springframework.stereotype.Component; /** * Implement uniform state-logging using aspects. This scheme depends on two * annotations: @Domain labels a class for which calls to the constructor are * logged. @StateChange labels a method that must be logged (with its arguments) * when it is called. Log output is a single text line consisting of the * following fields, separated by double colon :: strings * (assuming the log4j config puts out the msec data): * <ol> * <li>milliseconds from start of log</li> * <li>class name</li> * <li>instance id value</li> * <li>method name ("new" for constructor)</li> * <li>method arguments, separated by ::</li> * </ol> * @author John Collins */ @Aspect @Component public class StateLogging { static private Logger log = LogManager.getLogger(StateLogging.class); private Logger stateLog = LogManager.getLogger("State"); // state-change methods @Pointcut("execution (@StateChange * * (..))") public void setState() { } @Pointcut("execution ((@Domain *).new (..))") public void domainConstructor() { } @Pointcut("execution (Object XStreamStateLoggable.readResolve())") public void readResolveMethod() { } @Pointcut("execution (@ChainedConstructor *.new (..))") public void chainedConstructor() { } @Pointcut("(domainConstructor() && !chainedConstructor()) || readResolveMethod()") public void newState() { } @AfterReturning("setState()") public void setstate(JoinPoint jp) { Object thing = jp.getTarget(); Object[] args = jp.getArgs(); Signature sig = jp.getSignature(); Long id = findId(thing); writeLog(thing.getClass().getName(), id, sig.getName(), args); } @AfterReturning("newState()") public void newstate(JoinPoint jp) { Object thing = jp.getTarget(); Class<?> clazz = thing.getClass(); Object[] args = jp.getArgs(); Signature sig = jp.getSignature(); Long id = findId(thing); if ("readResolve".equals(sig.getName())) { args = collectProperties(thing); writeLog(clazz.getName(), id, "-rr", args); } else if (clazz.isAnnotationPresent(Domain.class)) { // Runtime check annotation to prevent logging subclasses of @Domain writeLog(clazz.getName(), id, "new", args); } } private Object[] collectProperties(Object thing) { ArrayList<Object> properties = new ArrayList<Object>(); try { //TODO: // - use XStream annotation to figure out fields to log instead // - cache fields list to reduce lookup Domain domain = thing.getClass().getAnnotation(Domain.class); if (domain instanceof Domain) { String[] fields = domain.fields(); for (String field : fields) { Object obj = PropertyUtils.getSimpleProperty(thing, field); properties.add(obj); } } } catch (IllegalAccessException e) { log.error("Failed to introspect " + thing.getClass().getSimpleName(), e); } catch (InvocationTargetException e) { log.error("Failed to introspect " + thing.getClass().getSimpleName(), e); } catch (NoSuchMethodException e) { log.error("Failed to introspect " + thing.getClass().getSimpleName(), e); } return properties.toArray(); } private void writeLog(String className, Long id, String methodName, Object[] args) { StringBuffer buf = new StringBuffer(); buf.append(className).append("::"); buf.append((id == null) ? "null" : id.toString()).append("::"); buf.append(methodName); for (Object arg : args) { buf.append("::"); writeArg(buf, arg); } stateLog.info(buf.toString()); } @SuppressWarnings("rawtypes") private void writeArg(StringBuffer buf, Object arg) { Long argId = findId(arg); if (argId != null) buf.append(argId.toString()); else if (arg == null) buf.append("null"); else if (arg instanceof Collection) { buf.append("("); String delimiter = ""; for (Object item : (Collection) arg) { buf.append(delimiter); writeArg(buf, item); delimiter = ","; } buf.append(")"); } else if (arg.getClass().isArray()) { buf.append("["); int length = Array.getLength(arg); for (int index = 0; index < length; index++) { writeArg(buf, Array.get(arg, index)); if (index < length - 1) { buf.append(","); } } buf.append("]"); } else { buf.append(arg.toString()); } } Long findId(Object thing) { Long id = null; try { Method getId = thing.getClass().getMethod("getId"); id = (Long) getId.invoke(thing); } catch (Exception ex) { } return id; } }