Java tutorial
/** * Copyright 2008 The University of North Carolina at Chapel Hill * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package edu.unc.lib.dl.fedora; import static edu.unc.lib.dl.util.ContentModelHelper.Administrative_PID.REPOSITORY; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.io.StringWriter; import java.math.BigInteger; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.apache.commons.httpclient.HttpClient; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager; import org.apache.commons.httpclient.UsernamePasswordCredentials; import org.apache.commons.httpclient.methods.GetMethod; import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource; import org.apache.commons.httpclient.methods.multipart.FilePart; import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity; import org.apache.commons.httpclient.methods.multipart.Part; import org.apache.commons.httpclient.params.HttpMethodParams; import org.apache.commons.io.FileUtils; import org.jdom2.Document; import org.jdom2.JDOMException; import org.jdom2.input.SAXBuilder; import org.jdom2.output.XMLOutputter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.oxm.jaxb.Jaxb2Marshaller; import org.springframework.ws.WebServiceMessage; import org.springframework.ws.client.WebServiceFaultException; import org.springframework.ws.client.WebServiceIOException; import org.springframework.ws.client.core.WebServiceMessageCallback; import org.springframework.ws.client.core.WebServiceTemplate; import org.springframework.ws.soap.SoapMessage; import org.springframework.ws.soap.client.SoapFaultClientException; import org.springframework.ws.soap.saaj.SaajSoapMessageFactory; import org.springframework.ws.transport.http.CommonsHttpMessageSender; import org.xml.sax.SAXException; import edu.unc.lib.dl.acl.util.AccessGroupSet; import edu.unc.lib.dl.acl.util.GroupsThreadStore; import edu.unc.lib.dl.fedora.AuthorizationException.AuthorizationErrorType; import edu.unc.lib.dl.fedora.types.AddDatastream; import edu.unc.lib.dl.fedora.types.AddDatastreamResponse; import edu.unc.lib.dl.fedora.types.AddRelationship; import edu.unc.lib.dl.fedora.types.AddRelationshipResponse; import edu.unc.lib.dl.fedora.types.ArrayOfString; import edu.unc.lib.dl.fedora.types.Datastream; import edu.unc.lib.dl.fedora.types.Export; import edu.unc.lib.dl.fedora.types.ExportResponse; import edu.unc.lib.dl.fedora.types.GetDatastream; import edu.unc.lib.dl.fedora.types.GetDatastreamResponse; import edu.unc.lib.dl.fedora.types.GetNextPID; import edu.unc.lib.dl.fedora.types.GetNextPIDResponse; import edu.unc.lib.dl.fedora.types.GetObjectXML; import edu.unc.lib.dl.fedora.types.GetObjectXMLResponse; import edu.unc.lib.dl.fedora.types.Ingest; import edu.unc.lib.dl.fedora.types.IngestResponse; import edu.unc.lib.dl.fedora.types.MIMETypedStream; import edu.unc.lib.dl.fedora.types.ModifyDatastreamByReference; import edu.unc.lib.dl.fedora.types.ModifyDatastreamByReferenceResponse; import edu.unc.lib.dl.fedora.types.ModifyDatastreamByValue; import edu.unc.lib.dl.fedora.types.ModifyDatastreamByValueResponse; import edu.unc.lib.dl.fedora.types.ModifyObject; import edu.unc.lib.dl.fedora.types.ModifyObjectResponse; import edu.unc.lib.dl.fedora.types.ObjectProfile; import edu.unc.lib.dl.fedora.types.PurgeDatastream; import edu.unc.lib.dl.fedora.types.PurgeDatastreamResponse; import edu.unc.lib.dl.fedora.types.PurgeObject; import edu.unc.lib.dl.fedora.types.PurgeObjectResponse; import edu.unc.lib.dl.fedora.types.PurgeRelationship; import edu.unc.lib.dl.fedora.types.PurgeRelationshipResponse; import edu.unc.lib.dl.fedora.types.SetDatastreamVersionable; import edu.unc.lib.dl.fedora.types.SetDatastreamVersionableResponse; import edu.unc.lib.dl.httpclient.HttpClientUtil; import edu.unc.lib.dl.util.ContentModelHelper; import edu.unc.lib.dl.util.IllegalRepositoryStateException; import edu.unc.lib.dl.util.PIDLocker; import edu.unc.lib.dl.util.PremisEventLogger; import edu.unc.lib.dl.util.TripleStoreQueryService; public class ManagementClient extends WebServiceTemplate { private AccessClient accessClient = null; private TripleStoreQueryService tripleStoreQueryService; private MultiThreadedHttpConnectionManager httpManager; private HttpClient httpClient; // ENUMS private enum Action { addDatastream("addDatastream"), getDatastream("getDatastream"), addRelationship("addRelationship"), export( "export"), getNextPID("getNextPID"), ingest( "ingest"), modifyDatastreamByValue("modifyDatastreamByValue"), modifyDatastreamByReference( "modifyDatastreamByReference"), modifyObject("modifyObject"), purgeDatastream( "purgeDatastream"), purgeObject("purgeObject"), purgeRelationship( "purgeRelationship"), getObjectXML( "getObjectXML"), setDatastreamVersionable( "setDatastreamVersionable"); String uri = null; Action(String action) { uri = "http://www.fedora.info/definitions/1/0/api/#" + action; } WebServiceMessageCallback callback() { return new WebServiceMessageCallback() { @Override public void doWithMessage(WebServiceMessage message) { ((SoapMessage) message).setSoapAction(uri); } }; } } public enum ChecksumType { DEFAULT("DEFAULT"), DISABLED("DISABLED"), HAVAL("HAVAL"), MD5("MD5"), SHA_1("SHA-1"), SHA_256( "SHA-256"), SHA_385("SHA-385"), SHA_512("SHA-512"), TIGER("TIGER"), WHIRLPOOL("WHIRLPOOL"); private final String id; ChecksumType(String id) { this.id = id; } @Override public String toString() { return this.id; } } public enum Context { ARCHIVE("archive"), MIGRATE("migrate"), PUBLIC("public"); private final String id; Context(String id) { this.id = id; } @Override public String toString() { return this.id; } } public enum Format { ATOM_1_0("ATOM-1.0"), FOXML_1_0("FOXML-1.0"), FOXML_1_1("FOXML-1.1"), METS_FED_EXT_1_1("METSFedoraExt-1.1"); private final String id; Format(String id) { this.id = "info:fedora/fedora-system:" + id; } @Override public String toString() { return this.id; } } public enum State { ACTIVE("A"), INACTIVE("I"), DELETED("D"); private final String id; State(String id) { this.id = id; } @Override public String toString() { return this.id; } } private static final Logger log = LoggerFactory.getLogger(ManagementClient.class); private String fedoraContextUrl; private String password; private String username; @Autowired(required = false) private PIDLocker pidWriteLock; public String addManagedDatastream(PID pid, String dsid, boolean force, String message, List<String> altids, String label, boolean versionable, String mimetype, String locationURI) throws FedoraException { AddDatastream req = new AddDatastream(); req.setPid(pid.getPid()); req.setDsID(dsid); req.setLogMessage(message); req.setDsState(State.ACTIVE.id); req.setControlGroup("M"); req.setDsLocation(locationURI); // req.setChecksum("none"); req.setChecksumType(ChecksumType.MD5.id); ArrayOfString alts = new ArrayOfString(); alts.getItem().addAll(altids); req.setAltIDs(alts); req.setDsLabel(label); req.setFormatURI(""); req.setMIMEType(mimetype); req.setVersionable(versionable); AddDatastreamResponse resp = (AddDatastreamResponse) this.callService(req, Action.addDatastream); String id = resp.getDatastreamID(); // String timestamp = this.modifyInlineXMLDatastream(pid, dsid, // force, message, altids, label, // xml); return id; } public String addInlineXMLDatastream(PID pid, String dsid, boolean force, String message, List<String> altids, String label, boolean versionable, Document xml) throws FedoraException { File file = ClientUtils.writeXMLToTempFile(xml); return addInlineXMLDatastream(pid, dsid, force, message, altids, label, versionable, file); } public String addInlineXMLDatastream(PID pid, String dsid, boolean force, String message, List<String> altids, String label, boolean versionable, File contentFile) throws FedoraException { String location = null; location = this.upload(contentFile); contentFile.delete(); AddDatastream req = new AddDatastream(); req.setPid(pid.getPid()); req.setDsID(dsid); req.setLogMessage(message); req.setDsState(State.ACTIVE.id); req.setControlGroup(ContentModelHelper.ControlGroup.INTERNAL.getAttributeValue()); req.setDsLocation(location); req.setChecksumType(ChecksumType.MD5.id); ArrayOfString alts = new ArrayOfString(); alts.getItem().addAll(altids); req.setAltIDs(alts); req.setDsLabel(label); req.setFormatURI(""); req.setMIMEType("text/xml"); req.setVersionable(versionable); AddDatastreamResponse resp = (AddDatastreamResponse) this.callService(req, Action.addDatastream); String id = resp.getDatastreamID(); return id; } public boolean addLiteralStatement(PID pid, String relationship, String literal, String datatype) throws FedoraException { AddRelationship req = new AddRelationship(); req.setPid(pid.getPid()); req.setDatatype(datatype); req.setIsLiteral(true); req.setRelationship(relationship); req.setObject(literal); AddRelationshipResponse resp = (AddRelationshipResponse) this.callService(req, Action.addRelationship); return resp.isAdded(); } /** * Selectively turn versioning on or off for selected datastream. When versioning is disabled, subsequent * modifications to the datastream replace the current datastream contents and no versioning history is preserved. To * put it another way: No new datastream versions will be made, but all the existing versions will be retained. All * changes to the datastream will be to the current version. * * @param pid * The PID of the object. * @param dsid * The datastream ID. * @param versionable * Enable versioning of the datastream. * @param message * A log message. * @return timestamp of the current version * @throws FedoraException */ public String setDatastreamVersionable(PID pid, String dsid, boolean versionable, String message) throws FedoraException { SetDatastreamVersionable req = new SetDatastreamVersionable(); req.setPid(pid.getPid()); req.setDsID(dsid); req.setVersionable(versionable); req.setLogMessage(message); SetDatastreamVersionableResponse resp = (SetDatastreamVersionableResponse) this.callService(req, Action.setDatastreamVersionable); return resp.getModifiedDate(); } public boolean addObjectRelationship(PID pid, String relationship, PID pid2) throws FedoraException { AddRelationship req = new AddRelationship(); req.setPid(pid.getURI()); req.setIsLiteral(false); req.setRelationship(relationship); req.setObject(pid2.getURI()); AddRelationshipResponse resp = (AddRelationshipResponse) this.callService(req, Action.addRelationship); return resp.isAdded(); } public Datastream getDatastream(PID pid, String dsID) throws FedoraException { GetDatastream req = new GetDatastream(); req.setPid(pid.getPid()); req.setDsID(dsID); req.setAsOfDateTime(""); GetDatastreamResponse resp = (GetDatastreamResponse) this.callService(req, Action.getDatastream); return resp.getDatastream(); } public Datastream getDatastream(PID pid, String dsID, String asOfDateTime) throws FedoraException { GetDatastream req = new GetDatastream(); req.setPid(pid.getPid()); req.setDsID(dsID); req.setAsOfDateTime(asOfDateTime); GetDatastreamResponse resp = (GetDatastreamResponse) this.callService(req, Action.getDatastream); return resp.getDatastream(); } public boolean addResourceStatement(PID pid, String relationship, String uri) throws FedoraException { try { AddRelationship req = new AddRelationship(); req.setPid(pid.getURI()); // req.setDatatype(); req.setIsLiteral(false); req.setRelationship(relationship); req.setObject(uri); AddRelationshipResponse resp = (AddRelationshipResponse) this.callService(req, Action.addRelationship); return resp.isAdded(); } catch (SoapFaultClientException e) { log.debug("Soap exception", e); throw new NotFoundException(e); } } private Object callService(Object request, Action action) throws FedoraException { return callService(request, action, true); } private Object callService(Object request, Action action, boolean retry) throws FedoraException { Object response = null; try { response = this.marshalSendAndReceive(request, action.callback()); } catch (WebServiceIOException e) { // Connection reset: Apache restarted during a call to ingest (at midnight) // 503: an error indicating that Apache is already restarting if (e.getMessage() != null && (e.getMessage().contains("503") || e.getMessage().contains("Connection reset"))) { throw new FedoraTimeoutException(e); } else if (java.net.SocketTimeoutException.class.isInstance(e.getCause())) { throw new FedoraTimeoutException(e); } else { throw new ServiceException(e); } } catch (SoapFaultClientException e) { log.debug("GOT SoapFaultClientException", e); try { FedoraFaultMessageResolver.resolveFault(e); } catch (AuthorizationException ae) { if (retry && AuthorizationErrorType.NOT_APPLICABLE.equals(ae.getType())) { log.warn("Authorization was not applicable, attempting to reestablish connection to Fedora."); try { this.initializeConnections(); } catch (Exception e1) { log.error("Failed to reestablish connection to Fedora", e); throw ae; } return callService(request, action, false); } throw ae; } } catch (WebServiceFaultException e) { throw new ServiceException("Failed to call service", e); } return response; } public Document export(Context context, Format format, String pid) throws FedoraException { byte[] data = this.exportRaw(context, format, pid); Document result = null; try { result = ClientUtils.parseXML(data); } catch (SAXException e) { throw new ServiceException("Could not parse reply.", e); } return result; } public byte[] exportRaw(Context context, Format format, String pid) throws FedoraException { Export o = new Export(); o.setContext(context.id); o.setFormat(format.id); o.setPid(pid); ExportResponse response = (ExportResponse) this.callService(o, Action.export); return response.getObjectXML(); } public String getFedoraContextUrl() { return fedoraContextUrl; } public List<PID> getNextPID(int number, String namespace) throws FedoraException { List<PID> result = new ArrayList<PID>(); GetNextPID req = new GetNextPID(); req.setNumPIDs(BigInteger.valueOf(number)); if (namespace != null) { req.setPidNamespace(namespace); } GetNextPIDResponse resp = (GetNextPIDResponse) this.callService(req, Action.getNextPID); List<String> pids = resp.getPid(); for (String pid : pids) { result.add(new PID(pid)); } return result; } public Document getObjectXML(PID pid) throws FedoraException { GetObjectXML req = new GetObjectXML(); req.setPid(pid.getPid()); GetObjectXMLResponse resp = (GetObjectXMLResponse) this.callService(req, Action.getObjectXML); try { return ClientUtils.parseXML(resp.getObjectXML()); } catch (SAXException e) { throw new FedoraException("Fedora Object XML could not be parsed"); } } public String getPassword() { return password; } public String getUsername() { return username; } public PID ingest(Document xml, Format format, String message) throws FedoraException { return ingestRaw(ClientUtils.serializeXML(xml), format, message); } public PID ingestRaw(byte[] xmlData, Format format, String message) throws FedoraException { Ingest ingest = new Ingest(); ingest.setFormat(format.id); ingest.setLogMessage(message); ingest.setObjectXML(xmlData); IngestResponse response = (IngestResponse) this.callService(ingest, Action.ingest); PID result = new PID(response.getObjectPID()); return result; } public void init() throws Exception { this.httpManager = new MultiThreadedHttpConnectionManager(); this.httpManager.getParams().setConnectionTimeout(5000); initializeConnections(); } private void initializeConnections() throws Exception { SaajSoapMessageFactory msgFactory = new SaajSoapMessageFactory(); msgFactory.afterPropertiesSet(); this.setMessageFactory(msgFactory); Jaxb2Marshaller marshaller = new Jaxb2Marshaller(); marshaller.setContextPath("edu.unc.lib.dl.fedora.types"); marshaller.afterPropertiesSet(); this.setMarshaller(marshaller); this.setUnmarshaller(marshaller); CommonsHttpMessageSender messageSender = new CommonsHttpMessageSender(); UsernamePasswordCredentials creds = new UsernamePasswordCredentials(this.getUsername(), this.getPassword()); messageSender.setCredentials(creds); messageSender.setReadTimeout(300 * 1000); messageSender.afterPropertiesSet(); this.setMessageSender(messageSender); // this.setFaultMessageResolver(new FedoraFaultMessageResolver()); this.setDefaultUri(this.getFedoraContextUrl() + "/services/management"); this.afterPropertiesSet(); this.httpClient = HttpClientUtil.getAuthenticatedClient(this.getFedoraContextUrl(), this.getUsername(), this.getPassword(), this.httpManager); } public void destroy() { if (this.httpManager != null) this.httpManager.shutdown(); } // DEPENDENCY SETTERS AND GETTERS public String modifyDatastreamByValue(PID pid, String dsid, boolean force, String message, List<String> altids, String label, String mimetype, String checksum, ChecksumType checksumType, File contentFile) throws FedoraException { byte[] contentBytes; try { contentBytes = FileUtils.readFileToByteArray(contentFile); return modifyDatastreamByValue(pid, dsid, force, message, altids, label, mimetype, checksum, checksumType, contentBytes); } catch (IOException e) { log.error("Could not read the new content file", e); } return null; } public String modifyDatastreamByValue(PID pid, String dsid, boolean force, String message, List<String> altids, String label, String mimetype, String checksum, ChecksumType checksumType, byte[] content) throws FedoraException { // TODO: add checksum calculation ModifyDatastreamByValue req = new ModifyDatastreamByValue(); req.setPid(pid.getPid()); req.setDsID(dsid); req.setForce(force); req.setLogMessage(message); ArrayOfString alts = new ArrayOfString(); if (altids != null) { alts.getItem().addAll(altids); } req.setAltIDs(alts); if (label != null) { req.setDsLabel(label); } req.setFormatURI(""); if (mimetype != null) { req.setMIMEType(mimetype); } if (checksum != null) { req.setChecksum(checksum); } // else { // req.setChecksum("none"); // } if (checksumType != null) { req.setChecksumType(checksumType.id); } else { req.setChecksumType(ChecksumType.MD5.id); } req.setDsContent(content); ModifyDatastreamByValueResponse resp = (ModifyDatastreamByValueResponse) this.callService(req, Action.modifyDatastreamByValue); return resp.getModifiedDate(); } public String modifyDatastreamByReference(PID pid, String dsid, boolean force, String message, List<String> altids, String label, String mimetype, String checksum, ChecksumType checksumType, String dsLocation) throws FedoraException { // TODO: add checksum calculation ModifyDatastreamByReference req = new ModifyDatastreamByReference(); req.setPid(pid.getPid()); req.setDsID(dsid); req.setForce(force); req.setLogMessage(message); ArrayOfString alts = new ArrayOfString(); if (altids != null) { alts.getItem().addAll(altids); } req.setAltIDs(alts); if (label != null) { req.setDsLabel(label); } req.setFormatURI(""); if (mimetype != null) { req.setMIMEType(mimetype); } if (checksum != null) { req.setChecksum(checksum); } // } else { // req.setChecksum("none"); // } if (checksumType != null) { req.setChecksumType(checksumType.id); } else { req.setChecksumType(ChecksumType.MD5.id); } req.setDsLocation(dsLocation); ModifyDatastreamByReferenceResponse resp = (ModifyDatastreamByReferenceResponse) this.callService(req, Action.modifyDatastreamByReference); return resp.getModifiedDate(); } public String modifyInlineXMLDatastream(PID pid, String dsid, boolean force, String message, List<String> altids, String label, Document content) throws FedoraException { byte[] data = ClientUtils.serializeXML(content); // String checksum = new Checksum().getChecksum(data); String timestamp = this.modifyDatastreamByValue(pid, dsid, force, message, altids, label, "text/xml", null, ChecksumType.MD5, data); return timestamp; } public String modifyObject(PID pid, String label, String ownerid, State state, String message) throws FedoraException { ModifyObject req = new ModifyObject(); req.setLabel(label); req.setLogMessage(message); req.setOwnerId(ownerid); req.setPid(pid.getPid()); req.setState(state.id); ModifyObjectResponse resp = (ModifyObjectResponse) this.callService(req, Action.modifyObject); return resp.getModifiedDate(); } public List<String> purgeDatastream(PID pid, String dsid, String message, boolean force, String start, String end) throws FedoraException { PurgeDatastream req = new PurgeDatastream(); req.setPid(pid.getPid()); req.setDsID(dsid); req.setForce(force); req.setLogMessage(message); req.setStartDT(start); req.setEndDT(end); PurgeDatastreamResponse resp = (PurgeDatastreamResponse) this.callService(req, Action.purgeDatastream); return resp.getPurgedVersionDate(); } public boolean setExclusiveLiteral(PID pid, String relationship, String literal, String datatype) throws FedoraException { List<String> rel = tripleStoreQueryService.fetchAllTriples(pid).get(relationship); if (rel != null) { if (rel.contains(literal)) { rel.remove(literal); } else { // add missing rel this.addLiteralStatement(pid, relationship, literal, datatype); } // remove any other same predicate triples for (String oldValue : rel) { this.purgeLiteralStatement(pid, relationship, oldValue, datatype); } return true; } else { // add missing rel return this.addLiteralStatement(pid, relationship, literal, datatype); } } public boolean purgeLiteralStatement(PID pid, String relationship, String literal, String datatype) throws FedoraException { PurgeRelationship req = new PurgeRelationship(); req.setPid(pid.getURI()); req.setDatatype(datatype); req.setIsLiteral(true); req.setRelationship(relationship); req.setObject(literal); PurgeRelationshipResponse resp = (PurgeRelationshipResponse) this.callService(req, Action.purgeRelationship); return resp.isPurged(); } public String purgeObject(PID pid, String message, boolean force) throws FedoraException { PurgeObject req = new PurgeObject(); req.setPid(pid.getPid()); req.setLogMessage(message); req.setForce(force); PurgeObjectResponse resp = (PurgeObjectResponse) this.callService(req, Action.purgeObject); return resp.getPurgedDate(); } public boolean purgeObjectRelationship(PID pid, String relationship, PID pid2) throws FedoraException { PurgeRelationship req = new PurgeRelationship(); req.setPid(pid.getURI()); req.setIsLiteral(false); req.setRelationship(relationship); req.setObject(pid2.getURI()); PurgeRelationshipResponse resp = (PurgeRelationshipResponse) this.callService(req, Action.purgeRelationship); return resp.isPurged(); } public void setFedoraContextUrl(String fedoraContextUrl) { this.fedoraContextUrl = fedoraContextUrl; } public void setPassword(String password) { this.password = password; } public void setUsername(String username) { this.username = username; } public String upload(File file) { return upload(file, true); } public String upload(File file, boolean retry) { String result = null; String uploadURL = this.getFedoraContextUrl() + "/upload"; PostMethod post = new PostMethod(uploadURL); post.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, false); log.debug("Uploading file with forwarded groups: " + GroupsThreadStore.getGroupString()); post.addRequestHeader(HttpClientUtil.FORWARDED_GROUPS_HEADER, GroupsThreadStore.getGroupString()); try { log.debug("Uploading to " + uploadURL); Part[] parts = { new FilePart("file", file) }; post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams())); int status = httpClient.executeMethod(post); StringWriter sw = new StringWriter(); try (InputStream in = post.getResponseBodyAsStream(); PrintWriter pw = new PrintWriter(sw)) { int b; while ((b = in.read()) != -1) { pw.write(b); } } switch (status) { case HttpStatus.SC_OK: case HttpStatus.SC_CREATED: case HttpStatus.SC_ACCEPTED: result = sw.toString().trim(); log.info("Upload complete, response=" + result); break; case HttpStatus.SC_FORBIDDEN: log.warn("Authorization to Fedora failed, attempting to reestablish connection."); try { this.initializeConnections(); return upload(file, false); } catch (Exception e) { log.error("Failed to reestablish connection to Fedora", e); } break; case HttpStatus.SC_SERVICE_UNAVAILABLE: throw new FedoraTimeoutException("Fedora service unavailable, upload failed"); default: log.warn("Upload failed, response=" + HttpStatus.getStatusText(status)); log.debug(sw.toString().trim()); break; } } catch (ServiceException ex) { throw ex; } catch (Exception ex) { throw new ServiceException(ex); } finally { post.releaseConnection(); } return result; } public String upload(String content) { return this.upload(content.getBytes(), "tmp_" + System.nanoTime()); } public String upload(Document xml) { // write the document to a byte array XMLOutputter out = new XMLOutputter(); try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { out.output(xml, baos); return this.upload(baos.toByteArray(), "md_events.xml"); } catch (IOException e) { throw new ServiceException("Unexpected error writing to byte array output stream", e); } } public String upload(byte[] bytes, String fileName) { String result = null; // construct a post request to Fedora upload service String uploadURL = this.getFedoraContextUrl() + "/upload"; PostMethod post = new PostMethod(uploadURL); post.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE, false); log.debug("Uploading XML with forwarded groups: " + GroupsThreadStore.getGroupString()); post.addRequestHeader(HttpClientUtil.FORWARDED_GROUPS_HEADER, GroupsThreadStore.getGroupString()); try { log.debug("Uploading to " + uploadURL); Part[] parts = { new FilePart("file", new ByteArrayPartSource(fileName, bytes)) }; post.setRequestEntity(new MultipartRequestEntity(parts, post.getParams())); int status = httpClient.executeMethod(post); StringWriter sw = new StringWriter(); try (InputStream in = post.getResponseBodyAsStream(); PrintWriter pw = new PrintWriter(sw)) { int b; while ((b = in.read()) != -1) { pw.write(b); } } if (status == HttpStatus.SC_OK || status == HttpStatus.SC_CREATED || status == HttpStatus.SC_ACCEPTED) { result = sw.toString().trim(); log.debug("Upload complete, response=" + result); } else { log.warn("Upload failed, response=" + HttpStatus.getStatusText(status)); log.debug(sw.toString().trim()); } } catch (Exception ex) { log.error("Upload failed due to error", ex); throw new ServiceException(ex); } finally { post.releaseConnection(); } return result; } public String getIrodsPath(String dsFedoraLocationToken) { log.debug("getting iRODS path for " + dsFedoraLocationToken); String result = null; GetMethod get = null; try { String url = getFedoraContextUrl() + "/storagelocation"; get = new GetMethod(url); get.setQueryString("pid=" + URLEncoder.encode(dsFedoraLocationToken, "utf-8")); int statusCode = httpClient.executeMethod(get); if (statusCode != HttpStatus.SC_OK) { throw new RuntimeException("CDR storage location GET method failed: " + get.getStatusLine()); } else { log.debug("CDR storage location GET method: " + get.getStatusLine()); byte[] resultBytes = get.getResponseBody(); result = new String(resultBytes, "utf-8"); result = result.trim(); } } catch (Exception e) { throw new ServiceException("Error while contacting iRODS location service with datastream location " + dsFedoraLocationToken, e); } finally { if (get != null) get.releaseConnection(); } return result; } /** * Poll Fedora until the PID is found or timeout. This method will blocking until at most the specified timeout plus * the timeout of the underlying HTTP connection. * * @param pid * the PID to look for * @param delay * the delay between Fedora requests in seconds * @param timeout * the total polling time in seconds * @return true when the object is found within timeout, false on timeout */ public boolean pollForObject(PID pid, int delay, int timeout) throws InterruptedException { long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < timeout * 1000) { if (Thread.interrupted()) throw new InterruptedException(); try { ObjectProfile doc = this.getAccessClient().getObjectProfile(pid, null); if (doc != null) return true; } catch (ServiceException e) { if (log.isDebugEnabled()) log.debug("Expected service exception while polling fedora", e); } catch (FedoraException e) { // fedora responded, but object not found log.debug("got exception from fedora", e); } if (Thread.interrupted()) throw new InterruptedException(); log.info(pid + " not found, waiting " + delay + " seconds.."); Thread.sleep(delay * 1000); } return false; } /** * Returns true if the repository is available for connections * * @return */ public boolean isRepositoryAvailable() { try { ObjectProfile doc = this.getAccessClient().getObjectProfile(REPOSITORY.getPID(), null); return doc != null; } catch (Exception e) { // If an exception occurs, then the repository is not reachable } return false; } public String writePremisEventsToFedoraObject(PremisEventLogger eventLogger, PID pid) throws FedoraException { Document dom = null; try { pidWriteLock.lock(pid); MIMETypedStream mts = this.getAccessClient().getDatastreamDissemination(pid, "MD_EVENTS", null); ByteArrayInputStream bais = new ByteArrayInputStream(mts.getStream()); try { dom = new SAXBuilder().build(bais); bais.close(); } catch (JDOMException e) { throw new IllegalRepositoryStateException("Cannot parse MD_EVENTS: " + pid, e); } catch (IOException e) { throw new Error(e); } eventLogger.appendLogEvents(pid, dom.getRootElement()); String eventsLoc = this.upload(dom); String logTimestamp = this.modifyDatastreamByReference(pid, "MD_EVENTS", false, "adding PREMIS events", new ArrayList<String>(), "PREMIS Events", "text/xml", null, null, eventsLoc); return logTimestamp; } finally { pidWriteLock.unlock(pid); } } public void writePremisEventsToFedoraObject(final PremisEventLogger eventLogger, final Collection<PID> pids) { final AccessGroupSet groups = GroupsThreadStore.getGroups(); Runnable premisRunnable = new Runnable() { @Override public void run() { try { GroupsThreadStore.storeGroups(groups); for (PID pid : pids) { try { writePremisEventsToFedoraObject(eventLogger, pid); } catch (FedoraException e) { log.error("Failed to update premis for {}", pid, e); } } } finally { GroupsThreadStore.clearGroups(); } } }; Thread thread = new Thread(premisRunnable); thread.start(); } // @Override // public Object sendAndReceive(String uriString, WebServiceMessageCallback requestCallback, // WebServiceMessageExtractor responseExtractor) { // Assert.notNull(responseExtractor, "'responseExtractor' must not be null"); // Assert.hasLength(uriString, "'uri' must not be empty"); // TransportContext previousTransportContext = TransportContextHolder.getTransportContext(); // WebServiceConnection connection = null; // try { // connection = createConnection(URI.create(uriString)); // if(connection instanceof CommonsHttpConnection) { // CommonsHttpConnection commonsConn = (CommonsHttpConnection)connection; // commonsConn.getPostMethod().addRequestHeader("myGroupsHeader", "someshibbolethgroups"); // } // TransportContextHolder.setTransportContext(new DefaultTransportContext(connection)); // MessageContext messageContext = new DefaultMessageContext(getMessageFactory()); // // return doSendAndReceive(messageContext, connection, requestCallback, responseExtractor); // } catch (TransportException ex) { // throw new WebServiceTransportException("Could not use transport: " + ex.getMessage(), ex); // } catch (IOException ex) { // throw new WebServiceIOException("I/O error: " + ex.getMessage(), ex); // } finally { // TransportUtils.closeConnection(connection); // TransportContextHolder.setTransportContext(previousTransportContext); // } // } public void setPidWriteLock(PIDLocker pidLock) { this.pidWriteLock = pidLock; } /** * @param accessClient * the accessClient to set */ public void setAccessClient(AccessClient accessClient) { this.accessClient = accessClient; } /** * @return the accessClient */ public AccessClient getAccessClient() { return accessClient; } public void setTripleStoreQueryService(TripleStoreQueryService tripleStoreQueryService) { this.tripleStoreQueryService = tripleStoreQueryService; } }