Subversion Repositories aduna

Compare Revisions

Rev 10203 → Rev 10206

trunk/LICENSE.txt
1,4 → 1,4
Copyright (c) 2006-2009, James Leigh All rights reserved.
Copyright (c) 2006-2010, James Leigh All rights reserved.
 
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
trunk/object-repository/src/test/java/org/openrdf/repository/object/InterceptTest.java
1,8 → 1,17
package org.openrdf.repository.object;
 
import javax.xml.datatype.DatatypeConfigurationException;
import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.XMLGregorianCalendar;
 
import junit.framework.Test;
 
import org.openrdf.model.impl.URIImpl;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.model.vocabulary.XMLSchema;
import org.openrdf.repository.object.annotations.iri;
import org.openrdf.repository.object.annotations.parameterTypes;
import org.openrdf.repository.object.base.ObjectRepositoryTestCase;
import org.openrdf.repository.object.concepts.Message;
12,6 → 21,32
return ObjectRepositoryTestCase.suite(InterceptTest.class);
}
 
@iri("urn:test:IConcept")
public interface IConcept extends RDFObject {
@iri("urn:test:date")
public XMLGregorianCalendar getDate();
public void setDate(XMLGregorianCalendar date);
@iri("urn:test:time")
public XMLGregorianCalendar getTime();
public void setTime(XMLGregorianCalendar time);
}
 
public abstract static class CatchBehaviour implements IConcept {
@parameterTypes({})
public XMLGregorianCalendar getTime(Message msg) {
try {
msg.proceed();
} catch (IllegalArgumentException e) {
try {
return DatatypeFactory.newInstance().newXMLGregorianCalendar();
} catch (DatatypeConfigurationException e1) {
return null;
}
}
return (XMLGregorianCalendar) msg.getFunctionalLiteralResponse();
}
}
 
public static class CConcept {
public static int count;
 
40,8 → 75,10
}
 
public void setUp() throws Exception {
config.addConcept(CConcept.class, new URIImpl("urn:test:Concept"));
config.addBehaviour(Behaviour.class, new URIImpl("urn:test:Concept"));
config.addConcept(CConcept.class, "urn:test:Concept");
config.addBehaviour(Behaviour.class, "urn:test:Concept");
config.addConcept(IConcept.class);
config.addBehaviour(CatchBehaviour.class);
super.setUp();
}
 
64,4 → 101,32
assertEquals(1, Behaviour.count);
assertEquals(0, CConcept.count);
}
 
public void testIllegalArgument() throws Exception {
IConcept concept = con.addDesignation(
con.getObject("urn:test:concept"), IConcept.class);
ValueFactory vf = con.getValueFactory();
Resource subj = concept.getResource();
URI pred = vf.createURI("urn:test:date");
Literal lit = vf.createLiteral("noon", XMLSchema.DATETIME);
con.add(subj, pred, lit);
try {
concept.getDate();
fail();
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("noon"));
}
}
 
public void testCatchIllegalArgument() throws Exception {
IConcept concept = con.addDesignation(
con.getObject("urn:test:concept"), IConcept.class);
ValueFactory vf = con.getValueFactory();
Resource subj = concept.getResource();
URI pred = vf.createURI("urn:test:time");
Literal lit = vf.createLiteral("noon", XMLSchema.DATETIME);
con.add(subj, pred, lit);
XMLGregorianCalendar zero = DatatypeFactory.newInstance().newXMLGregorianCalendar();
assertEquals(zero, concept.getTime());
}
}
trunk/object-server/src/test/java/org/openrdf/http/object/DataResourceTest.java
1,7 → 1,5
package org.openrdf.http.object;
 
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.Date;
74,24 → 72,6
}
}
 
public void testGETIfModifiedSince() throws Exception {
WebResource hello = client.path("hello");
hello.put("world");
Date lastModified = hello.head().getLastModified();
try {
hello.header("If-Modified-Since", lastModified).get(String.class);
fail();
} catch (UniformInterfaceException e) {
assertEquals(304, e.getResponse().getStatus());
}
Thread.sleep(1000);
File dir = new File(new File(dataDir, host.replace(':', File.separatorChar)), "hello");
FileWriter out = new FileWriter(dir.listFiles()[0]);
out.write("bad world");
out.close();
assertEquals("bad world", hello.header("If-Modified-Since", lastModified).get(String.class));
}
 
public void testPUTIfUnmodifiedSince() throws Exception {
WebResource hello = client.path("hello");
hello.put("world");
trunk/object-server/src/test/java/org/openrdf/http/object/behaviours/PUTSupport.java
2,12 → 2,9
 
import info.aduna.net.ParsedURI;
 
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
 
import org.openrdf.http.object.annotations.header;
import org.openrdf.http.object.annotations.method;
21,9 → 18,6
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.exceptions.BehaviourException;
 
import eu.medsea.mimeutil.MimeType;
import eu.medsea.mimeutil.MimeUtil;
 
public abstract class PUTSupport implements HTTPFileObject {
 
@operation( {})
46,7 → 40,7
in.close();
}
if (mediaType == null) {
setInternalMediaType(getMimeType());
setInternalMediaType("application/octet-stream");
} else {
setInternalMediaType(mediaType);
}
61,37 → 55,6
}
}
 
private String getMimeType() throws IOException {
List<MimeType> types = getMimeTypes();
MimeType mimeType = null;
double specificity = 0;
for (MimeType mt : types) {
int spec = mt.getSpecificity() * 2;
if (!mt.getSubType().startsWith("x-")) {
spec += 1;
}
if (spec > specificity) {
mimeType = mt;
specificity = spec;
}
}
if (mimeType == null)
return "application/octet-stream";
return mimeType.toString();
}
 
private List<MimeType> getMimeTypes() throws IOException {
InputStream in = new BufferedInputStream(openInputStream());
try {
List<MimeType> types = new ArrayList<MimeType>();
types.addAll(MimeUtil.getMimeTypes(in));
types.addAll(MimeUtil.getMimeTypes(getResource().stringValue()));
return types;
} finally {
in.close();
}
}
 
private void setInternalMediaType(String mediaType) {
ObjectConnection con = getObjectConnection();
ValueFactory vf = con.getValueFactory();
trunk/object-server/src/test/java/org/openrdf/http/object/base/MetadataServerTestCase.java
3,10 → 3,8
import info.aduna.io.FileUtil;
 
import java.io.File;
import java.net.BindException;
import java.net.ConnectException;
 
import javax.xml.transform.TransformerConfigurationException;
 
import junit.framework.TestCase;
 
import org.openrdf.http.object.HTTPObjectServer;
21,6 → 19,7
import org.openrdf.sail.optimistic.OptimisticRepository;
 
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientHandlerException;
import com.sun.jersey.api.client.UniformInterfaceException;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.filter.GZIPContentEncodingFilter;
42,15 → 41,8
vf = repository.getValueFactory();
dataDir = FileUtil.createTempDir("metadata");
server = createServer();
while (true) {
try {
server.setPort(port++);
server.start();
break;
} catch (BindException e) {
continue;
}
}
server.setPort(port);
server.start();
host = "localhost:" + server.getPort();
client = Client.create().resource("http://" + host);
addContentEncoding(client);
79,6 → 71,11
} catch (UniformInterfaceException e) {
System.out.println(e.getResponse().getEntity(String.class));
throw e;
} catch (ClientHandlerException e) {
if (e.getCause() instanceof ConnectException) {
System.out.println("Could not connect to port " + server.getPort());
}
throw e;
}
}
 
trunk/object-server/src/test/java/org/openrdf/http/object/RangeTest.java
73,6 → 73,7
WebResource web = client.path("/hello");
ClientResponse resp = web.type("application/string").put(ClientResponse.class, "Hello World!");
String m = resp.getHeaders().getFirst("Last-Modified");
assertNotNull(m);
assertEquals("Hello", web.header("If-Range", m).header("Range", "bytes=0-4").get(String.class));
}
 
80,6 → 81,7
WebResource web = client.path("/hello");
ClientResponse resp = web.type("application/string").put(ClientResponse.class, "Hello World!");
String m = resp.getHeaders().getFirst("Last-Modified");
assertNotNull(m);
Thread.sleep(1000);
web.type("application/string").put("Hey World!");
assertEquals("Hey World!", web.header("If-Range", m).header("Range", "bytes=0-4").get(String.class));
trunk/object-server/src/test/java/org/openrdf/http/object/providers/ModelMessageProvider.java
12,6 → 12,7
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.Map;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
24,6 → 25,7
import javax.ws.rs.ext.MessageBodyWriter;
 
import org.openrdf.http.object.helpers.BackgroundGraphResult;
import org.openrdf.http.object.util.SharedExecutors;
import org.openrdf.model.Model;
import org.openrdf.model.Statement;
import org.openrdf.model.impl.LinkedHashModel;
41,7 → 43,7
 
public class ModelMessageProvider implements MessageBodyReader<Model>,
MessageBodyWriter<Model> {
private static ExecutorService executor = Executors.newFixedThreadPool(3);
private static Executor executor = SharedExecutors.getParserThreadPool();
private Class<Model> type = Model.class;
private RDFParserRegistry parsers = RDFParserRegistry.getInstance();
private RDFWriterRegistry writers = RDFWriterRegistry.getInstance();
trunk/object-server/src/test/java/org/openrdf/http/object/providers/RDFObjectProviderTest.java
49,10 → 49,13
 
public void testNamedAuthor() throws Exception {
ObjectConnection con = repository.getConnection();
try {
Person author = con.addDesignation(con.getObject(base+"/auth"), Person.class);
author.setName("James");
con.addDesignation(con.getObject(base+"/doc"), Document.class).setAuthor(author);
con.close();
} finally {
con.close();
}
WebResource web = client.path("/doc").queryParam("author", "");
Model model = web.accept("application/rdf+xml").get(Model.class);
assertTrue(model.contains(null, vf.createURI("urn:test:name"), vf.createLiteral("James")));
trunk/object-server/src/main/java/org/openrdf/http/object/HTTPObjectServlet.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/readers/RDFObjectReader.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
84,7 → 84,8
}
subj = vf.createURI(location);
}
if (media != null) {
if (media != null && !media.contains("*")
&& !"application/octet-stream".equals(media)) {
Class<GraphQueryResult> t = GraphQueryResult.class;
GraphQueryResult result = delegate.readFrom(t, t, media, in,
charset, base, location, con);
trunk/object-server/src/main/java/org/openrdf/http/object/readers/SetOfRDFObjectReader.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
87,7 → 87,8
if (media == null && location != null) {
ValueFactory vf = con.getValueFactory();
subjects.add(vf.createURI(location));
} else if (media != null) {
} else if (media != null && !media.contains("*")
&& !"application/octet-stream".equals(media)) {
Class<GraphQueryResult> t = GraphQueryResult.class;
GraphQueryResult result = delegate.readFrom(t, t, media, in,
charset, base, location, con);
trunk/object-server/src/main/java/org/openrdf/http/object/readers/GraphMessageReader.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
30,11 → 30,11
 
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Executor;
 
import org.openrdf.http.object.helpers.BackgroundGraphResult;
import org.openrdf.http.object.readers.base.MessageReaderBase;
import org.openrdf.http.object.util.SharedExecutors;
import org.openrdf.query.GraphQueryResult;
import org.openrdf.rio.RDFFormat;
import org.openrdf.rio.RDFParser;
45,11 → 45,11
* Reads RDF graph messages.
*
* @author James Leigh
*
*
*/
public class GraphMessageReader extends
MessageReaderBase<RDFFormat, RDFParserFactory, GraphQueryResult> {
private static ExecutorService executor = Executors.newFixedThreadPool(3);
private static Executor executor = SharedExecutors.getParserThreadPool();
 
public GraphMessageReader() {
super(RDFParserRegistry.getInstance(), GraphQueryResult.class);
trunk/object-server/src/main/java/org/openrdf/http/object/HTTPObjectRequestHandler.java New file
0,0 → 1,522
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object;
 
import info.aduna.concurrent.locks.Lock;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PipedOutputStream;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
 
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
 
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.nio.ContentDecoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.entity.ConsumingNHttpEntity;
import org.apache.http.nio.entity.ConsumingNHttpEntityTemplate;
import org.apache.http.nio.entity.ContentListener;
import org.apache.http.nio.protocol.NHttpRequestHandler;
import org.apache.http.nio.protocol.NHttpResponseTrigger;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpExpectationVerifier;
import org.openrdf.OpenRDFException;
import org.openrdf.http.object.exceptions.InternalServerError;
import org.openrdf.http.object.exceptions.ResponseException;
import org.openrdf.http.object.locks.FileLockManager;
import org.openrdf.http.object.model.ErrorInputStream;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.InputStreamHttpEntity;
import org.openrdf.http.object.model.Request;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
import org.openrdf.http.object.util.NamedThreadFactory;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.object.ObjectRepository;
import org.openrdf.sail.optimistic.exceptions.ConcurrencyException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
* Applies the filters and handles the HTTP requests.
*
* @author James Leigh
*
*/
public class HTTPObjectRequestHandler implements NHttpRequestHandler,
HttpExpectationVerifier {
private static final String HANDLER_ATTR = Task.class.getName();
 
private final class Task implements Runnable {
private Request request;
private ResourceOperation operation;
private CountDownLatch latch = new CountDownLatch(1);
private NHttpResponseTrigger trigger;
private Task child;
private volatile boolean done;
 
private Task(Request request) {
this.request = request;
}
 
private Task(Request request, ResourceOperation operation) {
this.request = request;
this.operation = operation;
}
 
public boolean isOperation() {
return operation != null;
}
 
public boolean isStorable() {
return request.isStorable();
}
 
public boolean isSafe() {
return request.isSafe();
}
 
public long getReceivedOn() {
return request.getReceivedOn();
}
 
public void setTrigger(NHttpResponseTrigger trigger) {
this.trigger = trigger;
if (child != null) {
child.setTrigger(trigger);
}
if (trigger != null) {
latch.countDown();
}
}
 
public void run() {
try {
try {
if (operation == null) {
processRequest(request);
} else {
handleOperation(operation);
}
} catch (HttpException e) {
latch.await(1, TimeUnit.SECONDS);
if (trigger == null) {
logger.error(e.toString(), e);
} else {
trigger.handleException(e);
}
} catch (IOException e) {
latch.await(1, TimeUnit.SECONDS);
if (trigger == null) {
logger.error(e.toString(), e);
} else {
trigger.handleException(e);
}
}
} catch (InterruptedException e) {
logger.error(e.toString(), e);
}
}
 
private void processRequest(Request req) throws HttpException,
IOException, InterruptedException {
HttpResponse resp = intercept(req);
if (resp == null) {
try {
ResourceOperation op = new ResourceOperation(dataDir,
request, repository);
child = new Task(request, op);
child.setTrigger(trigger);
executor.execute(child);
} catch (Exception e) {
latch.await(1, TimeUnit.SECONDS);
if (trigger == null) {
logger.error(e.toString(), e);
} else {
try {
resp = createHttpResponse(new Response().server(e));
trigger.submitResponse(filter(req, resp));
} catch (IOException e1) {
trigger.handleException(e1);
} catch (Exception e1) {
trigger.handleException(new IOException(e1));
}
}
}
} else {
latch.await();
trigger.submitResponse(filter(req, resp));
}
}
 
private void handleOperation(ResourceOperation operation)
throws HttpException, IOException, InterruptedException {
Response resp;
try {
resp = handle(operation);
} finally {
done = true;
HttpEntity entity = operation.getEntity();
if (entity != null) {
entity.consumeContent();
}
}
latch.await();
trigger.submitResponse(filter(request, resp));
}
 
public boolean isDone() {
return done;
}
 
@Override
public String toString() {
return request.toString();
}
}
 
private final class Listener implements ContentListener {
private final ErrorInputStream in;
private final Task task;
private final PipedOutputStream out;
private ByteBuffer buf = ByteBuffer.allocate(1024);
 
private Listener(ErrorInputStream in, Task task, PipedOutputStream out) {
this.in = in;
this.task = task;
this.out = out;
}
 
public void contentAvailable(ContentDecoder decoder, IOControl ioctrl)
throws IOException {
try {
decoder.read(buf);
if (!task.isDone() && out != null) {
int p = buf.position();
out.write(buf.array(), buf.arrayOffset(), p);
}
buf.clear();
if (decoder.isCompleted() && out != null) {
out.close();
}
} catch (IOException e) {
in.error(e);
throw e;
}
}
 
public void finished() {
try {
if (out != null) {
out.close();
}
} catch (IOException e) {
in.error(e);
}
}
}
 
private static final Executor executor;
static {
int n = Runtime.getRuntime().availableProcessors();
Comparator<Runnable> cmp = new Comparator<Runnable>() {
public int compare(Runnable o1, Runnable o2) {
if (!(o1 instanceof Task) || !(o2 instanceof Task))
return 0;
Task t1 = (Task) o1;
Task t2 = (Task) o2;
if (!t1.isOperation() && t2.isOperation())
return -1;
if (t1.isOperation() && !t2.isOperation())
return 1;
if (t1.isSafe() && !t2.isSafe())
return -1;
if (!t1.isSafe() && t2.isSafe())
return 1;
if (t1.isStorable() && !t2.isStorable())
return -1;
if (!t1.isStorable() && t2.isStorable())
return 1;
if (t1.getReceivedOn() < t2.getReceivedOn())
return -1;
if (t1.getReceivedOn() > t2.getReceivedOn())
return 1;
return System.identityHashCode(t1)
- System.identityHashCode(t2);
};
};
BlockingQueue<Runnable> queue = new PriorityBlockingQueue<Runnable>(
n * 10, cmp);
executor = new ThreadPoolExecutor(n, n * 5, 60L, TimeUnit.SECONDS,
queue, new NamedThreadFactory("HTTP Handler"));
}
 
private Logger logger = LoggerFactory
.getLogger(HTTPObjectRequestHandler.class);
private Filter filter;
private Handler handler;
private ObjectRepository repository;
private File dataDir;
private FileLockManager locks = new FileLockManager();
 
public HTTPObjectRequestHandler(Filter filter, Handler handler,
ObjectRepository repository, File dataDir) {
this.filter = filter;
this.handler = handler;
this.repository = repository;
this.dataDir = dataDir;
}
 
public void verify(HttpRequest request, HttpResponse response,
HttpContext context) throws HttpException {
// TODO Auto-generated method stub
 
}
 
public ConsumingNHttpEntity entityRequest(
HttpEntityEnclosingRequest request, HttpContext context)
throws HttpException, IOException {
PipedOutputStream out = new PipedOutputStream();
ErrorInputStream in = new ErrorInputStream(out);
Task handler = new Task(process(request, in));
context.setAttribute(HANDLER_ATTR, handler);
executor.execute(handler);
ContentListener listener = new Listener(in, handler, out);
return new ConsumingNHttpEntityTemplate(request.getEntity(), listener);
}
 
public void handle(HttpRequest request, HttpResponse response,
NHttpResponseTrigger trigger, HttpContext context)
throws HttpException, IOException {
Task handler = (Task) context.getAttribute(HANDLER_ATTR);
if (handler == null) {
Task task = new Task(process(request, null));
task.setTrigger(trigger);
executor.execute(task);
} else {
context.removeAttribute(HANDLER_ATTR);
handler.setTrigger(trigger);
}
}
 
private Request process(HttpRequest request, InputStream in)
throws IOException {
Request req = new Request(request);
if (in == null) {
req.setEntity(null);
} else {
String type = req.getHeader("Content-Type");
String length = req.getHeader("Content-Length");
long size = -1;
if (length != null) {
size = Long.parseLong(length);
}
req.setEntity(new InputStreamHttpEntity(type, size, in));
}
return filter.filter(req);
}
 
private HttpResponse intercept(Request request) throws HttpException,
IOException {
return filter.intercept(request);
}
 
private Response handle(final ResourceOperation req) throws HttpException,
IOException {
try {
boolean close = true;
try {
req.init();
final Lock lock = createFileLock(req.getMethod(), req.getFile());
try {
Response resp = handler.handle(req);
if (req.isSafe()) {
req.rollback();
} else {
req.commit();
}
if (resp.isContent() && !resp.isException()
&& !resp.isHead()) {
resp.onClose(new Runnable() {
public void run() {
try {
req.close();
} catch (RepositoryException e) {
logger.error(e.toString(), e);
}
}
});
close = false;
}
return resp;
} finally {
if (lock != null) {
lock.release();
}
}
} finally {
if (close) {
req.close();
}
}
} catch (HttpException e) {
throw e;
} catch (IOException e) {
throw e;
} catch (ResponseException e) {
return new Response().exception(e);
} catch (Exception e) {
return new Response().exception(new InternalServerError(e));
}
}
 
private HttpResponse filter(Request request, Response resp)
throws IOException {
HttpResponse response;
try {
try {
response = createHttpResponse(resp);
} catch (ResponseException e) {
response = createHttpResponse(new Response().exception(e));
} catch (ConcurrencyException e) {
response = createHttpResponse(new Response().conflict(e));
} catch (MimeTypeParseException e) {
response = createHttpResponse(new Response().status(406,
"Not Acceptable"));
} catch (Exception e) {
logger.error(e.toString(), e);
response = createHttpResponse(new Response().server(e));
}
} catch (Exception e) {
logger.error(e.toString(), e);
ProtocolVersion ver = new ProtocolVersion("HTTP", 1, 1);
response = new BasicHttpResponse(ver, 500, "Internal Server Error");
}
return filter(request, response);
}
 
private HttpResponse filter(Request request, HttpResponse response)
throws IOException {
return filter.filter(request, response);
}
 
private HttpResponse createHttpResponse(Response resp) throws IOException,
OpenRDFException, XMLStreamException, TransformerException,
ParserConfigurationException, MimeTypeParseException {
ProtocolVersion ver = new ProtocolVersion("HTTP", 1, 1);
int code = resp.getStatus();
String phrase = resp.getMessage();
HttpResponse response = new BasicHttpResponse(ver, code, phrase);
for (Header hd : resp.getAllHeaders()) {
response.addHeader(hd);
}
if (resp.isException()) {
String type = "text/plain;charset=UTF-8";
response.setHeader("Content-Type", type);
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(out, "UTF-8");
PrintWriter print = new PrintWriter(writer);
try {
resp.getException().printTo(print);
} finally {
print.close();
}
byte[] body = out.toByteArray();
int size = body.length;
response.setHeader("Content-Length", String.valueOf(size));
ByteArrayInputStream in = new ByteArrayInputStream(body);
List<Runnable> onClose = resp.getOnClose();
response.setEntity(new InputStreamHttpEntity(type, size, in,
onClose));
} else if (resp.isContent()) {
String type = resp.getHeader("Content-Type");
MimeType mediaType = new MimeType(type);
Charset charset = getCharset(mediaType);
long size = resp.getSize(type, charset);
if (size >= 0) {
response.setHeader("Content-Length", String.valueOf(size));
} else {
response.setHeader("Transfer-Encoding", "chunked");
}
InputStream in = resp.write(type, charset);
List<Runnable> onClose = resp.getOnClose();
response.setEntity(new InputStreamHttpEntity(type, size, in,
onClose));
}
return response;
}
 
private Lock createFileLock(String method, File file)
throws InterruptedException {
if (!method.equals("PUT") && (file == null || !file.exists()))
return null;
boolean shared = method.equals("GET") || method.equals("HEAD")
|| method.equals("OPTIONS") || method.equals("TRACE")
|| method.equals("POST") || method.equals("PROPFIND");
return locks.lock(file, shared);
}
 
private Charset getCharset(MimeType m) {
if (m == null)
return null;
String name = m.getParameters().get("charset");
if (name == null)
return null;
return Charset.forName(name);
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/MessageBodyWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,7 → 29,7
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.OutputStream;
import java.io.InputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
 
56,9 → 56,8
String getContentType(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Charset charset);
 
void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, T result, String base, Charset charset,
OutputStream out, int bufSize) throws IOException,
OpenRDFException, XMLStreamException, TransformerException,
ParserConfigurationException;
InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, T result, String base, Charset charset)
throws IOException, OpenRDFException, XMLStreamException,
TransformerException, ParserConfigurationException;
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/RDFObjectWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
31,6 → 31,7
import static org.openrdf.query.QueryLanguage.SPARQL;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
103,6 → 104,21
}
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, RDFObject result, String base, Charset charset)
throws IOException, OpenRDFException {
ObjectConnection con = result.getObjectConnection();
Resource resource = result.getResource();
try {
Model model = new LinkedHashModel();
describeInto(con, resource, model);
return delegate.write(mimeType, Model.class, Model.class, of,
model, base, charset);
} catch (MalformedQueryException e) {
throw new AssertionError(e);
}
}
 
private void describeInto(ObjectConnection con, Resource resource,
Model model) throws MalformedQueryException, RepositoryException,
QueryEvaluationException {
trunk/object-server/src/main/java/org/openrdf/http/object/writers/SetOfRDFObjectWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
31,6 → 31,7
import static org.openrdf.query.QueryLanguage.SPARQL;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
83,22 → 84,28
 
public String getContentType(String mimeType, Class<?> type,
Type genericType, ObjectFactory of, Charset charset) {
return delegate.getContentType(mimeType, Model.class,
Model.class, of, charset);
return delegate.getContentType(mimeType, Model.class, Model.class, of,
charset);
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Set<?> set, String base, Charset charset,
OutputStream out, int bufSize) throws IOException, OpenRDFException {
Model result = getGraphResult(set);
delegate
.writeTo(mimeType, Model.class,
Model.class, of, result, base, charset, out,
bufSize);
delegate.writeTo(mimeType, Model.class, Model.class, of, result, base,
charset, out, bufSize);
}
 
private Model getGraphResult(Set<?> set)
throws RepositoryException, QueryEvaluationException {
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Set<?> set, String base, Charset charset)
throws IOException, OpenRDFException {
Model result = getGraphResult(set);
return delegate.write(mimeType, Model.class, Model.class, of, result,
base, charset);
}
 
private Model getGraphResult(Set<?> set) throws RepositoryException,
QueryEvaluationException {
Model model = new LinkedHashModel();
if (!set.isEmpty()) {
ObjectConnection con = null;
trunk/object-server/src/main/java/org/openrdf/http/object/writers/ModelMessageWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,6 → 29,7
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
96,4 → 97,13
GraphQueryResult.class, of, result, base, charset, out,
bufSize);
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Model model, String base, Charset charset)
throws IOException, OpenRDFException {
GraphQueryResult result = new GraphQueryResultImpl(model
.getNamespaces(), model);
return delegate.write(mimeType, GraphQueryResult.class,
GraphQueryResult.class, of, result, base, charset);
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/FormMapMessageWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,8 → 28,10
*/
package org.openrdf.http.object.writers;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
88,6 → 90,16
return "application/x-www-form-urlencoded";
}
 
public InputStream write(final String mimeType, final Class<?> type,
final Type genericType, final ObjectFactory of, final Map<String, Object> result,
final String base, final Charset charset) throws IOException,
OpenRDFException, XMLStreamException, TransformerException,
ParserConfigurationException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(mimeType, type, genericType, of, result, base, charset, out, 1024);
return new ByteArrayInputStream(out.toByteArray());
}
 
public void writeTo(String mimeType, Class<?> ctype, Type gtype,
ObjectFactory of, Map<String, Object> result, String base,
Charset charset, OutputStream out, int bufSize) throws IOException,
151,7 → 163,16
String txt = "text/plain";
Charset cs = Charset.forName("ISO-8859-1");
ByteArrayOutputStream out = new ByteArrayOutputStream();
delegate.writeTo(txt, ctype, gtype, of, value, base, cs, out, 4096);
InputStream in = delegate.write(txt, ctype, gtype, of, value, base, cs);
try {
int read;
byte[] buf = new byte[1024];
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
}
} finally {
in.close();
}
return out.toString("ISO-8859-1");
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/XMLEventMessageWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,21 → 29,27
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedOutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
 
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLOutputFactory;
import javax.xml.stream.XMLStreamException;
 
import org.openrdf.http.object.model.ErrorInputStream;
import org.openrdf.http.object.util.SharedExecutors;
import org.openrdf.repository.object.ObjectFactory;
 
/**
* Writes an XMLEventReader into an OutputStream.
*/
public class XMLEventMessageWriter implements MessageBodyWriter<XMLEventReader> {
private static Executor executor = SharedExecutors.getWriterThreadPool();
private static final Charset UTF8 = Charset.forName("UTF-8");
private XMLOutputFactory factory;
{
104,4 → 110,31
result.close();
}
}
 
public InputStream write(final String mimeType, final Class<?> type,
final Type genericType, final ObjectFactory of,
final XMLEventReader result, final String base,
final Charset charset) throws IOException {
final PipedOutputStream out = new PipedOutputStream();
final ErrorInputStream in = new ErrorInputStream(out);
executor.execute(new Runnable() {
public void run() {
try {
try {
writeTo(mimeType, type, genericType, of, result, base,
charset, out, 1024);
} finally {
out.close();
}
} catch (IOException e) {
in.error(e);
} catch (Exception e) {
in.error(new IOException(e));
} catch (Error e) {
in.error(new IOException(e));
}
}
});
return in;
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/ByteArrayMessageWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,7 → 28,9
*/
package org.openrdf.http.object.writers;
 
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
67,4 → 69,9
OutputStream out, int bufSize) throws IOException {
out.write(result);
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, byte[] result, String base, Charset charset) throws IOException {
return new ByteArrayInputStream(result);
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/DocumentFragmentMessageWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
32,14 → 32,18
import static javax.xml.transform.OutputKeys.OMIT_XML_DECLARATION;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedOutputStream;
import java.io.Reader;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
 
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
52,6 → 56,9
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
 
import org.openrdf.OpenRDFException;
import org.openrdf.http.object.model.ErrorInputStream;
import org.openrdf.http.object.util.SharedExecutors;
import org.openrdf.repository.object.ObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
67,6 → 74,7
private static final String XSL_FRAGMENT = "<stylesheet version='1.0' xmlns='http://www.w3.org/1999/XSL/Transform'>"
+ "<template match='/root'><copy-of select='*|text()|comment()'/></template></stylesheet>";
private static final Charset UTF8 = Charset.forName("UTF-8");
private static Executor executor = SharedExecutors.getWriterThreadPool();
 
private static class ErrorCatcher implements ErrorListener {
private Logger logger = LoggerFactory.getLogger(ErrorCatcher.class);
138,6 → 146,33
return mimeType;
}
 
public InputStream write(final String mimeType, final Class<?> type,
final Type genericType, final ObjectFactory of, final DocumentFragment result,
final String base, final Charset charset) throws IOException,
OpenRDFException, XMLStreamException, TransformerException,
ParserConfigurationException {
final PipedOutputStream out = new PipedOutputStream();
final ErrorInputStream in = new ErrorInputStream(out);
executor.execute(new Runnable() {
public void run() {
try {
try {
writeTo(mimeType, type, genericType, of, result, base, charset, out, 1024);
} finally {
out.close();
}
} catch (IOException e) {
in.error(e);
} catch (Exception e) {
in.error(new IOException(e));
} catch (Error e) {
in.error(new IOException(e));
}
}
});
return in;
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, DocumentFragment node, String base,
Charset charset, OutputStream out, int bufSize) throws IOException,
trunk/object-server/src/main/java/org/openrdf/http/object/writers/DatatypeWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, Zepheira All rights reserved.
* Copyright 2009-2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,6 → 29,7
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
82,4 → 83,12
charset, out, bufSize);
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Object object, String base, Charset charset)
throws IOException {
String label = of.createLiteral(object).getLabel();
return delegate.write(mimeType, String.class, String.class, of, label,
base, charset);
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/ReadableBodyWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,19 → 29,25
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PipedOutputStream;
import java.io.Writer;
import java.lang.reflect.Type;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
 
import org.openrdf.http.object.model.ErrorInputStream;
import org.openrdf.http.object.util.SharedExecutors;
import org.openrdf.repository.object.ObjectFactory;
 
/**
* Writes a Readable object into an OutputStream.
*/
public class ReadableBodyWriter implements MessageBodyWriter<Readable> {
private static Executor executor = SharedExecutors.getWriterThreadPool();
 
public boolean isWriteable(String mimeType, Class<?> type,
Type genericType, ObjectFactory of) {
70,6 → 76,31
return mimeType + ";charset=" + charset.name();
}
 
public InputStream write(final String mimeType, final Class<?> type,
final Type genericType, final ObjectFactory of, final Readable result,
final String base, final Charset charset) throws IOException {
final PipedOutputStream out = new PipedOutputStream();
final ErrorInputStream in = new ErrorInputStream(out);
executor.execute(new Runnable() {
public void run() {
try {
try {
writeTo(mimeType, type, genericType, of, result, base, charset, out, 1024);
} finally {
out.close();
}
} catch (IOException e) {
in.error(e);
} catch (Exception e) {
in.error(new IOException(e));
} catch (Error e) {
in.error(new IOException(e));
}
}
});
return in;
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Readable result, String base, Charset charset,
OutputStream out, int bufSize) throws IOException {
trunk/object-server/src/main/java/org/openrdf/http/object/writers/ByteArrayStreamMessageWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,8 → 28,10
*/
package org.openrdf.http.object.writers;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
68,4 → 70,10
Charset charset, OutputStream out, int bufSize) throws IOException {
result.writeTo(out);
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, ByteArrayOutputStream result, String base,
Charset charset) throws IOException {
return new ByteArrayInputStream(result.toByteArray());
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/StringBodyWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,7 → 28,10
*/
package org.openrdf.http.object.writers;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
51,7 → 54,8
Type genericType, ObjectFactory of) {
if (!String.class.equals(type))
return false;
return mimeType == null || mimeType.startsWith("text/") || mimeType.startsWith("*");
return mimeType == null || mimeType.startsWith("text/")
|| mimeType.startsWith("*");
}
 
public long getSize(String mimeType, Class<?> type, Type genericType,
66,7 → 70,8
if (charset == null) {
charset = UTF8;
}
if (mimeType == null || mimeType.startsWith("*") || mimeType.startsWith("text/*")) {
if (mimeType == null || mimeType.startsWith("*")
|| mimeType.startsWith("text/*")) {
mimeType = "text/plain";
}
if (mimeType.contains("charset=") || !mimeType.startsWith("text/"))
84,4 → 89,13
writer.write(result);
writer.flush();
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, String result, String base, Charset charset)
throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(mimeType, type, genericType, of, result, base, charset, out,
1024);
return new ByteArrayInputStream(out.toByteArray());
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/DOMMessageWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
31,12 → 31,16
import static javax.xml.transform.OutputKeys.ENCODING;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedOutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
 
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.ErrorListener;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
47,6 → 51,9
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
 
import org.openrdf.OpenRDFException;
import org.openrdf.http.object.model.ErrorInputStream;
import org.openrdf.http.object.util.SharedExecutors;
import org.openrdf.repository.object.ObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
59,6 → 66,7
*/
public class DOMMessageWriter implements MessageBodyWriter<Node> {
private static final Charset UTF8 = Charset.forName("UTF-8");
private static Executor executor = SharedExecutors.getWriterThreadPool();
 
private static class ErrorCatcher implements ErrorListener {
private Logger logger = LoggerFactory.getLogger(ErrorCatcher.class);
128,6 → 136,33
return mimeType;
}
 
public InputStream write(final String mimeType, final Class<?> type,
final Type genericType, final ObjectFactory of, final Node result,
final String base, final Charset charset) throws IOException,
OpenRDFException, XMLStreamException, TransformerException,
ParserConfigurationException {
final PipedOutputStream out = new PipedOutputStream();
final ErrorInputStream in = new ErrorInputStream(out);
executor.execute(new Runnable() {
public void run() {
try {
try {
writeTo(mimeType, type, genericType, of, result, base, charset, out, 1024);
} finally {
out.close();
}
} catch (IOException e) {
in.error(e);
} catch (Exception e) {
in.error(new IOException(e));
} catch (Error e) {
in.error(new IOException(e));
}
}
});
return in;
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Node node, String base, Charset charset,
OutputStream out, int bufSize) throws IOException,
trunk/object-server/src/main/java/org/openrdf/http/object/writers/PrimitiveBodyWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, Zepheira All rights reserved.
* Copyright 2009-2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,6 → 29,7
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
79,6 → 80,13
of, charset);
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Object result, String base, Charset charset)
throws IOException {
return delegate.write(mimeType, String.class, String.class, of, String
.valueOf(result), base, charset);
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Object result, String base, Charset charset,
OutputStream out, int bufSize) throws IOException {
trunk/object-server/src/main/java/org/openrdf/http/object/writers/AggregateWriter.java
29,6 → 29,7
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.net.URL;
116,16 → 117,14
return findWriter(mimeType, type, genericType, of) != null;
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Object result, String base, Charset charset,
OutputStream out, int bufSize) throws IOException,
OpenRDFException, XMLStreamException, TransformerException,
ParserConfigurationException {
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, Object result, String base, Charset charset)
throws IOException, OpenRDFException, XMLStreamException,
TransformerException, ParserConfigurationException {
MessageBodyWriter writer = findWriter(mimeType, type, genericType, of);
if (writer == null)
throw new BadRequest("Cannot write " + type + " into " + mimeType);
writer.writeTo(mimeType, type, genericType, of, result, base, charset,
out, bufSize);
return writer.write(mimeType, type, genericType, of, result, base, charset);
}
 
private MessageBodyWriter findWriter(String mimeType, Class<?> type,
trunk/object-server/src/main/java/org/openrdf/http/object/writers/InputStreamBodyWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
59,7 → 59,8
 
public String getContentType(String mimeType, Class<?> type,
Type genericType, ObjectFactory of, Charset charset) {
if (mimeType == null || mimeType.startsWith("*") | mimeType.startsWith("application/*"))
if (mimeType == null || mimeType.startsWith("*")
| mimeType.startsWith("application/*"))
return "application/octet-stream";
return mimeType;
}
73,4 → 74,10
out.write(buf, 0, read);
}
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, InputStream result, String base, Charset charset)
throws IOException {
return result;
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/ReadableByteChannelBodyWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,9 → 29,11
package org.openrdf.http.object.writers;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Type;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.nio.charset.Charset;
 
77,4 → 79,10
buf.clear();
}
}
 
public InputStream write(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, ReadableByteChannel result, String base,
Charset charset) throws IOException {
return Channels.newInputStream(result);
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/base/URIListWriter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,7 → 28,10
*/
package org.openrdf.http.object.writers.base;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
46,12 → 49,15
import org.openrdf.http.object.writers.MessageBodyWriter;
import org.openrdf.http.object.writers.StringBodyWriter;
import org.openrdf.repository.object.ObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
* Writes text/uri-list files.
*/
public class URIListWriter<URI> implements MessageBodyWriter<URI> {
private static final Charset UTF8 = Charset.forName("UTF-8");
private Logger logger = LoggerFactory.getLogger(URIListWriter.class);
private StringBodyWriter delegate = new StringBodyWriter();
private Class<URI> componentType;
 
86,18 → 92,42
return delegate.getContentType(mimeType, t, t, of, charset);
}
 
public long getSize(String mimeType, Class<?> type, Type genericType,
public long getSize(String mimeType, Class<?> ctype, Type gtype,
ObjectFactory of, URI result, Charset charset) {
if (result == null)
return 0;
if (Set.class.equals(type))
if (Set.class.equals(ctype))
return -1;
GenericType<?> type = new GenericType(ctype, gtype);
if (mimeType == null || mimeType.startsWith("*")
|| mimeType.startsWith("text/*")) {
mimeType = "text/uri-list";
}
Class<String> t = String.class;
return delegate.getSize(mimeType, t, t, of, toString(result), charset);
if (type.isSetOrArray()) {
if (charset == null) {
charset = UTF8;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
Writer writer = new OutputStreamWriter(out, charset);
Iterator<URI> iter = (Iterator<URI>) type.iteratorOf(result);
while (iter.hasNext()) {
writer.write(toString(iter.next()));
if (iter.hasNext()) {
writer.write("\r\n");
}
}
writer.flush();
} catch (IOException e) {
logger.error(e.toString(), e);
return -1;
}
return out.size();
} else {
Class<String> t = String.class;
return delegate.getSize(mimeType, t, t, of, toString(result),
charset);
}
}
 
public void writeTo(String mimeType, Class<?> ctype, Type gtype,
112,7 → 142,7
|| mimeType.startsWith("text/*")) {
mimeType = "text/uri-list";
}
if (type.isSetOrArray()) {// TODO or array
if (type.isSetOrArray()) {
if (charset == null) {
charset = UTF8;
}
132,6 → 162,39
}
}
 
public InputStream write(String mimeType, Class<?> ctype, Type gtype,
ObjectFactory of, URI result, String base, Charset charset)
throws IOException, OpenRDFException, XMLStreamException,
TransformerException, ParserConfigurationException {
if (result == null)
return null;
GenericType<?> type = new GenericType(ctype, gtype);
if (mimeType == null || mimeType.startsWith("*")
|| mimeType.startsWith("text/*")) {
mimeType = "text/uri-list";
}
if (type.isSetOrArray()) {
if (charset == null) {
charset = UTF8;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
Writer writer = new OutputStreamWriter(out, charset);
Iterator<URI> iter = (Iterator<URI>) type.iteratorOf(result);
while (iter.hasNext()) {
writer.write(toString(iter.next()));
if (iter.hasNext()) {
writer.write("\r\n");
}
}
writer.flush();
return new ByteArrayInputStream(out.toByteArray());
} else {
Class<String> t = String.class;
return delegate.write(mimeType, t, t, of, toString(result), base,
charset);
}
}
 
protected String toString(URI result) {
return result.toString();
}
trunk/object-server/src/main/java/org/openrdf/http/object/writers/base/MessageWriterBase.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
32,11 → 32,17
import info.aduna.lang.service.FileFormatServiceRegistry;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedOutputStream;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
 
import org.openrdf.OpenRDFException;
import org.openrdf.http.object.model.ErrorInputStream;
import org.openrdf.http.object.util.SharedExecutors;
import org.openrdf.http.object.writers.MessageBodyWriter;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.TupleQueryResultHandlerException;
57,6 → 63,7
*/
public abstract class MessageWriterBase<FF extends FileFormat, S, T> implements
MessageBodyWriter<T> {
private static Executor executor = SharedExecutors.getWriterThreadPool();
private FileFormatServiceRegistry<FF, S> registry;
private Class<T> type;
 
99,6 → 106,31
return contentType;
}
 
public InputStream write(final String mimeType, final Class<?> type,
final Type genericType, final ObjectFactory of, final T result,
final String base, final Charset charset) throws IOException {
final PipedOutputStream out = new PipedOutputStream();
final ErrorInputStream in = new ErrorInputStream(out);
executor.execute(new Runnable() {
public void run() {
try {
try {
writeTo(mimeType, type, genericType, of, result, base, charset, out, 1024);
} finally {
out.close();
}
} catch (IOException e) {
in.error(e);
} catch (Exception e) {
in.error(new IOException(e));
} catch (Error e) {
in.error(new IOException(e));
}
}
});
return in;
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, T result, String base, Charset charset,
OutputStream out, int bufSize) throws IOException, OpenRDFException {
trunk/object-server/src/main/java/org/openrdf/http/object/writers/FormStringMessageWriter.java
1,6 → 1,37
/*
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.writers;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
37,6 → 68,16
return charset.encode(str).limit();
}
 
public InputStream write(final String mimeType, final Class<?> type,
final Type genericType, final ObjectFactory of, final String result,
final String base, final Charset charset) throws IOException,
OpenRDFException, XMLStreamException, TransformerException,
ParserConfigurationException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(mimeType, type, genericType, of, result, base, charset, out, 1024);
return new ByteArrayInputStream(out.toByteArray());
}
 
public void writeTo(String mimeType, Class<?> type, Type genericType,
ObjectFactory of, String result, String base, Charset charset,
OutputStream out, int bufSize) throws IOException,
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/HeadHandler.java New file
0,0 → 1,55
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
 
/**
* Trims the body of a request if it is a HEAD request.
*
* @author James Leigh
*
*/
public class HeadHandler implements Handler {
private final Handler delegate;
 
public HeadHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
Response rb = delegate.handle(request);
if (rb.isOk() && "HEAD".equals(request.getMethod()))
return rb.head();
return rb;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/ModifiedSinceHandler.java New file
0,0 → 1,66
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
 
/**
* Response with 304 and 412 when resource has not been modified.
*
* @author James Leigh
*
*/
public class ModifiedSinceHandler implements Handler {
private final Handler delegate;
 
public ModifiedSinceHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation req) throws Exception {
String method = req.getMethod();
String contentType = req.getResponseContentType();
String entityTag = req.getEntityTag(contentType);
long lastModified = req.getLastModified();
if ("GET".equals(method) || "HEAD".equals(method)) {
if (req.modifiedSince(entityTag, lastModified)) {
return delegate.handle(req);
}
return new Response().notModified();
} else if (req.modifiedSince(entityTag, lastModified)) {
return delegate.handle(req);
} else {
return new Response().preconditionFailed();
}
 
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/UnmodifiedSinceHandler.java New file
0,0 → 1,59
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
 
/**
* Responds with 412 if the resource has been modified.
*
* @author James Leigh
*
*/
public class UnmodifiedSinceHandler implements Handler {
private final Handler delegate;
 
public UnmodifiedSinceHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
String contentType = request.getResponseContentType();
String entityTag = request.getEntityTag(contentType);
long lastModified = request.getLastModified();
if (request.unmodifiedSince(entityTag, lastModified)) {
return delegate.handle(request);
} else {
return new Response().preconditionFailed();
}
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/ContentHeadersHandler.java New file
0,0 → 1,98
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
import org.openrdf.http.object.traits.Realm;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
 
/**
* Adds the HTTP headers: Cache-Control, Vary, ETag, Content-Type,
* Content-Encoding, and Last-Modified.
*
* @author James Leigh
*
*/
public class ContentHeadersHandler implements Handler {
private final Handler delegate;
 
public ContentHeadersHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
Class<?> type = request.getEntityType();
String contentType = request.getResponseContentType();
String contentEncoding = request.getResponseContentEncoding();
String cache = request.getResponseCacheControl();
Response rb = delegate.handle(request);
String entityTag = request.getEntityTag(contentType);
long lastModified = request.getLastModified();
if (cache != null) {
rb.header("Cache-Control", cache);
}
if (isVaryOrigin(request)) {
rb.header("Vary", "Origin");
}
for (String vary : request.getVary()) {
rb.header("Vary", vary);
}
if (entityTag != null) {
rb.header("ETag", entityTag);
}
if (contentType != null && rb.isContent()) {
rb.header("Content-Type", contentType);
}
if (contentEncoding != null && rb.isContent()) {
rb.header("Content-Encoding", contentEncoding);
}
if (lastModified > 0) {
rb.lastModified(lastModified);
}
rb.setEntityType(type);
return rb;
}
 
private boolean isVaryOrigin(ResourceOperation request)
throws QueryEvaluationException, RepositoryException {
for (Object o : request.getRealms()) {
if (o instanceof Realm) {
Realm realm = (Realm) o;
String allowed = realm.allowOrigin();
if (allowed != null && allowed.length() > 0)
return true;
}
}
return false;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/MethodNotAllowedHandler.java New file
0,0 → 1,48
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.openrdf.http.object.exceptions.MethodNotAllowed;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
 
/**
* Throws MethodNotAllowed exceptions.
*
* @author James Leigh
*
*/
public class MethodNotAllowedHandler implements Handler {
 
public Response handle(ResourceOperation request) throws Exception {
throw new MethodNotAllowed();
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/InvokeHandler.java New file
0,0 → 1,146
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import java.io.Closeable;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
 
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerConfigurationException;
 
import org.openrdf.http.object.annotations.expect;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
import org.openrdf.http.object.model.ResponseEntity;
import org.openrdf.repository.object.ObjectConnection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
* Executes a Java Method on the request target and response with the result.
*
* @author James Leigh
*
*/
public class InvokeHandler implements Handler {
private Logger logger = LoggerFactory.getLogger(InvokeHandler.class);
private final Handler delegate;
 
public InvokeHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
Method method = request.getJavaMethod();
if (method == null)
return delegate.handle(request);
return invoke(request, method, false);
}
 
private Response invoke(ResourceOperation req, Method method, boolean safe)
throws Exception {
try {
Object[] args;
try {
args = req.getParameters(method, req.getBody());
} catch (ParserConfigurationException e) {
throw e;
} catch (TransformerConfigurationException e) {
throw e;
} catch (Exception e) {
return new Response().badRequest(e);
}
try {
ObjectConnection con = req.getObjectConnection();
assert !con.isAutoCommit();
ResponseEntity entity = req.invoke(method, args, true);
if (!safe) {
req.flush();
}
return createResponse(req, method, entity);
} finally {
for (Object arg : args) {
if (arg instanceof Closeable) {
((Closeable) arg).close();
}
}
}
} catch (InvocationTargetException e) {
try {
throw e.getCause();
} catch (Error cause) {
throw cause;
} catch (Exception cause) {
throw cause;
} catch (Throwable cause) {
throw e;
}
}
}
 
private Response createResponse(ResourceOperation req, Method method,
ResponseEntity entity) throws Exception {
Response rb = new Response();
if (entity.isNoContent()) {
rb = rb.noContent();
} else if (entity.isRedirect()) {
rb = rb.status(307, "Temporary Redirect").location(
entity.getLocation());
} else if (entity.isSeeOther()) {
rb = rb.status(303, "See Other").location(entity.getLocation());
} else {
rb = rb.entity(entity);
}
if (method.isAnnotationPresent(expect.class)) {
String expect = method.getAnnotation(expect.class).value();
String[] values = expect.split("-");
try {
StringBuilder sb = new StringBuilder();
for (int i = 1; i < values.length; i++) {
sb.append(values[i].substring(0, 1).toUpperCase());
sb.append(values[i].substring(1));
sb.append(" ");
}
if (sb.length() > 1) {
rb
.status(Integer.parseInt(values[0]), sb.toString()
.trim());
}
} catch (NumberFormatException e) {
logger.error(expect, e);
} catch (IndexOutOfBoundsException e) {
logger.error(expect, e);
}
}
return rb;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/OptionsHandler.java New file
0,0 → 1,140
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
 
import org.openrdf.http.object.annotations.cacheControl;
import org.openrdf.http.object.annotations.header;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
 
/**
* Responds for OPTIONS requests.
*
* @author James Leigh
*
*/
public class OptionsHandler implements Handler {
private static final String ALLOW_HEADERS = "Authorization,Host,Cache-Control,Location,Range,"
+ "Accept,Accept-Charset,Accept-Encoding,Accept-Language,"
+ "Content-Encoding,Content-Language,Content-Length,Content-Location,Content-MD5,Content-Type,"
+ "If-Match,If-Modified-Since,If-None-Match,If-Range,If-Unmodified-Since";
private final Handler delegate;
 
public OptionsHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
if ("OPTIONS".equals(request.getMethod())) {
StringBuilder sb = new StringBuilder();
sb.append("OPTIONS, TRACE");
for (String method : request.getAllowedMethods()) {
sb.append(", ").append(method);
}
String allow = sb.toString();
Response rb = new Response();
rb = rb.header("Allow", allow);
rb = rb.header("Access-Control-Allow-Methods", allow);
StringBuilder headers = new StringBuilder();
headers.append(ALLOW_HEADERS);
for (String header : getAllowedHeaders(request)) {
headers.append(",");
headers.append(header);
}
rb = rb.header("Access-Control-Allow-Headers", headers.toString());
String max = getMaxAge(request.getRequestedResource().getClass());
if (max != null) {
rb = rb.header("Access-Control-Max-Age", max);
}
return rb;
} else {
return delegate.handle(request);
}
}
 
private Collection<String> getAllowedHeaders(ResourceOperation request) {
List<String> result = null;
Class<?> type = request.getRequestedResource().getClass();
for (Method method : type.getMethods()) {
for (Annotation[] anns : method.getParameterAnnotations()) {
for (Annotation ann : anns) {
if (ann.annotationType().equals(header.class)) {
if (result == null) {
result = new ArrayList<String>();
}
result.addAll(Arrays.asList(((header) ann).value()));
}
}
}
}
if (result == null)
return Collections.emptyList();
return result;
}
 
private String getMaxAge(Class<?> type) {
if (type.isAnnotationPresent(cacheControl.class)) {
for (String value : type.getAnnotation(cacheControl.class).value()) {
int m = value.indexOf("max-age=");
if (m >= 0) {
int c = value.indexOf(';', m);
if (c < 0) {
c = value.length();
}
String max = value.substring(m, c);
return max.trim();
}
}
} else {
if (type.getSuperclass() != null) {
String max = getMaxAge(type.getSuperclass());
if (max != null) {
return max;
}
}
for (Class<?> face : type.getInterfaces()) {
String max = getMaxAge(face);
if (max != null) {
return max;
}
}
}
return null;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/NotFoundHandler.java New file
0,0 → 1,57
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
 
/**
* Converts a 204 into a 404 for GET and HEAD requests.
*
* @author James Leigh
*
*/
public class NotFoundHandler implements Handler {
private final Handler delegate;
 
public NotFoundHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
Response rb = delegate.handle(request);
String method = request.getMethod();
if (("GET".equals(method) || "HEAD".equals(method)) && rb.isNoContent()) {
return new Response().notFound();
}
return rb;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/DateHandler.java New file
0,0 → 1,54
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.apache.http.protocol.HttpDateGenerator;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
 
/**
* Adds the HTTP Date Header.
*
* @author James Leigh
*
*/
public class DateHandler implements Handler {
private static final HttpDateGenerator DATE_GENERATOR = new HttpDateGenerator();
private final Handler delegate;
 
public DateHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
return delegate.handle(request).header("Date", DATE_GENERATOR.getCurrentDate());
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/ResponseExceptionHandler.java New file
0,0 → 1,76
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import org.openrdf.http.object.exceptions.BadRequest;
import org.openrdf.http.object.exceptions.MethodNotAllowed;
import org.openrdf.http.object.exceptions.NotAcceptable;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
 
/**
* Converts MethodNotAllowed, NotAcceptable, and BadRequest into HTTP responses.
*
* @author James Leigh
*
*/
public class ResponseExceptionHandler implements Handler {
private final Handler delegate;
 
public ResponseExceptionHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation request) throws Exception {
try {
return delegate.handle(request);
} catch (MethodNotAllowed e) {
return methodNotAllowed(request);
} catch (NotAcceptable e) {
return new Response().exception(e);
} catch (BadRequest e) {
return new Response().exception(e);
}
}
 
private Response methodNotAllowed(ResourceOperation request)
throws RepositoryException, QueryEvaluationException {
StringBuilder sb = new StringBuilder();
sb.append("OPTIONS, TRACE");
for (String method : request.getAllowedMethods()) {
sb.append(", ").append(method);
}
return new Response().status(405, "Method Not Allowed").header("Allow",
sb.toString());
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/AlternativeHandler.java New file
0,0 → 1,110
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import java.lang.reflect.Method;
 
import javax.activation.MimeTypeParseException;
 
import org.openrdf.http.object.annotations.operation;
import org.openrdf.http.object.exceptions.BadRequest;
import org.openrdf.http.object.exceptions.MethodNotAllowed;
import org.openrdf.http.object.exceptions.NotAcceptable;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
 
/**
* If a GET request cannot be satisfied send a redirect to another operation.
*
* @author James Leigh
*
*/
public class AlternativeHandler implements Handler {
private final Handler delegate;
 
public AlternativeHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation req) throws Exception {
try {
return delegate.handle(req);
} catch (MethodNotAllowed e) {
Response rb = findAlternate(req);
if (rb != null)
return rb;
return methodNotAllowed(req);
} catch (NotAcceptable e) {
Response rb = findAlternate(req);
if (rb != null)
return rb;
return new Response().exception(e);
} catch (BadRequest e) {
Response rb = findAlternate(req);
if (rb != null)
return rb;
return new Response().exception(e);
}
}
 
private Response methodNotAllowed(ResourceOperation req)
throws RepositoryException, QueryEvaluationException {
StringBuilder sb = new StringBuilder();
sb.append("OPTIONS, TRACE");
for (String method : req.getAllowedMethods()) {
sb.append(", ").append(method);
}
return new Response().status(405, "Method Not Allowed").header("Allow",
sb.toString());
}
 
private Response findAlternate(ResourceOperation req)
throws MimeTypeParseException, RepositoryException,
QueryEvaluationException {
String m = req.getMethod();
if (req.getOperation() != null
|| !("GET".equals(m) || "HEAD".equals(m)))
return null;
Method operation;
if ((operation = req.getOperationMethod("alternate")) != null) {
String loc = req.getURI() + "?"
+ operation.getAnnotation(operation.class).value()[0];
return new Response().status(302, "Found").location(loc);
} else if ((operation = req.getOperationMethod("describedby")) != null) {
String loc = req.getURI() + "?"
+ operation.getAnnotation(operation.class).value()[0];
return new Response().status(303, "See Other").location(loc);
}
return null;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/AuthenticationHandler.java New file
0,0 → 1,271
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
 
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.http.HttpEntity;
import org.apache.http.nio.entity.NByteArrayEntity;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
import org.openrdf.http.object.traits.Realm;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
* Ensures the request is authentic if protected.
*
* @author James Leigh
*
*/
public class AuthenticationHandler implements Handler {
private final Logger logger = LoggerFactory.getLogger(AuthenticationHandler.class);
private final Handler delegate;
private final String passwd;
 
public AuthenticationHandler(Handler delegate, String passwd) {
this.delegate = delegate;
this.passwd = passwd;
}
 
public Response handle(ResourceOperation request) throws Exception {
String method = request.getMethod();
if (request.isAuthenticating()
&& !isBoot(method, request.getHeader("Authorization"))) {
if (!isAuthorized(request)) {
InputStream message = unauthorized(request);
return new Response().unauthorized(message);
}
}
Response rb = delegate.handle(request);
if ("GET".equals(method) || "HEAD".equals(method)
|| "POST".equals(method) || "OPTIONS".equals(method)) {
String origins = allowOrigin(request);
if (origins == null || origins.length() < 1) {
rb = rb.header("Access-Control-Allow-Origin", "*");
} else {
rb = rb.header("Access-Control-Allow-Origin", origins);
}
}
return rb;
}
 
private InputStream unauthorized(ResourceOperation request)
throws QueryEvaluationException, RepositoryException, IOException {
for (Object r : request.getRealms()) {
if (r instanceof Realm) {
Realm realm = (Realm) r;
InputStream auth = realm.unauthorized();
if (auth != null)
return auth;
}
}
return null;
}
 
private boolean isAuthorized(ResourceOperation request)
throws QueryEvaluationException, RepositoryException, IOException {
String ad = request.getRemoteAddr();
String m = request.getMethod();
String or = request.getHeader("Origin");
String au = request.getHeader("Authorization");
String f = null;
String al = null;
byte[] e = null;
X509Certificate cret = request.getX509Certificate();
if (cret != null) {
PublicKey pk = cret.getPublicKey();
f = pk.getFormat();
al = pk.getAlgorithm();
e = pk.getEncoded();
}
for (Object r : request.getRealms()) {
if (r instanceof Realm) {
Realm realm = (Realm) r;
String allowed = realm.allowOrigin();
if (allowed != null && allowed.length() > 0) {
if (or != null && or.length() > 0
&& !isOriginAllowed(allowed, or))
continue;
}
if (au == null) {
if (realm.authorize(f, al, e, ad, m))
return true;
} else {
String rtar = request.getRequestTarget();
String md5 = request.getHeader("Content-MD5");
if (md5 == null) {
md5 = computeMD5(request);
}
Map<String, String[]> map = new HashMap<String, String[]>();
map.put("request-target", new String[] { rtar });
if (md5 != null) {
map.put("content-md5", new String[] { md5 });
}
map.put("authorization", new String[] { au });
if (realm.authorize(f, al, e, ad, m, map))
return true;
}
}
}
return false;
}
 
private String computeMD5(ResourceOperation request) throws IOException {
HttpEntity entity = request.getEntity();
if (entity != null) {
try {
MessageDigest digest = MessageDigest.getInstance("MD5");
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = entity.getContent();
try {
int read;
byte[] buf = new byte[1024];
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
digest.update(buf, 0, read);
}
byte[] bar = out.toByteArray();
NByteArrayEntity replacement = new NByteArrayEntity(bar);
replacement.setChunked(entity.isChunked());
replacement.setContentEncoding(entity.getContentEncoding());
replacement.setContentType(entity.getContentType());
request.setEntity(replacement);
} finally {
in.close();
}
byte[] hash = Base64.encodeBase64(digest.digest());
return new String(hash, "UTF-8");
} catch (NoSuchAlgorithmException e) {
logger.error(e.toString(), e);
return null;
}
}
return null;
}
 
private boolean isOriginAllowed(String allowed, String o) {
for (String ao : allowed.split("\\s*,\\s*")) {
if (o.startsWith(ao))
return true;
}
return false;
}
 
private String allowOrigin(ResourceOperation request)
throws QueryEvaluationException, RepositoryException {
StringBuilder sb = new StringBuilder();
for (Object o : request.getRealms()) {
if (o instanceof Realm) {
Realm realm = (Realm) o;
if (sb.length() > 0) {
sb.append(", ");
}
String origin = realm.allowOrigin();
if ("*".equals(origin))
return origin;
if (origin != null && origin.length() > 0) {
sb.append(origin);
}
}
}
return sb.toString();
}
 
private boolean isBoot(String method, String auth)
throws UnsupportedEncodingException {
if (passwd == null || auth == null)
return false;
if (auth.startsWith("Basic")) {
byte[] bytes = ("boot:" + passwd).getBytes("UTF-8");
String encoded = new String(Base64.encodeBase64(bytes));
return auth.equals("Basic " + encoded);
} else if (auth.startsWith("Digest")) {
String string = auth.substring("Digest ".length());
Map<String, String> options = parseOptions(string);
if (options == null)
return false;
if (!"boot".equals(options.get("username")))
return false;
String realm = options.get("realm");
String nonce = options.get("nonce");
String response = options.get("response");
String a1 = "boot:" + realm + ":" + passwd;
String a2 = method + ":" + options.get("uri");
String legacy = md5(a1) + ":" + nonce + ":" + md5(a2);
if (md5(legacy).equals(response))
return true;
String digest = md5(a1) + ":" + nonce + ":" + options.get("nc")
+ ":" + options.get("cnonce") + ":" + options.get("qop")
+ ":" + md5(a2);
return md5(digest).equals(response);
}
return false;
}
 
private String md5(String a2) {
return DigestUtils.md5Hex(a2);
}
 
private Map<String, String> parseOptions(String options) {
Map<String, String> result = new HashMap<String, String>();
for (String keyvalue : options.split("\\s*,\\s*")) {
int idx = keyvalue.indexOf('=');
if (idx < 0)
return null;
String key = keyvalue.substring(0, idx);
if (keyvalue.charAt(idx + 1) == '"') {
int eq = keyvalue.lastIndexOf('"');
if (eq <= idx + 2)
return null;
String value = keyvalue.substring(idx + 2, eq);
result.put(key, value);
} else {
String value = keyvalue.substring(idx + 1);
result.put(key, value);
}
}
return result;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/handlers/LinksHandler.java New file
0,0 → 1,123
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.handlers;
 
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
 
import javax.activation.MimeTypeParseException;
 
import org.openrdf.http.object.annotations.rel;
import org.openrdf.http.object.annotations.title;
import org.openrdf.http.object.annotations.type;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.model.ResourceOperation;
import org.openrdf.http.object.model.Response;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
 
/**
* Add an HTTP Header called 'Link' with other operations available to this resource.
*
* @author James Leigh
*
*/
public class LinksHandler implements Handler {
private final Handler delegate;
 
public LinksHandler(Handler delegate) {
this.delegate = delegate;
}
 
public Response handle(ResourceOperation req) throws Exception {
Response rb = delegate.handle(req);
String method = req.getMethod();
int status = rb.getStatus();
if (("GET".equals(method) || "HEAD".equals(method)) && 200 <= status
&& status < 400) {
return addLinks(req, rb);
} else if ("OPTIONS".equals(method)) {
return addLinks(req, rb);
} else {
return rb;
}
}
 
private Response addLinks(ResourceOperation request, Response rb)
throws RepositoryException, QueryEvaluationException,
MimeTypeParseException {
if (!request.isQueryStringPresent()) {
for (String link : getLinks(request)) {
rb = rb.header("Link", link);
}
}
return rb;
}
 
private List<String> getLinks(ResourceOperation request)
throws RepositoryException, QueryEvaluationException {
Map<String, List<Method>> map = request
.getOperationMethods("GET", true);
List<String> result = new ArrayList<String>(map.size());
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, List<Method>> e : map.entrySet()) {
sb.delete(0, sb.length());
sb.append("<").append(request.getURI());
sb.append("?").append(e.getKey()).append(">");
for (Method m : e.getValue()) {
if (m.isAnnotationPresent(rel.class)) {
sb.append("; rel=\"");
for (String value : m.getAnnotation(rel.class).value()) {
sb.append(value).append(" ");
}
sb.setCharAt(sb.length() - 1, '"');
}
if (m.isAnnotationPresent(type.class)) {
sb.append("; type=\"");
if (m.isAnnotationPresent(type.class)) {
for (String value : m.getAnnotation(type.class).value()) {
sb.append(value).append(" ");
}
}
sb.setCharAt(sb.length() - 1, '"');
}
if (m.isAnnotationPresent(title.class)) {
for (String value : m.getAnnotation(title.class).value()) {
sb.append("; title=\"").append(value).append("\"");
}
}
}
result.add(sb.toString());
}
return result;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/RequestHeader.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/model/ResourceRequest.java New file
0,0 → 1,561
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.model;
 
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
 
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.openrdf.http.object.annotations.type;
import org.openrdf.http.object.concepts.HTTPFileObject;
import org.openrdf.http.object.traits.VersionedObject;
import org.openrdf.http.object.util.GenericType;
import org.openrdf.http.object.writers.AggregateWriter;
import org.openrdf.http.object.writers.MessageBodyWriter;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.ObjectFactory;
import org.openrdf.repository.object.ObjectRepository;
import org.openrdf.repository.object.traits.RDFObjectBehaviour;
import org.openrdf.result.Result;
 
/**
* Tracks the target resource with the request.
*
* @author James Leigh
*
*/
public class ResourceRequest extends Request {
private static Type parameterMapType;
static {
try {
parameterMapType = ResourceRequest.class.getDeclaredMethod(
"getParameterMap").getGenericReturnType();
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
private ObjectFactory of;
private ValueFactory vf;
private ObjectConnection con;
private File dataDir;
private File file;
private VersionedObject target;
private URI uri;
private MessageBodyWriter writer = AggregateWriter.getInstance();
private BodyEntity body;
private Accepter accepter;
private List<String> vary = new ArrayList<String>();
private Result<HTTPFileObject> result;
 
public ResourceRequest(File dataDir, HttpEntityEnclosingRequest request,
ObjectRepository repository) throws QueryEvaluationException,
RepositoryException, MimeTypeParseException {
super(request);
this.con = repository.getConnection();
con.setAutoCommit(false); // begin()
this.dataDir = dataDir;
this.vf = con.getValueFactory();
this.of = con.getObjectFactory();
this.uri = vf.createURI(getURI());
Enumeration headers = getVaryHeaders("Accept");
if (headers.hasMoreElements()) {
StringBuilder sb = new StringBuilder();
while (headers.hasMoreElements()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append((String) headers.nextElement());
}
accepter = new Accepter(sb.toString());
} else {
accepter = new Accepter();
}
result = con.getObjects(HTTPFileObject.class, uri);
}
 
public void init() throws RepositoryException, QueryEvaluationException,
MimeTypeParseException {
if (target == null) {
target = result.singleResult();
if (target instanceof HTTPFileObject) {
File base = new File(dataDir, safe(getAuthority()));
String path = getPath();
if (path == null) {
file = new File(base, safe(uri.stringValue()));
} else {
file = new File(base, safe(path));
}
if (!file.isFile()) {
int dot = file.getName().lastIndexOf('.');
String name = Integer.toHexString(uri.hashCode());
if (dot > 0) {
name = '$' + name + file.getName().substring(dot);
} else {
name = '$' + name;
}
file = new File(file, name);
}
((HTTPFileObject) target).initLocalFileObject(file, isSafe());
}
}
}
 
public Enumeration getVaryHeaders(String name) {
if (!vary.contains(name)) {
vary.add(name);
}
return getHeaderEnumeration(name);
}
 
public List<String> getVary() {
return vary;
}
 
public ResponseEntity createResultEntity(Object result, Class<?> ctype,
Type gtype, String[] mimeTypes) {
GenericType<?> type = new GenericType(ctype, gtype);
if (result != null && type.isSet()) {
Set set = (Set) result;
Iterator iter = set.iterator();
try {
if (!iter.hasNext()) {
result = null;
ctype = type.getComponentClass();
gtype = type.getComponentType();
} else {
Object object = iter.next();
if (!iter.hasNext()) {
result = object;
ctype = type.getComponentClass();
gtype = type.getComponentType();
}
}
} finally {
getObjectConnection().close(iter);
}
} else if (result != null && type.isArray()) {
int len = Array.getLength(result);
if (len == 0) {
result = null;
ctype = type.getComponentClass();
gtype = type.getComponentType();
} else if (len == 1) {
result = Array.get(result, 0);
ctype = type.getComponentClass();
gtype = type.getComponentType();
}
}
if (result instanceof RDFObjectBehaviour) {
result = ((RDFObjectBehaviour) result).getBehaviourDelegate();
}
return new ResponseEntity(mimeTypes, result, ctype, gtype, uri
.stringValue(), con);
}
 
public URI createURI(String uriSpec) {
return vf.createURI(parseURI(uriSpec).toString());
}
 
public void flush() throws RepositoryException, QueryEvaluationException,
IOException {
ObjectConnection con = getObjectConnection();
con.commit(); // flush()
this.target = con.getObject(HTTPFileObject.class, target.getResource());
}
 
public void rollback() throws RepositoryException {
ObjectConnection con = getObjectConnection();
con.rollback();
con.setAutoCommit(true); // rollback()
}
 
public void commit() throws IOException, RepositoryException {
try {
ObjectConnection con = getObjectConnection();
con.setAutoCommit(true); // commit()
} catch (RepositoryException e) {
rollback();
}
}
 
public void close() throws RepositoryException {
ObjectConnection con = getObjectConnection();
con.rollback();
con.close();
}
 
public Entity getBody() throws MimeTypeParseException {
if (body != null)
return body;
String mediaType = getHeader("Content-Type");
String mime = removeParamaters(mediaType);
String location = getResolvedHeader("Content-Location");
if (location != null) {
location = createURI(location).stringValue();
}
Charset charset = getCharset(mediaType);
return body = new BodyEntity(mime, isMessageBody(), charset, uri
.stringValue(), location, con) {
 
@Override
protected InputStream getInputStream() throws IOException {
HttpEntity entity = getEntity();
if (entity == null)
return null;
return entity.getContent();
}
};
}
 
public String getContentType(Method method) throws MimeTypeParseException {
Class<?> type = method.getReturnType();
Type genericType = method.getGenericReturnType();
if (method.isAnnotationPresent(type.class)) {
String[] mediaTypes = method.getAnnotation(type.class).value();
for (MimeType m : accepter.getAcceptable(mediaTypes)) {
if (writer.isWriteable(m.toString(), type, genericType, of)) {
return getContentType(type, genericType, m);
}
}
} else {
for (MimeType m : accepter.getAcceptable()) {
if (writer.isWriteable(m.toString(), type, genericType, of)) {
return getContentType(type, genericType, m);
}
}
}
return null;
}
 
public File getFile() {
return file;
}
 
public ObjectConnection getObjectConnection() {
return con;
}
 
public String getOperation() {
Map<String, String[]> params = getParameterMap();
if (params != null) {
for (String key : params.keySet()) {
String[] values = params.get(key);
if (values == null || values.length == 0 || values.length == 1
&& (values[0] == null || values[0].length() == 0)) {
return key;
}
}
}
return null;
}
 
public Entity getHeader(String[] mediaTypes, String... names) {
String[] values = getHeaderValues(names);
return new ParameterEntity(mediaTypes, "text/plain", values, uri
.stringValue(), con);
}
 
public Entity getParameter(String[] mediaTypes, String... names) {
String[] values = getParameterValues(names);
return new ParameterEntity(mediaTypes, "text/plain", values, uri
.stringValue(), con);
}
 
public Entity getQueryString(String[] mediaTypes) {
String mimeType = "application/x-www-form-urlencoded";
String value = getQueryString();
if (value == null) {
return new ParameterEntity(mediaTypes, mimeType, new String[0], uri
.stringValue(), con);
}
return new ParameterEntity(mediaTypes, mimeType,
new String[] { value }, uri.stringValue(), con);
}
 
public VersionedObject getRequestedResource() {
return target;
}
 
public boolean isAcceptable(Class<?> type, Type genericType)
throws MimeTypeParseException {
return isAcceptable(null, type, genericType);
}
 
public boolean isAcceptable(Method method) throws MimeTypeParseException {
Class<?> type = method.getReturnType();
Type genericType = method.getGenericReturnType();
if (method.isAnnotationPresent(type.class)) {
for (String media : method.getAnnotation(type.class).value()) {
if (isAcceptable(media, type, genericType))
return true;
}
return false;
}
return isAcceptable(type, genericType);
}
 
public boolean isAcceptable(String mediaType) throws MimeTypeParseException {
return isAcceptable(mediaType, null, null);
}
 
public boolean isAcceptable(String mediaType, Class<?> type,
Type genericType) throws MimeTypeParseException {
if (type == null)
return accepter.isAcceptable(mediaType);
for (MimeType accept : accepter.getAcceptable(mediaType)) {
String mime = accept.getPrimaryType() + "/" + accept.getSubType();
if (writer.isWriteable(mime, type, genericType, of))
return true;
}
return false;
}
 
public boolean isQueryStringPresent() {
return getQueryString() != null;
}
 
public boolean modifiedSince(String entityTag, long lastModified)
throws MimeTypeParseException {
boolean notModified = false;
try {
if (lastModified > 0) {
long modified = getDateHeader("If-Modified-Since");
notModified = modified > 0;
if (notModified && modified < lastModified)
return true;
}
} catch (IllegalArgumentException e) {
// invalid date header
}
Enumeration matchs = getHeaderEnumeration("If-None-Match");
boolean mustMatch = matchs.hasMoreElements();
if (mustMatch) {
while (matchs.hasMoreElements()) {
String match = (String) matchs.nextElement();
if (match(entityTag, match))
return false;
}
}
return !notModified || mustMatch;
}
 
public boolean unmodifiedSince(String entityTag, long lastModified)
throws MimeTypeParseException {
Enumeration matchs = getHeaderEnumeration("If-Match");
boolean mustMatch = matchs.hasMoreElements();
try {
if (lastModified > 0) {
long unmodified = getDateHeader("If-Unmodified-Since");
if (unmodified > 0 && lastModified > unmodified)
return false;
}
} catch (IllegalArgumentException e) {
// invalid date header
}
while (matchs.hasMoreElements()) {
String match = (String) matchs.nextElement();
if (match(entityTag, match))
return true;
}
return !mustMatch;
}
 
private Charset getCharset(String mediaType) throws MimeTypeParseException {
if (mediaType == null)
return null;
MimeType m = new MimeType(mediaType);
String name = m.getParameters().get("charset");
if (name == null)
return null;
return Charset.forName(name);
}
 
private String getContentType(Class<?> type, Type genericType, MimeType m) {
Charset charset = null;
String cname = m.getParameters().get("charset");
try {
if (cname != null) {
charset = Charset.forName(cname);
return writer.getContentType(m.toString(), type, genericType,
of, charset);
}
} catch (UnsupportedCharsetException e) {
// ignore
}
if (charset == null) {
int rating = 0;
Enumeration<String> accept = getHeaderEnumeration("Accept-Charset");
while (accept.hasMoreElements()) {
String header = accept.nextElement().replaceAll("\\s", "");
for (String item : header.split(",")) {
int q = 1;
String name = item;
int c = item.indexOf(';');
if (c > 0) {
name = item.substring(0, c);
q = getQuality(item);
}
if (q > rating) {
try {
charset = Charset.forName(name);
rating = q;
} catch (UnsupportedCharsetException e) {
// ignore
}
}
}
}
}
String contentType = writer.getContentType(m.toString(), type,
genericType, of, charset);
if (contentType.contains("charset=")) {
getVaryHeaders("Accept-Charset");
}
return contentType;
}
 
private Map<String, String[]> getParameterMap() {
try {
return getQueryString(null).read(Map.class, parameterMapType,
new String[] { "application/x-www-form-urlencoded" });
} catch (Exception e) {
return Collections.emptyMap();
}
}
 
private String[] getParameterValues(String... names) {
if (names.length == 0) {
return new String[0];
} else {
Map<String, String[]> map = getParameterMap();
if (names.length == 1) {
return map.get(names[0]);
} else {
List<String> list = new ArrayList<String>(names.length * 2);
for (String name : names) {
list.addAll(Arrays.asList(map.get(name)));
}
return list.toArray(new String[list.size()]);
}
}
}
 
private String[] getHeaderValues(String... names) {
if (names.length == 0)
return new String[0];
List<String> list = new ArrayList<String>(names.length * 2);
for (String name : names) {
Enumeration en = getVaryHeaders(name);
while (en.hasMoreElements()) {
list.add((String) en.nextElement());
}
}
return list.toArray(new String[list.size()]);
}
 
private int getQuality(String item) {
int s = item.indexOf(";q=");
if (s > 0) {
int e = item.indexOf(';', s + 1);
if (e < 0) {
e = item.length();
}
try {
return Integer.parseInt(item.substring(s + 3, e));
} catch (NumberFormatException exc) {
// ignore q
}
}
return 1;
}
 
private boolean match(String tag, String match) {
if (tag == null)
return false;
if ("*".equals(match))
return true;
if (match.equals(tag))
return true;
int md = match.indexOf('-');
int td = tag.indexOf('-');
if (td >= 0 && md >= 0)
return false;
if (md < 0) {
md = match.lastIndexOf('"');
}
if (td < 0) {
td = tag.lastIndexOf('"');
}
int mq = match.indexOf('"');
int tq = tag.indexOf('"');
if (mq < 0 || tq < 0 || md < 0 || td < 0)
return false;
return match.substring(mq, md).equals(tag.substring(tq, td));
}
 
private String removeParamaters(String mediaType) {
if (mediaType == null)
return null;
int idx = mediaType.indexOf(';');
if (idx > 0)
return mediaType.substring(0, idx);
return mediaType;
}
 
private String safe(String path) {
if (path == null)
return "";
path = path.replace('/', File.separatorChar);
path = path.replace('\\', File.separatorChar);
path = path.replace(':', File.separatorChar);
return path.replaceAll("[^a-zA-Z0-9/\\\\]", "_");
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/EditableHttpEntityEnclosingRequest.java New file
0,0 → 1,167
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.model;
 
import org.apache.http.Header;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpRequest;
import org.apache.http.ProtocolVersion;
import org.apache.http.RequestLine;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.params.HttpParams;
 
/**
* Allows the request line and entity to be changed.
*
* @author James Leigh
*
*/
public class EditableHttpEntityEnclosingRequest implements
HttpEntityEnclosingRequest, Cloneable {
private HttpRequest request;
private RequestLine requestLine;
private HttpEntity entity;
 
public EditableHttpEntityEnclosingRequest(HttpRequest request) {
this.request = request;
this.requestLine = request.getRequestLine();
if (request instanceof HttpEntityEnclosingRequest) {
this.entity = ((HttpEntityEnclosingRequest) request).getEntity();
}
}
 
@Override
public EditableHttpEntityEnclosingRequest clone() {
EditableHttpEntityEnclosingRequest cloned;
try {
cloned = (EditableHttpEntityEnclosingRequest) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(e);
}
cloned.request = new BasicHttpEntityEnclosingRequest(requestLine);
cloned.request.setHeaders(request.getAllHeaders());
cloned.setEntity(entity);
return cloned;
}
 
public ProtocolVersion getProtocolVersion() {
return getRequestLine().getProtocolVersion();
}
 
public RequestLine getRequestLine() {
return requestLine;
}
 
public void setRequestLine(RequestLine requestLine) {
this.requestLine = requestLine;
}
 
public boolean expectContinue() {
if (request instanceof HttpEntityEnclosingRequest)
return ((HttpEntityEnclosingRequest) request).expectContinue();
return false;
}
 
public HttpEntity getEntity() {
return entity;
}
 
public void setEntity(HttpEntity entity) {
if (request instanceof HttpEntityEnclosingRequest)
((HttpEntityEnclosingRequest) request).setEntity(entity);
this.entity = entity;
}
 
public void addHeader(Header header) {
request.addHeader(header);
}
 
public void addHeader(String name, String value) {
request.addHeader(name, value);
}
 
public boolean containsHeader(String name) {
return request.containsHeader(name);
}
 
public Header[] getAllHeaders() {
return request.getAllHeaders();
}
 
public Header getFirstHeader(String name) {
return request.getFirstHeader(name);
}
 
public Header[] getHeaders(String name) {
return request.getHeaders(name);
}
 
public Header getLastHeader(String name) {
return request.getLastHeader(name);
}
 
public HttpParams getParams() {
return request.getParams();
}
 
public HeaderIterator headerIterator() {
return request.headerIterator();
}
 
public HeaderIterator headerIterator(String name) {
return request.headerIterator(name);
}
 
public void removeHeader(Header header) {
request.removeHeader(header);
}
 
public void removeHeaders(String name) {
request.removeHeaders(name);
}
 
public void setHeader(Header header) {
request.setHeader(header);
}
 
public void setHeader(String name, String value) {
request.setHeader(name, value);
}
 
public void setHeaders(Header[] headers) {
request.setHeaders(headers);
}
 
public void setParams(HttpParams params) {
request.setParams(params);
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/ErrorInputStream.java New file
0,0 → 1,109
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.model;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
 
/**
* Pipes the data and error messages of an OuputStream as an InputStream.
*
* @author James Leigh
*
*/
public class ErrorInputStream extends InputStream {
private InputStream delegate;
private IOException e;
 
public ErrorInputStream(PipedOutputStream pipe) throws IOException {
this.delegate = new PipedInputStream(pipe);
}
 
public void error(IOException e) {
this.e = e;
}
 
public int available() throws IOException {
throwIOException();
return delegate.available();
}
 
public void close() throws IOException {
throwIOException();
delegate.close();
}
 
public void mark(int readlimit) {
delegate.mark(readlimit);
}
 
public boolean markSupported() {
return delegate.markSupported();
}
 
public int read() throws IOException {
throwIOException();
return delegate.read();
}
 
public int read(byte[] b, int off, int len) throws IOException {
throwIOException();
return delegate.read(b, off, len);
}
 
public int read(byte[] b) throws IOException {
throwIOException();
return delegate.read(b);
}
 
public void reset() throws IOException {
throwIOException();
delegate.reset();
}
 
public long skip(long n) throws IOException {
throwIOException();
return delegate.skip(n);
}
 
public String toString() {
return delegate.toString();
}
 
private void throwIOException() throws IOException {
try {
if (e != null)
throw e;
} finally {
e = null;
}
}
}
\ No newline at end of file
trunk/object-server/src/main/java/org/openrdf/http/object/model/Filter.java New file
0,0 → 1,67
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.model;
 
import java.io.IOException;
 
import org.apache.http.HttpResponse;
 
/**
* Interface used to stack HTTP filters.
*
* @author James Leigh
*
*/
public class Filter {
private final Filter delegate;
 
public Filter(Filter delegate) {
this.delegate = delegate;
}
 
public Request filter(Request request) throws IOException {
if (delegate == null)
return request;
return delegate.filter(request);
}
 
public HttpResponse intercept(Request request) throws IOException {
if (delegate == null)
return null;
return delegate.intercept(request);
}
 
public HttpResponse filter(Request request, HttpResponse response)
throws IOException {
if (delegate == null)
return response;
return delegate.filter(request, response);
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/InputStreamHttpEntity.java New file
0,0 → 1,161
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.model;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Arrays;
import java.util.List;
 
import org.apache.http.Header;
import org.apache.http.message.BasicHeader;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.entity.ProducingNHttpEntity;
 
/**
* Allows an InputStream to be used as an HttpEntity.
*
* @author James Leigh
*
*/
public class InputStreamHttpEntity implements ProducingNHttpEntity {
private String contentType;
private long contentLength;
private InputStream in;
private ByteBuffer buf = ByteBuffer.allocate(1024);
private ReadableByteChannel cin;
private List<Runnable> onClose;
 
public InputStreamHttpEntity(String type, long length, InputStream in) {
this(type, length, in, (List<Runnable>) null);
}
 
public InputStreamHttpEntity(String type, long length, InputStream in,
Runnable... onClose) {
this(type, length, in, Arrays.asList(onClose));
}
 
public InputStreamHttpEntity(String type, long length, InputStream in,
List<Runnable> onClose) {
this.contentType = type;
this.contentLength = length;
this.in = in;
cin = Channels.newChannel(in);
this.onClose = onClose;
}
 
public final void consumeContent() throws IOException {
finish();
}
 
public InputStream getContent() throws IOException {
return in;
}
 
public Header getContentEncoding() {
return new BasicHeader("Content-Encoding", "identity");
}
 
public long getContentLength() {
return contentLength;
}
 
public Header getContentType() {
return new BasicHeader("Content-Type", contentType);
}
 
public boolean isChunked() {
return getContentLength() < 0;
}
 
public boolean isRepeatable() {
return false;
}
 
public boolean isStreaming() {
return true;
}
 
public void writeTo(OutputStream out) throws IOException {
InputStream in = getContent();
try {
byte[] buf = new byte[1024];
int read;
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
}
} catch (Error e) {
throw e;
} catch (RuntimeException e) {
throw e;
} catch (IOException e) {
throw e;
} catch (Exception e) {
throw new IOException(e);
} finally {
in.close();
}
}
 
public void finish() throws IOException {
try {
in.close();
} finally {
try {
cin.close();
} finally {
if (onClose != null) {
for (Runnable task : onClose) {
try {
task.run();
} catch (RuntimeException e) {
} catch (Error e) {
}
}
}
}
}
}
 
public void produceContent(ContentEncoder encoder, IOControl ioctrl)
throws IOException {
buf.clear();
if (cin.read(buf) < 0) {
encoder.complete();
} else {
buf.flip();
encoder.write(buf);
}
}
}
\ No newline at end of file
trunk/object-server/src/main/java/org/openrdf/http/object/model/Request.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,504 → 28,264
*/
package org.openrdf.http.object.model;
 
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.nio.charset.Charset;
import java.nio.charset.UnsupportedCharsetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import info.aduna.net.ParsedURI;
 
import java.net.URISyntaxException;
import java.security.cert.X509Certificate;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Vector;
 
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.http.Header;
import org.apache.http.HttpRequest;
import org.openrdf.http.object.exceptions.BadRequest;
 
import org.openrdf.http.object.annotations.type;
import org.openrdf.http.object.concepts.HTTPFileObject;
import org.openrdf.http.object.traits.VersionedObject;
import org.openrdf.http.object.util.GenericType;
import org.openrdf.http.object.writers.AggregateWriter;
import org.openrdf.http.object.writers.MessageBodyWriter;
import org.openrdf.model.URI;
import org.openrdf.model.ValueFactory;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.ObjectFactory;
import org.openrdf.repository.object.traits.RDFObjectBehaviour;
 
/**
* Utility class for {@link HttpServletRequest}.
*
* @author James Leigh
*
*/
public class Request extends RequestHeader {
private static Type parameterMapType;
static {
try {
parameterMapType = Request.class.getDeclaredMethod(
"getParameterMap").getGenericReturnType();
} catch (NoSuchMethodException e) {
throw new AssertionError(e);
}
}
protected ObjectFactory of;
protected ValueFactory vf;
private ObjectConnection con;
private File file;
private HttpServletRequest request;
private VersionedObject target;
private URI uri;
private MessageBodyWriter writer = AggregateWriter.getInstance();
private BodyEntity body;
private Accepter accepter;
public class Request extends EditableHttpEntityEnclosingRequest {
private long received = System.currentTimeMillis();
 
public Request(File dataDir, HttpServletRequest request,
ObjectConnection con) throws QueryEvaluationException,
RepositoryException, MimeTypeParseException {
public Request(HttpRequest request) {
super(request);
this.request = request;
this.con = con;
this.vf = con.getValueFactory();
this.of = con.getObjectFactory();
this.uri = vf.createURI(getURI());
target = con.getObject(HTTPFileObject.class, uri);
if (target instanceof HTTPFileObject) {
File base = new File(dataDir, safe(getAuthority()));
String path = getPath();
if (path == null) {
file = new File(base, safe(uri.stringValue()));
} else {
file = new File(base, safe(path));
}
if (!file.isFile()) {
int dot = file.getName().lastIndexOf('.');
String name = Integer.toHexString(uri.hashCode());
if (dot > 0) {
name = '$' + name + file.getName().substring(dot);
} else {
name = '$' + name;
}
file = new File(file, name);
}
((HTTPFileObject) target).initLocalFileObject(file, isSafe());
}
Enumeration headers = getVaryHeaders("Accept");
if (headers.hasMoreElements()) {
StringBuilder sb = new StringBuilder();
while (headers.hasMoreElements()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append((String) headers.nextElement());
}
accepter = new Accepter(sb.toString());
} else {
accepter = new Accepter();
}
}
 
public ResponseEntity createResultEntity(Object result, Class<?> ctype,
Type gtype, String[] mimeTypes) {
GenericType<?> type = new GenericType(ctype, gtype);
if (result != null && type.isSet()) {
Set set = (Set) result;
Iterator iter = set.iterator();
try {
if (!iter.hasNext()) {
result = null;
ctype = type.getComponentClass();
gtype = type.getComponentType();
} else {
Object object = iter.next();
if (!iter.hasNext()) {
result = object;
ctype = type.getComponentClass();
gtype = type.getComponentType();
}
}
} finally {
target.getObjectConnection().close(iter);
}
} else if (result != null && type.isArray()) {
int len = Array.getLength(result);
if (len == 0) {
result = null;
ctype = type.getComponentClass();
gtype = type.getComponentType();
} else if (len == 1) {
result = Array.get(result, 0);
ctype = type.getComponentClass();
gtype = type.getComponentType();
}
}
if (result instanceof RDFObjectBehaviour) {
result = ((RDFObjectBehaviour) result).getBehaviourDelegate();
}
return new ResponseEntity(mimeTypes, result, ctype, gtype, uri
.stringValue(), con);
public long getReceivedOn() {
return received;
}
 
public URI createURI(String uriSpec) {
return vf.createURI(parseURI(uriSpec).toString());
public void setReceivedOn(long received) {
this.received = received;
}
 
public void flush() throws RepositoryException, QueryEvaluationException, IOException {
ObjectConnection con = target.getObjectConnection();
con.commit(); // flush()
this.target = con.getObject(HTTPFileObject.class, target
.getResource());
@Override
public Request clone() {
Request clone = (Request) super.clone();
clone.received = received;
return clone;
}
 
public void rollback() throws RepositoryException {
ObjectConnection con = target.getObjectConnection();
con.rollback();
con.setAutoCommit(true); // rollback()
public String getHeader(String name) {
Header[] headers = getHeaders(name);
if (headers == null || headers.length == 0)
return null;
return headers[0].getValue();
}
 
public void commit() throws IOException, RepositoryException {
public long getDateHeader(String name) {
String value = getHeader(name);
if (value == null)
return -1;
try {
con.setAutoCommit(true); // prepare()
ObjectConnection con = target.getObjectConnection();
con.setAutoCommit(true); // commit()
} catch (RepositoryException e) {
rollback();
return DateUtil.parseDate(value).getTime();
} catch (DateParseException e) {
return -1;
}
}
 
public Entity getBody() throws MimeTypeParseException {
if (body != null)
return body;
String mediaType = getContentType();
String mime = removeParamaters(mediaType);
String location = getResolvedHeader("Content-Location");
if (location != null) {
location = createURI(location).stringValue();
}
Charset charset = getCharset(mediaType);
return body = new BodyEntity(mime, isMessageBody(), charset, uri
.stringValue(), location, con) {
public String resolve(String url) {
if (url == null)
return null;
return parseURI(url).toString();
}
 
@Override
protected InputStream getInputStream() throws IOException {
return request.getInputStream();
}
};
public String getResolvedHeader(String name) {
String value = getHeader(name);
if (value == null)
return null;
return resolve(value);
}
 
public String getContentType(Method method) throws MimeTypeParseException {
Class<?> type = method.getReturnType();
Type genericType = method.getGenericReturnType();
if (method.isAnnotationPresent(type.class)) {
String[] mediaTypes = method.getAnnotation(type.class).value();
for (MimeType m : accepter.getAcceptable(mediaTypes)) {
if (writer.isWriteable(m.toString(), type, genericType, of)) {
return getContentType(type, genericType, m);
}
}
} else {
for (MimeType m : accepter.getAcceptable()) {
if (writer.isWriteable(m.toString(), type, genericType, of)) {
return getContentType(type, genericType, m);
}
}
}
public X509Certificate getX509Certificate() {
// TODO getAttribute("javax.servlet.request.X509Certificate");
return null;
}
 
public File getFile() {
return file;
}
 
public InputStream getInputStream() throws IOException {
return request.getInputStream();
}
 
public ObjectConnection getObjectConnection() {
return con;
}
 
public String getOperation() {
Map<String, String[]> params = getParameterMap();
if (params != null) {
for (String key : params.keySet()) {
String[] values = params.get(key);
if (values == null || values.length == 0 || values.length == 1
&& (values[0] == null || values[0].length() == 0)) {
return key;
}
}
}
public String getRemoteAddr() {
// TODO REMOTE_ADDR
return null;
}
 
public Entity getHeader(String[] mediaTypes, String... names) {
String[] values = getHeaderValues(names);
return new ParameterEntity(mediaTypes, "text/plain", values, uri
.stringValue(), con);
public int getMaxAge() {
return getCacheControl("max-age", Integer.MAX_VALUE);
}
 
public Entity getParameter(String[] mediaTypes, String... names) {
String[] values = getParameterValues(names);
return new ParameterEntity(mediaTypes, "text/plain", values, uri
.stringValue(), con);
public int getMinFresh() {
return getCacheControl("min-fresh", 0);
}
 
public Entity getQueryString(String[] mediaTypes) {
String mimeType = "application/x-www-form-urlencoded";
String value = request.getQueryString();
if (value == null) {
return new ParameterEntity(mediaTypes, mimeType, new String[0], uri
.stringValue(), con);
}
return new ParameterEntity(mediaTypes, mimeType,
new String[] { value }, uri.stringValue(), con);
public int getMaxStale() {
return getCacheControl("max-stale", 0);
}
 
public VersionedObject getRequestedResource() {
return target;
public boolean isStorable() {
boolean safe = isSafe();
return safe && !isMessageBody() && getCacheControl("no-store", 0) == 0;
}
 
public boolean isAcceptable(Class<?> type, Type genericType)
throws MimeTypeParseException {
return isAcceptable(null, type, genericType);
public boolean isSafe() {
String method = getMethod();
return method.equals("HEAD") || method.equals("GET")
|| method.equals("OPTIONS") || method.equals("PROFIND");
}
 
public boolean isAcceptable(Method method) throws MimeTypeParseException {
Class<?> type = method.getReturnType();
Type genericType = method.getGenericReturnType();
if (method.isAnnotationPresent(type.class)) {
for (String media : method.getAnnotation(type.class).value()) {
if (isAcceptable(media, type, genericType))
return true;
}
return false;
}
return isAcceptable(type, genericType);
public boolean invalidatesCache() {
String method = getMethod();
return !isSafe() && !method.equals("TRACE") && !method.equals("COPY")
&& !method.equals("LOCK") && !method.equals("UNLOCK");
}
 
public boolean isAcceptable(String mediaType) throws MimeTypeParseException {
return isAcceptable(mediaType, null, null);
public boolean isNoCache() {
return isStorable() && getCacheControl("no-cache", 0) > 0;
}
 
public boolean isAcceptable(String mediaType, Class<?> type,
Type genericType) throws MimeTypeParseException {
if (type == null)
return accepter.isAcceptable(mediaType);
for (MimeType accept : accepter.getAcceptable(mediaType)) {
String mime = accept.getPrimaryType() + "/" + accept.getSubType();
if (writer.isWriteable(mime, type, genericType, of))
return true;
}
return false;
public boolean isOnlyIfCache() {
return isStorable() && getCacheControl("only-if-cached", 0) > 0;
}
 
public boolean isQueryStringPresent() {
return request.getQueryString() != null;
public String getMethod() {
return getRequestLine().getMethod();
}
 
public boolean modifiedSince(String entityTag, long lastModified)
throws MimeTypeParseException {
boolean notModified = false;
try {
if (lastModified > 0) {
long modified = getDateHeader("If-Modified-Since");
notModified = modified > 0;
if (notModified && modified < lastModified)
return true;
}
} catch (IllegalArgumentException e) {
// invalid date header
}
Enumeration matchs = getHeaders("If-None-Match");
boolean mustMatch = matchs.hasMoreElements();
if (mustMatch) {
while (matchs.hasMoreElements()) {
String match = (String) matchs.nextElement();
if (match(entityTag, match))
return false;
}
}
return !notModified || mustMatch;
public String getRequestTarget() {
Object value = null;
// TODO
// request.getAttribute(IndentityPathFilter.ORIGINAL_REQUEST_TARGET);
if (value != null)
return value.toString();
return getRequestLine().getUri();
}
 
public boolean unmodifiedSince(String entityTag, long lastModified)
throws MimeTypeParseException {
Enumeration matchs = getHeaders("If-Match");
boolean mustMatch = matchs.hasMoreElements();
try {
if (lastModified > 0) {
long unmodified = getDateHeader("If-Unmodified-Since");
if (unmodified > 0 && lastModified > unmodified)
return false;
}
} catch (IllegalArgumentException e) {
// invalid date header
}
while (matchs.hasMoreElements()) {
String match = (String) matchs.nextElement();
if (match(entityTag, match))
return true;
}
return !mustMatch;
public String getQueryString() {
String qs = getRequestLine().getUri();
int idx = qs.indexOf('?');
if (idx < 0)
return null;
return qs.substring(idx + 1);
}
 
private Charset getCharset(String mediaType) throws MimeTypeParseException {
if (mediaType == null)
return null;
MimeType m = new MimeType(mediaType);
String name = m.getParameters().get("charset");
if (name == null)
return null;
return Charset.forName(name);
public String getRequestURL() {
String qs = getQueryString();
if (qs == null)
return getURI();
return getURI() + "?" + qs;
}
 
private String getContentType(Class<?> type, Type genericType, MimeType m) {
Charset charset = null;
String cname = m.getParameters().get("charset");
try {
if (cname != null) {
charset = Charset.forName(cname);
return writer.getContentType(m.toString(), type, genericType,
of, charset);
}
} catch (UnsupportedCharsetException e) {
// ignore
public String getURI() {
String uri = getRequestLine().getUri();
if (uri.indexOf('?') > 0) {
uri = uri.substring(0, uri.indexOf('?'));
}
if (charset == null) {
int rating = 0;
Enumeration<String> accept = getHeaders("Accept-Charset");
while (accept.hasMoreElements()) {
String header = accept.nextElement().replaceAll("\\s", "");
for (String item : header.split(",")) {
int q = 1;
String name = item;
int c = item.indexOf(';');
if (c > 0) {
name = item.substring(0, c);
q = getQuality(item);
}
if (q > rating) {
try {
charset = Charset.forName(name);
rating = q;
} catch (UnsupportedCharsetException e) {
// ignore
}
}
if (uri.startsWith("/")) {
String scheme = getScheme().toLowerCase();
String host = getAuthority();
String path = getPath();
try {
java.net.URI net;
int idx = host.indexOf(':');
if (idx > 0) {
String hostname = host.substring(0, idx);
int port = Integer.parseInt(host.substring(idx + 1));
net = new java.net.URI(scheme, null, hostname, port, path,
null, null);
} else {
net = new java.net.URI(scheme, host, path, null);
}
uri = net.toASCIIString();
} catch (URISyntaxException e) {
// bad Host header
throw new BadRequest(e.getMessage());
}
}
String contentType = writer.getContentType(m.toString(), type, genericType, of,
charset);
if (contentType.contains("charset=")) {
getVaryHeaders("Accept-Charset");
}
return contentType;
return uri;
}
 
private Map<String, String[]> getParameterMap() {
try {
return getQueryString(null).read(Map.class, parameterMapType,
new String[]{"application/x-www-form-urlencoded"});
} catch (Exception e) {
return Collections.emptyMap();
}
public ParsedURI parseURI(String uriSpec) {
ParsedURI base = new ParsedURI(getURI());
base.normalize();
ParsedURI uri = new ParsedURI(uriSpec);
return base.resolve(uri);
}
 
private String[] getParameterValues(String... names) {
if (names.length == 0) {
return new String[0];
} else if (names.length == 1) {
return request.getParameterValues(names[0]);
} else {
List<String> list = new ArrayList<String>(names.length * 2);
for (String name : names) {
list.addAll(Arrays.asList(request.getParameterValues(name)));
}
return list.toArray(new String[list.size()]);
}
public boolean isMessageBody() {
return getHeader("Content-Length") != null
|| getHeader("Transfer-Encoding") != null;
}
 
private String[] getHeaderValues(String... names) {
if (names.length == 0)
return new String[0];
List<String> list = new ArrayList<String>(names.length * 2);
for (String name : names) {
Enumeration en = getVaryHeaders(name);
while (en.hasMoreElements()) {
list.add((String) en.nextElement());
}
}
return list.toArray(new String[list.size()]);
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getMethod()).append(" ").append(getRequestURL());
return sb.toString();
}
 
private int getQuality(String item) {
int s = item.indexOf(";q=");
if (s > 0) {
int e = item.indexOf(';', s + 1);
if (e < 0) {
e = item.length();
}
public String getAuthority() {
String uri = getRequestLine().getUri();
if (uri != null && !uri.equals("*") && !uri.startsWith("/")) {
try {
return Integer.parseInt(item.substring(s + 3, e));
} catch (NumberFormatException exc) {
// ignore q
return new java.net.URI(uri).getAuthority();
} catch (URISyntaxException e) {
// try the host header
}
}
return 1;
String host = getHeader("Host");
if (host != null)
return host.toLowerCase();
throw new BadRequest("Missing Host Header");
}
 
private boolean match(String tag, String match) {
if (tag == null)
return false;
if ("*".equals(match))
return true;
if (match.equals(tag))
return true;
int md = match.indexOf('-');
int td = tag.indexOf('-');
if (td >= 0 && md >= 0)
return false;
if (md < 0) {
md = match.lastIndexOf('"');
public String getPath() {
String path = getRequestLine().getUri();
if (path == null || path.equals("*"))
return null;
if (!path.startsWith("/")) {
try {
return new java.net.URI(path).getPath();
} catch (URISyntaxException e) {
return null;
}
}
if (td < 0) {
td = tag.lastIndexOf('"');
int idx = path.indexOf('?');
if (idx > 0) {
path = path.substring(0, idx);
}
int mq = match.indexOf('"');
int tq = tag.indexOf('"');
if (mq < 0 || tq < 0 || md < 0 || td < 0)
return false;
return match.substring(mq, md).equals(tag.substring(tq, td));
return path;
}
 
private String removeParamaters(String mediaType) {
if (mediaType == null)
return null;
int idx = mediaType.indexOf(';');
if (idx > 0)
return mediaType.substring(0, idx);
return mediaType;
private String getScheme() {
// TODO compute scheme
return "http";
}
 
private String safe(String path) {
if (path == null)
return "";
path = path.replace('/', File.separatorChar);
path = path.replace('\\', File.separatorChar);
path = path.replace(':', File.separatorChar);
return path.replaceAll("[^a-zA-Z0-9/\\\\]", "_");
protected Enumeration getHeaderEnumeration(String name) {
Vector values = new Vector();
for (Header hd : getHeaders(name)) {
values.add(hd.getValue());
}
return values.elements();
}
 
private int getCacheControl(String directive, int def) {
Enumeration headers = getHeaderEnumeration("Cache-Control");
while (headers.hasMoreElements()) {
String value = (String) headers.nextElement();
for (String v : value.split("\\s*,\\s*")) {
int idx = v.indexOf('=');
if (idx >= 0 && directive.equals(v.substring(0, idx))) {
try {
return Integer.parseInt(v.substring(idx + 1));
} catch (NumberFormatException e) {
// invalid number
}
} else if (directive.equals(v)) {
return Integer.MAX_VALUE;
}
}
}
return def;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/ResourceOperation.java New file
0,0 → 1,827
/*
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.model;
 
import info.aduna.net.ParsedURI;
 
import java.io.File;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Enumeration;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
 
import javax.activation.MimeTypeParseException;
import javax.tools.FileObject;
import javax.xml.datatype.XMLGregorianCalendar;
 
import org.apache.http.HttpEntityEnclosingRequest;
import org.openrdf.http.object.annotations.cacheControl;
import org.openrdf.http.object.annotations.encoding;
import org.openrdf.http.object.annotations.header;
import org.openrdf.http.object.annotations.method;
import org.openrdf.http.object.annotations.operation;
import org.openrdf.http.object.annotations.parameter;
import org.openrdf.http.object.annotations.realm;
import org.openrdf.http.object.annotations.rel;
import org.openrdf.http.object.annotations.transform;
import org.openrdf.http.object.annotations.type;
import org.openrdf.http.object.concepts.Transaction;
import org.openrdf.http.object.exceptions.BadRequest;
import org.openrdf.http.object.exceptions.MethodNotAllowed;
import org.openrdf.http.object.exceptions.NotAcceptable;
import org.openrdf.http.object.traits.Realm;
import org.openrdf.http.object.traits.VersionedObject;
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.object.ObjectConnection;
import org.openrdf.repository.object.ObjectRepository;
import org.openrdf.repository.object.RDFObject;
import org.openrdf.repository.object.annotations.iri;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
* Utility class for {@link HttpServletRequest}.
*
* @author James Leigh
*
*/
public class ResourceOperation extends ResourceRequest {
private static int MAX_TRANSFORM_DEPTH = 100;
private Logger logger = LoggerFactory.getLogger(ResourceOperation.class);
 
private Method method;
private Method transformMethod;
private MethodNotAllowed notAllowed;
private BadRequest badRequest;
private NotAcceptable notAcceptable;
private List<?> realms;
private String[] realmURIs;
 
public ResourceOperation(File dataDir, HttpEntityEnclosingRequest request,
ObjectRepository repository) throws QueryEvaluationException,
RepositoryException, MimeTypeParseException {
super(dataDir, request, repository);
}
 
public void init() throws MimeTypeParseException, RepositoryException,
QueryEvaluationException {
super.init();
if (method == null) {
try {
String m = getMethod();
if ("GET".equals(m) || "HEAD".equals(m)) {
method = findMethod(m, true);
} else if ("PUT".equals(m) || "DELETE".equals(m)) {
method = findMethod(m, false);
} else {
method = findMethod(m);
}
transformMethod = getTransformMethodOf(method);
} catch (MethodNotAllowed e) {
notAllowed = e;
} catch (BadRequest e) {
badRequest = e;
} catch (NotAcceptable e) {
notAcceptable = e;
}
}
}
 
public String getResponseContentType() throws MimeTypeParseException {
Method m = getTransformMethod();
if (m == null || m.getReturnType().equals(Void.TYPE))
return null;
if (URL.class.equals(m.getReturnType()))
return null;
return getContentType(m);
}
 
public String getResponseContentEncoding() {
Method m = getTransformMethod();
if (m == null || m.getReturnType().equals(Void.TYPE))
return null;
if (URL.class.equals(m.getReturnType()))
return null;
if (!m.isAnnotationPresent(encoding.class))
return null;
StringBuilder sb = new StringBuilder();
for (String value : m.getAnnotation(encoding.class).value()) {
sb.append(",").append(value);
}
return sb.substring(1);
}
 
public String getEntityTag(String contentType)
throws MimeTypeParseException {
VersionedObject target = getRequestedResource();
Method m = this.method;
int headers = getHeaderCodeFor(m);
String method = getMethod();
if (contentType != null) {
return target.variantTag(contentType, headers);
} else if ("GET".equals(method) || "HEAD".equals(method)) {
if (m != null && contentType == null)
return target.revisionTag(headers);
if (m != null)
return target.variantTag(contentType, headers);
Method operation;
if ((operation = getOperationMethod("alternate")) != null) {
String type = getContentType(getTransformMethodOf(operation));
headers = getHeaderCodeFor(operation);
return target.variantTag(type, headers);
} else if ((operation = getOperationMethod("describedby")) != null) {
String type = getContentType(getTransformMethodOf(operation));
headers = getHeaderCodeFor(operation);
return target.variantTag(type, headers);
}
} else if ("PUT".equals(method)) {
Method get;
try {
headers = 0;
get = findMethod("GET", true);
headers = getHeaderCodeFor(get);
get = getTransformMethodOf(get);
} catch (MethodNotAllowed e) {
get = null;
} catch (BadRequest e) {
get = null;
} catch (NotAcceptable e) {
get = null;
}
if (get == null) {
return target.variantTag(getResponseContentType(), headers);
} else if (URL.class.equals(get.getReturnType())) {
return target.revisionTag(headers);
} else {
return target.variantTag(getContentType(get), headers);
}
} else {
Method get;
try {
headers = 0;
get = findMethod("GET", true);
headers = getHeaderCodeFor(get);
get = getTransformMethodOf(get);
} catch (MethodNotAllowed e) {
get = null;
} catch (BadRequest e) {
get = null;
} catch (NotAcceptable e) {
get = null;
}
if (get == null || URL.class.equals(get.getReturnType())) {
return target.revisionTag(headers);
} else {
return target.variantTag(getContentType(get), headers);
}
}
return null;
}
 
public Class<?> getEntityType() throws MimeTypeParseException {
String method = getMethod();
Method m = getTransformMethod();
if (m == null || "PUT".equals(method) || "DELETE".equals(method)
|| "OPTIONS".equals(method))
return null;
return m.getReturnType();
}
 
public long getLastModified() throws MimeTypeParseException {
String method = getMethod();
Method m = this.method;
if (m != null && !"PUT".equals(method) && !"DELETE".equals(method)
&& !"OPTIONS".equals(method)) {
if (m.isAnnotationPresent(cacheControl.class)) {
for (String value : m.getAnnotation(cacheControl.class).value()) {
if (value.contains("must-reevaluate"))
return System.currentTimeMillis() / 1000 * 1000;
}
}
}
VersionedObject target = getRequestedResource();
if (mustReevaluate(target.getClass()))
return System.currentTimeMillis() / 1000 * 1000;
if (target instanceof FileObject)
return ((FileObject) target).getLastModified() / 1000 * 1000;
Transaction trans = target.getRevision();
if (trans != null) {
XMLGregorianCalendar xgc = trans.getCommittedOn();
if (xgc != null) {
GregorianCalendar cal = xgc.toGregorianCalendar();
cal.set(Calendar.MILLISECOND, 0);
return cal.getTimeInMillis();
}
}
return 0;
}
 
public String getResponseCacheControl() {
if (!isStorable())
return null;
StringBuilder sb = new StringBuilder();
if (method != null && method.isAnnotationPresent(cacheControl.class)) {
for (String value : method.getAnnotation(cacheControl.class)
.value()) {
if (value != null) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append(value);
}
}
}
if (sb.length() <= 0) {
setCacheControl(getRequestedResource().getClass(), sb);
}
if (sb.indexOf("private") < 0 && sb.indexOf("public") < 0) {
if (isAuthenticating() && sb.indexOf("s-maxage") < 0) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append("s-maxage=0");
} else if (!isAuthenticating()) {
if (sb.length() > 0) {
sb.append(", ");
}
sb.append("public");
}
}
if (sb.length() > 0)
return sb.toString();
return null;
}
 
public boolean isAuthenticating() {
return getRealmURIs().length > 0;
}
 
public Set<String> getAllowedMethods() throws RepositoryException {
Set<String> set = new LinkedHashSet<String>();
String name = getOperation();
File file = getFile();
RDFObject target = getRequestedResource();
if (!isQueryStringPresent() && file != null && file.canRead()
|| getOperationMethods("GET", true).containsKey(name)) {
set.add("GET");
set.add("HEAD");
}
if (!isQueryStringPresent() && file != null) {
if (!file.exists() || file.canWrite()) {
set.add("PUT");
}
if (file.exists() && file.getParentFile().canWrite()) {
set.add("DELETE");
}
} else if (getOperationMethods("PUT", false).containsKey(name)) {
set.add("PUT");
} else if (getOperationMethods("DELETE", false).containsKey(name)) {
set.add("DELETE");
}
Map<String, List<Method>> map = getPostMethods(target);
for (String method : map.keySet()) {
set.add(method);
}
return set;
}
 
public Method getJavaMethod() {
if (notAllowed != null)
throw notAllowed;
if (badRequest != null)
throw badRequest;
if (notAcceptable != null)
throw notAcceptable;
return method;
}
 
public Method getOperationMethod(String rel) throws MimeTypeParseException {
Map<String, List<Method>> map = getOperationMethods("GET", true);
for (Map.Entry<String, List<Method>> e : map.entrySet()) {
for (Method m : e.getValue()) {
if (m.isAnnotationPresent(rel.class)) {
for (String value : m.getAnnotation(rel.class).value()) {
if (rel.equals(value) && isAcceptable(m)) {
return m;
}
}
}
}
}
return null;
}
 
public Map<String, List<Method>> getOperationMethods(String method,
Boolean isRespBody) {
Map<String, List<Method>> map = new HashMap<String, List<Method>>();
for (Method m : getRequestedResource().getClass().getMethods()) {
boolean content = !m.getReturnType().equals(Void.TYPE);
if (isRespBody != null && isRespBody != content)
continue;
operation ann = m.getAnnotation(operation.class);
if (ann == null)
continue;
if (m.isAnnotationPresent(method.class)) {
for (String v : m.getAnnotation(method.class).value()) {
if (method.equals(v)) {
put(map, ann.value(), m);
break;
}
}
} else if ("OPTIONS".equals(method)) {
put(map, ann.value(), m);
} else {
boolean body = isRequestBody(m);
if (("GET".equals(method) || "HEAD".equals(method)) && content
&& !body) {
put(map, ann.value(), m);
} else if (("PUT".equals(method) || "DELETE".equals(method))
&& !content && body) {
put(map, ann.value(), m);
} else if ("POST".equals(method) && content && body) {
put(map, ann.value(), m);
}
}
}
return map;
}
 
public Object[] getParameters(Method method, Entity input) throws Exception {
Class<?>[] ptypes = method.getParameterTypes();
Annotation[][] anns = method.getParameterAnnotations();
Type[] gtypes = method.getGenericParameterTypes();
Object[] args = new Object[ptypes.length];
for (int i = 0; i < args.length; i++) {
String[] types = getParameterMediaTypes(anns[i]);
args[i] = getParameter(anns[i], ptypes[i], input).read(ptypes[i],
gtypes[i], types);
}
return args;
}
 
public ResponseEntity invoke(Method method, Object[] args, boolean follow)
throws Exception {
Object result = method.invoke(getRequestedResource(), args);
ResponseEntity input = createResultEntity(result, method
.getReturnType(), method.getGenericReturnType(),
getTypes(method));
if (follow && method.isAnnotationPresent(transform.class)) {
for (String uri : method.getAnnotation(transform.class).value()) {
Method transform = getTransform(uri);
if (isAcceptable(transform, 0)) {
return invoke(transform, getParameters(transform, input),
follow);
}
}
}
return input;
}
 
private boolean isRequestBody(Method method) {
for (Annotation[] anns : method.getParameterAnnotations()) {
if (getParameterNames(anns) == null && getHeaderNames(anns) == null)
return true;
}
return false;
}
 
private String[] getTypes(Method method) {
if (method.isAnnotationPresent(type.class))
return method.getAnnotation(type.class).value();
return new String[0];
}
 
private Method findBestMethod(List<Method> methods)
throws MimeTypeParseException {
Method best = null;
boolean acceptable = true;
loop: for (Method method : methods) {
if (!isReadable(getBody(), method, 0))
continue loop;
if (method.getReturnType().equals(Void.TYPE)
|| method.getReturnType().equals(URL.class)
|| isAcceptable(method, 0)) {
panns: for (Annotation[] anns : method
.getParameterAnnotations()) {
for (Annotation ann : anns) {
if (ann.annotationType().equals(parameter.class))
continue panns;
if (ann.annotationType().equals(header.class))
continue panns;
}
for (Annotation ann : anns) {
if (ann.annotationType().equals(type.class)) {
Accepter accepter = new Accepter(((type) ann)
.value());
if (accepter.isAcceptable(getResponseContentType()))
return method; // compatible
continue loop; // incompatible
}
}
}
best = method;
} else {
acceptable = false;
}
}
if (best == null && !acceptable)
throw new NotAcceptable();
return best;
}
 
private Method findMethod(String method) throws MimeTypeParseException {
return findMethod(method, null);
}
 
private Method findMethod(String req_method, Boolean isResponsePresent)
throws MimeTypeParseException {
Method method = null;
boolean isMethodPresent = false;
String name = getOperation();
RDFObject target = getRequestedResource();
if (name != null) {
// lookup method
List<Method> methods = getOperationMethods(req_method,
isResponsePresent).get(name);
if (methods != null) {
isMethodPresent = true;
method = findBestMethod(methods);
}
}
if (method == null) {
List<Method> methods = new ArrayList<Method>();
for (Method m : target.getClass().getMethods()) {
method ann = m.getAnnotation(method.class);
if (ann == null)
continue;
if (!Arrays.asList(ann.value()).contains(req_method))
continue;
if (name != null && isOperationProhibited(m))
continue;
methods.add(m);
}
if (!methods.isEmpty()) {
isMethodPresent = true;
method = findBestMethod(methods);
}
}
if (method == null) {
if (isMethodPresent)
throw new BadRequest();
throw new MethodNotAllowed();
}
return method;
}
 
private boolean isOperationProhibited(Method m) {
return m.isAnnotationPresent(operation.class)
&& m.getAnnotation(operation.class).value().length == 0;
}
 
private Entity getParameter(Annotation[] anns, Class<?> ptype, Entity input)
throws Exception {
String[] names = getParameterNames(anns);
String[] headers = getHeaderNames(anns);
String[] types = getParameterMediaTypes(anns);
if (names == null && headers == null) {
return getValue(anns, input);
} else if (headers != null) {
return getValue(anns, getHeader(types, headers));
} else if (names.length == 1 && names[0].equals("*")) {
return getValue(anns, getQueryString(types));
} else {
return getValue(anns, getParameter(types, names));
}
}
 
private Entity getValue(Annotation[] anns, Entity input) throws Exception {
for (String uri : getTransforms(anns)) {
Method transform = getTransform(uri);
if (isReadable(input, transform, 0)) {
Object[] args = getParameters(transform, input);
return invoke(transform, args, false);
}
}
return input;
}
 
private String[] getParameterNames(Annotation[] annotations) {
for (int i = 0; i < annotations.length; i++) {
if (annotations[i].annotationType().equals(parameter.class))
return ((parameter) annotations[i]).value();
}
return null;
}
 
private String[] getHeaderNames(Annotation[] annotations) {
for (int i = 0; i < annotations.length; i++) {
if (annotations[i].annotationType().equals(header.class))
return ((header) annotations[i]).value();
}
return null;
}
 
private String[] getParameterMediaTypes(Annotation[] annotations) {
for (int i = 0; i < annotations.length; i++) {
if (annotations[i].annotationType().equals(type.class))
return ((type) annotations[i]).value();
}
return null;
}
 
private Map<String, List<Method>> getPostMethods(RDFObject target) {
Map<String, List<Method>> map = new HashMap<String, List<Method>>();
for (Method m : target.getClass().getMethods()) {
method ann = m.getAnnotation(method.class);
if (ann == null) {
if (m.isAnnotationPresent(operation.class)
&& !m.getReturnType().equals(Void.TYPE)
&& isRequestBody(m)) {
put(map, new String[] { "POST" }, m);
}
} else {
put(map, ann.value(), m);
}
}
return map;
}
 
private Method getTransform(String uri) {
for (Method m : getRequestedResource().getClass().getMethods()) {
if (m.isAnnotationPresent(iri.class)) {
if (uri.equals(m.getAnnotation(iri.class).value())) {
return m;
}
}
}
logger.warn("Method not found: {}", uri);
return null;
}
 
private Method getTransformMethod() {
return transformMethod;
}
 
private Method getTransformMethodOf(Method method)
throws MimeTypeParseException {
if (method == null)
return method;
if (method.isAnnotationPresent(transform.class)) {
for (String uri : method.getAnnotation(transform.class).value()) {
Method transform = getTransform(uri);
if (isAcceptable(transform, 0))
return getTransformMethodOf(transform);
}
}
return method;
}
 
private String[] getTransforms(Annotation[] anns) {
for (Annotation ann : anns) {
if (ann.annotationType().equals(transform.class)) {
return ((transform) ann).value();
}
}
return new String[0];
}
 
private int getHeaderCodeFor(Method method) throws MimeTypeParseException {
if (method == null)
return 0;
Set<String> names = getHeaderNamesFor(method, new HashSet<String>());
if (names.isEmpty())
return 0;
Map<String, String> headers = new HashMap<String, String>();
for (String name : names) {
Enumeration e = getHeaderEnumeration(name);
while (e.hasMoreElements()) {
String value = e.nextElement().toString();
if (headers.containsKey(name)) {
headers.put(name, headers.get(name) + "," + value);
} else {
headers.put(name, value);
}
}
}
return headers.hashCode();
}
 
private Set<String> getHeaderNamesFor(Method method, Set<String> names)
throws MimeTypeParseException {
for (Annotation[] anns : method.getParameterAnnotations()) {
String[] ar = getHeaderNames(anns);
if (ar != null) {
names.addAll(Arrays.asList(ar));
}
}
if (method.isAnnotationPresent(transform.class)) {
for (String uri : method.getAnnotation(transform.class).value()) {
Method transform = getTransform(uri);
if (isAcceptable(transform, 0))
return getHeaderNamesFor(transform, names);
}
}
return names;
}
 
private boolean isAcceptable(Method method, int depth)
throws MimeTypeParseException {
if (method == null)
return false;
if (depth > MAX_TRANSFORM_DEPTH) {
logger.error("Max transform depth exceeded: {}", method.getName());
return false;
}
if (method.isAnnotationPresent(transform.class)) {
for (String uri : method.getAnnotation(transform.class).value()) {
if (isAcceptable(getTransform(uri), ++depth))
return true;
}
}
if (method.isAnnotationPresent(type.class)) {
for (String media : getTypes(method)) {
if (isAcceptable(media, method.getReturnType(), method
.getGenericReturnType()))
return true;
}
return false;
} else {
return isAcceptable(method.getReturnType(), method
.getGenericReturnType());
}
}
 
private boolean isReadable(Entity input, Annotation[] anns, Class<?> ptype,
Type gtype, int depth) throws MimeTypeParseException {
if (getHeaderNames(anns) != null)
return true;
if (getParameterNames(anns) != null)
return true;
for (String uri : getTransforms(anns)) {
if (isReadable(input, getTransform(uri), ++depth))
return true;
}
return input.isReadable(ptype, gtype, getParameterMediaTypes(anns));
}
 
private boolean isReadable(Entity input, Method method, int depth)
throws MimeTypeParseException {
if (method == null)
return false;
if (depth > MAX_TRANSFORM_DEPTH) {
logger.error("Max transform depth exceeded: {}", method.getName());
return false;
}
Class<?>[] ptypes = method.getParameterTypes();
Annotation[][] anns = method.getParameterAnnotations();
Type[] gtypes = method.getGenericParameterTypes();
Object[] args = new Object[ptypes.length];
for (int i = 0; i < args.length; i++) {
if (!isReadable(input, anns[i], ptypes[i], gtypes[i], depth))
return false;
}
return true;
}
 
private void setCacheControl(Class<?> type, StringBuilder sb) {
if (type.isAnnotationPresent(cacheControl.class)) {
for (String value : type.getAnnotation(cacheControl.class).value()) {
if (value != null) {
if (sb.length() > 0) {
sb.append(", ");
} else {
sb.append(value);
}
}
}
} else {
if (type.getSuperclass() != null) {
setCacheControl(type.getSuperclass(), sb);
}
for (Class<?> face : type.getInterfaces()) {
setCacheControl(face, sb);
}
}
}
 
private String[] getRealmURIs() {
if (realmURIs != null)
return realmURIs;
RDFObject target = getRequestedResource();
if (method != null && method.isAnnotationPresent(realm.class)) {
realmURIs = method.getAnnotation(realm.class).value();
} else {
ArrayList<String> list = new ArrayList<String>();
addRealms(list, target.getClass());
if (Realm.OPERATIONS.contains(getOperation())) {
list.remove(getURI());
}
realmURIs = list.toArray(new String[list.size()]);
}
ParsedURI base = null;
for (int i = 0; i < realmURIs.length; i++) {
if (realmURIs[i].startsWith("/")) {
if (base == null) {
base = new ParsedURI(target.getResource().stringValue());
}
realmURIs[i] = base.resolve(realmURIs[i]).toString();
}
}
return realmURIs;
}
 
public List<?> getRealms() throws QueryEvaluationException,
RepositoryException {
if (realms != null)
return realms;
String[] values = getRealmURIs();
if (values.length == 0)
return Collections.emptyList();
ObjectConnection con = getObjectConnection();
return realms = con.getObjects(Realm.class, values).asList();
}
 
private void addRealms(ArrayList<String> list, Class<?> type) {
if (type.isAnnotationPresent(realm.class)) {
for (String value : type.getAnnotation(realm.class).value()) {
list.add(value);
}
} else {
if (type.getSuperclass() != null) {
addRealms(list, type.getSuperclass());
}
for (Class<?> face : type.getInterfaces()) {
addRealms(list, face);
}
}
}
 
private boolean mustReevaluate(Class<?> type) {
if (type.isAnnotationPresent(cacheControl.class)) {
for (String value : type.getAnnotation(cacheControl.class).value()) {
if (value.contains("must-reevaluate"))
return true;
}
} else {
if (type.getSuperclass() != null) {
if (mustReevaluate(type.getSuperclass()))
return true;
}
for (Class<?> face : type.getInterfaces()) {
if (mustReevaluate(face))
return true;
}
}
return false;
}
 
private void put(Map<String, List<Method>> map, String[] keys, Method m) {
for (String key : keys) {
List<Method> list = map.get(key);
if (list == null) {
map.put(key, list = new ArrayList<Method>());
}
list.add(m);
}
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/ResponseEntity.java
1,5 → 1,5
/*
* Copyright (c) 2009, Zepheira All rights reserved.
* Copyright 2009-2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
30,14 → 30,9
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
 
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
66,33 → 61,6
* Wraps a message response to output to an HTTP response.
*/
public class ResponseEntity implements Entity {
private final class PipedStream extends PipedInputStream {
private OutputStream out;
private IOException exception;
 
public PipedStream() throws IOException {
out = new PipedOutputStream(this);
}
 
public OutputStream getOutputStream() {
return out;
}
 
public void fatal(IOException e) {
if (exception != null) {
exception = e;
}
}
 
@Override
public void close() throws IOException {
super.close();
if (exception != null)
throw exception;
}
}
 
private static Executor executor = Executors.newCachedThreadPool();
private MessageBodyWriter writer = AggregateWriter.getInstance();
private MessageBodyReader reader = AggregateReader.getInstance();
private String[] mimeTypes;
121,7 → 89,8
return String.valueOf(result);
}
 
public boolean isReadable(Class<?> type, Type genericType, String[] mediaTypes) throws MimeTypeParseException {
public boolean isReadable(Class<?> type, Type genericType,
String[] mediaTypes) throws MimeTypeParseException {
Accepter accepter = new Accepter(mediaTypes);
if (!accepter.isAcceptable(mimeTypes))
return false;
139,11 → 108,9
}
 
public <T> T read(Class<T> type, Type genericType, String[] mediaTypes)
throws QueryResultParseException, TupleQueryResultHandlerException,
QueryEvaluationException, RepositoryException,
TransformerConfigurationException, IOException, XMLStreamException,
ParserConfigurationException, SAXException, TransformerException,
MimeTypeParseException {
throws OpenRDFException, TransformerConfigurationException,
IOException, XMLStreamException, ParserConfigurationException,
SAXException, TransformerException, MimeTypeParseException {
if (this.type.equals(type) && this.genericType.equals(genericType))
return (T) (result);
Accepter accepter = new Accepter(mediaTypes);
153,25 → 120,7
String mime = removeParamaters(contentType);
Charset charset = getCharset(contentType);
if (isReadable(type, genericType, mime)) {
final PipedStream in = new PipedStream();
final OutputStream out = in.getOutputStream();
executor.execute(new Runnable() {
public void run() {
try {
writeTo(mimeType.toString(), null, out, 1024);
} catch (IOException e) {
in.fatal(e);
} catch (Exception e) {
in.fatal(new IOException(e));
} finally {
try {
out.close();
} catch (IOException e) {
in.fatal(e);
}
}
}
});
InputStream in = write(mimeType.toString(), null);
return (T) (readFrom(type, genericType, mime, charset, in));
}
}
216,12 → 165,11
return writer.getSize(mimeType, type, genericType, of, result, charset);
}
 
public void writeTo(String mimeType, Charset charset, OutputStream out,
int bufSize) throws IOException, OpenRDFException,
XMLStreamException, TransformerException,
ParserConfigurationException {
writer.writeTo(mimeType, type, genericType, of, result, base, charset,
out, bufSize);
public InputStream write(String mimeType, Charset charset)
throws IOException, OpenRDFException, XMLStreamException,
TransformerException, ParserConfigurationException {
return writer.write(mimeType, type, genericType, of, result, base,
charset);
}
 
private boolean isReadable(Class<?> type, Type genericType, String mime) {
trunk/object-server/src/main/java/org/openrdf/http/object/model/Entity.java
1,5 → 1,5
/*
* Copyright (c) 2009, Zepheira All rights reserved.
* Copyright (c) 2009-2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
37,10 → 37,7
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
 
import org.openrdf.query.QueryEvaluationException;
import org.openrdf.query.TupleQueryResultHandlerException;
import org.openrdf.query.resultio.QueryResultParseException;
import org.openrdf.repository.RepositoryException;
import org.openrdf.OpenRDFException;
import org.xml.sax.SAXException;
 
/**
52,10 → 49,8
throws MimeTypeParseException;
 
<T> T read(Class<T> class1, Type type, String[] mediaTypes)
throws QueryResultParseException, TupleQueryResultHandlerException,
QueryEvaluationException, RepositoryException,
TransformerConfigurationException, IOException, XMLStreamException,
ParserConfigurationException, SAXException, TransformerException,
MimeTypeParseException;
throws TransformerConfigurationException, IOException,
XMLStreamException, ParserConfigurationException, SAXException,
TransformerException, MimeTypeParseException, OpenRDFException;
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/Handler.java New file
0,0 → 1,39
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.model;
 
/**
* Interface used to stack request handlers.
*
* @author James Leigh
*
*/
public interface Handler {
Response handle(ResourceOperation request) throws Exception;
}
trunk/object-server/src/main/java/org/openrdf/http/object/model/Response.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
31,18 → 31,22
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.TimeZone;
 
import javax.activation.MimeTypeParseException;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.TransformerException;
 
import org.apache.http.Header;
import org.apache.http.ProtocolVersion;
import org.apache.http.message.AbstractHttpMessage;
import org.openrdf.OpenRDFException;
import org.openrdf.http.object.exceptions.BadRequest;
import org.openrdf.http.object.exceptions.Conflict;
56,19 → 60,41
*
* @author James Leigh
*/
public class Response {
public class Response extends AbstractHttpMessage {
/** Date format pattern used to generate the header in RFC 1123 format. */
public static final String PATTERN_RFC1123 = "EEE, dd MMM yyyy HH:mm:ss zzz";
/** The time zone to use in the date header. */
public static final TimeZone GMT = TimeZone.getTimeZone("GMT");
private static final DateFormat dateformat;
static {
dateformat = new SimpleDateFormat(PATTERN_RFC1123, Locale.US);
dateformat.setTimeZone(GMT);
}
 
private ResponseEntity entity;
private ResponseException exception;
private boolean head;
private Map<String, String> headers = new HashMap<String, String>();
private long lastModified;
private Class<?> type;
private int status = 204;
private String msg;
private Class<?> type;
private String phrase = "No Content";
private List<Runnable> onclose = new LinkedList<Runnable>();
 
public Response() {
setHeader("Content-Length", "0");
}
 
public Response onClose(Runnable task) {
onclose.add(task);
return this;
}
 
public List<Runnable> getOnClose() {
return onclose;
}
 
public Response unauthorized(InputStream message) throws IOException {
this.status = 401;
this.msg = "Unauthorized";
status(401, "Unauthorized");
if (message == null)
return this;
StringWriter headers = new StringWriter();
100,14 → 126,15
}
this.type = InputStream.class;
this.entity = new ResponseEntity(mimeTypes, in, type, type, null, null);
removeHeaders("Content-Length");
return this;
}
 
public Response exception(ResponseException e) {
this.status = e.getStatusCode();
this.msg = e.getMessage();
status(e.getStatusCode(), e.getMessage());
this.exception = e;
this.entity = null;
setHeader("Content-Length", "0");
return this;
}
 
120,12 → 147,13
}
 
public Response entity(ResponseEntity entity) {
this.status = 200;
status(200, "OK");
this.entity = entity;
removeHeaders("Content-Length");
return this;
}
 
public ResponseEntity getEntity() {
public ResponseEntity getResponseEntity() {
return entity;
}
 
138,13 → 166,12
}
 
public String getHeader(String header) {
return headers.get(header);
Header hd = getFirstHeader(header);
if (hd == null)
return null;
return hd.getValue();
}
 
public Set<String> getHeaderNames() throws MimeTypeParseException {
return headers.keySet();
}
 
public Long getLastModified() {
return lastModified;
}
154,11 → 181,11
}
 
public int getStatus() {
return status;
return getStatusCode();
}
 
public String getMessage() {
return msg;
return phrase;
}
 
public Response head() {
168,13 → 195,13
 
public Response header(String header, String value) {
if (value == null) {
headers.remove(header);
removeHeaders(header);
} else {
String existing = headers.get(header);
String existing = getHeader(header);
if (existing == null) {
headers.put(header, value);
} else {
headers.put(header, existing + "," + value);
setHeader(header, value);
} else if (!existing.equals(value)) {
setHeader(header, existing + "," + value);
}
}
return this;
193,11 → 220,11
}
 
public boolean isNoContent() {
return status == 204;
return getStatusCode() == 204;
}
 
public boolean isOk() {
return status == 200;
return getStatusCode() == 200;
}
 
public Response lastModified(long lastModified) {
208,6 → 235,7
if (pre >= lastModified)
return this;
this.lastModified = lastModified;
setDateHeader("Last-Modified", lastModified);
return this;
}
 
217,9 → 245,9
}
 
public Response noContent() {
this.status = 204;
this.msg = "No Content";
status(204, "No Content");
this.entity = null;
setHeader("Content-Length", "0");
return this;
}
 
228,16 → 256,16
}
 
public Response notModified() {
this.status = 304;
this.msg = "Not Modified";
status(304, "Not Modified");
this.entity = null;
setHeader("Content-Length", "0");
return this;
}
 
public Response preconditionFailed() {
this.status = 412;
this.msg = "Precondition Failed";
status(412, "Precondition Failed");
this.entity = null;
setHeader("Content-Length", "0");
return this;
}
 
249,25 → 277,32
this.type = type;
}
 
public Response status(int status) {
public Response status(int status, String msg) {
this.status = status;
this.phrase = msg;
return this;
}
 
public Response status(String status) {
this.msg = status;
return this;
public String toString() {
return phrase;
}
 
public String toString() {
return Integer.toString(status);
public InputStream write(String mimeType, Charset charset)
throws IOException, OpenRDFException, XMLStreamException,
TransformerException, ParserConfigurationException {
return entity.write(mimeType, charset);
}
 
public void writeTo(String mimeType, Charset charset, OutputStream out,
int bufSize) throws IOException, OpenRDFException,
XMLStreamException, TransformerException,
ParserConfigurationException {
entity.writeTo(mimeType, charset, out, bufSize);
public void setDateHeader(String name, long time) {
header(name, dateformat.format(time));
}
 
public int getStatusCode() {
return status;
}
 
public ProtocolVersion getProtocolVersion() {
return new ProtocolVersion("HTTP", 1, 1);
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/cache/InMemoryResponseHeader.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/cache/FileResponse.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/cache/ReadableResponse.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/cache/CachedRequest.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
37,7 → 37,8
import java.util.LinkedList;
import java.util.List;
 
import org.openrdf.http.object.model.RequestHeader;
import org.apache.http.HttpResponse;
import org.openrdf.http.object.model.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
112,7 → 113,7
return dir;
}
 
public CachedEntity find(RequestHeader req) {
public CachedEntity find(Request req) {
String method = req.getMethod();
if ("HEAD".equals(method)) {
method = "GET";
133,7 → 134,7
return null;
}
 
public String findCachedETags(RequestHeader req) {
public String findCachedETags(Request req) {
String url = req.getRequestURL();
String method = req.getMethod();
if ("HEAD".equals(method)) {
152,25 → 153,27
return sb.toString();
}
 
public CachedEntity find(FileResponse response) throws IOException,
public CachedEntity find(Request req, HttpResponse response, File tmp) throws IOException,
InterruptedException {
String method = response.getMethod();
String url = response.getUrl();
String entityTag = response.getEntityTag();
String method = req.getMethod();
String url = req.getRequestURL();
String value = response.getFirstHeader("ETag").getValue();
int start = value.indexOf('"');
int end = value.lastIndexOf('"');
String entityTag = value.substring(start + 1, end);
for (CachedEntity cached : responses) {
if (cached.getEntityTag().equals(entityTag)
&& cached.getMethod().equals(method)
&& cached.getURL().equals(url)) {
cached.setResponse(response);
cached.setResponse(response, tmp);
return cached;
}
}
assert response.isModified();
String hex = Integer.toHexString(url.hashCode());
String name = "$" + method + '-' + hex + '-' + entityTag;
File body = new File(dir, name);
File head = new File(dir, name + "-head");
return new CachedEntity(method, url, response, head, body);
return new CachedEntity(method, url, response, tmp, head, body);
}
 
public void replace(CachedEntity stale, CachedEntity fresh)
trunk/object-server/src/main/java/org/openrdf/http/object/cache/CachingFilter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
31,30 → 31,36
import info.aduna.concurrent.locks.Lock;
 
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
 
import org.openrdf.http.object.model.RequestHeader;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.protocol.HttpDateGenerator;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.InputStreamHttpEntity;
import org.openrdf.http.object.model.Request;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
public class CachingFilter implements Filter {
public class CachingFilter extends Filter {
private static AtomicLong seq = new AtomicLong(0);
private static final HttpDateGenerator DATE_GENERATOR = new HttpDateGenerator();
private static String hostname;
static {
try {
68,7 → 74,8
private Logger logger = LoggerFactory.getLogger(CachingFilter.class);
private CacheIndex cache;
 
public CachingFilter(File dataDir, int maxCapacity) {
public CachingFilter(Filter delegate, File dataDir, int maxCapacity) {
super(delegate);
this.cache = new CacheIndex(dataDir, maxCapacity);
}
 
80,84 → 87,180
cache.setMaxCapacity(maxCapacity);
}
 
public void init(FilterConfig config) throws ServletException {
// no-op
@Override
public Request filter(Request request) throws IOException {
try {
if (request.isStorable()) {
CachableRequest req = forCache(request);
if (req != null)
return super.filter(req);
}
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
return super.filter(request);
}
 
public void destroy() {
// no-op
}
 
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
RequestHeader headers = new RequestHeader(req);
@Override
public HttpResponse intercept(Request headers) throws IOException {
if (headers.isStorable()) {
try {
useCache(headers, req, res, chain);
HttpResponse resp = useCache(headers);
if (resp != null)
return resp;
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
res.sendError(504); // Gateway Timeout
return;
return respond(504, "Gateway Timeout");
}
} else {
if (headers.invalidatesCache()) {
invalidate(headers, req, chain, res);
}
return super.intercept(headers);
}
 
@Override
public HttpResponse filter(Request request, HttpResponse response)
throws IOException {
response = super.filter(request, response);
try {
if (request instanceof CachableRequest) {
CachableRequest cr = (CachableRequest) request;
HttpResponse resp = saveInCache(cr, response);
if (resp != null)
return resp;
} else {
chain.doFilter(req, res);
invalidate(request);
}
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
return response;
}
 
private void useCache(RequestHeader headers, HttpServletRequest req,
HttpServletResponse res, FilterChain chain) throws IOException,
InterruptedException, ServletException {
Lock used = null;
private HttpResponse respond(int code, String reason) {
ProtocolVersion ver = new ProtocolVersion("HTTP", 1, 1);
BasicHttpResponse resp = new BasicHttpResponse(ver, code, reason);
resp.setHeader("Date", DATE_GENERATOR.getCurrentDate());
resp.setHeader("Content-Length", "0");
return resp;
}
 
private CachableRequest forCache(Request headers) throws IOException,
InterruptedException {
long now = headers.getReceivedOn();
CachedEntity cached = null;
String url = headers.getRequestURL();
CachedRequest index = cache.findCachedRequest(url);
Lock lock = index.lock();
try {
long now = System.currentTimeMillis();
CachedEntity cached = null;
String url = headers.getRequestURL();
CachedRequest index = cache.findCachedRequest(url);
Lock lock = index.lock();
cached = index.find(headers);
boolean stale = isStale(cached, headers, now);
if (stale && !headers.isOnlyIfCache()) {
String match = index.findCachedETags(headers);
return new CachableRequest(headers, cached, match);
}
return null;
} finally {
lock.release();
}
}
 
private HttpResponse useCache(Request headers) throws IOException,
InterruptedException {
long now = headers.getReceivedOn();
CachedEntity cached = null;
String url = headers.getRequestURL();
CachedRequest index = cache.findCachedRequest(url);
Lock lock = index.lock();
try {
cached = index.find(headers);
boolean stale = isStale(cached, headers, now);
if (stale && !headers.isOnlyIfCache()) {
return null;
} else if (cached == null && headers.isOnlyIfCache()) {
return respond(504, "Gateway Timeout");
} else {
return respondWithCache(now, headers, cached);
}
} finally {
lock.release();
}
}
 
private HttpResponse saveInCache(CachableRequest headers, HttpResponse res)
throws IOException, InterruptedException {
long now = headers.getReceivedOn();
CachedEntity cached = null;
String url = headers.getRequestURL();
CachedRequest index = cache.findCachedRequest(url);
Lock lock = index.lock();
try {
cached = index.find(headers);
if (isCachable(res)) {
File body = saveMessageBody(res, index.getDirectory(), url);
CachedEntity fresh = index.find(headers, res, body);
fresh.addRequest(headers);
index.replace(cached, fresh);
cached = fresh;
return respondWithCache(now, headers, cached);
}
} finally {
lock.release();
}
return null;
}
 
private File saveMessageBody(HttpResponse res, File dir, String url)
throws FileNotFoundException, IOException {
HttpEntity entity = res.getEntity();
if (entity == null)
return null;
long id = seq.incrementAndGet();
String hex = Integer.toHexString(url.hashCode());
File file = new File(dir, "$" + hex + '-' + id + ".part");
MessageDigest digest = null;
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
logger.debug(e.toString(), e);
}
dir.mkdirs();
FileOutputStream out = new FileOutputStream(file);
try {
InputStream in = entity.getContent();
try {
cached = index.find(headers);
boolean stale = isStale(cached, headers, now);
if (stale && !headers.isOnlyIfCache()) {
File dir = index.getDirectory();
CachableRequest cachable;
FileResponse body;
String match = index.findCachedETags(headers);
cachable = new CachableRequest(req, cached, match);
body = new FileResponse(url, cachable, res, dir, lock);
chain.doFilter(cachable, body);
body.flushBuffer();
if (body.isCachable()) {
CachedEntity fresh = index.find(body);
fresh.addRequest(headers);
index.replace(cached, fresh);
cached = fresh;
used = cached.open();
int read;
byte[] buf = new byte[1024];
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
if (digest != null) {
digest.update(buf, 0, read);
}
} else if (stale) {
res.setStatus(504);
} else {
used = cached.open();
}
} finally {
lock.release();
in.close();
}
if (used != null) {
respondWithCache(now, req, cached, res);
}
} finally {
if (used != null) {
used.release();
}
out.close();
entity.consumeContent();
}
if (digest != null) {
byte[] hash = Base64.encodeBase64(digest.digest());
String contentMD5 = new String(hash, "UTF-8");
res.setHeader("Content-MD5", contentMD5);
}
return file;
}
 
private boolean isStale(CachedEntity cached, RequestHeader headers, long now)
private boolean isCachable(HttpResponse res) {
for (Header hd : res.getHeaders("Cache-Control")) {
if (hd.getValue().contains("no-store"))
return false;
if (hd.getValue().contains("private"))
return false;
}
return res.containsHeader("ETag");
}
 
private boolean isStale(CachedEntity cached, Request headers, long now)
throws IOException {
if (cached == null || headers.isNoCache() || cached.isStale())
return true;
175,29 → 278,34
return age > maxage || !fresh;
}
 
private void respondWithCache(long now, HttpServletRequest req,
CachedEntity cached, HttpServletResponse res) throws IOException {
private HttpResponse respondWithCache(long now, Request req,
CachedEntity cached) throws IOException, InterruptedException {
if (req instanceof CachableRequest) {
req = ((CachableRequest) req).getOriginalRequest();
}
int status = cached.getStatus();
String statusText = cached.getStatusText();
ProtocolVersion ver = new ProtocolVersion("HTTP", 1, 1);
BasicHttpResponse res = new BasicHttpResponse(ver, status, statusText);
boolean unmodifiedSince = unmodifiedSince(req, cached);
boolean modifiedSince = modifiedSince(req, cached);
List<Long> range = range(req, cached);
int status = cached.getStatus();
String statusText = cached.getStatusText();
String method = req.getMethod();
if (!unmodifiedSince) {
res.setStatus(412); // Precondition Failed
res.setStatusLine(ver, 412, "Precondition Failed");
} else if (!modifiedSince
&& ("GET".equals(method) || "HEAD".equals(method))) {
res.setStatus(304); // Not Modified
res.setStatusLine(ver, 304, "Not Modified");
} else if (!modifiedSince) {
res.setStatus(412); // Precondition Failed
res.setStatusLine(ver, 412, "Precondition Failed");
} else if (status == 200 && range != null && range.isEmpty()) {
res.setStatus(416); // Requested Range Not Satisfiable
res.setStatusLine(ver, 416, "Requested Range Not Satisfiable");
} else if (status == 200 && range != null) {
res.setStatus(206); // Partial Content
res.setStatusLine(ver, 206, "Partial Content");
} else if (statusText == null) {
res.setStatus(status);
res.setStatusCode(status);
} else {
res.setStatus(status, statusText);
res.setStatusLine(ver, status, statusText);
}
sendEntityHeaders(now, cached, res);
if (unmodifiedSince && modifiedSince) {
207,10 → 315,13
} else {
sendMessageBody(method, cached, res);
}
} else {
res.setHeader("Content-Length", "0");
}
return res;
}
 
private boolean unmodifiedSince(HttpServletRequest req, CachedEntity cached) {
private boolean unmodifiedSince(Request req, CachedEntity cached) {
try {
long lastModified = cached.lastModified();
if (lastModified > 0) {
221,20 → 332,19
} catch (IllegalArgumentException e) {
// invalid date header
}
Enumeration matchs = req.getHeaders("If-Match");
boolean mustMatch = matchs.hasMoreElements();
Header[] matchs = req.getHeaders("If-Match");
boolean mustMatch = matchs != null && matchs.length > 0;
if (mustMatch) {
String entityTag = cached.getETag();
while (matchs.hasMoreElements()) {
String match = (String) matchs.nextElement();
if (match(entityTag, match))
for (Header match : matchs) {
if (match(entityTag, match.getValue()))
return true;
}
}
return !mustMatch;
}
 
private boolean modifiedSince(HttpServletRequest req, CachedEntity cached) {
private boolean modifiedSince(Request req, CachedEntity cached) {
boolean notModified = false;
try {
long lastModified = cached.lastModified();
247,13 → 357,12
} catch (IllegalArgumentException e) {
// invalid date header
}
Enumeration matchs = req.getHeaders("If-None-Match");
boolean mustMatch = matchs.hasMoreElements();
Header[] matchs = req.getHeaders("If-None-Match");
boolean mustMatch = matchs != null && matchs.length > 0;
if (mustMatch) {
String entityTag = cached.getETag();
while (matchs.hasMoreElements()) {
String match = (String) matchs.nextElement();
if (match(entityTag, match))
for (Header match : matchs) {
if (match(entityTag, match.getValue()))
return false;
}
}
264,7 → 373,7
* None range request return null. Not satisfiable requests return an empty
* list. Satisfiable requests return a list of start and length pairs.
*/
private List<Long> range(HttpServletRequest req, CachedEntity cached) {
private List<Long> range(Request req, CachedEntity cached) {
if (!cached.isBodyPresent())
return null;
String tag = req.getHeader("If-Range");
351,9 → 460,9
}
 
private void sendEntityHeaders(long now, CachedEntity cached,
HttpServletResponse res) throws IOException {
HttpResponse res) throws IOException {
int age = cached.getAge(now);
res.setIntHeader("Age", age);
res.setHeader("Age", Integer.toString(age));
if (age > cached.getLifeTime()) {
res.addHeader("Warning", WARN_110);
}
373,11 → 482,11
}
String type = cached.getContentType();
if (type != null) {
res.setContentType(type);
res.setHeader("Content-Type", type);
}
}
 
private void sendContentHeaders(CachedEntity cached, HttpServletResponse res) {
private void sendContentHeaders(CachedEntity cached, HttpResponse res) {
for (Map.Entry<String, String> e : cached.getContentHeaders()
.entrySet()) {
if (e.getValue() != null && e.getValue().length() > 0) {
387,7 → 496,8
}
 
private void sendRangeBody(String method, List<Long> range,
CachedEntity cached, HttpServletResponse res) throws IOException {
CachedEntity cached, HttpResponse res) throws IOException,
InterruptedException {
if (range.size() == 0)
return;
long contentLength = cached.contentLength();
400,82 → 510,92
res.setHeader("Content-Range", contentRange);
res.setHeader("Content-Length", Long.toString(length));
if (!"HEAD".equals(method)) {
ServletOutputStream out = res.getOutputStream();
try {
cached.writeBodyTo(out, res.getBufferSize(), start, length);
} finally {
out.close();
String type = null;
Header hd = res.getFirstHeader("Content-Type");
if (hd != null) {
type = hd.getValue();
}
InputStream in = cached.writeBody(start, length);
final Lock inUse = cached.open();
res.setEntity(new InputStreamHttpEntity(type, length, in,
new Runnable() {
public void run() {
inUse.release();
}
}));
}
} else {
String boundary = "THIS_STRING_SEPARATES";
res.setContentType("multipart/byteranges; boundary=" + boundary);
String type = "multipart/byteranges; boundary=" + boundary;
res.setHeader("ContentType", type);
if (!"HEAD".equals(method)) {
ServletOutputStream out = res.getOutputStream();
try {
CatInputStream out = new CatInputStream();
out.print("--");
out.println(boundary);
for (int i = 0, n = range.size(); i < n; i += 2) {
long start = range.get(i);
long length = range.get(i + 1);
long end = start + length - 1;
String ctype = cached.getContentType();
if (ctype != null) {
out.print("Content-Type: ");
out.println(ctype);
}
out.print("Content-Length: ");
out.println(Long.toString(length));
out.print("Content-Range: bytes ");
out.print(Long.toString(start));
out.print("-");
out.print(Long.toString(end));
out.print("/");
out.println(Long.toString(contentLength));
out.println();
out.append(cached.writeBody(start, length));
out.println();
out.print("--");
out.println(boundary);
for (int i = 0, n = range.size(); i < n; i += 2) {
long start = range.get(i);
long length = range.get(i + 1);
long end = start + length - 1;
String type = cached.getContentType();
if (type != null) {
out.print("Content-Type: ");
out.println(type);
}
out.print("Content-Length: ");
out.println(Long.toString(length));
out.print("Content-Range: bytes ");
out.print(start);
out.print("-");
out.print(end);
out.print("/");
out.println(contentLength);
out.println();
cached.writeBodyTo(out, res.getBufferSize(), start,
length);
out.println();
out.print("--");
out.println(boundary);
}
} finally {
out.close();
}
final Lock inUse = cached.open();
res.setEntity(new InputStreamHttpEntity(type, -1, out,
new Runnable() {
public void run() {
inUse.release();
}
}));
}
}
}
 
private void sendMessageBody(String method, CachedEntity cached,
HttpServletResponse res) throws IOException {
HttpResponse res) throws IOException, InterruptedException {
res.setHeader("Accept-Ranges", "bytes");
if (cached.getContentLength() != null) {
res.setHeader("Content-Length", cached.getContentLength());
String length = cached.getContentLength();
int size = -1;
if (length != null) {
res.setHeader("Content-Length", length);
size = Integer.parseInt(length);
}
if (!"HEAD".equals(method) && cached.isBodyPresent()) {
ServletOutputStream out = res.getOutputStream();
try {
cached.writeBodyTo(out, res.getBufferSize());
} finally {
out.close();
String type = null;
Header hd = res.getFirstHeader("Content-Type");
if (hd != null) {
type = hd.getValue();
}
InputStream in = cached.writeBody();
final Lock inUse = cached.open();
Runnable onClose = new Runnable() {
public void run() {
inUse.release();
}
};
res.setEntity(new InputStreamHttpEntity(type, size, in, onClose));
}
}
 
private void invalidate(RequestHeader headers, HttpServletRequest req,
FilterChain chain, HttpServletResponse res) throws IOException,
ServletException {
try {
cache.invalidate(headers.getRequestURL(), headers
.getResolvedHeader("Location"), headers
.getResolvedHeader("Content-Location"));
ReadableResponse resp = new ReadableResponse(res);
chain.doFilter(req, resp);
cache.invalidate(headers.getResolvedHeader("Location"),
headers.getResolvedHeader("Content-Location"));
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
res.sendError(503); // Service Unavailable
}
private void invalidate(Request headers) throws IOException,
InterruptedException {
String loc = headers.getResolvedHeader("Location");
String cloc = headers.getResolvedHeader("Content-Location");
cache.invalidate(headers.getRequestURL(), loc, cloc);
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/cache/CatInputStream.java New file
0,0 → 1,152
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.cache;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.LinkedList;
import java.util.Queue;
 
/**
* Allows multiple InputStreams to appear as one.
*
* @author James Leigh
*
*/
public class CatInputStream extends InputStream {
private Queue<InputStream> queue = new LinkedList<InputStream>();
private ByteArrayOutputStream out;
private OutputStreamWriter writer;
 
public void append(InputStream in) throws IOException {
peek();
queue.add(in);
}
 
public void print(CharSequence csq) throws IOException {
if (writer == null) {
out = new ByteArrayOutputStream();
writer = new OutputStreamWriter(out, Charset.forName("ISO-8859-1"));
}
writer.append(csq);
}
 
public void println(CharSequence csq) throws IOException {
print(csq);
print("\r\n");
}
 
public void println() throws IOException {
print("\r\n");
}
 
private InputStream peek() throws IOException {
if (writer != null) {
writer.close();
writer = null;
append(new ByteArrayInputStream(out.toByteArray()));
}
return queue.peek();
}
 
@Override
public void close() throws IOException {
IOException ioe = null;
RuntimeException re = null;
Error er = null;
for (InputStream in : queue) {
try {
in.close();
} catch (IOException e) {
ioe = e;
} catch (RuntimeException e) {
re = e;
} catch (Error e) {
er = e;
}
}
if (er != null)
throw er;
if (re != null)
throw re;
if (ioe != null)
throw ioe;
}
 
@Override
public int available() throws IOException {
InputStream in = peek();
if (in == null)
return 0;
return in.available();
}
 
@Override
public int read(byte[] b, int off, int len) throws IOException {
InputStream in = peek();
if (in == null)
return -1;
int read = in.read(b, off, len);
if (read < 0) {
queue.remove().close();
return read(b, off, len);
}
return read;
}
 
@Override
public long skip(long n) throws IOException {
InputStream in = peek();
if (in == null)
return -1;
long read = super.skip(n);
if (read < 0) {
queue.remove().close();
return skip(n);
}
return read;
}
 
@Override
public int read() throws IOException {
InputStream in = peek();
if (in == null)
return -1;
int read = in.read();
if (read < 0) {
queue.remove().close();
return read();
}
return read;
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/cache/CachedEntity.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
39,14 → 39,12
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
55,9 → 53,9
import java.util.Set;
import java.util.TimeZone;
 
import org.openrdf.http.object.model.RequestHeader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.openrdf.http.object.model.Request;
 
public class CachedEntity {
private static ThreadLocal<DateFormat> format = new ThreadLocal<DateFormat>() {
89,7 → 87,6
}
}
 
private Logger logger = LoggerFactory.getLogger(CachedEntity.class);
private final File body;
private Map<String, String> cacheDirectives = new HashMap<String, String>();
private long date;
148,22 → 145,19
}
}
 
public CachedEntity(String method, String url, FileResponse store,
public CachedEntity(String method, String url, HttpResponse store, File tmp,
File head, File body) throws IOException {
this.method = method;
this.url = url;
this.stale = false;
this.head = head;
this.body = body;
Map<String, String> headers = store.getHeaders();
for (Map.Entry<String, String> e : headers.entrySet()) {
String name = e.getKey();
String value = e.getValue();
setHeader(name, value);
for (Header hd : store.getAllHeaders()) {
setHeader(hd.getName(), hd.getValue());
}
head.getParentFile().mkdirs();
try {
setResponse(store);
setResponse(store, tmp);
} catch (InterruptedException e) {
throw new AssertionError(e);
}
189,15 → 183,14
}
}
 
public void addRequest(RequestHeader req) throws IOException {
public void addRequest(Request req) throws IOException {
Map<String, String> map;
map = Collections.emptyMap();
if (vary != null) {
map = new LinkedHashMap<String, String>();
for (String name : vary) {
Enumeration values = req.getHeaders(name);
while (values.hasMoreElements()) {
String value = (String) values.nextElement();
for (Header hd : req.getHeaders(name)) {
String value = hd.getValue();
String existing = map.get(name);
if (existing == null) {
map.put(name.toLowerCase(), value);
211,22 → 204,23
writeHeaders(head);
}
 
public void setResponse(FileResponse store) throws IOException,
public void setResponse(HttpResponse store, File tmp) throws IOException,
InterruptedException {
Lock lock = locker.getWriteLock();
try {
warning = store.getHeader("Warning");
date = store.getDate();
lastModified = store.getLastModified();
if (store.isModified()) {
this.status = store.getStatus();
String statusText = store.getStatusText();
warning = getFirstHeaderValue(store, "Warning");
date = getFirstDateHeader(store, "Date");
lastModified = getFirstDateHeader(store, "Last-Modified");
int code = store.getStatusLine().getStatusCode();
if (code != 412 && code != 304) {
this.status = code;
String statusText = store.getStatusLine().getReasonPhrase();
this.statusText = statusText == null ? "" : statusText;
for (String name : CONTENT_HEADERS) {
setHeader(name, store.getHeader(name));
for (Header hd : store.getHeaders(name)) {
setHeader(name, hd.getValue());
}
}
setHeader("Content-MD5", store.getContentMD5());
File tmp = store.getMessageBody();
if (body.exists()) {
body.delete();
}
243,7 → 237,25
}
}
 
public boolean isVariation(RequestHeader req) {
private String getFirstHeaderValue(HttpResponse store, String string) {
Header hd = store.getFirstHeader(string);
if (hd == null)
return null;
return hd.getValue();
}
 
private long getFirstDateHeader(HttpResponse store, String string) {
Header hd = store.getFirstHeader(string);
if (hd == null)
return -1;
try {
return format.get().parse(hd.getValue()).getTime();
} catch (ParseException e) {
return -1;
}
}
 
public boolean isVariation(Request req) {
if (vary == null)
return true;
search: for (Map<String, String> headers : requests) {
374,36 → 386,12
return contentLength != null;
}
 
public void writeBodyTo(OutputStream out, int blockSize) throws IOException {
InputStream in = new FileInputStream(body);
try {
byte[] buf = new byte[blockSize];
int read;
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
}
} finally {
in.close();
}
public InputStream writeBody() throws IOException {
return new FileInputStream(body);
}
 
public void writeBodyTo(OutputStream out, int blockSize, long start,
long length) throws IOException {
InputStream in = new FileInputStream(body);
try {
byte[] buf = new byte[blockSize];
int read;
in.skip(start);
long rest = length;
while ((read = in.read(buf, 0, (int) Math.min(rest, buf.length))) >= 0) {
out.write(buf, 0, read);
rest -= read;
if (rest == 0)
break;
}
} finally {
in.close();
}
public InputStream writeBody(long start, long length) throws IOException {
return new RangeInputStream(new FileInputStream(body), start, length);
}
 
public String toString() {
422,21 → 410,20
}
}
 
private boolean equals(String s1, Enumeration<String> s2) {
private boolean equals(String s1, Header[] s2) {
if (s1 == null)
return !s2.hasMoreElements();
if (!s2.hasMoreElements())
return s2.length == 0;
if (s2.length == 0)
return s1 == null;
String first = s2.nextElement();
if (!s2.hasMoreElements())
String first = s2[0].getValue();
if (s2.length == 1)
return s1.equals(first);
StringBuilder sb = new StringBuilder();
sb.append(first);
while (s2.hasMoreElements()) {
for (Header hd : s2) {
sb.append(hd.getValue());
sb.append(",");
sb.append(s2.nextElement());
}
return s1.equals(sb.toString());
return s1.equals(sb.subSequence(0, sb.length() - 1));
}
 
private void setHeader(String name, String value) {
trunk/object-server/src/main/java/org/openrdf/http/object/cache/RangeInputStream.java New file
0,0 → 1,87
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.cache;
 
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
 
/**
* Exposes an portion of an InputStream.
*
* @author James Leigh
*
*/
public class RangeInputStream extends FilterInputStream {
private long position;
private long limit;
 
protected RangeInputStream(InputStream in, long start, long length) throws IOException {
super(in);
in.skip(start);
this.limit = length;
}
 
@Override
public int read() throws IOException {
if (position >= limit)
return -1;
int read = super.read();
if (read >= 0) {
position += 1;
}
return read;
}
 
@Override
public int read(byte[] b, int off, int len) throws IOException {
if (position >= limit)
return -1;
int read = super.read(b, off, (int) Math.min(len, limit - position));
if (read >= 0) {
position += read;
}
return read;
}
 
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
 
@Override
public long skip(long n) throws IOException {
long read = super.skip(n);
if (read >= 0) {
position += read;
}
return read;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/cache/CachableRequest.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
31,101 → 31,44
import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Enumeration;
import java.util.Vector;
 
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import org.apache.http.ProtocolVersion;
import org.apache.http.RequestLine;
import org.apache.http.message.BasicRequestLine;
import org.openrdf.http.object.model.Request;
 
/**
* Wraps a request that will have its response cached for later use.
* Clones a request that will have its response cached for later use.
*/
public class CachableRequest extends HttpServletRequestWrapper {
private Collection<String> hidden = Arrays.asList("If-None-Match",
"If-Modified-Since", "If-Match", "If-Unmodified-Since", "If-Range",
"Range");
private Vector<String> empty = new Vector<String>();
private String ifNoneMatch;
private String lastModified;
private Long longModified;
public class CachableRequest extends Request {
private static final Collection<String> hidden = Arrays.asList(
"If-None-Match", "If-Modified-Since", "If-Match",
"If-Unmodified-Since", "If-Range", "Range");
private Request originalRequest;
 
public CachableRequest(HttpServletRequest request, CachedEntity stale,
public CachableRequest(Request request, CachedEntity stale,
String ifNoneMatch) throws IOException {
super(request);
if (ifNoneMatch != null && ifNoneMatch.length() > 0) {
this.ifNoneMatch = ifNoneMatch;
super(request.clone());
this.originalRequest = request;
setReceivedOn(request.getReceivedOn());
RequestLine rl = getRequestLine();
if ("HEAD".equals(rl.getMethod())) {
ProtocolVersion ver = rl.getProtocolVersion();
setRequestLine(new BasicRequestLine("GET", rl.getUri(), ver));
}
if (stale != null) {
this.lastModified = stale.getLastModified();
this.longModified = stale.lastModified();
for (String name : hidden) {
removeHeaders(name);
}
}
 
@Override
public String getMethod() {
String method = super.getMethod();
if (method.equals("HEAD"))
return "GET";
return method;
}
 
@Override
public long getDateHeader(String name) {
if (longModified != null && "If-Modified-Since".equalsIgnoreCase(name))
return longModified;
if (hidden.contains(name))
return 0;
return super.getDateHeader(name);
}
 
@Override
public String getHeader(String name) {
if (ifNoneMatch != null && "If-None-Match".equalsIgnoreCase(name))
return ifNoneMatch;
if (lastModified != null && "If-Modified-Since".equalsIgnoreCase(name))
return lastModified;
if (hidden.contains(name))
return null;
return super.getHeader(name);
}
 
@Override
public Enumeration getHeaders(String name) {
if (ifNoneMatch != null && "If-None-Match".equalsIgnoreCase(name)) {
Vector<String> list = new Vector<String>();
list.add(ifNoneMatch);
return list.elements();
if (ifNoneMatch != null && ifNoneMatch.length() > 0) {
setHeader("If-None-Match", ifNoneMatch);
}
if (lastModified != null && "If-Modified-Since".equalsIgnoreCase(name)) {
Vector<String> list = new Vector<String>();
list.add(lastModified);
return list.elements();
if (stale != null && stale.getLastModified() != null) {
setHeader("If-Modified-Since", stale.getLastModified());
}
if (hidden.contains(name))
return empty.elements();
return super.getHeaders(name);
}
 
public String toString() {
StringBuilder sb = new StringBuilder();
sb.append(getMethod()).append(' ').append(getRequestURI()).append("\n");
if (ifNoneMatch != null) {
sb.append("If-None-Match: ").append(ifNoneMatch).append("\n");
}
if (lastModified != null) {
sb.append("If-Modified-Since: ").append(lastModified).append("\n");
}
Enumeration names = getHeaderNames();
while (names.hasMoreElements()) {
String name = (String) names.nextElement();
Enumeration headers = getHeaders(name);
while (headers.hasMoreElements()) {
String value = (String) headers.nextElement();
sb.append(name).append(": ").append(value).append("\n");
}
}
sb.append("\n");
return sb.toString();
public Request getOriginalRequest() {
return originalRequest;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/behaviours/HTTPFileObjectSupport.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,7 → 28,6
*/
package org.openrdf.http.object.behaviours;
 
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
233,9 → 232,7
String m = getParameterMediaType(panns[i], ptypes[i],
gtypes[i]);
String txt = m == null ? "text/plain" : m;
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(txt, ptypes[i], gtypes[i], param[i], out, cs);
String value = out.toString("ISO-8859-1");
String value = writeToString(txt, ptypes[i], gtypes[i], param[i], cs);
for (String name : ((header) ann).value()) {
List<String> list = map.get(name.toLowerCase());
if (list == null) {
361,9 → 358,7
Charset cs = Charset.forName("ISO-8859-1");
if ("*".equals(name)) {
String form = m == null ? "application/x-www-form-urlencoded" : m;
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(form, ptype, gtype, param, out, cs);
InputStream in = new ByteArrayInputStream(out.toByteArray());
InputStream in = write(form, ptype, gtype, param, cs);
FormMapMessageReader reader = new FormMapMessageReader();
ObjectConnection con = getObjectConnection();
Class<Map> mt = Map.class;
377,9 → 372,7
List<String> values = new ArrayList<String>();
String txt = m == null ? "text/plain" : m;
for (Object o : (Set) param) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(txt, cc, ctype, o, out, cs);
values.add(out.toString("ISO-8859-1"));
values.add(writeToString(txt, cc, ctype, o, cs));
}
map.put(name, values.toArray(new String[values.size()]));
} else if (type.isArray()) {
387,16 → 380,12
int len = Array.getLength(param);
String[] values = new String[len];
for (int i = 0; i < len; i++) {
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(txt, cc, ctype, Array.get(param, i), out, cs);
values[i] = out.toString("ISO-8859-1");
values[i] = writeToString(txt, cc, ctype, Array.get(param, i), cs);
}
map.put(name, values);
} else {
String txt = m == null ? "text/plain" : m;
ByteArrayOutputStream out = new ByteArrayOutputStream();
writeTo(txt, ptype, gtype, param, out, cs);
String value = out.toString("ISO-8859-1");
String value = writeToString(txt, ptype, gtype, param, cs);
map.put(name, new String[] { value });
}
}
415,14 → 404,29
return null;
}
 
private void writeTo(String mediaType, Class<?> ptype, Type gtype,
Object result, OutputStream out, Charset charset) throws Exception {
private InputStream write(String mediaType, Class<?> ptype, Type gtype,
Object result, Charset charset) throws Exception {
String uri = getResource().stringValue();
ObjectFactory of = getObjectConnection().getObjectFactory();
writer.writeTo(mediaType, ptype, gtype, of, result, uri, charset, out,
4096);
return writer.write(mediaType, ptype, gtype, of, result, uri, charset);
}
 
private String writeToString(String mediaType, Class<?> ptype, Type gtype,
Object result, Charset cs) throws Exception {
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream in = write(mediaType, ptype, gtype, result, cs);
try {
int read;
byte[] buf = new byte[1024];
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
}
} finally {
in.close();
}
return out.toString(cs.name());
}
 
private String enc(String value) throws AssertionError {
try {
return URLEncoder.encode(value, "ISO-8859-1");
trunk/object-server/src/main/java/org/openrdf/http/object/behaviours/RemoteConnection.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
116,8 → 116,16
}
OutputStream out = con.getOutputStream();
try {
writer.writeTo(mediaType, ptype, gtype, of, result, uri, null, out,
4096);
InputStream in = writer.write(mediaType, ptype, gtype, of, result, uri, null);
try {
int read;
byte[] buf = new byte[1024];
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
}
} finally {
in.close();
}
} finally {
out.close();
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GUnzipOutputStream.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/MD5ValidationRequest.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/InputServletStream.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/OutputServletStream.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GUnzipRequest.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/ContentMD5Filter.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GZipResponse.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/ContentMD5Request.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GUnzipResponse.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/ContentMD5Stream.java File deleted
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GZipFilter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
30,43 → 30,50
 
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Request;
 
/**
* Compresses safe responses.
*/
public class GZipFilter implements Filter {
public class GZipFilter extends Filter {
 
public void init(FilterConfig config) throws ServletException {
// no-op
public GZipFilter(Filter delegate) {
super(delegate);
}
 
public void destroy() {
// no-op
}
 
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
public HttpResponse filter(Request req, HttpResponse resp) throws IOException {
resp = super.filter(req, resp);
String method = req.getMethod();
if (method.equals("GET") || method.equals("PROFIND")) {
GZipResponse gzip = new GZipResponse(resp, false);
chain.doFilter(req, gzip);
gzip.flush();
} else if (method.equals("HEAD")) {
GZipResponse gzip = new GZipResponse(resp, true);
chain.doFilter(req, gzip);
gzip.flush();
} else {
chain.doFilter(req, resp);
boolean head = method.equals("HEAD");
Header contentType = resp.getFirstHeader("Content-Type");
if (contentType != null && (head || method.equals("GET") || method.equals("PROFIND"))) {
Header encoding = resp.getFirstHeader("Content-Encoding");
Header cache = resp.getFirstHeader("Cache-Control");
boolean identity = encoding == null || "identity".equals(encoding.getValue());
boolean transformable = cache == null
|| !cache.getValue().contains("no-transform");
String type = contentType.getValue();
boolean compressable = type.startsWith("text/")
|| type.startsWith("application/xml")
|| type.startsWith("application/x-turtle")
|| type.startsWith("application/trix")
|| type.startsWith("application/x-trig")
|| type.startsWith("application/postscript")
|| type.startsWith("application/")
&& (type.endsWith("+xml") || type.contains("+xml;"));
if (identity && compressable && transformable) {
Header length = resp.getFirstHeader("Content-Length");
if (length == null || Integer.parseInt(length.getValue()) > 500) {
resp.removeHeaders("Content-MD5");
resp.removeHeaders("Content-Length");
resp.setHeader("Content-Encoding", "gzip");
resp.setEntity(new GZipEntity(resp.getEntity()));
}
}
}
return resp;
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GUnzipFilter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,39 → 29,48
package org.openrdf.http.object.filters;
 
import java.io.IOException;
import java.util.Enumeration;
import java.net.InetAddress;
import java.net.UnknownHostException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Request;
 
/**
* Uncompresses the response if the requesting client does not explicitly say it accepts gzip.
* Uncompresses the response if the requesting client does not explicitly say it
* accepts gzip.
*/
public class GUnzipFilter implements Filter {
public class GUnzipFilter extends Filter {
private static String hostname;
static {
try {
hostname = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
hostname = "AliBaba";
}
}
private static String WARN_214 = "214 " + hostname
+ " \"Transformation applied\"";
 
public void init(FilterConfig config) throws ServletException {
// no-op
public GUnzipFilter(Filter delegate) {
super(delegate);
}
 
public void destroy() {
// no-op
public Request filter(Request req) throws IOException {
if ("gzip".equals(req.getHeader("Content-Encoding"))) {
req.setHeader("Content-Encoding", "identity");
req.setEntity(new GUnzipEntity(req.getEntity()));
}
return super.filter(req);
}
 
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse res = (HttpServletResponse) response;
public HttpResponse filter(Request req, HttpResponse resp) throws IOException {
resp = super.filter(req, resp);
Boolean gzip = null;
boolean encode = false; // gunzip by default
Enumeration<String> ae = req.getHeaders("Accept-Encoding");
while (ae.hasMoreElements()) {
for (String value : ae.nextElement().split("\\s*,\\s*")) {
for (Header header : req.getHeaders("Accept-Encoding")) {
for (String value : header.getValue().split("\\s*,\\s*")) {
String[] items = value.split("\\s*;\\s*");
int q = 1;
for (int i = 1; i < items.length; i++) {
76,18 → 85,17
}
}
}
if ("gzip".equals(req.getHeader("Content-Encoding"))) {
req = new GUnzipRequest(req);
} else if (req.getHeader("Content-Encoding") != null
&& !"identity".equals(req.getHeader("Content-Encoding"))) {
res.sendError(415); // Unsupported Media Type
if (gzip == null ? encode : gzip)
return resp;
Header encoding = resp.getFirstHeader("Content-Encoding");
if (encoding != null && "gzip".equals(encoding.getValue())) {
resp.removeHeaders("ETag");
resp.removeHeaders("Content-MD5");
resp.removeHeaders("Content-Length");
resp.removeHeaders("Content-Encoding");
resp.addHeader("Warning", WARN_214);
resp.setEntity(new GUnzipEntity(resp.getEntity()));
}
if (gzip == null ? encode : gzip) {
chain.doFilter(req, res);
} else {
GUnzipResponse gunzip = new GUnzipResponse(res, "HEAD".equals(req.getMethod()));
chain.doFilter(req, gunzip);
gunzip.flush();
}
return resp;
}
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/TraceFilter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
29,64 → 29,58
package org.openrdf.http.object.filters;
 
import java.io.IOException;
import java.util.Enumeration;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.RequestLine;
import org.apache.http.message.BasicHttpResponse;
import org.apache.http.nio.entity.NStringEntity;
import org.apache.http.protocol.HttpDateGenerator;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Request;
 
/**
* Handles the TRACE and OPTIONS * requests.
*/
public class TraceFilter implements Filter {
public class TraceFilter extends Filter {
private static final HttpDateGenerator DATE_GENERATOR = new HttpDateGenerator();
 
public void init(FilterConfig arg0) throws ServletException {
// no-op
public TraceFilter(Filter delegate) {
super(delegate);
}
 
public void destroy() {
// no-op
}
 
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
public HttpResponse intercept(Request req) throws IOException {
RequestLine line = req.getRequestLine();
if ("TRACE".equals(req.getMethod())) {
int responseLength;
String CRLF = "\r\n";
String responseString = "TRACE " + req.getRequestURI() +
" " + req.getProtocol();
String CRLF = "\r\n";
StringBuilder sb = new StringBuilder();
sb.append("TRACE ").append(line.getUri()).append(" ");
sb.append(line.getProtocolVersion());
 
Enumeration reqHeaderEnum = req.getHeaderNames();
while (reqHeaderEnum.hasMoreElements()) {
String headerName = (String) reqHeaderEnum.nextElement();
responseString += CRLF + headerName + ": " +
req.getHeader(headerName);
}
for (Header hd : req.getAllHeaders()) {
sb.append(CRLF).append(hd.getName());
sb.append(": ").append(hd.getValue());
}
 
responseString += CRLF;
responseLength = responseString.length();
resp.setContentType("message/http");
resp.setContentLength(responseLength);
ServletOutputStream out = resp.getOutputStream();
try {
out.print(responseString);
} finally {
out.close();
}
} else if ("OPTIONS".equals(req.getMethod()) && "*".equals(req.getRequestURI())) {
resp.setStatus(204);
resp.setDateHeader("Date", System.currentTimeMillis());
sb.append(CRLF);
ProtocolVersion ver = new ProtocolVersion("HTTP", 1, 1);
BasicHttpResponse resp = new BasicHttpResponse(ver, 200, "OK");
resp.setHeader("Date", DATE_GENERATOR.getCurrentDate());
NStringEntity entity = new NStringEntity(sb.toString());
entity.setContentType("message/http");
entity.setChunked(false);
resp.setEntity(entity);
return resp;
} else if ("OPTIONS".equals(req.getMethod())
&& "*".equals(line.getUri())) {
ProtocolVersion ver = new ProtocolVersion("HTTP", 1, 1);
BasicHttpResponse resp = new BasicHttpResponse(ver, 204, "No Content");
resp.setHeader("Date", DATE_GENERATOR.getCurrentDate());
resp.setHeader("Allow", "OPTIONS, TRACE, GET, HEAD, PUT, DELETE");
return resp;
} else {
chain.doFilter(request, response);
return super.intercept(req);
}
}
 
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GZipEntity.java New file
0,0 → 1,95
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.filters;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedOutputStream;
import java.util.concurrent.Executor;
import java.util.zip.GZIPOutputStream;
 
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
import org.openrdf.http.object.model.ErrorInputStream;
import org.openrdf.http.object.util.SharedExecutors;
 
/**
* Compresses the message body.
*/
public class GZipEntity extends HttpEntityWrapper {
private static Executor executor = SharedExecutors.getEncoderThreadPool();
 
public GZipEntity(HttpEntity entity) {
super(entity);
}
 
public InputStream getContent() throws IOException, IllegalStateException {
PipedOutputStream zout = new PipedOutputStream();
final ErrorInputStream error = new ErrorInputStream(zout);
final OutputStream out = new GZIPOutputStream(zout);
executor.execute(new Runnable() {
public void run() {
try {
InputStream in = GZipEntity.super.getContent();
try {
int read;
byte[] buf = new byte[512];
while ((read = in.read(buf)) >= 0) {
out.write(buf, 0, read);
}
} finally {
try {
in.close();
} finally {
out.close();
}
}
} catch (IOException e) {
error.error(e);
}
}
});
return error;
}
 
public Header getContentEncoding() {
return new BasicHeader("Content-Encoding", "gzip");
}
 
public long getContentLength() {
return -1;
}
 
public boolean isChunked() {
return true;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/MD5ValidationFilter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
30,36 → 30,24
 
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Request;
 
/**
* If the request has Content-MD5 header, ensure the request body matches.
*/
public class MD5ValidationFilter implements Filter {
public class MD5ValidationFilter extends Filter {
 
public void init(FilterConfig arg0) throws ServletException {
// no-op
public MD5ValidationFilter(Filter delegate) {
super(delegate);
}
 
public void destroy() {
// no-op
}
 
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
public Request filter(Request req) throws IOException {
String md5 = req.getHeader("Content-MD5");
if (md5 == null) {
chain.doFilter(req, response);
} else {
chain.doFilter(new MD5ValidationRequest(req), response);
if (md5 != null) {
req.setEntity(new MD5ValidationEntity(req.getEntity(), md5));
}
return super.filter(req);
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/GUnzipEntity.java New file
0,0 → 1,64
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.filters;
 
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.GZIPInputStream;
 
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.message.BasicHeader;
 
/**
* Decompresses the message body.
*/
public class GUnzipEntity extends HttpEntityWrapper {
 
public GUnzipEntity(HttpEntity entity) {
super(entity);
}
 
public InputStream getContent() throws IOException, IllegalStateException {
return new GZIPInputStream(super.getContent());
}
 
public Header getContentEncoding() {
return new BasicHeader("Content-Encoding", "identity");
}
 
public long getContentLength() {
return -1;
}
 
public boolean isChunked() {
return true;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/IndentityPathFilter.java
1,5 → 1,5
/*
* Copyright 2010, Zepheira Some rights reserved.
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
33,16 → 33,13
import java.net.URISyntaxException;
import java.net.URLDecoder;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.RequestLine;
import org.apache.http.message.BasicRequestLine;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Request;
 
/**
* Extracts a percent encoded URI from the URL path.
50,101 → 47,13
* @author James Leigh
*
*/
public class IndentityPathFilter implements Filter {
private static final class DivertedRequest extends
HttpServletRequestWrapper {
private final String uri;
public class IndentityPathFilter extends Filter {
private String prefix;
 
private DivertedRequest(HttpServletRequest request, String uri) {
super(request);
this.uri = uri;
}
 
public String getRequestURI() {
return uri;
}
 
public StringBuffer getRequestURL() {
String qs = getQueryString();
StringBuffer sb = new StringBuffer();
sb.append(getRequestURI());
if (qs != null) {
sb.append('?').append(qs);
}
return sb;
}
 
@Override
public Object getAttribute(String name) {
Object value = super.getAttribute(name);
if (value == null && ORIGINAL_REQUEST_TARGET.equals(name)) {
if (getQueryString() == null)
return super.getRequestURI();
return super.getRequestURI() + "?" + getQueryString();
}
return value;
}
public IndentityPathFilter(Filter delegate) {
super(delegate);
}
 
private final static class DivertURIResponse extends
HttpServletResponseWrapper {
private final String from;
private final String to;
 
private DivertURIResponse(HttpServletResponse response, String from,
String to) {
super(response);
this.from = from;
this.to = to;
}
 
public void addHeader(String name, String value) {
if (name != null && name.equalsIgnoreCase("Location")) {
super.addHeader(name, divert(value));
} else {
super.addHeader(name, value);
}
}
 
public void setHeader(String name, String value) {
if (name != null && name.equalsIgnoreCase("Location")) {
super.setHeader(name, divert(value));
} else {
super.setHeader(name, value);
}
}
 
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
 
public String encodeRedirectURL(String url) {
return super.encodeRedirectURL(divert(url));
}
 
public String encodeUrl(String url) {
return encodeURL(url);
}
 
public String encodeURL(String url) {
return super.encodeURL(divert(url));
}
 
private String divert(String url) {
if (url == null)
return url;
int idx = url.lastIndexOf('?');
if (idx < 0 && url.equals(from))
return to;
if (idx >= 0 && url.substring(0, idx).equals(from))
return to + url.substring(idx);
return url;
}
}
 
public static final String ORIGINAL_REQUEST_TARGET = "X-Original-Request-TARGET";
private String prefix;
 
public String getIdentityPathPrefix() {
return prefix;
}
153,40 → 62,70
this.prefix = prefix;
}
 
public void init(FilterConfig config) throws ServletException {
// no-op
public Request filter(Request req) throws IOException {
try {
URI net = getRequestTargetWithoutQueryString(req);
String path = net.getPath();
if (prefix != null && path != null && path.startsWith(prefix)) {
String encoded = path.substring(prefix.length());
String uri = URLDecoder.decode(encoded, "UTF-8");
RequestLine line = req.getRequestLine();
String target = uri;
int idx = line.getUri().indexOf('?');
if (idx > 0) {
target = uri + line.getUri().substring(idx);
}
String method = line.getMethod();
ProtocolVersion version = line.getProtocolVersion();
line = new BasicRequestLine(method, target, version);
req.setRequestLine(line);
// TODO make original request-target available, but not spoofable
}
} catch (URISyntaxException e) {
// unrecognisable request URI
}
return super.filter(req);
}
 
public void destroy() {
// no-op
}
 
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
public HttpResponse filter(Request req, HttpResponse resp)
throws IOException {
resp = super.filter(req, resp);
try {
URI net = getRequestTargetWithoutQueryString(req);
String path = net.getPath();
if (prefix != null && path != null && path.startsWith(prefix)) {
String encoded = path.substring(prefix.length());
String uri = URLDecoder.decode(encoded, "UTF-8");
req = new DivertedRequest(req, uri);
resp = new DivertURIResponse(resp, uri, net.toASCIIString());
RequestLine line = req.getRequestLine();
int idx = line.getUri().indexOf('?');
Header hd = resp.getFirstHeader("Location");
if (hd == null)
return resp;
String location = hd.getValue();
idx = location.indexOf('?');
if (idx > 0 && location.substring(0, idx).equals(uri)) {
resp.setHeader("Location", net.toASCIIString() + location.substring(idx));
} else if (location.equals(uri)) {
resp.setHeader("Location", net.toASCIIString());
}
return resp;
}
} catch (URISyntaxException e) {
// unrecognisable request URI
}
chain.doFilter(req, resp);
return resp;
}
 
private URI getRequestTargetWithoutQueryString(HttpServletRequest req)
private URI getRequestTargetWithoutQueryString(Request req)
throws URISyntaxException {
String path = req.getRequestURI();
String path = req.getRequestLine().getUri();
if ("*".equals(path)) {
path = null;
}
if (path != null && !path.equals("*") && !path.startsWith("/")) {
if (path != null && path.indexOf('?') > 0) {
path = path.substring(0, path.indexOf('?'));
}
if (path != null && !path.startsWith("/")) {
try {
return new URI(path);
} catch (URISyntaxException e) {
194,36 → 133,13
}
}
try {
String scheme = req.getScheme().toLowerCase();
String authority = getAuthority(req);
return new URI(scheme, authority, path, null);
String host = req.getHeader("Host");
String authority = host == null ? null : host.toLowerCase();
return new URI("http", authority, path, null);
} catch (URISyntaxException e) {
// bad Host header
return new URI(req.getRequestURL().toString());
}
}
 
private String getAuthority(HttpServletRequest request) {
String uri = request.getRequestURI();
if (uri != null && !uri.equals("*") && !uri.startsWith("/")) {
try {
return new java.net.URI(uri).getAuthority();
} catch (URISyntaxException e) {
// try the host header
}
}
String host = request.getHeader("Host");
if (host != null)
return host.toLowerCase();
if (request.getServerName() != null) {
int port = request.getServerPort();
if (port == 80 && "http".equals(request.getScheme()))
return request.getServerName().toLowerCase();
if (port == 443 && "https".equals(request.getScheme()))
return request.getServerName();
return request.getServerName().toLowerCase() + ":" + port;
}
return null;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/KeepAliveFilter.java New file
0,0 → 1,64
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.filters;
 
import java.io.IOException;
 
import org.apache.http.HttpResponse;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Request;
 
/**
* Tells clients that the TCP connection can be kept open for a period of time.
*
* @author James Leigh
*
*/
public class KeepAliveFilter extends Filter {
private String timeout;
 
public KeepAliveFilter(Filter delegate, int timeout) {
super(delegate);
if (timeout > 1000) {
this.timeout = "timeout=" + Integer.toString(timeout / 1000) + ", max=99";
}
}
 
@Override
public HttpResponse filter(Request request, HttpResponse response)
throws IOException {
HttpResponse rb = super.filter(request, response);
rb.setHeader("Connection", "Keep-Alive");
if (timeout != null) {
rb.setHeader("Keep-Alive", timeout);
}
return rb;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/HttpEntityWrapper.java New file
0,0 → 1,133
/*
* Copyright 2010, Zehperia LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.filters;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
 
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.nio.ContentEncoder;
import org.apache.http.nio.IOControl;
import org.apache.http.nio.entity.ProducingNHttpEntity;
 
/**
* Implements the ProducingNHttpEntity interface for subclasses.
*
* @author James Leigh
*
*/
public class HttpEntityWrapper implements ProducingNHttpEntity {
private HttpEntity entity;
private ReadableByteChannel cin;
private ByteBuffer buf = ByteBuffer.allocate(1024);
 
public HttpEntityWrapper(HttpEntity entity) {
this.entity = entity;
}
 
public final void consumeContent() throws IOException {
finish();
}
 
public InputStream getContent() throws IOException, IllegalStateException {
return entity.getContent();
}
 
public Header getContentEncoding() {
return entity.getContentEncoding();
}
 
public long getContentLength() {
return entity.getContentLength();
}
 
public Header getContentType() {
return entity.getContentType();
}
 
public boolean isChunked() {
return entity.isChunked();
}
 
public final boolean isRepeatable() {
return false;
}
 
public final boolean isStreaming() {
return true;
}
 
public void writeTo(OutputStream out) throws IOException {
InputStream in = getContent();
try {
int l;
byte[] buf = new byte[2048];
while ((l = in.read(buf)) != -1) {
out.write(buf, 0, l);
}
} finally {
in.close();
}
}
 
public final void finish() throws IOException {
try {
if (entity instanceof ProducingNHttpEntity) {
((ProducingNHttpEntity) entity).finish();
} else {
entity.consumeContent();
}
} finally {
if (cin != null) {
cin.close();
}
}
}
 
public final void produceContent(ContentEncoder encoder, IOControl ioctrl)
throws IOException {
if (cin == null) {
cin = Channels.newChannel(getContent());
}
buf.clear();
if (cin.read(buf) < 0) {
encoder.complete();
} else {
buf.flip();
encoder.write(buf);
}
 
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/MD5ValidationEntity.java New file
0,0 → 1,61
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.filters;
 
import java.io.IOException;
import java.io.InputStream;
import java.security.NoSuchAlgorithmException;
 
import org.apache.http.HttpEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
* Ensures the message body matches the Content-MD5 header.
*/
public class MD5ValidationEntity extends HttpEntityWrapper {
private String md5;
 
public MD5ValidationEntity(HttpEntity entity, String md5) {
super(entity);
this.md5 = md5;
}
 
public InputStream getContent() throws IOException, IllegalStateException {
InputStream in = super.getContent();
try {
return new MD5ValidatingStream(in, md5);
} catch (NoSuchAlgorithmException e) {
Logger logger = LoggerFactory.getLogger(MD5ValidationEntity.class);
logger.warn(e.getMessage(), e);
return in;
}
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/filters/ServerNameFilter.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
30,21 → 30,18
 
import java.io.IOException;
 
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;
import org.apache.http.HttpResponse;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Request;
 
/**
* Add a Server header to the response.
*/
public class ServerNameFilter implements Filter {
public class ServerNameFilter extends Filter {
private String name;
 
public ServerNameFilter(String name) {
public ServerNameFilter(String name, Filter delegate) {
super(delegate);
this.name = name;
}
 
56,20 → 53,11
this.name = name;
}
 
public void init(FilterConfig arg0) throws ServletException {
// no-op
}
 
public void destroy() {
// no-op
}
 
public void doFilter(ServletRequest req, ServletResponse resp,
FilterChain chain) throws IOException, ServletException {
public HttpResponse filter(Request req, HttpResponse resp) throws IOException {
if (name != null) {
((HttpServletResponse)resp).setHeader("Server", name);
resp.setHeader("Server", name);
}
chain.doFilter(req, resp);
return super.filter(req, resp);
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/HTTPObjectServer.java
1,5 → 1,5
/*
* Copyright (c) 2009, James Leigh All rights reserved.
* Copyright 2009-2010, James Leigh and Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
28,31 → 28,76
*/
package org.openrdf.http.object;
 
import static org.apache.http.params.CoreConnectionPNames.SOCKET_BUFFER_SIZE;
import static org.apache.http.params.CoreConnectionPNames.SO_TIMEOUT;
import static org.apache.http.params.CoreConnectionPNames.STALE_CONNECTION_CHECK;
import static org.apache.http.params.CoreConnectionPNames.TCP_NODELAY;
import info.aduna.io.MavenUtil;
 
import java.io.File;
import java.io.IOException;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
 
import javax.servlet.Filter;
import javax.xml.transform.TransformerConfigurationException;
 
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Handler;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.servlet.FilterHolder;
import org.mortbay.jetty.servlet.ServletHandler;
import org.mortbay.jetty.servlet.ServletHolder;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpMessage;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestFactory;
import org.apache.http.MethodNotSupportedException;
import org.apache.http.RequestLine;
import org.apache.http.impl.DefaultConnectionReuseStrategy;
import org.apache.http.impl.DefaultHttpResponseFactory;
import org.apache.http.impl.nio.DefaultNHttpServerConnection;
import org.apache.http.impl.nio.DefaultServerIOEventDispatch;
import org.apache.http.impl.nio.codecs.HttpRequestParser;
import org.apache.http.impl.nio.reactor.DefaultListeningIOReactor;
import org.apache.http.message.BasicHttpEntityEnclosingRequest;
import org.apache.http.message.BasicHttpRequest;
import org.apache.http.nio.NHttpMessageParser;
import org.apache.http.nio.NHttpServerIOTarget;
import org.apache.http.nio.protocol.AsyncNHttpServiceHandler;
import org.apache.http.nio.protocol.NHttpRequestHandler;
import org.apache.http.nio.protocol.NHttpRequestHandlerResolver;
import org.apache.http.nio.reactor.IOEventDispatch;
import org.apache.http.nio.reactor.IOReactorStatus;
import org.apache.http.nio.reactor.IOSession;
import org.apache.http.nio.reactor.ListeningIOReactor;
import org.apache.http.nio.reactor.SessionInputBuffer;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.BasicHttpProcessor;
import org.openrdf.http.object.cache.CachingFilter;
import org.openrdf.http.object.filters.ContentMD5Filter;
import org.openrdf.http.object.filters.GUnzipFilter;
import org.openrdf.http.object.filters.GZipFilter;
import org.openrdf.http.object.filters.IndentityPathFilter;
import org.openrdf.http.object.filters.KeepAliveFilter;
import org.openrdf.http.object.filters.MD5ValidationFilter;
import org.openrdf.http.object.filters.IndentityPathFilter;
import org.openrdf.http.object.filters.ServerNameFilter;
import org.openrdf.http.object.filters.TraceFilter;
import org.openrdf.http.object.handlers.AlternativeHandler;
import org.openrdf.http.object.handlers.AuthenticationHandler;
import org.openrdf.http.object.handlers.ContentHeadersHandler;
import org.openrdf.http.object.handlers.DateHandler;
import org.openrdf.http.object.handlers.HeadHandler;
import org.openrdf.http.object.handlers.InvokeHandler;
import org.openrdf.http.object.handlers.LinksHandler;
import org.openrdf.http.object.handlers.MethodNotAllowedHandler;
import org.openrdf.http.object.handlers.ModifiedSinceHandler;
import org.openrdf.http.object.handlers.NotFoundHandler;
import org.openrdf.http.object.handlers.OptionsHandler;
import org.openrdf.http.object.handlers.ResponseExceptionHandler;
import org.openrdf.http.object.handlers.UnmodifiedSinceHandler;
import org.openrdf.http.object.model.Filter;
import org.openrdf.http.object.model.Handler;
import org.openrdf.http.object.util.NamedThreadFactory;
import org.openrdf.repository.Repository;
import org.openrdf.repository.object.ObjectRepository;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
 
/**
* Manages the start and stop stages of the server.
65,34 → 110,96
"org.openrdf.alibaba", "alibaba-server-object", "devel");
private static final String APP_NAME = "OpenRDF AliBaba object-server";
protected static final String DEFAULT_NAME = APP_NAME + "/" + VERSION;
private static Executor executor = Executors
.newCachedThreadPool(new NamedThreadFactory("HTTP Object Server"));
 
private Server server;
private Logger logger = LoggerFactory.getLogger(HTTPObjectServer.class);
private ListeningIOReactor server;
private IOEventDispatch dispatch;
private ObjectRepository repository;
private int port;
private HTTPObjectServlet servlet;
private ServerNameFilter name;
private IndentityPathFilter abs;
 
public HTTPObjectServer(ObjectRepository repository, File www, File cache, String passwd)
throws TransformerConfigurationException {
public HTTPObjectServer(ObjectRepository repository, File www, File cache,
String passwd) throws IOException {
this.repository = repository;
servlet = new HTTPObjectServlet(repository, www, passwd);
ServletHandler handler = new ServletHandler();
handler.addServletWithMapping(new ServletHolder(servlet), "/*");
add(handler, name = new ServerNameFilter(DEFAULT_NAME));
add(handler, abs = new IndentityPathFilter());
add(handler, new TraceFilter());
add(handler, new MD5ValidationFilter());
add(handler, new ContentMD5Filter());
add(handler, new GUnzipFilter());
add(handler, new CachingFilter(cache, 1024));
add(handler, new GZipFilter());
server = new Server();
server.addHandler(handler);
}
HttpParams params = new BasicHttpParams();
int timeout = 0;// FIXME 15000;
params.setIntParameter(SO_TIMEOUT, timeout);
params.setIntParameter(SOCKET_BUFFER_SIZE, 8 * 1024);
params.setBooleanParameter(STALE_CONNECTION_CHECK, false);
params.setBooleanParameter(TCP_NODELAY, true);
int n = Runtime.getRuntime().availableProcessors();
Handler handler = new MethodNotAllowedHandler();
handler = new InvokeHandler(handler);
handler = new NotFoundHandler(handler);
handler = new AlternativeHandler(handler);
handler = new ResponseExceptionHandler(handler);
handler = new OptionsHandler(handler);
handler = new LinksHandler(handler);
handler = new ModifiedSinceHandler(handler);
handler = new UnmodifiedSinceHandler(handler);
handler = new ContentHeadersHandler(handler);
handler = new AuthenticationHandler(handler, passwd);
handler = new DateHandler(handler);
handler = new HeadHandler(handler);
Filter filter = new GZipFilter(null);
filter = new CachingFilter(filter, cache, 1024);
filter = new GUnzipFilter(filter);
filter = new MD5ValidationFilter(filter);
filter = new TraceFilter(filter);
filter = abs = new IndentityPathFilter(filter);
filter = new KeepAliveFilter(filter, timeout);
filter = name = new ServerNameFilter(DEFAULT_NAME, filter);
final HTTPObjectRequestHandler triage;
triage = new HTTPObjectRequestHandler(filter, handler, repository, www);
AsyncNHttpServiceHandler async = new AsyncNHttpServiceHandler(
new BasicHttpProcessor(), new DefaultHttpResponseFactory(),
new DefaultConnectionReuseStrategy(), params);
async.setExpectationVerifier(triage);
async.setHandlerResolver(new NHttpRequestHandlerResolver() {
public NHttpRequestHandler lookup(String requestURI) {
return triage;
}
});
dispatch = new DefaultServerIOEventDispatch(async, params) {
@Override
protected NHttpServerIOTarget createConnection(IOSession session) {
return new DefaultNHttpServerConnection(session,
createHttpRequestFactory(), this.allocator, this.params) {
@Override
protected NHttpMessageParser createRequestParser(
SessionInputBuffer buffer,
HttpRequestFactory requestFactory, HttpParams params) {
return new HttpRequestParser(buffer, null,
requestFactory, params) {
@Override
public HttpMessage parse() throws IOException,
HttpException {
return removeEntityIfNoContent(super.parse());
}
};
}
};
}
 
private void add(ServletHandler h, Filter f) {
h.addFilterWithMapping(new FilterHolder(f), "/*", Handler.ALL);
@Override
protected HttpRequestFactory createHttpRequestFactory() {
return new HttpRequestFactory() {
public HttpRequest newHttpRequest(RequestLine requestline)
throws MethodNotSupportedException {
return new BasicHttpEntityEnclosingRequest(requestline);
}
 
public HttpRequest newHttpRequest(String method, String uri)
throws MethodNotSupportedException {
return new BasicHttpEntityEnclosingRequest(method, uri);
};
};
}
};
server = new DefaultListeningIOReactor(n, params);
}
 
public int getPort() {
124,21 → 231,49
}
 
public void start() throws BindException, Exception {
Connector connector = new SocketConnector();
connector.setPort(port);
server.setConnectors(new Connector[] { connector });
server.start();
final CountDownLatch latch = new CountDownLatch(1);
server.listen(new InetSocketAddress(getPort()));
executor.execute(new Runnable() {
public void run() {
try {
latch.countDown();
server.execute(dispatch);
} catch (IOException e) {
logger.error(e.toString(), e);
}
}
});
latch.await();
for (int i = 0; i < 100; i++) {
Thread.sleep(100);
if (isRunning())
break;
}
if (!isRunning())
throw new BindException("Could not bind to port " + getPort());
}
 
public boolean isRunning() {
return server.isRunning();
return server.getStatus() == IOReactorStatus.ACTIVE;
}
 
/**
* Method may return before socket is released.
*/
public void stop() throws Exception {
server.stop();
server.shutdown();
}
 
private HttpMessage removeEntityIfNoContent(HttpMessage msg) {
if (msg instanceof HttpEntityEnclosingRequest
&& !msg.containsHeader("Content-Length")
&& !msg.containsHeader("Transfer-Encoding")) {
HttpEntityEnclosingRequest body = (HttpEntityEnclosingRequest) msg;
BasicHttpRequest req = new BasicHttpRequest(body.getRequestLine());
req.setHeaders(body.getAllHeaders());
return req;
}
return msg;
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/util/NamedThreadFactory.java New file
0,0 → 1,51
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.util;
 
import java.util.concurrent.ThreadFactory;
 
/**
* Gives new threads a common prefix.
*
* @author James Leigh
*
*/
public class NamedThreadFactory implements ThreadFactory {
private String name;
private volatile int COUNT = 0;
 
public NamedThreadFactory(String name) {
this.name = name;
}
 
public Thread newThread(final Runnable r) {
return new Thread(r, name + " " + (++COUNT));
}
 
}
trunk/object-server/src/main/java/org/openrdf/http/object/util/SharedExecutors.java New file
0,0 → 1,59
/*
* Copyright 2010, Zepheira LLC Some rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
* - 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.
* - Neither the name of the openrdf.org nor the names of its contributors may
* be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS 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 COPYRIGHT OWNER OR 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.
*
*/
package org.openrdf.http.object.util;
 
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
 
/**
* Common Executors used.
*
* @author James Leigh
*
*/
public class SharedExecutors {
private static Executor encoderThreadPool = Executors
.newCachedThreadPool(new NamedThreadFactory("Encoder"));
private static Executor parserThreadPool = Executors
.newCachedThreadPool(new NamedThreadFactory("Parser"));
private static Executor writerThreadPool = Executors
.newCachedThreadPool(new NamedThreadFactory("Writer"));
 
public static Executor getEncoderThreadPool() {
return encoderThreadPool;
}
 
public static Executor getParserThreadPool() {
return parserThreadPool;
}
 
public static Executor getWriterThreadPool() {
return writerThreadPool;
}
}
trunk/object-server/pom.xml
43,14 → 43,10
<artifactId>alibaba-repository-optimistic</artifactId>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
</dependency>
<dependency>
<groupId>eu.medsea.mimeutil</groupId>
<artifactId>mime-util</artifactId>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
</dependency>
trunk/pom.xml
48,7 → 48,7
</distributionManagement>
<properties>
<sesame.version>2.3.1-SNAPSHOT</sesame.version>
<slf4j.version>1.5.8</slf4j.version>
<slf4j.version>1.5.10</slf4j.version>
<javassist.version>3.11.0.GA</javassist.version>
</properties>
<dependencies>
210,15 → 210,10
<version>1.0.3</version>
</dependency>
<dependency>
<groupId>org.mortbay.jetty</groupId>
<artifactId>jetty</artifactId>
<version>6.1.16</version>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpcore-nio</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>eu.medsea.mimeutil</groupId>
<artifactId>mime-util</artifactId>
<version>1.3</version>
</dependency>
</dependencies>
</dependencyManagement>
<issueManagement>