/*
* 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: JournalManager.java 2528 2015-02-26 20:20:37Z alan $
*/
package com.bluedragon.journal;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import org.aw20.io.StreamUtil;
import com.bluedragon.platform.java.WalkDirectory;
import com.nary.io.FileUtils;
import com.naryx.tagfusion.cfm.engine.cfEngine;
import com.naryx.tagfusion.cfm.engine.cfSession;
import com.naryx.tagfusion.expression.function.hashBinary;
public class JournalManager implements IJournalManager {
private String password, ignoreUri;
private static final String JOURNAL_PARAM = "_openbdjournal";
private File journalDir = null;
private int filecount = 0;
private long journalDirSize = 0, bytesLimit = 0;
public JournalManager(){
password = cfEngine.thisInstance.getSystemParameters().getString("server.system.journal");
if ( password != null && password.isEmpty() )
password = null;
ignoreUri = cfEngine.thisInstance.getSystemParameters().getString("server.system.journalignoreuri");
if ( ignoreUri == null || ( ignoreUri != null && ignoreUri.isEmpty() ) )
ignoreUri = "/journal";
else if ( ignoreUri.charAt(0) != '/' )
ignoreUri = "/" + ignoreUri;
int mblimit = cfEngine.thisInstance.getSystemParameters().getInt("server.system.journalmb",50);
bytesLimit = mblimit * 1000000;
if ( password == null )
cfEngine.log("JournalManager: Disabled; Set [server.system.journal] to enable" );
else
cfEngine.log("JournalManager: Enabled; URI parameter/Cookie/Header " + JOURNAL_PARAM + "=" + password + "; LogLimit=" + mblimit + " MB; ignoreuri=" + ignoreUri );
try {
journalDir = FileUtils.checkAndCreateDirectory(cfEngine.thisPlatform.getFileIO().getWorkingDirectory(), "journal", false);
journalDirSize = new WalkDirectory(journalDir).getTotalFileSize();
} catch (Exception e) {
cfEngine.log("JournalManager: Disabled; failed to create directory: " + cfEngine.thisPlatform.getFileIO().getWorkingDirectory() );
}
}
/**
* Determines if this request can be instrumented. We are looking for the JOURNAL_PARAM to appear in either
* the URI params, an HTTP header, or the cookie. This has to match the value of the param to ensure no undue
* load or hacking attempts.
*
* @param req
* @return
*/
public JournalSession getJournalSession( HttpServletRequest req ){
if ( password == null )
return null;
// Check to see if it is in the request parameters
String v = req.getParameter(JOURNAL_PARAM);
if ( v != null && v.startsWith(password) && !req.getRequestURI().startsWith(ignoreUri) ){
return new JournalSession( this, v.endsWith("-1") );
}
// Check to see if it is in cookie
Cookie[] cookies = req.getCookies();
if ( cookies != null && cookies.length > 0 ){
for ( int x=0; x < cookies.length; x++ ){
if ( cookies[x].getName().equals(JOURNAL_PARAM) && cookies[x].getValue().startsWith(password) && !req.getRequestURI().startsWith(ignoreUri) )
return new JournalSession( this, cookies[x].getValue().endsWith("-1") );
}
}
// Check to see if the header is there
v = req.getHeader(JOURNAL_PARAM);
if ( v != null && v.startsWith(password) && !req.getRequestURI().startsWith(ignoreUri) )
return new JournalSession( this, v.endsWith("-1") );
// no param was found
return null;
}
@Override
public int getFileCount(){
filecount++;
if (filecount > 1000 )
filecount = 0;
return filecount;
}
@Override
public void onRequestEnd(JournalSession journalSession) {
cfSession session = journalSession.getSession();
File logFile = new File( journalSession.getDirectory(), journalSession.getLogFileName() );
PrintWriter pwriter = null;
try{
List<String> logList = journalSession.getTraceList();
Map<String,Integer> fileIndexMap = journalSession.getFileMap();
pwriter = new PrintWriter( new BufferedWriter( new FileWriter(logFile) ) );
// Print the header
pwriter.print("{");
pwriter.print( "\"_uri\":\"" + session.getRequestURI() );
if ( session.REQ.getQueryString() != null && !session.REQ.getQueryString().isEmpty() )
pwriter.print( "?" + session.REQ.getQueryString().replace('"', '\'') );
pwriter.print( "\"" );
pwriter.print( ",\"_querycount\":" + session.getMetricQuery() );
pwriter.print( ",\"_querytime\":" + session.getMetricQueryTotalTime() );
pwriter.print( ",\"_bytes\":" + session.getBytesSent() );
pwriter.print( ",\"_timems\":" + (System.currentTimeMillis()-journalSession.getRequestEpoch()) );
pwriter.print( ",\"_method\":\"" + session.REQ.getMethod() + "\"" );
pwriter.print( ",\"_session\":" + (journalSession.hasSession() ? "true" : "false") + "" );
// Add the file map
pwriter.print( "," );
String hash;
hashBinary hasher = new hashBinary();
Iterator<Entry<String,Integer>> fit = fileIndexMap.entrySet().iterator();
while ( fit.hasNext() ){
Entry<String,Integer> e = fit.next();
File f = new File(e.getKey());
if ( f.exists() )
hash = hasher.getFileHash( f, "MD5");
else
hash = "abcd1234";
pwriter.print( "\"" + e.getKey().replace('\\','/') + "\":{\"id\":" + e.getValue() + ",\"hash\":\"" + hash + "\"}," );
}
pwriter.println( "\"root\":{\"id\":0,\"hash\":\"0\"}}" );
// Print the trace
Iterator<String> it = logList.iterator();
while ( it.hasNext() )
pwriter.println( it.next() );
pwriter.flush();
}catch(Exception e){
cfEngine.log("JournalManager.onRequestEnd: " + e.getMessage() );
}finally{
StreamUtil.closeStream( pwriter );
}
onLogFileWrite(logFile);
}
private synchronized void onLogFileWrite(File logfile){
journalDirSize += logfile.length();
if ( journalDirSize < bytesLimit )
return;
// We are over the size limit now; so let us delete the older files and come down to 90% of disk space
long bytesLimitNinetyPercent = (long)(bytesLimit * 0.95);
WalkDirectory walker = new WalkDirectory( journalDir );
Iterator<File> it = walker.iteratorAllFiles();
long totalSize = 0, totalDeleted = 0;
int filesDeleted = 0;
while ( it.hasNext() ){
File f = it.next();
totalSize += f.length();
if ( totalSize > bytesLimitNinetyPercent ){
totalDeleted += f.length();
f.delete();
filesDeleted++;
totalSize -= f.length();
}
}
cfEngine.log("JournalManager: Disk clean up; files removed=" + filesDeleted + "; bytes=" + totalDeleted );
// reset the disk space
journalDirSize = walker.getTotalFileSize();
}
@Override
public File getDirectory() {
return journalDir;
}
@Override
public void addBytesWritten(long bytes){
journalDirSize += bytes;
}
}