package com.dbxml.labrador.rest;
/*
* The dbXML Labrador Software License, Version 1.0
*
*
* Copyright (c) 2003 The dbXML Group, L.L.C. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by The
* dbXML Group, L.L.C. (http://www.dbxml.com/)."
* Alternately, this acknowledgment may appear in the software
* itself, if and wherever such third-party acknowledgments normally
* appear.
*
* 4. The names "Labrador" and "dbXML Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* info@dbxml.com
*
* 5. Products derived from this software may not be called "Labrador",
* nor may "Labrador" appear in their name, without prior written
* permission of The dbXML Group, L.L.C..
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE DBXML GROUP, L.L.C. OR ITS
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* $Id: RESTHandler.java,v 1.32 2003/12/14 05:17:57 bradford Exp $
*/
import com.dbxml.labrador.*;
import java.io.*;
import com.dbxml.labrador.broker.Broker;
import com.dbxml.labrador.broker.BrokerContext;
import com.dbxml.labrador.configuration.Configuration;
import com.dbxml.labrador.configuration.ConfigurationCallback;
import com.dbxml.labrador.configuration.ConfigurationException;
import com.dbxml.labrador.exceptions.RequestException;
import com.dbxml.labrador.http.HTTP;
import com.dbxml.labrador.types.ArrayConversions;
import com.dbxml.labrador.types.Types;
import com.dbxml.labrador.types.Variant;
import com.dbxml.labrador.util.SimpleConfigurable;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.xml.bind.Element;
import org.w3c.dom.Node;
/**
* RESTHandler
*/
public final class RESTHandler extends SimpleConfigurable implements Handler {
private static final int BUFFER_SIZE = 8192;
private static final String PROTOCOL = "REST";
public static final String PREFIX = "/rest/";
public static final String CONTENT_TYPE = "REST_CONTENT_TYPE";
public static final String DEFAULT_METHOD = "REST_DEFAULT_METHOD";
private static final String METHOD = "method";
private static final String MIMEMAPPINGS = "mime-mappings";
private static final String MIMEMAPPING = "mime-mapping";
private static final String EXT = "ext";
private static final String TYPE = "type";
private Map mimeTypes = new HashMap();
public RESTHandler() {
}
public String getProtocol() {
return PROTOCOL;
}
public boolean isRequestValid(Request request) {
return request.getPath().startsWith(PREFIX);
}
public ID getInstanceID(Request request) {
return new ID(request.getPath().substring(PREFIX.length() - 1));
}
public void setConfig(Configuration config) throws ConfigurationException {
super.setConfig(config);
Configuration mimeCfg = config.getChild(MIMEMAPPINGS);
if ( mimeCfg != null ) {
mimeCfg.processChildren(MIMEMAPPING, new ConfigurationCallback() {
public void process(Configuration cfg) {
String ext = cfg.getAttribute(EXT);
String type = cfg.getAttribute(TYPE);
mimeTypes.put(ext, type);
}
});
}
}
private String getMimeType(ID id) {
String path = id.toString();
int idx = path.lastIndexOf('.');
if ( idx != -1 ) {
String ext = path.substring(idx + 1);
return (String)mimeTypes.get(ext);
}
return null;
}
private void writeValue(OutputStream os, Object o) throws IOException {
if ( o != null && o.getClass().isArray() )
writeArray(os, o);
else
writeVariant(os, new Variant(o));
}
private void writeValue(PrintWriter pw, Object o) throws IOException {
if ( o != null && o.getClass().isArray() )
writeArray(pw, o);
else
writeVariant(pw, new Variant(o));
}
private void writeArray(OutputStream os, Object o) throws IOException {
if ( o instanceof byte[] ) {
byte[] b = (byte[])o;
os.write(b);
}
else {
List l = ArrayConversions.toList(o);
writeVariant(os, new Variant(l));
}
}
private void writeArray(PrintWriter pw, Object o) throws IOException {
if ( o instanceof byte[] ) {
byte[] b = (byte[])o;
pw.write(new String(b));
}
else {
List l = ArrayConversions.toList(o);
writeVariant(pw, new Variant(l));
}
}
private void writeVariant(PrintWriter pw, Variant v) throws IOException {
if ( v.isNull() )
return;
switch ( v.getType() ) {
case Types.VOID:
case Types.EMPTY:
break;
case Types.BOOLEAN:
case Types.BYTE:
case Types.SHORT:
case Types.INT:
case Types.LONG:
case Types.DOUBLE:
case Types.FLOAT:
case Types.STRING:
case Types.CHAR:
case Types.NODE:
case Types.DATE:
pw.println(v.toString());
break;
case Types.LIST:
Iterator l = v.getList().iterator();
while ( l.hasNext() )
writeVariant(pw, new Variant(l.next()));
break;
case Types.MAP:
Map m = v.getMap();
Iterator k = m.keySet().iterator();
while ( k.hasNext() ) {
Object key = k.next();
pw.print(key + "=");
writeVariant(pw, new Variant(m.get(key)));
}
break;
case Types.OBJECT:
writeValue(pw, v.getObject());
break;
}
}
private void writeVariant(OutputStream os, Variant v) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(os, "UTF8");
BufferedWriter bw = new BufferedWriter(osw, 4096);
PrintWriter pw = new PrintWriter(bw);
writeVariant(pw, v);
pw.flush();
}
public void processRequest(Request request, Response response, Instance instance) throws RequestException {
String methodName = request.getValue(METHOD);
if ( methodName == null || methodName.length() == 0 ) {
Object val = instance.getProperty(DEFAULT_METHOD);
if ( val instanceof String )
methodName = (String)val;
else
throw new RequestException("No method name was set");
}
instance.setMethodName(methodName);
BrokerContext context = Broker.getInstance().getBrokerContext();
Discovery disc = context.getDiscovery();
String[] paramNames = disc.listParams(methodName);
boolean postUsed = false;
for ( int i = 0; paramNames != null && i < paramNames.length; i++ ) {
boolean array = disc.isParamArray(methodName, i);
String[] vals = request.getValues(paramNames[i]);
if ( vals != null ) {
if ( array )
instance.setParameter(i, vals);
else
instance.setParameter(i, vals[0]);
}
else if ( !postUsed && request.hasContent() ) {
// If there is POST data, and this parameter is a String, Document,
// or byte array, then let us assume that the POST data is intended
// for this one.
try {
InputStream is = request.getInputStream();
ByteArrayOutputStream bos = new ByteArrayOutputStream(BUFFER_SIZE);
byte[] buffer = new byte[BUFFER_SIZE];
int size;
do {
size = is.read(buffer);
if ( size > 0 )
bos.write(buffer, 0, size);
}
while ( size != -1 );
instance.setParameter(i, bos.toByteArray());
postUsed = true;
}
catch ( IOException e ) {
/** @todo Hmmm */
}
}
else
instance.setParameter(i, null);
}
// Initially set the MIME type in case we get an HTTP 403 Exception
ID id = context.getId();
String contentType = getMimeType(id);
if ( contentType != null )
response.setHeader(HTTP.HEADER_CONTENT_TYPE, contentType);
Object o = instance.invoke();
Object type = context.getProperty(CONTENT_TYPE);
if ( type == null )
type = instance.getProperty(CONTENT_TYPE+'_'+methodName);
if ( type instanceof String ) {
contentType = (String)type;
response.setHeader(HTTP.HEADER_CONTENT_TYPE, contentType);
}
if ( contentType == null ) {
if ( o instanceof Node || o instanceof Element )
contentType = Headers.TYPE_TEXT_XML;
else
contentType = Headers.TYPE_TEXT_PLAIN;
response.setHeader(HTTP.HEADER_CONTENT_TYPE, contentType);
}
try {
OutputStream os = response.getOutputStream();
writeValue(os, o);
os.flush(); // Is this needed?
os.close(); // Is this needed?
response.close();
}
catch ( IOException e ) {
throw new RequestException(e.getMessage());
}
}
public void processError(Response response, String code, String message) {
// NOOP
}
}