/*
* Copyright (c) xlightweb.org, 2008 - 2009. All rights reserved.
*
* 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.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* Please refer to the LGPL license at: http://www.gnu.org/copyleft/lesser.txt
* The latest copy of this software may be found on http://www.xlightweb.org/
*/
package org.xlightweb;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URLDecoder;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.xsocket.DataConverter;
import org.xsocket.Execution;
import org.xsocket.Resource;
import org.xsocket.connection.ConnectionUtils;
import org.xsocket.connection.IServer;
import org.xsocket.connection.IWriteCompletionHandler;
/**
* A HTTP utility class
*
* @author grro@xlightweb.org
*/
public final class HttpUtils {
static final byte CR = 13;
static final byte LF = 10;
static final byte SPACE = 32;
static final byte HTAB = 9;
private static final Logger LOG = Logger.getLogger(HttpUtils.class.getName());
@SuppressWarnings("unchecked")
private static final Map<Class, Boolean> bodyDataExecutionModeCache = newMapCache(25);
@SuppressWarnings("unchecked")
private static final Map<Class, Boolean> bodyCompleteListenerExecutionModeCache = newMapCache(25);
@SuppressWarnings("unchecked")
private static final Map<Class, Boolean> requestTimeoutHandlerExecutionModeCache = newMapCache(25);
@SuppressWarnings("unchecked")
private static final Map<Class, CompletionHandlerInfo> completionHandlerInfoCache = ConnectionUtils.newMapCache(25);
@SuppressWarnings("unchecked")
private static final Map<Class, Boolean> bodyCloseListenerExecutionModeCache = newMapCache(25);
@SuppressWarnings("unchecked")
private static final Map<Class, RequestHandlerInfo> httpRequestHandlerInfoCache = HttpUtils.newMapCache(25);
private static RequestHandlerInfo emptyHttpRequestHandlerInfo;
@SuppressWarnings("unchecked")
private static final Map<Class, PartHandlerInfo> partHandlerInfoCache = HttpUtils.newMapCache(25);
private static PartHandlerInfo emptyPartHandlerInfo;
@SuppressWarnings("unchecked")
private static final Map<Class, ResponseHandlerInfo> httpResponseHandlerInfoCache = HttpUtils.newMapCache(25);
private static ResponseHandlerInfo emptyResponseHandlerInfo;
@SuppressWarnings("unchecked")
private static final Map<Class, HttpConnectionHandlerInfo> httpConnectionHandlerInfoCache = HttpUtils.newMapCache(25);
private static final HttpConnectionHandlerInfo EMPTY_HTTP_CONNECTION_HANDLER_INFO = new HttpConnectionHandlerInfo(null);
private static String implementationVersion;
private static String implementationDate;
private static String xSocketImplementationVersion;
private static Map<String, String> mimeTypeMap;
private static boolean showDetailedError;
static {
showDetailedError = Boolean.parseBoolean(System.getProperty(IHttpExchange.SHOW_DETAILED_ERROR_KEY, IHttpExchange.SHOW_DETAILED_ERROR_DEFAULT));
}
private static final char[] ISO_8859_1_Array = new char[256];
static {
for (int i = 0; i < ISO_8859_1_Array.length; i++) {
try {
ISO_8859_1_Array[i] = new String(new byte[] { (byte) i }, "ISO-8859-1").charAt(0);
} catch (UnsupportedEncodingException use) {
throw new RuntimeException(use);
}
}
}
private static final byte base64[] = {
(byte) 'A', (byte) 'B', (byte) 'C', (byte) 'D',
(byte) 'E', (byte) 'F', (byte) 'G', (byte) 'H',
(byte) 'I', (byte) 'J', (byte) 'K', (byte) 'L',
(byte) 'M', (byte) 'N', (byte) 'O', (byte) 'P',
(byte) 'Q', (byte) 'R', (byte) 'S', (byte) 'T',
(byte) 'U', (byte) 'V', (byte) 'W', (byte) 'X',
(byte) 'Y', (byte) 'Z', (byte) 'a', (byte) 'b',
(byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f',
(byte) 'g', (byte) 'h', (byte) 'i', (byte) 'j',
(byte) 'k', (byte) 'l', (byte) 'm', (byte) 'n',
(byte) 'o', (byte) 'p', (byte) 'q', (byte) 'r',
(byte) 's', (byte) 't', (byte) 'u', (byte) 'v',
(byte) 'w', (byte) 'x', (byte) 'y', (byte) 'z',
(byte) '0', (byte) '1', (byte) '2', (byte) '3',
(byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) '+', (byte) '/',
(byte) '='
};
private HttpUtils() { }
/**
* returns a ISO_8859_1 byte array
*
* @return a ISO_8859_1 byte array
*/
static char[] getISO_8859_1_Array() {
return ISO_8859_1_Array;
}
/**
* returns true, if detailed error messages should been shown. See {@link IHttpExchange#SHOW_DETAILED_ERROR_KEY}
*
* @return true, if detailed error messages should been shown
*/
public static boolean isShowDetailedError() {
return showDetailedError;
}
/**
* returns the reason text
*
* @param statusCode the status code
* @return the reason text
*/
public static String getReason(int statusCode) {
switch (statusCode) {
case 200:
return "OK";
case 201:
return "Created";
case 304:
return "Not Modifed";
case 401:
return "Unauthorized";
case 404:
return "Not found";
case 500:
return "Internal Server Error";
case 501:
return "Not Implemented";
case 502:
return "Bad Gateway";
case 503:
return "Service Unavailable";
case 504:
return "Gateway Timeout";
case 505:
return "HTTP Version Not Supported";
default:
return " ";
}
}
/**
* encodes the given byte array
*
* @param bytes byte array to encode
* @return the encoded byte array
* @throws IOException if an exception occurs
*/
public static byte[] encodeBase64(byte bytes[]) throws IOException {
ByteArrayInputStream in = new ByteArrayInputStream(bytes);
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte buffer[] = new byte[1024];
int got = -1;
int off = 0;
int count = 0;
while ((got = in.read(buffer, off, 1024 - off)) > 0) {
if (got >= 3) {
got += off;
off = 0;
while (off + 3 <= got) {
int c1 = (buffer[off] & 0xfc) >> 2;
int c2 = ((buffer[off]&0x3) << 4) | ((buffer[off+1]&0xf0) >>> 4);
int c3 = ((buffer[off+1] & 0x0f) << 2) | ((buffer[off+2] & 0xc0) >>> 6);
int c4 = buffer[off+2] & 0x3f;
switch (count) {
case 73:
out.write(base64[c1]);
out.write(base64[c2]);
out.write(base64[c3]);
out.write ('\n') ;
out.write(base64[c4]);
count = 1 ;
break ;
case 74:
out.write(base64[c1]);
out.write(base64[c2]);
out.write ('\n') ;
out.write(base64[c3]);
out.write(base64[c4]) ;
count = 2 ;
break ;
case 75:
out.write(base64[c1]);
out.write ('\n') ;
out.write(base64[c2]);
out.write(base64[c3]);
out.write(base64[c4]) ;
count = 3 ;
break ;
case 76:
out.write('\n') ;
out.write(base64[c1]);
out.write(base64[c2]);
out.write(base64[c3]);
out.write(base64[c4]);
count = 4;
break;
default:
out.write(base64[c1]);
out.write(base64[c2]);
out.write(base64[c3]);
out.write(base64[c4]);
count += 4;
break;
}
off += 3;
}
for ( int i = 0 ; i < 3 ;i++) {
buffer[i] = (i < got-off) ? buffer[off+i] : ((byte) 0);
}
off = got-off ;
} else {
off += got;
}
}
switch (off) {
case 1:
out.write(base64[(buffer[0] & 0xfc) >> 2]);
out.write(base64[((buffer[0]&0x3) << 4) | ((buffer[1]&0xf0) >>> 4)]);
out.write('=');
out.write('=');
break ;
case 2:
out.write(base64[(buffer[0] & 0xfc) >> 2]);
out.write(base64[((buffer[0]&0x3) << 4) | ((buffer[1]&0xf0) >>> 4)]);
out.write(base64[((buffer[1] & 0x0f) << 2) | ((buffer[2] & 0xc0) >>> 6)]);
out.write('=');
}
return out.toByteArray();
}
/**
* validate, based on a leading int length field. The length field will be removed
*
* @param connection the connection
* @return the length
* @throws IOException if an exception occurs
* @throws BufferUnderflowException if not enough data is available
*/
public static int validateSufficientDatasizeByIntLengthField(NonBlockingBodyDataSource stream) throws IOException, BufferUnderflowException {
return validateSufficientDatasizeByIntLengthField(stream, true) ;
}
/**
* validate, based on a leading int length field, that enough data (getNumberOfAvailableBytes() >= length) is available. If not,
* an BufferUnderflowException will been thrown.
*
* @param connection the connection
* @param removeLengthField true, if length field should be removed
* @return the length
* @throws IOException if an exception occurs
* @throws BufferUnderflowException if not enough data is available
*/
public static int validateSufficientDatasizeByIntLengthField(NonBlockingBodyDataSource stream, boolean removeLengthField) throws IOException, BufferUnderflowException {
stream.resetToReadMark();
stream.markReadPosition();
// check if enough data is available
int length = stream.readInt();
if (stream.available() < length) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("insufficient data. require " + length + " got " + stream.available());
}
throw new BufferUnderflowException();
} else {
// ...yes, remove mark
if (!removeLengthField) {
stream.resetToReadMark();
}
stream.removeReadMark();
return length;
}
}
/**
* get the mime type file to extension map
*
* @return the mime type file to extension map
*/
public synchronized static Map<String, String> getMimeTypeMapping() {
if (mimeTypeMap == null) {
Map<String, String> map = new HashMap<String, String>();
mimeTypeMap = Collections.unmodifiableMap(map);
InputStreamReader isr = null;
LineNumberReader lnr = null;
try {
isr = new InputStreamReader(HttpUtils.class.getResourceAsStream("/org/xlightweb/mime.types"));
if (isr != null) {
lnr = new LineNumberReader(isr);
String line = null;
while (true) {
line = lnr.readLine();
if (line != null) {
line = line.trim();
if (!line.startsWith("#")) {
StringTokenizer st = new StringTokenizer(line);
if (st.hasMoreTokens()) {
String mimeType = st.nextToken();
while (st.hasMoreTokens()) {
String extension = st.nextToken();
map.put(extension, mimeType);
if (LOG.isLoggable(Level.FINER)) {
LOG.finer("mapping " + extension + " -> " + mimeType + " added");
}
}
} else {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("line " + line + "ignored");
}
}
}
} else {
break;
}
}
lnr.close();
}
} catch (Exception ioe) {
// eat and log exception
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("could not read mime.types. reason: " + ioe.toString());
}
} finally {
try {
if (lnr != null) {
lnr.close();
}
if (isr != null) {
isr.close();
}
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("exception occured by closing mime.types file stream " + ioe.toString());
}
}
}
}
return mimeTypeMap;
}
/**
* injects a server field
*
* @param handler the handler
* @param server the server to inject
*/
static void injectServerField(Object handler, IServer server) {
Field[] fields = handler.getClass().getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Resource.class)) {
Resource res = field.getAnnotation(Resource.class);
if ((field.getType() == IServer.class) || (res.type() == IServer.class)) {
field.setAccessible(true);
try {
field.set(handler, server);
} catch (IllegalAccessException iae) {
LOG.warning("could not set HandlerContext for attribute " + field.getName() + ". Reason " + iae.toString());
}
}
}
}
}
static boolean isContentTypeFormUrlencoded(IHttpMessage message) {
if (!message.hasBody()) {
return false;
}
String contentType = message.getContentType();
if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencoded"))) {
return true;
}
return false;
}
/**
* returns the encoding type of content typed FORM URL encoded message
*
* @param message the message
* @return the encoding type
*/
static String getContentTypedFormUrlencodedEncodingType(IHttpMessage message) {
String contentType = message.getContentType();
if ((contentType != null) && (contentType.startsWith("application/x-www-form-urlencoded"))) {
String[] parts = contentType.split(";");
if (parts.length > 1) {
for(int i = 1; i < parts.length; i++) {
String[] kvp = parts[i].split("=");
if (kvp[0].trim().toUpperCase().equals("CHARSET")) {
return kvp[1].trim();
}
}
}
}
return "UTF-8";
}
/**
* creates a FORM URL encoded body message wrapper
*
* @param request the request
* @return a FORM URL encoded body message wrapper
* @throws IOException if an exception occurs
*/
static IHttpRequest newFormEncodedRequestWrapper(IHttpRequest request) throws IOException {
if (request instanceof HttpRequestWrapper) {
return request;
}
return new HttpRequestWrapper(new ExtendedHttpRequestHeaderWrapper(request), request);
}
/**
* returns the request handler info
*
* @param httpRequestHandler the request handler
* @return the request handler info
*/
@SuppressWarnings("unchecked")
static RequestHandlerInfo getHttpRequestHandlerInfo(IHttpRequestHandler httpRequestHandler) {
if (httpRequestHandler == null) {
if (emptyHttpRequestHandlerInfo == null) {
emptyHttpRequestHandlerInfo = new RequestHandlerInfo(null);
}
return emptyHttpRequestHandlerInfo;
}
RequestHandlerInfo httpRequestHandlerInfo = httpRequestHandlerInfoCache.get(httpRequestHandler.getClass());
if (httpRequestHandlerInfo == null) {
httpRequestHandlerInfo = new RequestHandlerInfo((Class<IHttpRequestHandler>) httpRequestHandler.getClass());
httpRequestHandlerInfoCache.put(httpRequestHandler.getClass(), httpRequestHandlerInfo);
}
return httpRequestHandlerInfo;
}
static CompletionHandlerInfo getCompletionHandlerInfo(IWriteCompletionHandler handler) {
CompletionHandlerInfo completionHandlerInfo = completionHandlerInfoCache.get(handler.getClass());
if (completionHandlerInfo == null) {
completionHandlerInfo = new CompletionHandlerInfo(handler);
completionHandlerInfoCache.put(handler.getClass(), completionHandlerInfo);
}
return completionHandlerInfo;
}
/**
* returns the part handler info
*
* @param partHandler the part handler
* @return the part handler info
*/
@SuppressWarnings("unchecked")
static PartHandlerInfo getPartHandlerInfo(IPartHandler partHandler) {
if (partHandler == null) {
if (emptyPartHandlerInfo == null) {
emptyPartHandlerInfo = new PartHandlerInfo(null);
}
return emptyPartHandlerInfo;
}
PartHandlerInfo partHandlerInfo = partHandlerInfoCache.get(partHandler.getClass());
if (partHandlerInfo == null) {
partHandlerInfo = new PartHandlerInfo((Class<IPartHandler>) partHandler.getClass());
partHandlerInfoCache.put(partHandler.getClass(), partHandlerInfo);
}
return partHandlerInfo;
}
/**
* returns the response handler info
*
* @param httpResponseHandler the response handler
* @return the response handler info
*/
@SuppressWarnings("unchecked")
static ResponseHandlerInfo getHttpResponseHandlerInfo(IHttpResponseHandler httpResponseHandler) {
if (httpResponseHandler == null) {
if (emptyResponseHandlerInfo == null) {
emptyResponseHandlerInfo = new ResponseHandlerInfo(null);
}
return emptyResponseHandlerInfo;
}
ResponseHandlerInfo httpResponseHandlerInfo = httpResponseHandlerInfoCache.get(httpResponseHandler.getClass());
if (httpResponseHandlerInfo == null) {
httpResponseHandlerInfo = new ResponseHandlerInfo((Class<IHttpResponseHandler>) httpResponseHandler.getClass());
httpResponseHandlerInfoCache.put(httpResponseHandler.getClass(), httpResponseHandlerInfo);
}
return httpResponseHandlerInfo;
}
/**
* returns the connection handler info
*
* @param httpConnectionHandler the connection handler
* @return the connection handler info
*/
@SuppressWarnings("unchecked")
static HttpConnectionHandlerInfo getHttpConnectionHandlerInfo(IHttpConnectionHandler httpConnectionHandler) {
if (httpConnectionHandler == null) {
return EMPTY_HTTP_CONNECTION_HANDLER_INFO;
}
HttpConnectionHandlerInfo httpConnectionHandlerInfo = httpConnectionHandlerInfoCache.get(httpConnectionHandler.getClass());
if (httpConnectionHandlerInfo == null) {
httpConnectionHandlerInfo = new HttpConnectionHandlerInfo((Class<IHttpConnectionHandler>) httpConnectionHandler.getClass());
httpConnectionHandlerInfoCache.put(httpConnectionHandler.getClass(), httpConnectionHandlerInfo);
}
return httpConnectionHandlerInfo;
}
/**
* <b>This is a xSocket internal method and subject to change</b>
*/
static RequestHandlerInfo getRequestHandlerInfo(IHttpRequestHandler requestHandler) {
return getHttpRequestHandlerInfo(requestHandler);
}
/**
* <b>This is a xSocket internal method and subject to change</b>
*/
static ResponseHandlerInfo getResponseHandlerInfo(IHttpResponseHandler responseHandler) {
return getHttpResponseHandlerInfo(responseHandler);
}
/**
* returns true if the body handler is mutlithreaded
*
* @param bodyHandler the body handler
* @return true if the body handler is multithreaded
*/
static boolean isMutlithreaded(IBodyDataHandler bodyHandler) {
Boolean isMutlithreaded = bodyDataExecutionModeCache.get(bodyHandler.getClass());
if (isMutlithreaded == null) {
int mode = IBodyDataHandler.DEFAULT_EXECUTION_MODE;
Execution execution = bodyHandler.getClass().getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
try {
Method meth = bodyHandler.getClass().getMethod("onData", new Class[] { NonBlockingBodyDataSource.class });
execution = meth.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
} catch (NoSuchMethodException nsme) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("shouldn't occure because body handler has to have such a method " + nsme.toString());
}
}
isMutlithreaded = (mode == Execution.MULTITHREADED);
bodyDataExecutionModeCache.put(bodyHandler.getClass(), isMutlithreaded);
}
return isMutlithreaded;
}
static String parseEncoding(String contentType) {
// contains charset encoding?
int pos = contentType.indexOf(';');
if (pos > 0) {
int pos2 = contentType.indexOf("charset=", pos + 1);
if (pos2 > 0) {
String encoding = contentType.substring(pos2 + "charset=".length(), contentType.length()).trim();
if(encoding.indexOf(';') != -1) {
encoding = encoding.substring(0, encoding.indexOf(';'));
}
return encoding;
}
}
return null;
}
static String parseEncoding(String contentType, String dflt) {
String encoding = parseEncoding(contentType);
if (encoding == null) {
return dflt;
} else {
return encoding;
}
}
static boolean isBodyCompleteListenerMutlithreaded(IBodyCompleteListener completeListener) {
Boolean isMutlithreaded = bodyCompleteListenerExecutionModeCache.get(completeListener.getClass());
if (isMutlithreaded == null) {
int mode = IBodyDataHandler.DEFAULT_EXECUTION_MODE;
Execution execution = completeListener.getClass().getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
try {
Method meth = completeListener.getClass().getMethod("onComplete", new Class[] { });
execution = meth.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
} catch (NoSuchMethodException nsme) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("shouldn't occure because body handler has to have such a method " + nsme.toString());
}
}
isMutlithreaded = (mode == Execution.MULTITHREADED);
bodyCompleteListenerExecutionModeCache.put(completeListener.getClass(), isMutlithreaded);
}
return isMutlithreaded;
}
/**
* Copies a request. Only complete received message are supported
*
* @param request the request
* @return the copy
* @throws IOException if an exception occurs
*/
public static IHttpRequest copy(IHttpRequest request) throws IOException {
if (request.hasBody()) {
if (!request.getNonBlockingBody().isComplete()) {
throw new IOException("copy is only supported for complete received messages (hint: uses @InvokeOn(InvokeOn.MESSAGE_RECEIVED) annotation)");
}
return new HttpRequest(request.getRequestHeader().copy(), request.getNonBlockingBody().copyContent());
} else {
return new HttpRequest(request.getRequestHeader().copy());
}
}
/**
* Copies a response. Only complete received message are supported
*
* @param response the response
* @return the copy
* @throws IOException if an exception occurs
*/
public static IHttpResponse copy(IHttpResponse response) throws IOException {
if (response.hasBody()) {
if (!response.getNonBlockingBody().isComplete()) {
throw new IOException("copy is only supported for complete received messages (hint: uses @InvokeOn(InvokeOn.MESSAGE_RECEIVED) annotation)");
}
return new HttpResponse(response.getResponseHeader().copy(), response.getNonBlockingBody().copyContent());
} else {
return new HttpResponse(response.getResponseHeader().copy());
}
}
static int computeRemaining(ByteBuffer[] bufs) {
int remaining = 0;
for (ByteBuffer byteBuffer : bufs) {
remaining += byteBuffer.remaining();
}
return remaining;
}
/**
* copies the given buffer
*
* @param buffer the buffer to copy
* @return the copy
*/
static ByteBuffer copy(ByteBuffer buffer) {
if (buffer == null) {
return null;
}
return ByteBuffer.wrap(DataConverter.toBytes(buffer));
}
/**
* (deep) copy of the byte buffer array
*
* @param buffers the byte buffer array
* @return the copy
*/
static ByteBuffer[] copy(ByteBuffer[] buffers) {
if (buffers == null) {
return null;
}
ByteBuffer[] copy = new ByteBuffer[buffers.length];
for (int i = 0; i < copy.length; i++) {
copy[i] = copy(buffers[i]);
}
return copy;
}
/**
* returns the execution mode for request timeout handler
*
* @param requestTimeoutHandler the request timeout handler
* @return the execution mode
*/
static boolean isRequestTimeoutHandlerMultithreaded(Class<IHttpRequestTimeoutHandler> clazz) {
Boolean isMultithreaded = requestTimeoutHandlerExecutionModeCache.get(clazz);
if (isMultithreaded == null) {
int mode = IHttpRequestTimeoutHandler.DEFAULT_EXECUTION_MODE;
Execution execution = clazz.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
try {
Method meth = clazz.getMethod("onRequestTimeout", new Class[] { IHttpConnection.class });
execution = meth.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
} catch (NoSuchMethodException nsme) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("shouldn't occure because request timeout handlerr has to have such a method " + nsme.toString());
}
}
isMultithreaded = (mode == Execution.MULTITHREADED);
bodyCompleteListenerExecutionModeCache.put(clazz, isMultithreaded);
}
return isMultithreaded;
}
/**
* returns the execution mode for the given body complete listener
*
* @param bodyHandler the body handler
* @return the execution mode
*/
static boolean isBodyCloseListenerMutlithreaded(IBodyCloseListener closeListener) {
Boolean isMutlithreaded = bodyCloseListenerExecutionModeCache.get(closeListener.getClass());
if (isMutlithreaded == null) {
int mode = IBodyDataHandler.DEFAULT_EXECUTION_MODE;
Execution execution = closeListener.getClass().getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
try {
Method meth = closeListener.getClass().getMethod("onClose", new Class[] { });
execution = meth.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
} catch (NoSuchMethodException nsme) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("shouldn't occure because body handler has to have such a method " + nsme.toString());
}
}
isMutlithreaded = (mode == Execution.MULTITHREADED);
bodyCloseListenerExecutionModeCache.put(closeListener.getClass(), isMutlithreaded);
}
return isMutlithreaded;
}
static Map<String, List<String>> parseParamters(String txt, String encoding) {
Map<String, List<String>> result = new HashMap<String, List<String>>();
try {
String[] params = txt.split("&");
for (String param : params) {
String[] kv = param.split("=");
if (kv.length > 1) {
String name = URLDecoder.decode(kv[0], encoding);
List<String> values = result.get(name);
if (values == null) {
values = new ArrayList<String>();
result.put(name, values);
}
values.add(URLDecoder.decode(kv[1], encoding));
}
}
return result;
} catch (UnsupportedEncodingException use) {
throw new RuntimeException(use.toString());
}
}
/**
* get the implementation version
*
* @return the implementation version
*/
public static String getImplementationVersion() {
if (implementationVersion == null) {
readVersionFile();
}
return implementationVersion;
}
/**
* get the xSocket implementation version
*
* @return the xSocket implementation version
*/
static String getXSocketImplementationVersion() {
if (xSocketImplementationVersion == null) {
readVersionFile();
}
return xSocketImplementationVersion;
}
/**
* get the implementation date
*
* @return the implementation date
*/
public static String getImplementationDate() {
if (implementationDate== null) {
readVersionFile();
}
return implementationDate;
}
private static void readVersionFile() {
implementationVersion = "<unknown>";
implementationDate = "<unknown>";
InputStreamReader isr = null;
LineNumberReader lnr = null;
try {
isr = new InputStreamReader(HttpUtils.class.getResourceAsStream("/org/xlightweb/version.txt"));
if (isr != null) {
lnr = new LineNumberReader(isr);
String line = null;
do {
line = lnr.readLine();
if (line != null) {
if (line.startsWith("Implementation-Version=")) {
implementationVersion = line.substring("Implementation-Version=".length(), line.length()).trim();
} else if (line.startsWith("Implementation-Date=")) {
implementationDate = line.substring("Implementation-Date=".length(), line.length()).trim();
} else if (line.startsWith("Dependency.xSocket.Implementation-Version=")) {
xSocketImplementationVersion = line.substring("Dependency.xSocket.Implementation-Version=".length(), line.length()).trim();
}
}
} while (line != null);
lnr.close();
}
} catch (Exception ioe) {
implementationDate = "<unknown>";
implementationVersion = "<unknown>";
xSocketImplementationVersion = "<unknown>";
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("could not read version file. reason: " + ioe.toString());
}
} finally {
try {
if (lnr != null) {
lnr.close();
}
if (isr != null) {
isr.close();
}
} catch (IOException ioe) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("exception occured by closing version.txt file stream " + ioe.toString());
}
}
}
}
/**
* returns if handler is multi threaded
*
* <br/><br/><b>This is a xSocket internal method and subject to change</b>
*
* @param clazz the handler class
* @param dflt the default value
* @return true, if multi threaded
*/
public static boolean isHandlerMultithreaded(Class<? extends Object> clazz, boolean dflt) {
Execution execution = clazz.getAnnotation(Execution.class);
if (execution != null) {
if(execution.value() == Execution.NONTHREADED) {
return false;
} else {
return true;
}
} else {
return dflt;
}
}
/**
* returns if the handler method is multi threaded
*
* <br/><br/><b>This is a xSocket internal method and subject to change</b>
*
* @param clazz the handler class
* @param methodname the method name
* @param dflt the default value
* @param paramClass the method parameter classes
* @return true, if multi threaded
*/
@SuppressWarnings("unchecked")
public static boolean isMethodMultithreaded(Class clazz, String methodname, boolean dflt, Class... paramClass) {
try {
Method meth = clazz.getMethod(methodname, paramClass);
Execution execution = meth.getAnnotation(Execution.class);
if (execution != null) {
if(execution.value() == Execution.NONTHREADED) {
return false;
} else {
return true;
}
} else {
return dflt;
}
} catch (NoSuchMethodException nsme) {
return dflt;
}
}
static boolean isSynchronizedOnSession(Class<? extends Object> clazz, boolean dflt) {
SynchronizedOn synchronizedOn = clazz.getAnnotation(SynchronizedOn.class);
if (synchronizedOn != null) {
if(synchronizedOn.value() == SynchronizedOn.SESSION) {
return true;
} else {
return false;
}
} else {
return dflt;
}
}
@SuppressWarnings("unchecked")
static boolean isSynchronizedOnSession(Class clazz, String methodname, boolean dflt, Class... paramClass) {
try {
Method meth = clazz.getMethod(methodname, paramClass);
SynchronizedOn synchronizedOn = meth.getAnnotation(SynchronizedOn.class);
if (synchronizedOn != null) {
if(synchronizedOn.value() == SynchronizedOn.SESSION) {
return true;
} else {
return false;
}
} else {
return dflt;
}
} catch (NoSuchMethodException nsme) {
return dflt;
}
}
static boolean isInvokeOnMessageReceived(Class<? extends Object> clazz, boolean dflt) {
InvokeOn invokeOn = clazz.getAnnotation(InvokeOn.class);
if (invokeOn != null) {
if(invokeOn.value() == InvokeOn.MESSAGE_RECEIVED) {
return true;
} else {
return false;
}
} else {
return dflt;
}
}
@SuppressWarnings("unchecked")
static boolean isInvokeOnMessageReceived(Class clazz, String methodname, boolean dflt, Class... paramClass) {
try {
Method meth = clazz.getMethod(methodname, paramClass);
InvokeOn invokeOn = meth.getAnnotation(InvokeOn.class);
if (invokeOn != null) {
if(invokeOn.value() == InvokeOn.MESSAGE_RECEIVED) {
return true;
} else {
return false;
}
} else {
return dflt;
}
} catch (NoSuchMethodException nsme) {
return dflt;
}
}
/**
* returns a new map cache
*
* @param <T> the key type
* @param <E> the value type
* @param maxSize the max size
* @return the cache
*/
public static <T, E> Map<T, E> newMapCache(int maxSize) {
return Collections.synchronizedMap(new Cache<T, E>(maxSize));
}
private static final class Cache<T, E> extends LinkedHashMap<T, E> {
private static final long serialVersionUID = 4513864504007457500L;
private int maxSize = 0;
Cache(int maxSize) {
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Entry<T, E> eldest) {
return size() > maxSize;
}
}
static class HttpConnectionHandlerInfo {
private boolean isConnectHandler = false;
private boolean isConnectHandlerMultithreaded = true;
private boolean isDisconnectHandler = false;
private boolean isDisconnectHandlerMultithreaded = true;
@SuppressWarnings("unchecked")
public HttpConnectionHandlerInfo(Class clazz) {
if (clazz == null) {
return;
}
if (IHttpConnectHandler.class.isAssignableFrom(clazz)) {
isConnectHandler = true;
isConnectHandlerMultithreaded = isOnConnectMultithreaded(clazz);
}
if (IHttpDisconnectHandler.class.isAssignableFrom(clazz)) {
isDisconnectHandler = true;
isDisconnectHandlerMultithreaded = isOnDisconnectMultithreaded(clazz);
}
}
static boolean isOnConnectMultithreaded(Class<IHttpRequestHandler> serverHandlerClass) {
int mode = IHttpRequestHandler.DEFAULT_EXECUTION_MODE;
Execution execution = serverHandlerClass.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
return (mode == Execution.MULTITHREADED);
}
try {
Method meth = serverHandlerClass.getMethod("onConnect", new Class[] { IHttpConnection.class });
execution = meth.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
} catch (NoSuchMethodException nsme) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("shouldn't occure because body handler has to have such a method " + nsme.toString());
}
}
return (mode == Execution.MULTITHREADED);
}
static boolean isOnDisconnectMultithreaded(Class<IHttpRequestHandler> serverHandlerClass) {
int mode = IHttpRequestHandler.DEFAULT_EXECUTION_MODE;
Execution execution = serverHandlerClass.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
return (mode == Execution.MULTITHREADED);
}
try {
Method meth = serverHandlerClass.getMethod("onDisconnect", new Class[] { IHttpConnection.class });
execution = meth.getAnnotation(Execution.class);
if (execution != null) {
mode = execution.value();
}
} catch (NoSuchMethodException nsme) {
if (LOG.isLoggable(Level.FINE)) {
LOG.fine("shouldn't occure because body handler has to have such a method " + nsme.toString());
}
}
return (mode == Execution.MULTITHREADED);
}
public boolean isConnectHandler() {
return isConnectHandler;
}
public boolean isConnectHandlerMultithreaded() {
return isConnectHandlerMultithreaded;
}
public boolean isDisconnectHandler() {
return isDisconnectHandler;
}
public boolean isDisconnectHandlerMultithreaded() {
return isDisconnectHandlerMultithreaded;
}
}
private static final class ExtendedHttpRequestHeaderWrapper extends HttpRequestHeaderWrapper {
private static final Boolean NULL_BOOLEAN = null;
private Map<String, List<String>> bodyParamsMap = new HashMap<String, List<String>>();
ExtendedHttpRequestHeaderWrapper(IHttpRequest request) throws IOException {
super(request.getRequestHeader());
if (HttpUtils.isContentTypeFormUrlencoded(request) && request.hasBody()) {
bodyParamsMap.putAll(parseParamters(request.getNonBlockingBody().toString(), HttpUtils.getContentTypedFormUrlencodedEncodingType(request)));
}
}
public Integer getIntParameter(String name) {
String s = getParameter(name);
if (s != null) {
return Integer.parseInt(s);
} else {
return null;
}
}
public int getIntParameter(String name, int defaultVal) {
String s = getParameter(name);
if (s != null) {
try {
return Integer.parseInt(s);
} catch (Exception e) {
return defaultVal;
}
} else {
return defaultVal;
}
}
public Long getLongParameter(String name) {
String s = getParameter(name);
if (s != null) {
return Long.parseLong(s);
} else {
return null;
}
}
public long getLongParameter(String name, long defaultVal) {
String s = getParameter(name);
if (s != null) {
try {
return Long.parseLong(s);
} catch (Exception e) {
return defaultVal;
}
} else {
return defaultVal;
}
}
public Double getDoubleParameter(String name) {
String s = getParameter(name);
if (s != null) {
return Double.parseDouble(s);
} else {
return null;
}
}
public double getDoubleParameter(String name, double defaultVal) {
String s = getParameter(name);
if (s != null) {
try {
return Double.parseDouble(s);
} catch (Exception e) {
return defaultVal;
}
} else {
return defaultVal;
}
}
public Float getFloatParameter(String name) {
String s = getParameter(name);
if (s != null) {
return Float.parseFloat(s);
} else {
return null;
}
}
public float getFloatParameter(String name, float defaultVal) {
String s = getParameter(name);
if (s != null) {
try {
return Float.parseFloat(s);
} catch (Exception e) {
return defaultVal;
}
} else {
return defaultVal;
}
}
public Boolean getBooleanParameter(String name) {
String s = getParameter(name);
if (s != null) {
return Boolean.parseBoolean(s);
} else {
return NULL_BOOLEAN;
}
}
public boolean getBooleanParameter(String name, boolean defaultVal) {
String s = getParameter(name);
if (s != null) {
try {
return Boolean.parseBoolean(s);
} catch (Exception e) {
return defaultVal;
}
} else {
return defaultVal;
}
}
public String getParameter(String name) {
if (bodyParamsMap.containsKey(name)) {
return bodyParamsMap.get(name).get(0);
}
return getWrappedRequestHeader().getParameter(name);
}
public String[] getParameterValues(String name) {
ArrayList<String> result = new ArrayList<String>();
if (bodyParamsMap.containsKey(name)) {
result.addAll(bodyParamsMap.get(name));
}
String[] v = getWrappedRequestHeader().getParameterValues(name);
result.addAll(Arrays.asList(v));
return result.toArray(new String[result.size()]);
}
public void setParameter(String parameterName, String parameterValue) {
if (bodyParamsMap.containsKey(parameterName)) {
throw new RuntimeException("parameter is contained in body and can not be modified");
}
getWrappedRequestHeader().setParameter(parameterName, parameterValue);
}
public Set<String> getParameterNameSet() {
Set<String> result = new HashSet<String>();
result.addAll(getWrappedRequestHeader().getParameterNameSet());
result.addAll(bodyParamsMap.keySet());
return result;
}
@SuppressWarnings("unchecked")
public Enumeration getParameterNames() {
return Collections.enumeration(getParameterNameSet());
}
}
static final class CompletionHandlerInfo {
private boolean isOnWrittenMultithreaded = false;
private boolean isOnExceptionMultithreaded = false;
public CompletionHandlerInfo(IWriteCompletionHandler handler) {
boolean isHandlerMultithreaded = isHandlerMultithreaded(handler.getClass(), true);
isOnWrittenMultithreaded = isMethodMultithreaded(handler.getClass(), "onWritten", isHandlerMultithreaded, int.class);
isOnExceptionMultithreaded = isMethodMultithreaded(handler.getClass(), "onException", isHandlerMultithreaded, IOException.class);
}
public boolean isOnWrittenMultithreaded() {
return isOnWrittenMultithreaded;
}
public boolean isOnExceptionMutlithreaded() {
return isOnExceptionMultithreaded;
}
}
}