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