0001: /*
0002: * Copyright Aduna (http://www.aduna-software.com/) (c) 1997-2007.
0003: *
0004: * Licensed under the Aduna BSD-style license.
0005: */
0006: package org.openrdf.http.client;
0007:
0008: import static org.openrdf.http.protocol.Protocol.ACCEPT_PARAM_NAME;
0009:
0010: import java.io.IOException;
0011: import java.io.InputStream;
0012: import java.io.OutputStream;
0013: import java.io.OutputStreamWriter;
0014: import java.io.Reader;
0015: import java.net.HttpURLConnection;
0016: import java.net.MalformedURLException;
0017: import java.net.URL;
0018: import java.nio.charset.Charset;
0019: import java.util.ArrayList;
0020: import java.util.List;
0021: import java.util.Set;
0022: import java.util.regex.Matcher;
0023: import java.util.regex.Pattern;
0024:
0025: import org.apache.commons.httpclient.Header;
0026: import org.apache.commons.httpclient.HeaderElement;
0027: import org.apache.commons.httpclient.HttpClient;
0028: import org.apache.commons.httpclient.HttpConnectionManager;
0029: import org.apache.commons.httpclient.HttpMethod;
0030: import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
0031: import org.apache.commons.httpclient.NameValuePair;
0032: import org.apache.commons.httpclient.UsernamePasswordCredentials;
0033: import org.apache.commons.httpclient.auth.AuthScope;
0034: import org.apache.commons.httpclient.methods.DeleteMethod;
0035: import org.apache.commons.httpclient.methods.EntityEnclosingMethod;
0036: import org.apache.commons.httpclient.methods.GetMethod;
0037: import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
0038: import org.apache.commons.httpclient.methods.PostMethod;
0039: import org.apache.commons.httpclient.methods.PutMethod;
0040: import org.apache.commons.httpclient.methods.RequestEntity;
0041: import org.apache.commons.httpclient.methods.StringRequestEntity;
0042: import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
0043: import org.slf4j.Logger;
0044: import org.slf4j.LoggerFactory;
0045:
0046: import info.aduna.io.IOUtil;
0047: import info.aduna.net.http.HttpClientUtil;
0048:
0049: import org.openrdf.OpenRDFUtil;
0050: import org.openrdf.http.protocol.Protocol;
0051: import org.openrdf.http.protocol.UnauthorizedException;
0052: import org.openrdf.http.protocol.error.ErrorInfo;
0053: import org.openrdf.http.protocol.error.ErrorType;
0054: import org.openrdf.http.protocol.transaction.TransactionWriter;
0055: import org.openrdf.http.protocol.transaction.operations.TransactionOperation;
0056: import org.openrdf.model.Resource;
0057: import org.openrdf.model.URI;
0058: import org.openrdf.model.Value;
0059: import org.openrdf.model.ValueFactory;
0060: import org.openrdf.model.impl.URIImpl;
0061: import org.openrdf.model.impl.ValueFactoryImpl;
0062: import org.openrdf.query.Binding;
0063: import org.openrdf.query.Dataset;
0064: import org.openrdf.query.GraphQueryResult;
0065: import org.openrdf.query.MalformedQueryException;
0066: import org.openrdf.query.QueryLanguage;
0067: import org.openrdf.query.TupleQueryResult;
0068: import org.openrdf.query.TupleQueryResultHandler;
0069: import org.openrdf.query.TupleQueryResultHandlerException;
0070: import org.openrdf.query.UnsupportedQueryLanguageException;
0071: import org.openrdf.query.impl.GraphQueryResultImpl;
0072: import org.openrdf.query.impl.TupleQueryResultBuilder;
0073: import org.openrdf.query.resultio.BooleanQueryResultFormat;
0074: import org.openrdf.query.resultio.BooleanQueryResultParser;
0075: import org.openrdf.query.resultio.BooleanQueryResultParserRegistry;
0076: import org.openrdf.query.resultio.QueryResultIO;
0077: import org.openrdf.query.resultio.QueryResultParseException;
0078: import org.openrdf.query.resultio.TupleQueryResultFormat;
0079: import org.openrdf.query.resultio.TupleQueryResultParser;
0080: import org.openrdf.query.resultio.TupleQueryResultParserRegistry;
0081: import org.openrdf.query.resultio.UnsupportedQueryResultFormatException;
0082: import org.openrdf.repository.RepositoryException;
0083: import org.openrdf.rio.RDFFormat;
0084: import org.openrdf.rio.RDFHandler;
0085: import org.openrdf.rio.RDFHandlerException;
0086: import org.openrdf.rio.RDFParseException;
0087: import org.openrdf.rio.RDFParser;
0088: import org.openrdf.rio.RDFParserRegistry;
0089: import org.openrdf.rio.Rio;
0090: import org.openrdf.rio.UnsupportedRDFormatException;
0091: import org.openrdf.rio.helpers.StatementCollector;
0092:
0093: /**
0094: * Low-level HTTP client for Sesame's HTTP protocol. Methods correspond directly
0095: * to the functionality offered by the protocol.
0096: *
0097: * @author Herko ter Horst
0098: * @author Arjohn Kampman
0099: */
0100: public class HTTPClient {
0101:
0102: /*-----------*
0103: * Constants *
0104: *-----------*/
0105:
0106: final Logger logger = LoggerFactory.getLogger(this .getClass());
0107:
0108: /*-----------*
0109: * Variables *
0110: *-----------*/
0111:
0112: private ValueFactory valueFactory;
0113:
0114: private String serverURL;
0115:
0116: private String repositoryURL;
0117:
0118: private HttpClient httpClient;
0119:
0120: private AuthScope authScope;
0121:
0122: private TupleQueryResultFormat preferredTQRFormat = TupleQueryResultFormat.BINARY;
0123:
0124: private BooleanQueryResultFormat preferredBQRFormat = BooleanQueryResultFormat.TEXT;
0125:
0126: private RDFFormat preferredRDFFormat = RDFFormat.TURTLE;
0127:
0128: /*--------------*
0129: * Constructors *
0130: *--------------*/
0131:
0132: public HTTPClient() {
0133: valueFactory = ValueFactoryImpl.getInstance();
0134:
0135: // Use MultiThreadedHttpConnectionManager to allow concurrent access on
0136: // HttpClient
0137: HttpConnectionManager manager = new MultiThreadedHttpConnectionManager();
0138:
0139: // Allow 20 concurrent connections to the same host (default is 2)
0140: HttpConnectionManagerParams params = new HttpConnectionManagerParams();
0141: params.setDefaultMaxConnectionsPerHost(20);
0142: manager.setParams(params);
0143:
0144: httpClient = new HttpClient(manager);
0145: }
0146:
0147: /*-----------------*
0148: * Get/set methods *
0149: *-----------------*/
0150:
0151: protected final HttpClient getHttpClient() {
0152: return httpClient;
0153: }
0154:
0155: public void setValueFactory(ValueFactory valueFactory) {
0156: this .valueFactory = valueFactory;
0157: }
0158:
0159: public ValueFactory getValueFactory() {
0160: return valueFactory;
0161: }
0162:
0163: public void setServerURL(String serverURL) {
0164: if (serverURL == null) {
0165: throw new IllegalArgumentException(
0166: "serverURL must not be null");
0167: }
0168:
0169: this .serverURL = serverURL;
0170: }
0171:
0172: public String getServerURL() {
0173: return serverURL;
0174: }
0175:
0176: protected void checkServerURL() {
0177: if (serverURL == null) {
0178: throw new IllegalStateException(
0179: "Server URL has not been set");
0180: }
0181: }
0182:
0183: public void setRepositoryURL(final String repositoryURL) {
0184: if (repositoryURL == null) {
0185: throw new IllegalArgumentException(
0186: "repositoryURL must not be null");
0187: }
0188:
0189: this .repositoryURL = repositoryURL;
0190:
0191: // Try to parse the server URL from the repository URL
0192: Pattern urlPattern = Pattern.compile("(.*)/"
0193: + Protocol.REPOSITORIES + "/[^/]*/?");
0194: Matcher matcher = urlPattern.matcher(repositoryURL);
0195:
0196: if (matcher.matches() && matcher.groupCount() == 1) {
0197: setServerURL(matcher.group(1));
0198: }
0199: }
0200:
0201: public String getRepositoryURL() {
0202: return repositoryURL;
0203: }
0204:
0205: protected void checkRepositoryURL() {
0206: if (repositoryURL == null) {
0207: throw new IllegalStateException(
0208: "Repository URL has not been set");
0209: }
0210: }
0211:
0212: public void setRepositoryID(String repositoryID) {
0213: checkServerURL();
0214: repositoryURL = Protocol.getRepositoryLocation(serverURL,
0215: repositoryID);
0216: }
0217:
0218: /**
0219: * Sets the preferred format for encoding tuple query results. The
0220: * {@link TupleQueryResultFormat#BINARY binary} format is preferred by
0221: * default.
0222: *
0223: * @param format
0224: * The preferred {@link TupleQueryResultFormat}, or <tt>null</tt>
0225: * to indicate no specific format is preferred.
0226: */
0227: public void setPreferredTupleQueryResultFormat(
0228: TupleQueryResultFormat format) {
0229: preferredTQRFormat = format;
0230: }
0231:
0232: /**
0233: * Gets the preferred {@link TupleQueryResultFormat} for encoding tuple query
0234: * results.
0235: *
0236: * @return The preferred format, of <tt>null</tt> if no specific format is
0237: * preferred.
0238: */
0239: public TupleQueryResultFormat getPreferredTupleQueryResultFormat() {
0240: return preferredTQRFormat;
0241: }
0242:
0243: /**
0244: * Sets the preferred format for encoding RDF documents. The
0245: * {@link RDFFormat#TURTLE Turtle} format is preferred by default.
0246: *
0247: * @param format
0248: * The preferred {@link RDFFormat}, or <tt>null</tt> to indicate no
0249: * specific format is preferred.
0250: */
0251: public void setPreferredRDFFormat(RDFFormat format) {
0252: preferredRDFFormat = format;
0253: }
0254:
0255: /**
0256: * Gets the preferred {@link RDFFormat} for encoding RDF documents.
0257: *
0258: * @return The preferred format, of <tt>null</tt> if no specific format is
0259: * preferred.
0260: */
0261: public RDFFormat getPreferredRDFFormat() {
0262: return preferredRDFFormat;
0263: }
0264:
0265: /**
0266: * Sets the preferred format for encoding boolean query results. The
0267: * {@link BooleanQueryResultFormat#TEXT binary} format is preferred by
0268: * default.
0269: *
0270: * @param format
0271: * The preferred {@link BooleanQueryResultFormat}, or <tt>null</tt>
0272: * to indicate no specific format is preferred.
0273: */
0274: public void setPreferredBooleanQueryResultFormat(
0275: BooleanQueryResultFormat format) {
0276: preferredBQRFormat = format;
0277: }
0278:
0279: /**
0280: * Gets the preferred {@link BooleanQueryResultFormat} for encoding boolean
0281: * query results.
0282: *
0283: * @return The preferred format, of <tt>null</tt> if no specific format is
0284: * preferred.
0285: */
0286: public BooleanQueryResultFormat getPreferredBooleanQueryResultFormat() {
0287: return preferredBQRFormat;
0288: }
0289:
0290: /**
0291: * Set the username and password for authenication with the remote server.
0292: *
0293: * @param username
0294: * the username
0295: * @param password
0296: * the password
0297: */
0298: public void setUsernameAndPassword(String username, String password) {
0299: checkServerURL();
0300:
0301: if (username != null && password != null) {
0302: try {
0303: URL server = new URL(serverURL);
0304: authScope = new AuthScope(server.getHost(),
0305: AuthScope.ANY_PORT);
0306: httpClient.getState().setCredentials(
0307: authScope,
0308: new UsernamePasswordCredentials(username,
0309: password));
0310: } catch (MalformedURLException e) {
0311: logger
0312: .warn(
0313: "Unable to set username and password for malformed URL {}",
0314: serverURL);
0315: }
0316: } else {
0317: httpClient.getState().clearCredentials();
0318: }
0319: }
0320:
0321: /*------------------*
0322: * Protocol version *
0323: *------------------*/
0324:
0325: public String getServerProtocol() throws IOException,
0326: RepositoryException, UnauthorizedException {
0327: checkServerURL();
0328:
0329: GetMethod method = new GetMethod(Protocol
0330: .getProtocolLocation(serverURL));
0331: setDoAuthentication(method);
0332:
0333: try {
0334: int httpCode = httpClient.executeMethod(method);
0335:
0336: if (httpCode == HttpURLConnection.HTTP_OK) {
0337: return method.getResponseBodyAsString();
0338: } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0339: throw new UnauthorizedException();
0340: } else {
0341: ErrorInfo errInfo = getErrorInfo(method);
0342: throw new RepositoryException(
0343: "Failed to get server protocol: " + errInfo);
0344: }
0345: } finally {
0346: method.releaseConnection();
0347: }
0348: }
0349:
0350: /*-----------------*
0351: * Repository list *
0352: *-----------------*/
0353:
0354: public TupleQueryResult getRepositoryList() throws IOException,
0355: RepositoryException, UnauthorizedException {
0356: try {
0357: TupleQueryResultBuilder builder = new TupleQueryResultBuilder();
0358: getRepositoryList(builder);
0359: return builder.getQueryResult();
0360: } catch (TupleQueryResultHandlerException e) {
0361: // Found a bug in TupleQueryResultBuilder?
0362: throw new RuntimeException(e);
0363: }
0364: }
0365:
0366: public void getRepositoryList(TupleQueryResultHandler handler)
0367: throws IOException, TupleQueryResultHandlerException,
0368: RepositoryException, UnauthorizedException {
0369: checkServerURL();
0370:
0371: GetMethod method = new GetMethod(Protocol
0372: .getRepositoriesLocation(serverURL));
0373: setDoAuthentication(method);
0374:
0375: try {
0376: getTupleQueryResult(method, handler);
0377: } catch (MalformedQueryException e) {
0378: // This shouldn't happen as no queries are involved
0379: logger.warn(
0380: "Server reported unexpected malfored query error",
0381: e);
0382: throw new RepositoryException(e.getMessage(), e);
0383: } finally {
0384: releaseConnection(method);
0385: }
0386: }
0387:
0388: /*------------------*
0389: * Query evaluation *
0390: *------------------*/
0391:
0392: public TupleQueryResult sendTupleQuery(QueryLanguage ql,
0393: String query, Dataset dataset, boolean includeInferred,
0394: Binding... bindings) throws IOException,
0395: RepositoryException, MalformedQueryException,
0396: UnauthorizedException {
0397: try {
0398: TupleQueryResultBuilder builder = new TupleQueryResultBuilder();
0399: sendTupleQuery(ql, query, dataset, includeInferred,
0400: builder, bindings);
0401: return builder.getQueryResult();
0402: } catch (TupleQueryResultHandlerException e) {
0403: // Found a bug in TupleQueryResultBuilder?
0404: throw new RuntimeException(e);
0405: }
0406: }
0407:
0408: public void sendTupleQuery(QueryLanguage ql, String query,
0409: Dataset dataset, boolean includeInferred,
0410: TupleQueryResultHandler handler, Binding... bindings)
0411: throws IOException, TupleQueryResultHandlerException,
0412: RepositoryException, MalformedQueryException,
0413: UnauthorizedException {
0414: HttpMethod method = getQueryMethod(ql, query, dataset,
0415: includeInferred, bindings);
0416:
0417: try {
0418: getTupleQueryResult(method, handler);
0419: } finally {
0420: releaseConnection(method);
0421: }
0422: }
0423:
0424: public GraphQueryResult sendGraphQuery(QueryLanguage ql,
0425: String query, Dataset dataset, boolean includeInferred,
0426: Binding... bindings) throws IOException,
0427: RepositoryException, MalformedQueryException,
0428: UnauthorizedException {
0429: try {
0430: StatementCollector collector = new StatementCollector();
0431: sendGraphQuery(ql, query, dataset, includeInferred,
0432: collector, bindings);
0433: return new GraphQueryResultImpl(collector.getNamespaces(),
0434: collector.getStatements());
0435: } catch (RDFHandlerException e) {
0436: // Found a bug in TupleQueryResultBuilder?
0437: throw new RuntimeException(e);
0438: }
0439: }
0440:
0441: public void sendGraphQuery(QueryLanguage ql, String query,
0442: Dataset dataset, boolean includeInferred,
0443: RDFHandler handler, Binding... bindings)
0444: throws IOException, RDFHandlerException,
0445: RepositoryException, MalformedQueryException,
0446: UnauthorizedException {
0447: HttpMethod method = getQueryMethod(ql, query, dataset,
0448: includeInferred, bindings);
0449:
0450: try {
0451: getRDF(method, handler, false);
0452: } finally {
0453: releaseConnection(method);
0454: }
0455: }
0456:
0457: public boolean sendBooleanQuery(QueryLanguage ql, String query,
0458: Dataset dataset, boolean includeInferred,
0459: Binding... bindings) throws IOException,
0460: RepositoryException, MalformedQueryException,
0461: UnauthorizedException {
0462: HttpMethod method = getQueryMethod(ql, query, dataset,
0463: includeInferred, bindings);
0464:
0465: try {
0466: return getBoolean(method);
0467: } finally {
0468: releaseConnection(method);
0469: }
0470: }
0471:
0472: protected HttpMethod getQueryMethod(QueryLanguage ql, String query,
0473: Dataset dataset, boolean includeInferred,
0474: Binding... bindings) {
0475: PostMethod method = new PostMethod(getRepositoryURL());
0476: setDoAuthentication(method);
0477:
0478: method.setRequestHeader("Content-Type", Protocol.FORM_MIME_TYPE
0479: + "; charset=utf-8");
0480:
0481: List<NameValuePair> queryParams = getQueryMethodParameters(ql,
0482: query, dataset, includeInferred, bindings);
0483:
0484: method.setRequestBody(queryParams
0485: .toArray(new NameValuePair[queryParams.size()]));
0486:
0487: return method;
0488: }
0489:
0490: protected List<NameValuePair> getQueryMethodParameters(
0491: QueryLanguage ql, String query, Dataset dataset,
0492: boolean includeInferred, Binding... bindings) {
0493: List<NameValuePair> queryParams = new ArrayList<NameValuePair>(
0494: bindings.length + 10);
0495:
0496: queryParams.add(new NameValuePair(
0497: Protocol.QUERY_LANGUAGE_PARAM_NAME, ql.getName()));
0498: queryParams.add(new NameValuePair(Protocol.QUERY_PARAM_NAME,
0499: query));
0500: queryParams.add(new NameValuePair(
0501: Protocol.INCLUDE_INFERRED_PARAM_NAME, Boolean
0502: .toString(includeInferred)));
0503:
0504: if (dataset != null) {
0505: for (URI defaultGraphURI : dataset.getDefaultGraphs()) {
0506: queryParams.add(new NameValuePair(
0507: Protocol.DEFAULT_GRAPH_PARAM_NAME,
0508: defaultGraphURI.toString()));
0509: }
0510: for (URI namedGraphURI : dataset.getNamedGraphs()) {
0511: queryParams.add(new NameValuePair(
0512: Protocol.NAMED_GRAPH_PARAM_NAME, namedGraphURI
0513: .toString()));
0514: }
0515: }
0516:
0517: for (int i = 0; i < bindings.length; i++) {
0518: String paramName = Protocol.BINDING_PREFIX
0519: + bindings[i].getName();
0520: String paramValue = Protocol.encodeValue(bindings[i]
0521: .getValue());
0522: queryParams.add(new NameValuePair(paramName, paramValue));
0523: }
0524:
0525: return queryParams;
0526: }
0527:
0528: /*---------------------------*
0529: * Get/add/remove statements *
0530: *---------------------------*/
0531:
0532: public void getStatements(Resource subj, URI pred, Value obj,
0533: boolean includeInferred, RDFHandler handler,
0534: Resource... contexts) throws IOException,
0535: RDFHandlerException, RepositoryException,
0536: UnauthorizedException {
0537: checkRepositoryURL();
0538:
0539: GetMethod method = new GetMethod(Protocol
0540: .getStatementsLocation(getRepositoryURL()));
0541: setDoAuthentication(method);
0542:
0543: List<NameValuePair> params = new ArrayList<NameValuePair>(5);
0544: if (subj != null) {
0545: params.add(new NameValuePair(Protocol.SUBJECT_PARAM_NAME,
0546: Protocol.encodeValue(subj)));
0547: }
0548: if (pred != null) {
0549: params.add(new NameValuePair(Protocol.PREDICATE_PARAM_NAME,
0550: Protocol.encodeValue(pred)));
0551: }
0552: if (obj != null) {
0553: params.add(new NameValuePair(Protocol.OBJECT_PARAM_NAME,
0554: Protocol.encodeValue(obj)));
0555: }
0556: for (String encodedContext : Protocol.encodeContexts(contexts)) {
0557: params.add(new NameValuePair(Protocol.CONTEXT_PARAM_NAME,
0558: encodedContext));
0559: }
0560: params.add(new NameValuePair(
0561: Protocol.INCLUDE_INFERRED_PARAM_NAME, Boolean
0562: .toString(includeInferred)));
0563:
0564: method.setQueryString(params.toArray(new NameValuePair[params
0565: .size()]));
0566:
0567: try {
0568: getRDF(method, handler, true);
0569: } catch (MalformedQueryException e) {
0570: logger.warn(
0571: "Server reported unexpected malfored query error",
0572: e);
0573: throw new RepositoryException(e.getMessage(), e);
0574: } finally {
0575: releaseConnection(method);
0576: }
0577: }
0578:
0579: public void sendTransaction(
0580: final Iterable<? extends TransactionOperation> txn)
0581: throws IOException, RepositoryException,
0582: UnauthorizedException {
0583: checkRepositoryURL();
0584:
0585: PostMethod method = new PostMethod(Protocol
0586: .getStatementsLocation(getRepositoryURL()));
0587: setDoAuthentication(method);
0588:
0589: // Create a RequestEntity for the transaction data
0590: method.setRequestEntity(new RequestEntity() {
0591:
0592: public long getContentLength() {
0593: return -1; // don't know
0594: }
0595:
0596: public String getContentType() {
0597: return Protocol.TXN_MIME_TYPE;
0598: }
0599:
0600: public boolean isRepeatable() {
0601: return true;
0602: }
0603:
0604: public void writeRequest(OutputStream out)
0605: throws IOException {
0606: TransactionWriter txnWriter = new TransactionWriter();
0607: txnWriter.serialize(txn, out);
0608: }
0609: });
0610:
0611: try {
0612: int httpCode = httpClient.executeMethod(method);
0613:
0614: if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0615: throw new UnauthorizedException();
0616: } else if (!HttpClientUtil.is2xx(httpCode)) {
0617: ErrorInfo errInfo = getErrorInfo(method);
0618: throw new RepositoryException("Transaction failed: "
0619: + errInfo + " (" + httpCode + ")");
0620: }
0621: } finally {
0622: releaseConnection(method);
0623: }
0624: }
0625:
0626: public void upload(final Reader contents, String baseURI,
0627: final RDFFormat dataFormat, boolean overwrite,
0628: Resource... contexts) throws IOException,
0629: RDFParseException, RepositoryException,
0630: UnauthorizedException {
0631: final Charset charset = dataFormat.hasCharset() ? dataFormat
0632: .getCharset() : Charset.forName("UTF-8");
0633:
0634: RequestEntity entity = new RequestEntity() {
0635:
0636: public long getContentLength() {
0637: return -1; // don't know
0638: }
0639:
0640: public String getContentType() {
0641: return dataFormat.getDefaultMIMEType() + "; charset="
0642: + charset.name();
0643: }
0644:
0645: public boolean isRepeatable() {
0646: return false;
0647: }
0648:
0649: public void writeRequest(OutputStream out)
0650: throws IOException {
0651: OutputStreamWriter writer = new OutputStreamWriter(out,
0652: charset);
0653: IOUtil.transfer(contents, writer);
0654: writer.flush();
0655: }
0656: };
0657:
0658: upload(entity, baseURI, overwrite, contexts);
0659: }
0660:
0661: public void upload(InputStream contents, String baseURI,
0662: RDFFormat dataFormat, boolean overwrite,
0663: Resource... contexts) throws IOException,
0664: RDFParseException, RepositoryException,
0665: UnauthorizedException {
0666: // Set Content-Length to -1 as we don't know it and we also don't want to
0667: // cache
0668: RequestEntity entity = new InputStreamRequestEntity(contents,
0669: -1, dataFormat.getDefaultMIMEType());
0670: upload(entity, baseURI, overwrite, contexts);
0671: }
0672:
0673: protected void upload(RequestEntity reqEntity, String baseURI,
0674: boolean overwrite, Resource... contexts)
0675: throws IOException, RDFParseException, RepositoryException,
0676: UnauthorizedException {
0677: OpenRDFUtil.verifyContextNotNull(contexts);
0678:
0679: checkRepositoryURL();
0680:
0681: String uploadURL = Protocol
0682: .getStatementsLocation(getRepositoryURL());
0683:
0684: // Select appropriate HTTP method
0685: EntityEnclosingMethod method;
0686: if (overwrite) {
0687: method = new PutMethod(uploadURL);
0688: } else {
0689: method = new PostMethod(uploadURL);
0690: }
0691:
0692: setDoAuthentication(method);
0693:
0694: // Set relevant query parameters
0695: List<NameValuePair> params = new ArrayList<NameValuePair>(5);
0696: for (String encodedContext : Protocol.encodeContexts(contexts)) {
0697: params.add(new NameValuePair(Protocol.CONTEXT_PARAM_NAME,
0698: encodedContext));
0699: }
0700: if (baseURI != null && baseURI.trim().length() != 0) {
0701: String encodedBaseURI = Protocol.encodeValue(new URIImpl(
0702: baseURI));
0703: params.add(new NameValuePair(Protocol.BASEURI_PARAM_NAME,
0704: encodedBaseURI));
0705: }
0706: method.setQueryString(params.toArray(new NameValuePair[params
0707: .size()]));
0708:
0709: // Set payload
0710: method.setRequestEntity(reqEntity);
0711:
0712: // Send request
0713: try {
0714: int httpCode = httpClient.executeMethod(method);
0715:
0716: if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0717: throw new UnauthorizedException();
0718: } else if (httpCode == HttpURLConnection.HTTP_UNSUPPORTED_TYPE) {
0719: throw new UnsupportedRDFormatException(method
0720: .getResponseBodyAsString());
0721: } else if (!HttpClientUtil.is2xx(httpCode)) {
0722: ErrorInfo errInfo = ErrorInfo.parse(method
0723: .getResponseBodyAsString());
0724:
0725: if (errInfo.getErrorType() == ErrorType.MALFORMED_DATA) {
0726: throw new RDFParseException(errInfo
0727: .getErrorMessage());
0728: } else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_FILE_FORMAT) {
0729: throw new UnsupportedRDFormatException(errInfo
0730: .getErrorMessage());
0731: } else {
0732: throw new RepositoryException(
0733: "Failed to upload data: " + errInfo);
0734: }
0735: }
0736: } finally {
0737: releaseConnection(method);
0738: }
0739: }
0740:
0741: /*-------------*
0742: * Context IDs *
0743: *-------------*/
0744:
0745: public TupleQueryResult getContextIDs() throws IOException,
0746: RepositoryException, UnauthorizedException {
0747: try {
0748: TupleQueryResultBuilder builder = new TupleQueryResultBuilder();
0749: getContextIDs(builder);
0750: return builder.getQueryResult();
0751: } catch (TupleQueryResultHandlerException e) {
0752: // Found a bug in TupleQueryResultBuilder?
0753: throw new RuntimeException(e);
0754: }
0755: }
0756:
0757: public void getContextIDs(TupleQueryResultHandler handler)
0758: throws IOException, TupleQueryResultHandlerException,
0759: RepositoryException, UnauthorizedException {
0760: checkRepositoryURL();
0761:
0762: GetMethod method = new GetMethod(Protocol
0763: .getContextsLocation(getRepositoryURL()));
0764: setDoAuthentication(method);
0765:
0766: try {
0767: getTupleQueryResult(method, handler);
0768: } catch (MalformedQueryException e) {
0769: logger.warn(
0770: "Server reported unexpected malfored query error",
0771: e);
0772: throw new RepositoryException(e.getMessage(), e);
0773: } finally {
0774: releaseConnection(method);
0775: }
0776: }
0777:
0778: /*---------------------------*
0779: * Get/add/remove namespaces *
0780: *---------------------------*/
0781:
0782: public TupleQueryResult getNamespaces() throws IOException,
0783: RepositoryException, UnauthorizedException {
0784: try {
0785: TupleQueryResultBuilder builder = new TupleQueryResultBuilder();
0786: getNamespaces(builder);
0787: return builder.getQueryResult();
0788: } catch (TupleQueryResultHandlerException e) {
0789: // Found a bug in TupleQueryResultBuilder?
0790: throw new RuntimeException(e);
0791: }
0792: }
0793:
0794: public void getNamespaces(TupleQueryResultHandler handler)
0795: throws IOException, TupleQueryResultHandlerException,
0796: RepositoryException, UnauthorizedException {
0797: checkRepositoryURL();
0798:
0799: HttpMethod method = new GetMethod(Protocol
0800: .getNamespacesLocation(repositoryURL));
0801: setDoAuthentication(method);
0802:
0803: try {
0804: getTupleQueryResult(method, handler);
0805: } catch (MalformedQueryException e) {
0806: logger.warn(
0807: "Server reported unexpected malfored query error",
0808: e);
0809: throw new RepositoryException(e.getMessage(), e);
0810: } finally {
0811: releaseConnection(method);
0812: }
0813: }
0814:
0815: public String getNamespace(String prefix) throws IOException,
0816: RepositoryException, UnauthorizedException {
0817: checkRepositoryURL();
0818:
0819: HttpMethod method = new GetMethod(Protocol
0820: .getNamespacePrefixLocation(repositoryURL, prefix));
0821: setDoAuthentication(method);
0822:
0823: try {
0824: int httpCode = httpClient.executeMethod(method);
0825:
0826: if (httpCode == HttpURLConnection.HTTP_OK) {
0827: return method.getResponseBodyAsString();
0828: } else if (httpCode == HttpURLConnection.HTTP_NOT_FOUND) {
0829: return null;
0830: } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0831: throw new UnauthorizedException();
0832: } else {
0833: ErrorInfo errInfo = getErrorInfo(method);
0834: throw new RepositoryException(
0835: "Failed to get namespace: " + errInfo + " ("
0836: + httpCode + ")");
0837: }
0838: } finally {
0839: releaseConnection(method);
0840: }
0841: }
0842:
0843: public void setNamespacePrefix(String prefix, String name)
0844: throws IOException, RepositoryException,
0845: UnauthorizedException {
0846: checkRepositoryURL();
0847:
0848: EntityEnclosingMethod method = new PutMethod(Protocol
0849: .getNamespacePrefixLocation(repositoryURL, prefix));
0850: setDoAuthentication(method);
0851: method.setRequestEntity(new StringRequestEntity(name,
0852: "text/plain", "UTF-8"));
0853:
0854: try {
0855: int httpCode = httpClient.executeMethod(method);
0856:
0857: if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0858: throw new UnauthorizedException();
0859: } else if (!HttpClientUtil.is2xx(httpCode)) {
0860: ErrorInfo errInfo = getErrorInfo(method);
0861: throw new RepositoryException(
0862: "Failed to set namespace: " + errInfo + " ("
0863: + httpCode + ")");
0864: }
0865: } finally {
0866: releaseConnection(method);
0867: }
0868: }
0869:
0870: public void removeNamespacePrefix(String prefix)
0871: throws IOException, RepositoryException,
0872: UnauthorizedException {
0873: checkRepositoryURL();
0874:
0875: HttpMethod method = new DeleteMethod(Protocol
0876: .getNamespacePrefixLocation(repositoryURL, prefix));
0877: setDoAuthentication(method);
0878:
0879: try {
0880: int httpCode = httpClient.executeMethod(method);
0881:
0882: if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0883: throw new UnauthorizedException();
0884: } else if (!HttpClientUtil.is2xx(httpCode)) {
0885: ErrorInfo errInfo = getErrorInfo(method);
0886: throw new RepositoryException(
0887: "Failed to remove namespace: " + errInfo + " ("
0888: + httpCode + ")");
0889: }
0890: } finally {
0891: releaseConnection(method);
0892: }
0893: }
0894:
0895: public void clearNamespaces() throws IOException,
0896: RepositoryException, UnauthorizedException {
0897: checkRepositoryURL();
0898:
0899: HttpMethod method = new DeleteMethod(Protocol
0900: .getNamespacesLocation(repositoryURL));
0901: setDoAuthentication(method);
0902:
0903: try {
0904: int httpCode = httpClient.executeMethod(method);
0905:
0906: if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0907: throw new UnauthorizedException();
0908: } else if (!HttpClientUtil.is2xx(httpCode)) {
0909: ErrorInfo errInfo = getErrorInfo(method);
0910: throw new RepositoryException(
0911: "Failed to clear namespaces: " + errInfo + " ("
0912: + httpCode + ")");
0913: }
0914: } finally {
0915: releaseConnection(method);
0916: }
0917: }
0918:
0919: /*-------------------------*
0920: * Repository/context size *
0921: *-------------------------*/
0922:
0923: public long size(Resource... contexts) throws IOException,
0924: RepositoryException, UnauthorizedException {
0925: checkRepositoryURL();
0926:
0927: String[] encodedContexts = Protocol.encodeContexts(contexts);
0928:
0929: NameValuePair[] contextParams = new NameValuePair[encodedContexts.length];
0930: for (int i = 0; i < encodedContexts.length; i++) {
0931: contextParams[i] = new NameValuePair(
0932: Protocol.CONTEXT_PARAM_NAME, encodedContexts[i]);
0933: }
0934:
0935: HttpMethod method = new GetMethod(Protocol
0936: .getSizeLocation(repositoryURL));
0937: setDoAuthentication(method);
0938: method.setQueryString(contextParams);
0939:
0940: try {
0941: int httpCode = httpClient.executeMethod(method);
0942:
0943: if (httpCode == HttpURLConnection.HTTP_OK) {
0944: String response = method.getResponseBodyAsString();
0945: try {
0946: return Long.parseLong(response);
0947: } catch (NumberFormatException e) {
0948: throw new RepositoryException(
0949: "Server responded with invalid size value: "
0950: + response);
0951: }
0952: } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
0953: throw new UnauthorizedException();
0954: } else {
0955: ErrorInfo errInfo = getErrorInfo(method);
0956: throw new RepositoryException(errInfo.toString());
0957: }
0958: } finally {
0959: method.releaseConnection();
0960: }
0961: }
0962:
0963: /*------------------*
0964: * Response parsing *
0965: *------------------*/
0966:
0967: protected void getTupleQueryResult(HttpMethod method,
0968: TupleQueryResultHandler handler) throws IOException,
0969: TupleQueryResultHandlerException, RepositoryException,
0970: MalformedQueryException, UnauthorizedException {
0971: // Specify which formats we support using Accept headers
0972: Set<TupleQueryResultFormat> tqrFormats = TupleQueryResultParserRegistry
0973: .getInstance().getKeys();
0974: if (tqrFormats.isEmpty()) {
0975: throw new RepositoryException(
0976: "No tuple query result parsers have been registered");
0977: }
0978:
0979: for (TupleQueryResultFormat format : tqrFormats) {
0980: // Determine a q-value that reflects the user specified preference
0981: int qValue = 10;
0982:
0983: if (preferredTQRFormat != null
0984: && !preferredTQRFormat.equals(format)) {
0985: // Prefer specified format over other formats
0986: qValue -= 2;
0987: }
0988:
0989: for (String mimeType : format.getMIMETypes()) {
0990: String acceptParam = mimeType;
0991:
0992: if (qValue < 10) {
0993: acceptParam += ";q=0." + qValue;
0994: }
0995:
0996: method.addRequestHeader(ACCEPT_PARAM_NAME, acceptParam);
0997: }
0998: }
0999:
1000: int httpCode = httpClient.executeMethod(method);
1001:
1002: if (httpCode == HttpURLConnection.HTTP_OK) {
1003: String mimeType = getResponseMIMEType(method);
1004: try {
1005: TupleQueryResultFormat format = TupleQueryResultFormat
1006: .matchMIMEType(mimeType, tqrFormats);
1007: TupleQueryResultParser parser = QueryResultIO
1008: .createParser(format, getValueFactory());
1009: parser.setTupleQueryResultHandler(handler);
1010: parser.parse(method.getResponseBodyAsStream());
1011: } catch (UnsupportedQueryResultFormatException e) {
1012: throw new RepositoryException(
1013: "Server responded with an unsupported file format: "
1014: + mimeType);
1015: } catch (QueryResultParseException e) {
1016: throw new RepositoryException(
1017: "Malformed query result from server", e);
1018: }
1019: } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
1020: throw new UnauthorizedException();
1021: } else {
1022: ErrorInfo errInfo = getErrorInfo(method);
1023:
1024: // Throw appropriate exception
1025: if (errInfo.getErrorType() == ErrorType.MALFORMED_QUERY) {
1026: throw new MalformedQueryException(errInfo
1027: .getErrorMessage());
1028: } else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
1029: throw new UnsupportedQueryLanguageException(errInfo
1030: .getErrorMessage());
1031: } else {
1032: throw new RepositoryException(errInfo.toString());
1033: }
1034: }
1035: }
1036:
1037: protected void getRDF(HttpMethod method, RDFHandler handler,
1038: boolean requireContext) throws IOException,
1039: RDFHandlerException, RepositoryException,
1040: MalformedQueryException, UnauthorizedException {
1041: // Specify which formats we support using Accept headers
1042: Set<RDFFormat> rdfFormats = RDFParserRegistry.getInstance()
1043: .getKeys();
1044: if (rdfFormats.isEmpty()) {
1045: throw new RepositoryException(
1046: "No tuple RDF parsers have been registered");
1047: }
1048:
1049: for (RDFFormat format : rdfFormats) {
1050: // Determine a q-value that reflects the necessity of context
1051: // support and the user specified preference
1052: int qValue = 10;
1053:
1054: if (requireContext && !format.supportsContexts()) {
1055: // Prefer context-supporting formats over pure triple-formats
1056: qValue -= 5;
1057: }
1058:
1059: if (preferredRDFFormat != null
1060: && !preferredRDFFormat.equals(format)) {
1061: // Prefer specified format over other formats
1062: qValue -= 2;
1063: }
1064:
1065: if (!format.supportsNamespaces()) {
1066: // We like reusing namespace prefixes
1067: qValue -= 1;
1068: }
1069:
1070: for (String mimeType : format.getMIMETypes()) {
1071: String acceptParam = mimeType;
1072:
1073: if (qValue < 10) {
1074: acceptParam += ";q=0." + qValue;
1075: }
1076:
1077: method.addRequestHeader(ACCEPT_PARAM_NAME, acceptParam);
1078: }
1079: }
1080:
1081: int httpCode = httpClient.executeMethod(method);
1082:
1083: if (httpCode == HttpURLConnection.HTTP_OK) {
1084: String mimeType = getResponseMIMEType(method);
1085: try {
1086: RDFFormat format = RDFFormat.matchMIMEType(mimeType,
1087: rdfFormats);
1088: RDFParser parser = Rio.createParser(format,
1089: getValueFactory());
1090: parser.setPreserveBNodeIDs(true);
1091: parser.setRDFHandler(handler);
1092: parser.parse(method.getResponseBodyAsStream(), method
1093: .getURI().getURI());
1094: } catch (UnsupportedRDFormatException e) {
1095: throw new RepositoryException(
1096: "Server responded with an unsupported file format: "
1097: + mimeType);
1098: } catch (RDFParseException e) {
1099: throw new RepositoryException(
1100: "Malformed query result from server", e);
1101: }
1102: } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
1103: throw new UnauthorizedException();
1104: } else {
1105: ErrorInfo errInfo = getErrorInfo(method);
1106:
1107: // Throw appropriate exception
1108: if (errInfo.getErrorType() == ErrorType.MALFORMED_QUERY) {
1109: throw new MalformedQueryException(errInfo
1110: .getErrorMessage());
1111: } else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
1112: throw new UnsupportedQueryLanguageException(errInfo
1113: .getErrorMessage());
1114: } else {
1115: throw new RepositoryException(errInfo.toString());
1116: }
1117: }
1118: }
1119:
1120: protected boolean getBoolean(HttpMethod method) throws IOException,
1121: RepositoryException, MalformedQueryException,
1122: UnauthorizedException {
1123: // Specify which formats we support using Accept headers
1124: Set<BooleanQueryResultFormat> booleanFormats = BooleanQueryResultParserRegistry
1125: .getInstance().getKeys();
1126: if (booleanFormats.isEmpty()) {
1127: throw new RepositoryException(
1128: "No boolean query result parsers have been registered");
1129: }
1130:
1131: for (BooleanQueryResultFormat format : booleanFormats) {
1132: // Determine a q-value that reflects the user specified preference
1133: int qValue = 10;
1134:
1135: if (preferredBQRFormat != null
1136: && !preferredBQRFormat.equals(format)) {
1137: // Prefer specified format over other formats
1138: qValue -= 2;
1139: }
1140:
1141: for (String mimeType : format.getMIMETypes()) {
1142: String acceptParam = mimeType;
1143:
1144: if (qValue < 10) {
1145: acceptParam += ";q=0." + qValue;
1146: }
1147:
1148: method.addRequestHeader(ACCEPT_PARAM_NAME, acceptParam);
1149: }
1150: }
1151:
1152: int httpCode = httpClient.executeMethod(method);
1153:
1154: if (httpCode == HttpURLConnection.HTTP_OK) {
1155: String mimeType = getResponseMIMEType(method);
1156: try {
1157: BooleanQueryResultFormat format = BooleanQueryResultFormat
1158: .matchMIMEType(mimeType, booleanFormats);
1159: BooleanQueryResultParser parser = QueryResultIO
1160: .createParser(format);
1161: return parser.parse(method.getResponseBodyAsStream());
1162: } catch (UnsupportedQueryResultFormatException e) {
1163: throw new RepositoryException(
1164: "Server responded with an unsupported file format: "
1165: + mimeType);
1166: } catch (QueryResultParseException e) {
1167: throw new RepositoryException(
1168: "Malformed query result from server", e);
1169: }
1170: } else if (httpCode == HttpURLConnection.HTTP_UNAUTHORIZED) {
1171: throw new UnauthorizedException();
1172: } else {
1173: ErrorInfo errInfo = getErrorInfo(method);
1174:
1175: // Throw appropriate exception
1176: if (errInfo.getErrorType() == ErrorType.MALFORMED_QUERY) {
1177: throw new MalformedQueryException(errInfo
1178: .getErrorMessage());
1179: } else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
1180: throw new UnsupportedQueryLanguageException(errInfo
1181: .getErrorMessage());
1182: } else {
1183: throw new RepositoryException(method.getStatusText());
1184: }
1185: }
1186: }
1187:
1188: /*-------------------------*
1189: * General utility methods *
1190: *-------------------------*/
1191:
1192: /**
1193: * Gets the MIME type specified in the response headers of the supplied
1194: * method, if any. For example, if the response headers contain
1195: * <tt>Content-Type: application/xml;charset=UTF-8</tt>, this method will
1196: * return <tt>application/xml</tt> as the MIME type.
1197: *
1198: * @param method
1199: * The method to get the reponse MIME type from.
1200: * @return The response MIME type, or <tt>null</tt> if not available.
1201: */
1202: protected String getResponseMIMEType(HttpMethod method)
1203: throws IOException {
1204: Header[] headers = method.getResponseHeaders("Content-Type");
1205:
1206: for (Header header : headers) {
1207: HeaderElement[] headerElements = header.getElements();
1208:
1209: for (HeaderElement headerEl : headerElements) {
1210: String mimeType = headerEl.getName();
1211: if (mimeType != null) {
1212: logger.debug("reponse MIME type is {}", mimeType);
1213: return mimeType;
1214: }
1215: }
1216: }
1217:
1218: return null;
1219: }
1220:
1221: protected ErrorInfo getErrorInfo(HttpMethod method)
1222: throws RepositoryException {
1223: try {
1224: ErrorInfo errInfo = ErrorInfo.parse(method
1225: .getResponseBodyAsString());
1226: logger.warn("Server reports problem: {}", errInfo
1227: .getErrorMessage());
1228: return errInfo;
1229: } catch (IOException e) {
1230: logger.warn("Unable to retrieve error info from server");
1231: throw new RepositoryException(
1232: "Unable to retrieve error info from server", e);
1233: }
1234: }
1235:
1236: protected final void setDoAuthentication(HttpMethod method) {
1237: if (authScope != null
1238: && httpClient.getState().getCredentials(authScope) != null) {
1239: method.setDoAuthentication(true);
1240: } else {
1241: method.setDoAuthentication(false);
1242: }
1243: }
1244:
1245: protected final void releaseConnection(HttpMethod method) {
1246: try {
1247: // Read the entire response body to enable the reuse of the connection
1248: InputStream responseStream = method
1249: .getResponseBodyAsStream();
1250: if (responseStream != null) {
1251: while (responseStream.read() >= 0) {
1252: // do nothing
1253: }
1254: }
1255:
1256: method.releaseConnection();
1257: } catch (IOException e) {
1258: logger.warn("I/O error upon releasing connection", e);
1259: }
1260: }
1261: }
|