Java tutorial
/* * Copyright (C) 2018 Saxon State and University Library Dresden (SLUB) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.qucosa.camel.component.fcrepo3; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPut; import org.apache.http.client.utils.URIBuilder; import org.apache.http.entity.BasicHttpEntity; import org.apache.http.entity.BufferedHttpEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.util.EntityUtils; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Scanner; import java.util.Set; import static java.lang.String.format; public class Fcrepo3APIAccess implements Closeable { public static final String FEDORA_RESUMEFINDOBJECTS_URI_PATTERN = "http://%s:%s/fedora/objects?sessionToken=%s&pid=true&terms=%s&resultFormat=xml"; private static final String FEDORA_DATASTREAM_DISSEMINATION_URI_PATTERN = "http://%s:%s/fedora/objects/%s/datastreams/%s/content"; private static final String FEDORA_DATASTREAM_MODIFICATION_URI_PATTERN = "http://%s:%s/fedora/objects/%s/datastreams/%s"; private static final String FEDORA_OBJECTXML_URI_PATTERN = "http://%s:%s/fedora/objects/%s?format=xml"; private static final String FEDORA_FINDOBJECTS_URI_PATTERN = "http://%s:%s/fedora/objects?pid=true&terms=%s&resultFormat=xml"; public static final String END_OF_INPUT = "\\Z"; private final String host; private final CloseableHttpClient httpClient; private final String port; private final SAXParserFactory saxParserFactory = SAXParserFactory.newInstance(); private SAXParser saxParser; public Fcrepo3APIAccess(String host, String port, CloseableHttpClient httpClient) { this.host = host; this.port = port; this.httpClient = httpClient; } public Fcrepo3APIAccess(String host, String port, String user, String password) { CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); credentialsProvider.setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(user, password)); this.host = host; this.port = port; httpClient = HttpClientBuilder.create().setConnectionManager(new PoolingHttpClientConnectionManager()) .setDefaultCredentialsProvider(credentialsProvider).build(); } public String getObjectXml(String pid) throws Fcrepo3HTTPException { HttpResponse response = null; URI uri = URI.create(format(FEDORA_OBJECTXML_URI_PATTERN, host, port, pid)); try { HttpGet get = new HttpGet(uri); response = httpClient.execute(get); if (response.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { return new Scanner(response.getEntity().getContent()).useDelimiter(END_OF_INPUT).next(); } throw new Fcrepo3HTTPException(format("Cannot load XML of object %s.", pid), response.getStatusLine().getStatusCode(), uri); } catch (IOException e) { throw new Fcrepo3HTTPException(format("Cannot load XML of object %s.", pid), uri, e); } finally { consumeResponseEntity(response); } } public String getDatastreamDissemination(String pid, String dsid) throws Fcrepo3HTTPException { HttpResponse response = null; URI uri = URI.create(format(FEDORA_DATASTREAM_DISSEMINATION_URI_PATTERN, host, port, pid, dsid)); try { response = httpClient.execute(new HttpGet(uri)); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new Fcrepo3HTTPException(format("Cannot load datastream %s of object %s.", dsid, pid), response.getStatusLine().getStatusCode(), uri); } return new Scanner(response.getEntity().getContent()).useDelimiter(END_OF_INPUT).next(); } catch (IOException ex) { throw new Fcrepo3HTTPException(format("Cannot load datastream %s of object %s.", dsid, pid), uri, ex); } finally { consumeResponseEntity(response); } } @Override public void close() throws IOException { httpClient.close(); } public FindObjectsResponse findObjects(String terms) throws IOException { return startOrResumeFindObjects(terms, null); } public FindObjectsResponse resumeFindObjects(FindObjectsResponse find) throws IOException { if (find != null && find.isResumable()) { return startOrResumeFindObjects(find.getTerms(), find.getToken()); } else { return FindObjectsResponse.emptyResponse(); } } public void modifyDatastream(String pid, String dsid, Boolean versionable, InputStream content) throws IOException { HttpResponse response = null; try { BasicHttpEntity entity = new BasicHttpEntity(); entity.setContent(content); // Entity needs to be buffered because Fedora might reply in a way // forces resubmitting the entity BufferedHttpEntity bufferedHttpEntity = new BufferedHttpEntity(entity); URIBuilder uriBuilder = new URIBuilder( format(FEDORA_DATASTREAM_MODIFICATION_URI_PATTERN, host, port, pid, dsid)); if (versionable != null) { uriBuilder.addParameter("versionable", String.valueOf(versionable)); } URI uri = uriBuilder.build(); HttpPut put = new HttpPut(uri); put.setEntity(bufferedHttpEntity); response = httpClient.execute(put); if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { throw new IOException(format("Cannot modify datastream %s of object %s. Server responded: %s", dsid, pid, response.getStatusLine())); } } catch (URISyntaxException e) { throw new IOException("Cannot ", e); } finally { consumeResponseEntity(response); } } private FindObjectsResponse startOrResumeFindObjects(final String terms, final String token) throws IOException { HttpResponse httpResponse = null; List<String> pidList = Collections.emptyList(); String newToken = null; try { HttpGet get; if (token == null) { get = new HttpGet(format(FEDORA_FINDOBJECTS_URI_PATTERN, host, port, terms)); } else { get = new HttpGet(format(FEDORA_RESUMEFINDOBJECTS_URI_PATTERN, host, port, token, terms)); } httpResponse = httpClient.execute(get); if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { try { ValueCollector valueCollector = parseReturningValueCollector(httpResponse); pidList = valueCollector.getValuesFor("pid"); List<String> tokenList = valueCollector.getValuesFor("token"); if (!tokenList.isEmpty()) { newToken = tokenList.get(0); } } catch (ParserConfigurationException | SAXException e) { throw new IOException("Cannot parse content returned by Fedora", e); } } } finally { consumeResponseEntity(httpResponse); } return new FindObjectsResponse(terms, pidList, newToken); } private ValueCollector parseReturningValueCollector(HttpResponse response) throws ParserConfigurationException, SAXException, IOException { SAXParser saxParser = getOrCreateSaxParser(); ValueCollector valueCollector = new ValueCollector("pid", "token"); saxParser.parse(response.getEntity().getContent(), valueCollector); return valueCollector; } private SAXParser getOrCreateSaxParser() throws ParserConfigurationException, SAXException { if (saxParser == null) { saxParser = saxParserFactory.newSAXParser(); } else { saxParser.reset(); } return saxParser; } class ValueCollector extends DefaultHandler { private String current; private final Set<String> keys = new HashSet<>(); private final Map<String, List<String>> values = new HashMap<>(); public ValueCollector(String... keys) { this.keys.addAll(Arrays.asList(keys)); } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) { if (keys.contains(qName)) current = qName; } @Override public void characters(char[] ch, int start, int length) { if (current != null) { List<String> list; if (values.containsKey(current)) { list = values.get(current); } else { list = new LinkedList<>(); values.put(current, list); } list.add(String.copyValueOf(ch, start, length)); } } @Override public void endElement(String uri, String localName, String qName) { if (keys.contains(qName)) current = null; } public List<String> getValuesFor(String key) { return values.getOrDefault(key, Collections.emptyList()); } } private void consumeResponseEntity(HttpResponse response) { try { if (response != null) { EntityUtils.consume(response.getEntity()); } } catch (IOException ignored) { } } }