/* * Copyright (C) 2000-2015 aw2.0 LTD * * This file is part of Open BlueDragon (OpenBD) CFML Server Engine. * * OpenBD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * Free Software Foundation,version 3. * * OpenBD 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 OpenBD. If not, see http://www.gnu.org/licenses/ * * Additional permission under GNU GPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or combining * it with any of the JARS listed in the README.txt (or a modified version of * (that library), containing parts covered by the terms of that JAR, the * licensors of this Program grant you additional permission to convey the * resulting work. * README.txt @ http://www.openbluedragon.org/license/README.txt * * http://www.openbd.org/ * $Id: JournalSession.java 2503 2015-02-04 14:53:31Z alan $ */ package com.bluedragon.journal; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.aw20.io.ByteArrayOutputStreamRaw; import org.aw20.io.FileUtil; import org.aw20.io.StreamUtil; import org.aw20.util.DateUtil; import com.bluedragon.plugin.RequestListener; import com.naryx.tagfusion.cfm.engine.cfCatchData; import com.naryx.tagfusion.cfm.engine.cfEngine; import com.naryx.tagfusion.cfm.engine.cfSession; import com.naryx.tagfusion.cfm.engine.cfStructData; import com.naryx.tagfusion.cfm.engine.cfmBadFileException; import com.naryx.tagfusion.cfm.engine.cfmRunTimeException; import com.naryx.tagfusion.cfm.file.cfFile; import com.naryx.tagfusion.cfm.parser.CFExpression; import com.naryx.tagfusion.cfm.parser.script.CFParsedStatement; import com.naryx.tagfusion.cfm.parser.script.userDefinedFunction; import com.naryx.tagfusion.cfm.tag.cfFUNCTION; import com.naryx.tagfusion.cfm.tag.cfTRY; import com.naryx.tagfusion.cfm.tag.cfTag; import com.naryx.tagfusion.util.debuggerListener; public class JournalSession implements debuggerListener, RequestListener { private final IJournalManager journalmanager; private final long msEpoch; private int fileId = 0; private final Map<String,Integer> fileIndexMap; private cfSession thisSession; private final List<String> logList; private final StringBuilder sb; private File outputDir; private final String logFileName; private final boolean bSaveSession; private int sessionPagedCount = 0; private BufferedOutputStream fileOutputBufferedOS = null; private ByteArrayOutputStreamRaw previousSession = null; public JournalSession( IJournalManager journalmanager, boolean bSaveSession ){ this.journalmanager = journalmanager; this.bSaveSession = bSaveSession; fileIndexMap = new HashMap<String,Integer>(); logList = new LinkedList<String>(); msEpoch = System.currentTimeMillis(); sb = new StringBuilder(64); logFileName = DateUtil.getDateString( System.currentTimeMillis(), "yyyy-MM-dd_HH.mm.ss-" + journalmanager.getFileCount() ) + ".txt"; } public long getRequestEpoch(){ return msEpoch; } public cfSession getSession(){ return thisSession; } public boolean hasSession(){ return bSaveSession; } private int getFileId(cfFile file){ String t = file.getCfmlURI().getRealPath(); if ( fileIndexMap.containsKey(t) ) return fileIndexMap.get(t); fileIndexMap.put( t, ++fileId ); return fileId; } @Override public final void registerSession(cfSession thisSession) { this.thisSession = thisSession; // Get the path to the initial request; so we can make a director structure for this outputDir = new File( journalmanager.getDirectory(), thisSession.getPresentURIPath() ); if ( !outputDir.isDirectory() && !outputDir.mkdirs() ){ outputDir = journalmanager.getDirectory(); } if ( bSaveSession ) saveSession(); } @Override public void startScriptFunction(userDefinedFunction userDefinedFunction) { if ( bSaveSession ) saveSession(); sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "SF," ).append( getFileId(thisSession.activeFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( "," ).append( userDefinedFunction.getName() ) ; logList.add( sb.toString() ); } @Override public void endScriptFunction(userDefinedFunction userDefinedFunction) { if ( bSaveSession ) saveSession(); sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "SE," ).append( getFileId(thisSession.activeFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( "," ).append( userDefinedFunction.getName() ) ; logList.add( sb.toString() ); } @Override public final void startScriptStatement(CFParsedStatement statement) { sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "SS," ).append( getFileId(statement.getHostTag().getFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( "," ).append( '-' ) .append( "," ).append( statement.getLine() + statement.getHostTag().posLine - 1 ) .append( ',' ).append( statement.getColumn() ) .append( ',' ).append( statement.getLine() ) ; logList.add( sb.toString() ); } @Override public final void startTag(cfTag thisTag) { if ( thisTag instanceof cfFUNCTION ) return; sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(','); if ( thisTag.getEndMarker() == null ) sb.append( "TT," ); else sb.append( "TS," ); sb.append( getFileId(thisTag.getFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( ',' ).append( thisTag.getTagName() ) .append( ',' ).append( thisTag.posLine ) .append( ',' ).append( thisTag.posColumn ); logList.add( sb.toString() ); } @Override public final void endTag(cfTag thisTag) { if ( thisTag.getEndMarker() == null || thisTag instanceof cfFUNCTION ) return; sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "TE," ).append( getFileId(thisTag.getFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( ',' ).append( thisTag.getTagName() ) .append( ',' ).append( thisTag.posEndLine ) .append( ',' ).append( thisTag.posEndColumn ); logList.add( sb.toString() ); } @Override public final void startFunction(cfFUNCTION thisTag) { sb.setLength(0); if ( bSaveSession ) saveSession(); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "MS," ).append( getFileId(thisTag.getFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( ',' ).append( thisTag.getTagName() ) .append( ',' ).append( thisTag.posEndLine ) .append( ',' ).append( thisTag.posEndColumn ) .append( ',' ).append( thisTag.getFunctionName() ) ; logList.add( sb.toString() ); } @Override public final void endFunction(cfFUNCTION thisTag) { sb.setLength(0); if ( bSaveSession ) saveSession(); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "ME," ).append( getFileId(thisTag.getFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( ',' ).append( thisTag.getTagName() ) .append( ',' ).append( thisTag.posEndLine ) .append( ',' ).append( thisTag.posEndColumn ) .append( ',' ).append( thisTag.getFunctionName() ) ; logList.add( sb.toString() ); } @Override public final void startFile(cfFile thisFile) { sb.setLength(0); if ( bSaveSession ) saveSession(); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "FS," ).append( getFileId(thisFile) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) ; logList.add( sb.toString() ); } @Override public final void endFile(cfFile thisFile) { sb.setLength(0); if ( bSaveSession ) saveSession(); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "FE," ).append( getFileId(thisFile) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) ; logList.add( sb.toString() ); } @Override public final void writtenBytes( int total ){ sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "BW," ) .append( total ); logList.add( sb.toString() ); } @Override public final void runExpression(CFExpression expr){ sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "HE," ).append( getFileId(thisSession.activeFile()) ) .append( ',' ).append( sessionPagedCount ) .append( ',' ).append( thisSession.getFileStack().size() ) .append( ',' ).append( thisSession.getTagStack().size() ) .append( ",reserved,0,0," ) .append( expr.Decompile(0) ); logList.add( sb.toString() ); } @Override public final void setHTTPHeader(String name, String value) {} @Override public final void setHTTPStatus(int sc, String value) {} @Override public final void endSession() { if ( fileOutputBufferedOS != null ){ try { fileOutputBufferedOS.flush(); } catch (IOException e) {} StreamUtil.closeStream( fileOutputBufferedOS ); } } @Override public final void requestStart(cfSession session) { session.registerDebugger(this); } @Override public final void requestEnd(cfSession session) { journalmanager.onRequestEnd(this); } @Override public final void requestBadFileException(cfmBadFileException bfException, cfSession session) { sb.setLength(0); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "EF," ) .append( bfException.getMessage() ); logList.add( sb.toString() ); } @Override public final void requestRuntimeException(cfmRunTimeException cfException, cfSession session) { sb.setLength(0); cfCatchData catchdata = (cfCatchData)session.getData( cfTRY.CFCATCHVAR ); sb.append( System.currentTimeMillis() - msEpoch ).append(',') .append( "ER" ); if ( catchdata != null && catchdata.containsKey("message") ) sb.append(',').append( catchdata.getString("message") ); logList.add( sb.toString() ); } public final List<String> getTraceList() { return logList; } public final Map<String,Integer> getFileMap(){ return fileIndexMap; } public File getDirectory() { return outputDir; } private void saveSession() { Map<String, cfStructData> ds = thisSession.getDataStore(); // Clean up the data we want to store cfStructData dump = new cfStructData(); java.util.Iterator<Entry<String, cfStructData>> it = ds.entrySet().iterator(); while ( it.hasNext() ){ Entry<String, cfStructData> entry = it.next(); if ( entry.getKey().equals("cgi") || entry.getKey().equals("cookie") ){ // CGI/Cookie never changes; so only page it out on the start if ( sessionPagedCount == 0 ) dump.put(entry.getKey(), copyStruct(entry.getValue())); }else if ( entry.getKey().equals("request") ){ // the request uses the servletcontainer which may not be here the next time cfStructData s = copyStruct(entry.getValue()); if ( !s.isEmpty() ) dump.put(entry.getKey(), s); }else if ( entry.getKey().equals("server") ){ // We don't need to keep repeating the standard variables that appear in all releases cfStructData s = copyStruct(entry.getValue()); s.remove("os"); s.remove("bluedragon"); s.remove("coldfusion"); if ( !s.isEmpty() ) dump.put(entry.getKey(), s); }else{ if ( !entry.getValue().isEmpty() ) dump.put(entry.getKey(), entry.getValue()); } } ByteArrayOutputStreamRaw mout; try { mout = new ByteArrayOutputStreamRaw(128000); FileUtil.saveClass( mout, dump, true ); if ( !byteArrayEquals(previousSession, mout) ){ previousSession = mout; sessionPagedCount++; // if we don't have the file open; we need to do that if ( fileOutputBufferedOS == null ){ File sessionFile = new File( outputDir, logFileName + ".session" ); fileOutputBufferedOS = new BufferedOutputStream( new FileOutputStream( sessionFile ), 128000 ); } // We will write the size of the session page byte[] sizeBytes = ByteBuffer.allocate(4).putInt(previousSession.size()).array(); fileOutputBufferedOS.write( sizeBytes, 0, 4 ); // now we write the session size fileOutputBufferedOS.write(previousSession.getByteArray(), 0, previousSession.size() ); journalmanager.addBytesWritten( previousSession.size() + 4 ); } } catch ( Exception E ) { cfEngine.log( "JournalSession.saveSession() e=" + E.getMessage() ); } catch ( Throwable E ) {} } private boolean byteArrayEquals(ByteArrayOutputStreamRaw in1, ByteArrayOutputStreamRaw in2) { if ( in1 == null || in2 == null || in1.size() != in2.size() ) return false; byte[] in1B = in1.getByteArray(); byte[] in2B = in2.getByteArray(); int s = in1.size(); for ( int x=0; x < s; x++ ){ if ( in1B[x] != in2B[x] ) return false; } return true; } private cfStructData copyStruct( cfStructData src ){ cfStructData dest = new cfStructData(); Object[] keys = src.keys(); for ( Object key : keys ){ dest.put(key, src.get(key) ); } return dest; } public String getLogFileName() { return logFileName; } }