/*
* JLibs: Common Utilities for Java
* Copyright (C) 2009 Santhosh Kumar T <santhosh.tekuri@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*/
package jlibs.nio.http;
import jlibs.core.io.FileUtil;
import jlibs.nio.Reactors;
import jlibs.nio.http.expr.Expression;
import jlibs.nio.http.expr.Literal;
import jlibs.nio.http.expr.TypeConversion;
import jlibs.nio.http.msg.Message;
import jlibs.nio.http.msg.Request;
import jlibs.nio.http.msg.Response;
import jlibs.nio.log.LogHandler;
import jlibs.nio.log.LogRecord;
import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author Santhosh Kumar Tekuri
*/
public class AccessLog{
private static final Pattern PATTERN = Pattern.compile("([$%#])\\{(.*?)\\}");
private static final List<String> REQUEST_VARS = Arrays.asList(
"request",
"request_count",
"scheme",
"host",
"port",
"remote_ip",
"client_ip",
"id"
);
private static final List<String> CAPTURE_ON_FINISH = Arrays.asList(
"connection_status"
);
private final List<Attribute> attributes = new ArrayList<>();
public AccessLog(String format) throws ParseException{
Matcher matcher = PATTERN.matcher(format);
int cursor = 0;
while(cursor<format.length() && matcher.find(cursor)){
String literal = format.substring(cursor, matcher.start());
if(!literal.isEmpty())
attributes.add(new Attribute(literal));
String group1 = matcher.group(1);
Class exchangeType = null;
if(group1.equals("$"))
exchangeType = ServerExchange.class;
else if(group1.equals("%"))
exchangeType = ClientExchange.class;
String group2 = matcher.group(2);
Class messageType = Response.class;
if(REQUEST_VARS.contains(group2) || group2.startsWith("request."))
messageType = Request.class;
boolean captureOnFinish = CAPTURE_ON_FINISH.contains(group2);
attributes.add(new Attribute(Expression.compile(group2), exchangeType, messageType, captureOnFinish));
cursor = matcher.end();
}
String literal = format.substring(cursor, format.length());
if(!literal.isEmpty())
attributes.add(new Attribute(literal));
}
public Reactors.Pool<Record> records = new Reactors.Pool<>(Record::new);
public class Record implements LogRecord{
private Class<? extends Exchange> owner;
private int exchanges = 0;
private String values[] = new String[attributes.size()];
private LogHandler logHandler;
public void setLogHandler(LogHandler logHandler){
this.logHandler = logHandler;
}
public Class<? extends Exchange> getOwner(){
return owner;
}
public String[] getValues(){
return values;
}
public void process(Exchange exchange, Message msg){
if(owner==null)
owner = exchange.getClass();
if(msg instanceof Request)
++exchanges;
for(int i=0; i<values.length; i++){
Attribute attr = attributes.get(i);
if(!attr.captureOnFinish && attr.isApplicable(exchange, msg))
values[i] = attr.getValue(exchange);
}
}
public void finished(Exchange exchange){
--exchanges;
for(int i=0; i<values.length; i++){
Attribute attr = attributes.get(i);
if(attr.captureOnFinish && attr.isApplicable(exchange)){
String value = attr.getValue(exchange);
if(value!=null){
if(values[i]!=null)
value = Long.toString(Long.parseLong(values[i]) + Long.parseLong(value));
}
values[i] = value;
}
}
if(exchanges==0){
logHandler.publish(this);
reset();
records.free(this);
}
}
public void reset(){
owner = null;
exchanges = 0;
logHandler = null;
Arrays.fill(values, null);
}
@Override
public void publishTo(Appendable writer) throws IOException{
for(int i=0; i<values.length; i++){
writer.append(values[i]==null ? "-" : values[i]);
values[i] = null;
}
writer.append(FileUtil.LINE_SEPARATOR);
}
}
/*-------------------------------------------------[ Static Members ]---------------------------------------------------*/
private static class Attribute{
public final Class exchangeType;
public final Class messageType;
public final boolean captureOnFinish;
private Expression expr;
protected Attribute(Expression expr, Class exchangeType, Class messageType, boolean captureOnFinish){
this.expr = expr;
this.exchangeType = exchangeType;
this.messageType = messageType;
this.captureOnFinish = captureOnFinish;
}
protected Attribute(String literal){
this(new Literal(literal), null, Request.class, false);
}
public String getValue(Exchange exchange){
return TypeConversion.toString(expr.evaluate(exchange));
}
public boolean isApplicable(Exchange exchange){
return exchangeType==null || exchangeType==exchange.getClass();
}
public boolean isApplicable(Exchange exchange, Message message){
return isApplicable(exchange) && (messageType==null || messageType==message.getClass());
}
@Override
public String toString(){
if(exchangeType==ServerExchange.class)
return "${"+expr+'}';
else if(exchangeType==ClientExchange.class)
return "%{"+expr+'}';
else
return "#{"+expr+'}';
}
}
}