001 /*
002 * Copyright (c) 2013 by Oli B.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express orimplied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * (c)reated 31.08.2013 by Oli B. (boehm@javatux.de)
017 */
018
019 package patterntesting.runtime.log;
020
021 import java.io.*;
022
023 import org.apache.commons.io.IOUtils;
024 import org.aspectj.lang.JoinPoint;
025 import org.slf4j.*;
026
027 import patterntesting.annotation.check.runtime.NullArgsAllowed;
028 import patterntesting.runtime.io.*;
029 import patterntesting.runtime.util.JoinPointHelper;
030
031 /**
032 * This class allows you to record objects. Later you can replay the logged
033 * objects, e.g. to simulate external you can:
034 * <ul>
035 * <li>record the call to the external system,</li>
036 * <li>replay the recorded objects.</li>
037 * </ul>
038 * You must write your own aspect to realize it.
039 * <p>
040 * If you want a pure java solution you can try <a
041 * href="http://jtor.sourceforge.net/">ThOR</a>, the Java Test Object Recorder.
042 * </p>
043 *
044 * @author oliver (boehm@javatux.de)
045 * @since 1.3.1 (31.08.2013)
046 */
047 public class ObjectRecorder extends AbstractLogger {
048
049 private static final Logger log = LoggerFactory.getLogger(ObjectRecorder.class);
050 private static final AbstractSerializer serializer = AbstractSerializer.getInstance();
051 private final ObjectOutputStream objStream;
052
053 /**
054 * Instantiates a new object recorder. The logged objects are stored
055 * in a temporary file.
056 */
057 public ObjectRecorder() {
058 this(createTempLogFile("objects", ".rec"));
059 }
060
061 /**
062 * Instantiates a new object logger. The logged objects will be stored in
063 * the given log file.
064 *
065 * @param logFile the log file
066 */
067 public ObjectRecorder(final File logFile) {
068 this(getStreamFor(logFile));
069 log.info("Objects will be recorded to \"{}\".", logFile);
070 }
071
072 /**
073 * Instantiates a new object logger to the given stream.
074 *
075 * @param ostream the ostream
076 */
077 public ObjectRecorder(final OutputStream ostream) {
078 super(ostream);
079 try {
080 this.objStream = serializer.createObjectOutputStream(ostream);
081 } catch (IOException ioe) {
082 throw new IllegalArgumentException("cannot use " + ostream, ioe);
083 }
084 }
085
086 /**
087 * This method is called at shutdown to close the open stream.
088 * Otherwise the closing "</object-stream>" tag in the generated file
089 * would be missing (it is generated by the used XStream library).
090 *
091 * @see java.lang.Thread#run()
092 */
093 @Override
094 public void run() {
095 IOUtils.closeQuietly(this.objStream);
096 super.run();
097 }
098
099 /**
100 * Closes the stream with the logged objects.
101 */
102 @Override
103 public void close() {
104 IOUtils.closeQuietly(this.objStream);
105 super.close();
106 }
107
108 /**
109 * Both things are logged with this method: the call of a method (joinPoint)
110 * and the return value of this method. Constructors or method of type
111 * 'void' are not recorded because the have no return value.
112 * <p>
113 * Because the given joinPoint cannot be used as key for a map in
114 * {@link ObjectPlayer} it is saved as string. As a side effect this will
115 * speedup the serialization stuff and shorten the generated record file.
116 * </p>
117 *
118 * @param joinPoint the joinpoint
119 * @param returnValue the return value
120 */
121 @NullArgsAllowed
122 public void log(final JoinPoint joinPoint, final Object returnValue) {
123 String statement = JoinPointHelper.getAsLongString(joinPoint);
124 try {
125 save(statement, returnValue);
126 } catch (IOException ioe) {
127 log.debug("Logging to {} failed because of {}.", objStream, ioe.getMessage());
128 log.info("{} = {}", statement, returnValue);
129 }
130 }
131
132 /**
133 * Saves the given joinPoint / returnValue pair to the log stream.
134 *
135 * @param joinPoint the join point
136 * @param returnValue the return value
137 * @throws IOException Signals that an I/O exception has occurred.
138 */
139 protected void save(final String joinPoint, final Object returnValue) throws IOException {
140 log.debug("RECORD: {} = {}", joinPoint, returnValue);
141 objStream.writeObject(joinPoint);
142 objStream.writeObject(returnValue);
143 objStream.flush();
144 }
145
146 }
147