Java tutorial
/********************************************************************************** * $URL: https://source.sakaiproject.org/svn/rwiki/trunk/rwiki-tool/tool/src/java/uk/ac/cam/caret/sakai/rwiki/tool/ModelMigrationContextListener.java $ * $Id: ModelMigrationContextListener.java 20354 2007-01-17 10:30:57Z ian@caret.cam.ac.uk $ *********************************************************************************** * * Copyright (c) 2003, 2004, 2005, 2006, 2007 University of Cambridge. * * Licensed under the Educational Community License, Version 1.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.opensource.org/licenses/ecl1.php * * 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 org.sakaiproject.content.chh.dspace; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.util.ArrayList; import java.util.Iterator; import java.util.List; import java.util.Collection; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.sakaiproject.content.api.ContentCollection; import org.sakaiproject.content.api.ContentCollectionEdit; import org.sakaiproject.content.api.ContentEntity; import org.sakaiproject.content.api.ContentHostingHandler; import org.sakaiproject.content.api.ContentHostingHandlerResolver; import org.sakaiproject.content.api.ContentResource; import org.sakaiproject.content.api.ContentResourceEdit; import org.sakaiproject.content.chh.file.ContentEntityFileSystem; import org.sakaiproject.entity.api.Edit; import org.sakaiproject.entity.api.ResourcePropertiesEdit; import org.sakaiproject.exception.ServerOverloadException; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; /* * import org.dspace.app.dav.client.LNISoapServletServiceLocator; import * org.dspace.app.dav.client.LNISoapServlet; import * org.dspace.app.dav.client.LNIClientUtils; */ /** * Provides a read/write view of a DSpace repository through virtual content * hosting. CH collection IDs are the DSpace handles of the * communities/collections. CH resource IDs are the DSpace handles of the items. * Bitstreams are not needed to proxy DSpace through CH. In all cases, "handle" * excludes the "hdl:" prefix and the body is name-mangled so the '/' appears as * an underscore. * * @author johnf */ public class ContentHostingHandlerImplDSpace implements ContentHostingHandler { private static final String HANDLER_NAME = "DSpaceHandler"; private static final Log log = LogFactory.getLog(ContentHostingHandlerImplDSpace.class); public final static String XML_NODE_NAME = "mountpoint"; public final static String XML_ATTRIBUTE_ENDPOINT = "endpoint"; public final static String XML_ATTRIBUTE_BASE = "baseHandle"; public final static String XML_ATTRIBUTE_SEARCHABLE = "searchable"; protected static final String nameProp = "<propfind xmlns=\"DAV:\"><prop><displayname /></prop></propfind>"; protected static final String allProp = "<propfind xmlns=\"DAV:\"><allprop /></propfind>"; public static final boolean DSRESOURCES_AS_COLLECTIONS = true; /** * Queries DSpace for all objects inside the specified handle to the * specified depth. All DSpace item types are returned and all properties * are retrieved. * * @param handle * the dspace handle to be queried (list of handles previxed by dso_ * and separated by forward slashes) * @param depth * the depth of the dspace community/collection/resource tree to * explore */ private static final Document getDSpaceProps(String endpoint, String handle, int depth) { try { StringBuffer requestXMLbuf = buildDSpaceXMLRequestForAllProps("dso_" + handle.replaceAll("/", "%24"), depth); StringBuffer responseXMLbuf = hitDSpaceWithRequest(endpoint, requestXMLbuf); return extractAllPropsResponseFromSOAP(responseXMLbuf); } catch (Exception e) { log.warn("Error in CHH DSpace mechanism [" + e.toString() + "]"); return null; // invalid DSpace endpoint / URL / comms failure / // incomprehensible reply header } } private static final StringBuffer buildDSpaceXMLRequestForAllProps(String urlEncodedHandle, int depth) { StringBuffer requestXMLbuf = new StringBuffer(1024); requestXMLbuf.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); requestXMLbuf.append( "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\">"); requestXMLbuf.append("<soapenv:Body>"); requestXMLbuf.append( "<ns1:propfind soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xmlns:ns1=\"http://dspace.org/xmlns/lni\">"); requestXMLbuf.append("<uri xsi:type=\"xsd:string\">" + urlEncodedHandle + "</uri>"); requestXMLbuf.append( "<doc xsi:type=\"xsd:string\"><propfind xmlns="DAV:"><allprop /></propfind></doc>"); requestXMLbuf.append("<depth href=\"#id0\"/>"); requestXMLbuf.append("<types xsi:type=\"xsd:string\" xsi:nil=\"true\"/>"); requestXMLbuf.append("</ns1:propfind>"); requestXMLbuf.append( "<multiRef id=\"id0\" soapenc:root=\"0\" soapenv:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" xsi:type=\"xsd:int\" xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\">" + depth + "</multiRef>"); requestXMLbuf.append("</soapenv:Body>"); requestXMLbuf.append("</soapenv:Envelope>"); return requestXMLbuf; } private final static char[] base64chars = new char[64]; static { int i = 0; for (; i < 26; ++i) base64chars[i] = (char) (65 + i); // A-Z for (; i < 52; ++i) base64chars[i] = (char) (97 + i - 26); // a-z for (; i < 62; ++i) base64chars[i] = (char) (48 + i - 52); // 0-9 base64chars[62] = 43; // + base64chars[63] = 47; // / } private static final String base64Encode(String s) { // I don't like the sun.misc Base64Encoder because it isn't officially supported. // return new sun.misc.BASE64Encoder().encode(s.getBytes()); byte[] b = s.getBytes(); int l = b.length; // Base64 is a rate 80% code so x2 is long enough for StringBuffer to not have to resize StringBuffer result = new StringBuffer(2 * l); int p = 0; while (p < l) { byte a0 = b[p]; byte a1 = p < l - 1 ? b[p + 1] : 0; byte a2 = p < l - 2 ? b[p + 2] : 0; byte o0 = (byte) (a0 >>> 2); byte o1 = (byte) ((a0 & 0x03) << 4 | (a1 >>> 4)); byte o2 = (byte) ((a1 & 0x0F) << 2 | (a2 >>> 6)); byte o3 = (byte) (a2 & 0x3F); result.append(base64chars[o0]); result.append(base64chars[o1]); result.append(p < l - 1 ? base64chars[o2] : '='); result.append(p < l - 2 ? base64chars[o3] : '='); p += 3; } return result.toString(); } private static final StringBuffer hitDSpaceWithRequest(String endpoint, StringBuffer requestXMLbuf) throws IOException { URL url = new URL(endpoint); HttpURLConnection huc = (HttpURLConnection) url.openConnection(); huc.setRequestMethod("GET"); huc.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); huc.setRequestProperty("Accept", "application/soap+xml, application/dime, multipart/related, text/*"); huc.setRequestProperty("User-Agent", "Axis/1.3"); // huc.setRequestProperty("Host","localhost:8081"); huc.setRequestProperty("Cache-Control", "no-cache"); huc.setRequestProperty("Pragma", "no-cache"); huc.setRequestProperty("SOAPAction", ""); huc.setRequestProperty("Content-Length", "" + requestXMLbuf.length()); huc.setRequestProperty("Authorization", "Basic " + base64Encode(endpoint.substring(endpoint.indexOf("http://") + 7, endpoint.indexOf("@", endpoint.indexOf("http://") + 7)))); huc.setDoInput(true); huc.setDoOutput(true); huc.connect(); huc.getOutputStream().write(requestXMLbuf.toString().getBytes()); huc.getOutputStream().flush(); huc.getContent(); StringBuffer buf = new StringBuffer(1024); InputStream is = huc.getInputStream(); for (;;) { byte b[] = new byte[256]; int l = is.read(b); if (l > 0) buf.append(new String(b, 0, l)); else break; } return buf; } private static final Document extractAllPropsResponseFromSOAP(StringBuffer xml) throws ParserConfigurationException, SAXException, IOException { DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); if (db == null) return null; Document d = db.parse(new ByteArrayInputStream(xml.toString().getBytes())); if (d == null) return null; // the useful content is burried within 4 layers of enclosing XML junk NodeList nl = d.getChildNodes(); for (int i = 0; i < 4; ++i) for (int j = 0; (nl != null) && (j < nl.getLength()); ++j) if (nl.item(j).getNodeName() != null && !nl.item(j).getNodeName().equals("")) { nl = nl.item(j).getChildNodes(); break; } if (nl == null) return null; // did not find the expected 4 layers of // wrapper crap String x = nl.item(0).getNodeValue(); if (x == null) return null; x.replaceAll("<", "<"); x.replaceAll(">", ">"); x.replaceAll(""", "\""); return db.parse(new ByteArrayInputStream(x.getBytes())); } /* ---------------------------------------------------- */ private ContentHostingHandlerResolver contentHostingHandlerResolver = null; /** * @return the contentHandlerResover */ public ContentHostingHandlerResolver getContentHostingHandlerResolver() { return contentHostingHandlerResolver; } /** * @param contentHandlerResover * the contentHandlerResover to set */ public void setContentHostingHandlerResolver(ContentHostingHandlerResolver contentHostingHandlerResolver) { this.contentHostingHandlerResolver = contentHostingHandlerResolver; } /* ---------------------------------------------------- */ public void cancel(ContentCollectionEdit edit) { /* no work required -- no temporary changes to reverse */ } public void cancel(ContentResourceEdit edit) { /* no work required -- no temporary changes to reverse */ } public void commit(ContentCollectionEdit edit) { ContentCollectionDSpace ccds = null; if (edit instanceof ContentCollectionDSpace) ccds = (ContentCollectionDSpace) edit; else { ContentEntity tmp = edit.getVirtualContentEntity(); if (tmp instanceof ContentCollectionDSpace) ccds = (ContentCollectionDSpace) tmp; else if (tmp instanceof ContentResourceDSpace) ccds = ((ContentResourceDSpace) tmp).convertToCollection(); } if (ccds == null) return; // can't do anything if the resource isn't a // dspace resource! ContentEntityDSpace encloser = resolveDSpace(ccds.realParent, ccds.endpoint, ccds.basehandle, ccds.parentRelativePath, ccds.chh, ccds.searchable); if (encloser == null || encloser.dii == null || encloser.dii.handle == null) { log.warn( "Content Hosting Handler DSpace was unable to save the contents of a collection because the enclosing collection was not found."); return; // parent collection has been erased } // encloser.dii.handle is the parent collection handle to which we // http/dav PUT this resource try { if (encloser.dii.endpoint != null && encloser.dii.handle != null) { String puturl = ccds.dii.endpoint; puturl = puturl.substring(0, puturl.lastIndexOf("/")); puturl = puturl + "/dso_" + encloser.dii.handle.replace("/", "%24") + "?mkcol=true"; URL url = new URL(puturl); HttpURLConnection huc = (HttpURLConnection) url.openConnection(); huc.setRequestMethod("PUT"); huc.setRequestProperty("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"); huc.setRequestProperty("User-Agent", "Axis/1.3"); huc.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); huc.setRequestProperty("Authorization", "Basic am9obmYlNDBjYXJldC5jYW0uYWMudWs6cGFzc3dvcmQ="); huc.setDoOutput(true); huc.connect(); huc.getOutputStream().write(("<mkcol>" + ccds.dii.displayname + "</mkcol>").getBytes()); huc.getOutputStream().flush(); int httpResponseCode = huc.getResponseCode(); String handleInsideJunk = huc.getHeaderField("Location"); if (handleInsideJunk == null) return; ccds.dii.handle = handleInsideJunk .substring(5/* skip the /dso_ prefix */ + handleInsideJunk.lastIndexOf("/")) .replace("%24", "/"); } } catch (IOException e) { log.warn( "Content Hosting Handler DSpace was unable to save the contents of a collection because DSpace refused the operation.", e); return; // file system error -- operation cannot be performed } catch (SecurityException e) { log.warn( "Content Hosting Handler DSpace was unable to save the contents of a collection because the JVM SecurityManager refused the operation.", e); return; // permissions error -- operation cannot be performed } checkForUnmountRequest(edit); } public void commit(ContentResourceEdit edit) { ContentResourceDSpace crds = null; if (edit instanceof ContentResourceDSpace) crds = (ContentResourceDSpace) edit; else { ContentEntity tmp = edit.getVirtualContentEntity(); if (tmp instanceof ContentResourceDSpace) crds = (ContentResourceDSpace) tmp; } if (crds == null) return; // can't do anything if the resource isn't a // dspace resource! ContentEntityDSpace encloser = resolveDSpace(crds.realParent, crds.endpoint, crds.basehandle, crds.parentRelativePath, crds.chh, crds.searchable); if (encloser == null || encloser.dii == null || encloser.dii.handle == null) { log.warn( "Content Hosting Handler DSpace was unable to save the contents of a resource because the enclosing collection was not found."); return; // parent collection has been erased } // encloser.dii.handle is the parent collection handle to which we // http/dav PUT this resource InputStream is = null; OutputStream os = null; try { is = edit.streamContent(); if (is == null) return; // abort the commit if we can't stream it // through byte b[] = new byte[1024]; String puturl = crds.dii.endpoint; puturl = puturl.substring(0, puturl.lastIndexOf("/")); puturl = puturl + "/dso_" + encloser.dii.handle.replace("/", "%24") + "?package=PDF"; URL url = new URL(puturl); HttpURLConnection huc = (HttpURLConnection) url.openConnection(); huc.setRequestMethod("PUT"); huc.setRequestProperty("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"); huc.setRequestProperty("User-Agent", "Axis/1.3"); huc.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); huc.setRequestProperty("Content-Length", "" + is.available()); huc.setRequestProperty("Authorization", "Basic am9obmYlNDBjYXJldC5jYW0uYWMudWs6cGFzc3dvcmQ="); huc.setDoInput(true); huc.setDoOutput(true); huc.connect(); os = huc.getOutputStream(); while (is.available() > 0) { int l = is.read(b); if (l > 0) os.write(b, 0, l); else break; } os.flush(); is.close(); huc.getResponseCode(); String handleInsideJunk = huc.getHeaderField("Location"); if (handleInsideJunk == null) return; crds.dii.handle = handleInsideJunk .substring(5/* skip the /dso_ prefix */ + handleInsideJunk.lastIndexOf("/")) .replace("%24", "/"); } catch (IOException e) { log.warn( "Content Hosting Handler DSpace was unable to save the contents of a resource because DSpace refused the operation.", e); return; // file system error -- operation cannot be performed } catch (SecurityException e) { log.warn( "Content Hosting Handler DSpace was unable to save the contents of a resource because the JVM SecurityManager refused the operation.", e); return; // permissions error -- operation cannot be performed } catch (ServerOverloadException e) { log.warn( "Content Hosting Handler DSpace was unable to save the contents of a resource because the server threw a ServerOverloadException and was unable to stream the resource contents.", e); return; // sakai failed to deliver the contents of the file; saving // it is obviously impossible } finally { if (is != null) try { is.close(); } catch (IOException e) { } if (os != null) try { os.flush(); os.close(); } catch (IOException e) { } } checkForUnmountRequest(edit); } protected void checkForUnmountRequest(Edit edit) { /* Check to see if the resource being saved is the root of the virtual world. * If it is, save any changes to the mount point property to the real parent instead * of to the root virtual object. In particular, this allows mount points to be * unmounted! */ ContentEntityDSpace ceds = (ContentEntityDSpace) ((ContentEntity) edit).getVirtualContentEntity(); if (ceds.relativePath.equals("/")) { // take out an edit object on the real parent... ContentResourceEdit cre = this.contentHostingHandlerResolver.editResource(ceds.realParent.getId()); // ...take out an edit object on its properties... ResourcePropertiesEdit rpe = cre.getPropertiesEdit(); // ...set the CHH Bean property from the virtual object... String prop = edit.getPropertiesEdit().getProperty(ContentHostingHandlerResolver.CHH_BEAN_NAME); if (prop == null || prop.equals("")) rpe.removeProperty(ContentHostingHandlerResolver.CHH_BEAN_NAME); else rpe.addProperty(ContentHostingHandlerResolver.CHH_BEAN_NAME, prop); // ...and save it back again to the storage. try { this.contentHostingHandlerResolver.commitResource(cre); } catch (Exception e) { log.warn( "Content Hosting Handler DSpace was unable to save Sakai properties on the real parent of the virtual mountpoint: " + e.toString()); } } } public void commitDeleted(ContentResourceEdit edit, String uuid) { /* * No need to do anything -- removeResource/removeCollection does the * work */ } public List getCollections(ContentCollection collection) { ContentEntity cc = collection.getVirtualContentEntity(); if (!(cc instanceof ContentCollectionDSpace)) { return null; // this is not the correct handler for this resource // -- serious problems! } ContentCollectionDSpace ccds = (ContentCollectionDSpace) cc; List l = ccds.getMembers(); ArrayList<Edit> collections = new ArrayList<Edit>(l.size()); for (Iterator i = l.listIterator(); i.hasNext();) { String id = (String) i.next(); ContentEntityDSpace ceds = resolveDSpace(ccds.realParent, ccds.endpoint, ccds.basehandle, id.substring(ccds.realParent.getId().length() + 1), this, ccds.searchable); if (ceds instanceof ContentCollectionDSpace) collections.add(ceds.wrap()); } return collections; } public List getFlatResources(ContentEntity ce) { System.out.println("getFlatResources"); return null; } public byte[] getResourceBody(ContentResource resource) throws ServerOverloadException { if (!(resource instanceof ContentResourceDSpace)) return null; ContentResourceDSpace crds = (ContentResourceDSpace) resource; return crds.getContent(); } public List getResources(ContentCollection collection) { ContentEntity cc = collection.getVirtualContentEntity(); if (!(cc instanceof ContentCollectionDSpace)) { return null; // this is not the correct handler for this resource // -- serious problems! } ContentCollectionDSpace ccds = (ContentCollectionDSpace) cc; List l = ccds.getMemberResources(); return l; } /* ---------------------------------------------------- */ /* * This is the format of the XML we expect to get back from DSpace-LNI... * <?xml version="1.0" encoding="UTF-8"?> <multistatus xmlns="DAV:"> * <response> <href>/dso_123456789%241</href> <propstat> <prop> * <displayname>Test 1</displayname> <resourcetype> <collection /> * </resourcetype> <dspace:type * xmlns:dspace="http://www.dspace.org/xmlns/dspace"> <dspace:community /> * </dspace:type> <current-user-privilege-set> <privilege> <all /> * </privilege> </current-user-privilege-set> <dspace:short_description * xmlns:dspace="http://www.dspace.org/xmlns/dspace">This is the first test * community</dspace:short_description> <dspace:introductory_text * xmlns:dspace="http://www.dspace.org/xmlns/dspace"><b>woot!</b></dspace:introductory_text> * <dspace:side_bar_text * xmlns:dspace="http://www.dspace.org/xmlns/dspace"><i>fubar</i></dspace:side_bar_text> * <dspace:copyright_text * xmlns:dspace="http://www.dspace.org/xmlns/dspace">legal blurb goes here</dspace:copyright_text> * <dspace:handle * xmlns:dspace="http://www.dspace.org/xmlns/dspace">hdl:123456789/1</dspace:handle> * </prop> <status>HTTP/1.1 200 OK</status> </propstat> <propstat> <prop> * <dspace:logo xmlns:dspace="http://www.dspace.org/xmlns/dspace" /> </prop> * <status>HTTP/1.1 404 Not found</status> </propstat> </response> more of * these: <response>...</response> </multistatus> */ private final String LAYER1 = "multistatus"; private final String LAYER2 = "response"; private final String HREF = "href"; private final String LAYER3 = "propstat"; private final String LAYER4 = "prop"; private final String DISPNM = "displayname"; private final String LSTMOD = "getlastmodified"; private final String CNTLEN = "getcontentlength"; private final String CNTTYP = "getcontenttype"; private final String STATUS = "status"; private final String ST_OK = "HTTP/1.1 200 OK"; private final String DSTYPE = "dspace:type"; private final String DSWDRN = "dspace:withdrawn"; private DSpaceItemInfo queryDSpaceFor(String endpoint, String base, String needle) { try { /* PARSE XML RESPONSE... */ // This is a really horrible tree-walk but it is, at least, NOT // vulnerable to the whitespace in the XML being reformatted. // After *much* debate, we decided that this is no worse than other // ways of doing this! Document d = getDSpaceProps(endpoint, base, 1); if (d == null) return null; // find multistatus node Node node_multistatus = null; NodeList nl = d.getChildNodes(); for (int j = 0; j < nl.getLength(); ++j) if (nl.item(j).getNodeName() != null && nl.item(j).getNodeName().equals(LAYER1)) { node_multistatus = nl.item(j); break; } if (node_multistatus == null) return null; boolean foundMatch = false; // haven't found the named node so far DSpaceItemInfo dii = new DSpaceItemInfo(); // the return value // (unless we return // null) dii.displayname = needle; // will only be returned if we find the // name sought dii.endpoint = endpoint; // examine each response node, looking for ones which match the // string sought nl = node_multistatus.getChildNodes(); for (int j = 0; j < nl.getLength(); ++j) { // only interested in nodes with name="response" if (nl.item(j).getNodeName() == null || !nl.item(j).getNodeName().equals(LAYER2)) continue; Node node_response = nl.item(j); NodeList resources = node_response.getChildNodes(); // grab the resource handle String handle = null; for (int k = 0; k < resources.getLength(); ++k) if (resources.item(k).getNodeName() != null && resources.item(k).getNodeName().equals(HREF)) { handle = resources.item(k).getFirstChild().getNodeValue().trim(); break; } if (handle == null) continue; // skip this resource if it // doesn't have an 'href' node. // Communities and collections have a /-imploded handle // hierarchy. // The comm/coll's own handle is the last one. String[] handles = handle.split("/"); handle = handles[handles.length - 1]; // only interested in // the last one if (!handle.startsWith("bitstream_")) // this only affects // reading bitstreams // when queryDSpaceFor // is called for a // resource dii.handle = handle.replace("%24", "/").substring(4); else { if (handles.length > 1) dii.handle = handles[handles.length - 2].replace("%24", "/").substring(4); else continue; // a handle containing only "bitstream_x" is // not valid try { int indx; String numberPart = handle.substring("bitstream_".length()); if ((indx = numberPart.indexOf(".")) != -1) numberPart = numberPart.substring(0, indx); dii.bitstreamID = Integer.parseInt(numberPart); } catch (NumberFormatException e) { } } // now loop over the child nodes again looking at propstat // blocks... for (int k = 0; k < resources.getLength(); ++k) { if (resources.item(k).getNodeName() == null || !resources.item(k).getNodeName().equals(LAYER3)) continue; // only // want // 'propstat' // nodes NodeList propstats = resources.item(k).getChildNodes(); // first check whether status is OK (ignore all HTTP status // codes except 200) // no status node is assumed to mean OK. abort this propstat // if status is not OK. boolean status_ok = true; for (int l = 0; l < propstats.getLength(); ++l) if (propstats.item(l).getNodeName() != null && propstats.item(l).getNodeName().equals(STATUS) && !propstats.item(l).getFirstChild().getNodeValue().trim().equals(ST_OK)) { status_ok = false; break; } if (!status_ok) continue; // skip this propstat node // Look for child nodes with name 'prop', having nested // child called 'displayname'. // This is only complicated when the base handle is a DSpace // resource. There will // be child nodes (with handle=bitstream_<n>) and the same // display name as the resource. // We need to extract properties from the bitstream_1 child // (.._2 is the license). for (int l = 0; l < propstats.getLength(); ++l) { if (propstats.item(l).getNodeName() != null && propstats.item(l).getNodeName().equals(LAYER4)) { boolean recordFromThisChild = false; String lastmod = null, contentlength = null, contenttype = null, dstype = null; NodeList properties = propstats.item(l).getChildNodes(); for (int m = 0; m < properties.getLength(); ++m) if (properties.item(m).getNodeName() != null) { if (properties.item(m).getNodeName().equals(DISPNM) && properties.item(m) .getFirstChild().getNodeValue().trim().equals(needle)) { foundMatch = true; recordFromThisChild = true; } if (properties.item(m).getNodeName().equals(LSTMOD)) lastmod = properties.item(m).getFirstChild().getNodeValue().trim(); if (properties.item(m).getNodeName().equals(CNTLEN)) contentlength = properties.item(m).getFirstChild().getNodeValue().trim(); if (properties.item(m).getNodeName().equals(CNTTYP)) contenttype = properties.item(m).getFirstChild().getNodeValue().trim(); if (properties.item(m).getNodeName().equals(DSTYPE)) { Node n = properties.item(m).getFirstChild(); while (n != null) { String s = n.getNodeName(); if (!s.equals("#text")) { dstype = s; break; } n = n.getNextSibling(); } } } if (recordFromThisChild) { if (lastmod != null) dii.lastmodified = lastmod; if (contentlength != null) try { dii.contentLength = Long.parseLong(contentlength); } catch (NumberFormatException nfe) { } if (contenttype != null) dii.contentType = contenttype; if (dstype != null) dii.itemType = dstype; if (DSRESOURCES_AS_COLLECTIONS) return dii; } } } } } if (foundMatch) return dii; } catch (Exception e) { log.warn("Error in CHH DSpace mechanism: parse error: [" + e.toString() + "]"); } return null; // problem talking to DSpace or named resource has gone } protected ContentEntityDSpace resolveDSpace(ContentEntity realParent, String endpoint, String basehandle, String relativePath, ContentHostingHandlerImplDSpace chh, boolean searchable) { // return an item (resource) or a community/collection (collection) as // appropriate while (relativePath.length() > 0 && relativePath.charAt(0) == '/') relativePath = relativePath.substring(1); while (relativePath.length() > 0 && relativePath.charAt(relativePath.length() - 1) == '/') relativePath = relativePath.substring(0, relativePath.length() - 1); String[] items = relativePath.split("/"); DSpaceItemInfo dii = new DSpaceItemInfo(); dii.handle = basehandle; if (relativePath.equals("")) { dii.displayname = ""; dii.itemType = "dspace:community"; } else for (int i = 0; i < items.length; ++i) { dii = queryDSpaceFor(endpoint, dii.handle, items[i]); // walk // the // hierarchy if (dii == null || dii.handle == null) { // the resource is a new resource which has not yet been // commited, // or it has been removed from dspace // we construct a new ContentEntityDSpaceResource to model // the non-existent object dii = new DSpaceItemInfo(); // handle is left NULL // (important!) dii.endpoint = endpoint; dii.displayname = items[i]; ContentEntityDSpace ceds = new ContentResourceDSpace(realParent, endpoint, basehandle, relativePath, chh, contentHostingHandlerResolver, dii, searchable); ceds.wrap(); return ceds; // was return null; } } relativePath = "/" + relativePath; if (dii.isCollection()) { ContentEntityDSpace ceds = new ContentCollectionDSpace(realParent, endpoint, basehandle, relativePath, chh, contentHostingHandlerResolver, dii, searchable); ceds.wrap(); return ceds; } else { ContentEntityDSpace ceds = new ContentResourceDSpace(realParent, endpoint, basehandle, relativePath, chh, contentHostingHandlerResolver, dii, searchable); ceds.wrap(); return ceds; } } /** * getVirtualContentEntity is the entry point for the virtual entity * resolution mechanism. This parses the XML of the real parent to locate * the parameters required to talk to a DSpace instance. * * @see org.sakaiproject.content.api.ContentHostingHandler#getVirtualContentEntity(org.sakaiproject.content.api.ContentEntity, * java.lang.String) */ public ContentEntity getVirtualContentEntity(ContentEntity edit, String finalId) { // Algorithm: get the mount point from the XML file represented by // 'edit' (the real parent) // construct a new ContentEntityDSpace and return it try { boolean searchable = false; byte[] xml = ((ContentResource) edit).getContent(); if (xml == null) return null; DocumentBuilder db = DocumentBuilderFactory.newInstance().newDocumentBuilder(); if (db == null) return null; Document d = db.parse(new ByteArrayInputStream(xml)); if (d == null) return null; Node node_mountpoint = null; NodeList nl = d.getChildNodes(); for (int j = 0; j < nl.getLength(); ++j) if (nl.item(j).getNodeName() != null && nl.item(j).getNodeName().equals(XML_NODE_NAME)) { node_mountpoint = nl.item(j); break; } if (node_mountpoint == null) return null; Node node_endpoint = node_mountpoint.getAttributes().getNamedItem(XML_ATTRIBUTE_ENDPOINT); if (node_endpoint == null) return null; final String endpoint = node_endpoint.getNodeValue(); if (endpoint == null || endpoint.equals("")) return null; // invalid // mountpoint // specification Node node_basehandle = node_mountpoint.getAttributes().getNamedItem(XML_ATTRIBUTE_BASE); if (node_basehandle == null) return null; final String basehandle = node_basehandle.getNodeValue(); if (basehandle == null || basehandle.equals("")) return null; // invalid // mountpoint // specification Node node_searchable = node_mountpoint.getAttributes().getNamedItem(XML_ATTRIBUTE_SEARCHABLE); if (node_searchable != null) searchable = Boolean.parseBoolean(node_searchable.getNodeValue()); String relativePath = finalId.substring(edit.getId().length()); ContentEntityDSpace ceds = resolveDSpace(edit, endpoint, basehandle, relativePath, this, searchable); Edit ce = ceds.wrap(); if (ce == null) return null; // happens when the requested URL // requires a log on but the user is // not logged on return (ContentEntity) ce; } catch (Exception e) { log.warn("Content Hosting Handler DSpace: Invalid XML for the mountpoint [" + edit.getId() + "], exception was " + e.toString()); return (ContentEntity) (resolveDSpace(edit, "", "", "", this, false).wrap()); } } protected List listDSpaceItemsIn(String endpoint, String base) { List<String> resultList = new ArrayList<String>(); try { Document d = getDSpaceProps(endpoint, base, 1); if (d == null) return null; // find multistatus node Node node_multistatus = null; NodeList nl = d.getChildNodes(); for (int j = 0; j < nl.getLength(); ++j) if (nl.item(j).getNodeName() != null && nl.item(j).getNodeName().equals(LAYER1)) { node_multistatus = nl.item(j); break; } if (node_multistatus == null) return null; // examine each response node, looking for ones which match the // string sought nl = node_multistatus.getChildNodes(); for (int j = 0; j < nl.getLength(); ++j) { // only interested in nodes with name="response" if (nl.item(j).getNodeName() == null || !nl.item(j).getNodeName().equals(LAYER2)) continue; Node node_response = nl.item(j); NodeList resources = node_response.getChildNodes(); // grab the resource handle String handle = null; for (int k = 0; k < resources.getLength(); ++k) if (resources.item(k).getNodeName() != null && resources.item(k).getNodeName().equals(HREF)) { handle = resources.item(k).getFirstChild().getNodeValue().trim(); break; } if (handle == null) continue; // skip this resource if it // doesn't have an 'href' node. String[] handles = handle.split("/"); handle = handles[handles.length - 1]; // only interested in // the last one handle = handle.replace("%24", "/").substring(4); if (isWithdrawn(resources)) return new ArrayList(); // if // the // base // is // withdrawn, // ignore // all // children if (handle.equals(base)) continue; // response includes the // object queried itself -- // omit this from the output // list // loop over the child nodes looking at propstat blocks... for (int k = 0; k < resources.getLength(); ++k) { if (resources.item(k).getNodeName() == null || !resources.item(k).getNodeName().equals(LAYER3)) continue; // only // want // 'propstat' // nodes NodeList propstats = resources.item(k).getChildNodes(); // first check whether status is OK (ignore all HTTP status // codes except 200) // no status node is assumed to mean OK. abort this propstat // if status is not OK. boolean status_ok = true; for (int l = 0; l < propstats.getLength(); ++l) if (propstats.item(l).getNodeName() != null && propstats.item(l).getNodeName().equals(STATUS) && !propstats.item(l).getFirstChild().getNodeValue().trim().equals(ST_OK)) { status_ok = false; break; } if (!status_ok) continue; // skip this propstat node // look for child nodes with name 'prop', having nested // child called 'displayname' // if the displayname is the 'needle' being sought, return // handle. for (int l = 0; l < propstats.getLength(); ++l) { if (propstats.item(l).getNodeName() != null && propstats.item(l).getNodeName().equals(LAYER4)) { NodeList properties = propstats.item(l).getChildNodes(); for (int m = 0; m < properties.getLength(); ++m) if (properties.item(m).getNodeName() != null && properties.item(m).getNodeName().equals(DISPNM)) resultList.add(properties.item(m).getFirstChild().getNodeValue().trim()); } } } } } catch (Exception e) { log.warn("Error in CHH DSpace mechanism: parse error: [" + e.toString() + "]"); return new ArrayList(); } return resultList; // problem talking to DSpace or named resource has // gone } private boolean isWithdrawn(NodeList responsechilds) { for (int k = 0; k < responsechilds.getLength(); ++k) { if (responsechilds.item(k).getNodeName() == null || !responsechilds.item(k).getNodeName().equals(LAYER3)) continue; // only // want // 'propstat' // nodes NodeList propstatchilds = responsechilds.item(k).getChildNodes(); for (int l = 0; l < propstatchilds.getLength(); ++l) { if (propstatchilds.item(l).getNodeName() == null || !propstatchilds.item(l).getNodeName().equals(LAYER4)) continue; // only want 'prop' nodes NodeList propchilds = propstatchilds.item(l).getChildNodes(); for (int m = 0; m < propchilds.getLength(); ++m) { if (propchilds.item(m).getNodeName() != null && propchilds.item(m).getNodeName().equals(DSWDRN)) { NodeList vals = propchilds.item(m).getChildNodes(); for (int n = 0; n < vals.getLength(); ++n) { if (vals.item(n).getNodeValue() != null && vals.item(n).getNodeValue().contains("true")) return true; } } } } } return false; } public void removeCollection(ContentCollectionEdit edit) { ContentCollectionDSpace ccds = null; if (edit instanceof ContentCollectionDSpace) ccds = (ContentCollectionDSpace) edit; else { ContentEntity tmp = edit.getVirtualContentEntity(); if (tmp instanceof ContentCollectionDSpace) ccds = (ContentCollectionDSpace) tmp; } if (ccds == null) return; // can't do anything if the resource isn't a // dspace resource! try { byte b[] = new byte[1024]; String puturl = ccds.dii.endpoint; puturl = puturl.substring(0, puturl.lastIndexOf("/")); puturl = puturl + "/dso_" + ccds.dii.handle.replace("/", "%24") + "?delete=true"; URL url = new URL(puturl); HttpURLConnection huc = (HttpURLConnection) url.openConnection(); huc.setRequestMethod("PUT"); huc.setRequestProperty("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"); huc.setRequestProperty("User-Agent", "Axis/1.3"); huc.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); huc.setRequestProperty("Authorization", "Basic am9obmYlNDBjYXJldC5jYW0uYWMudWs6cGFzc3dvcmQ="); huc.setDoOutput(true); huc.connect(); huc.getOutputStream().write("<delete />".getBytes()); int httpResponseCode = huc.getResponseCode(); } catch (IOException e) { log.warn( "Content Hosting Handler DSpace was unable to delete a resource because DSpace refused the operation.", e); return; // socket error -- operation cannot be performed } catch (SecurityException e) { log.warn( "Content Hosting Handler DSpace was unable to delete a resource because the JVM SecurityManager refused the operation.", e); return; // permissions error -- operation cannot be performed } } public void removeResource(ContentResourceEdit edit) { ContentResourceDSpace crds = null; if (edit instanceof ContentResourceDSpace) crds = (ContentResourceDSpace) edit; else { ContentEntity tmp = edit.getVirtualContentEntity(); if (tmp instanceof ContentResourceDSpace) crds = (ContentResourceDSpace) tmp; } if (crds == null) return; // can't do anything if the resource isn't a // dspace resource! try { byte b[] = new byte[1024]; String puturl = crds.dii.endpoint; puturl = puturl.substring(0, puturl.lastIndexOf("/")); puturl = puturl + "/dso_" + crds.dii.handle.replace("/", "%24") + "?delete=true"; URL url = new URL(puturl); HttpURLConnection huc = (HttpURLConnection) url.openConnection(); huc.setRequestMethod("PUT"); huc.setRequestProperty("Accept", "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"); huc.setRequestProperty("User-Agent", "Axis/1.3"); huc.setRequestProperty("Content-Type", "text/xml; charset=utf-8"); huc.setRequestProperty("Authorization", "Basic am9obmYlNDBjYXJldC5jYW0uYWMudWs6cGFzc3dvcmQ="); huc.setDoOutput(true); huc.connect(); huc.getOutputStream().write("<delete />".getBytes()); int httpResponseCode = huc.getResponseCode(); } catch (IOException e) { log.warn( "Content Hosting Handler DSpace was unable to delete a resource because DSpace refused the operation.", e); return; // socket error -- operation cannot be performed } catch (SecurityException e) { log.warn( "Content Hosting Handler DSpace was unable to delete a resource because the JVM SecurityManager refused the operation.", e); return; // permissions error -- operation cannot be performed } } public InputStream streamResourceBody(ContentResource resource) throws ServerOverloadException { ContentEntity ce = resource.getVirtualContentEntity(); if (!(ce instanceof ContentResourceDSpace)) return null; ContentResourceDSpace crfs = (ContentResourceDSpace) ce; return crfs.streamContent(); } public int getMemberCount(ContentEntity edit) { if (edit instanceof ContentCollectionDSpace) return ((ContentCollectionDSpace) edit).getMemberCount(); if (edit.getVirtualContentEntity() instanceof ContentCollectionDSpace) return ((ContentCollectionDSpace) (edit.getVirtualContentEntity())).getMemberCount(); return 0; } /* (non-Javadoc) * @see org.sakaiproject.content.api.ContentHostingHandler#getContentCollectionEdit(java.lang.String) */ public ContentCollectionEdit getContentCollectionEdit(String id) { ContentCollectionEdit cce = (ContentCollectionEdit) this.contentHostingHandlerResolver .newCollectionEdit(id); cce.setContentHandler(this); return cce; } /* (non-Javadoc) * @see org.sakaiproject.content.api.ContentHostingHandler#getContentResourceEdit(java.lang.String) */ public ContentResourceEdit getContentResourceEdit(String id) { ContentResourceEdit cre = (ContentResourceEdit) this.contentHostingHandlerResolver.newResourceEdit(id); cre.setContentHandler(this); return cre; } /* (non-Javadoc) * @see org.sakaiproject.content.api.ContentHostingHandler#putDeleteResource(java.lang.String, java.lang.String, java.lang.String) */ public ContentResourceEdit putDeleteResource(String id, String uuid, String userId) { ContentResourceEdit cre = (ContentResourceEdit) this.contentHostingHandlerResolver.newResourceEdit(id); cre.setContentHandler(this); return cre; } public void getUuid(String id) { } public void setResourceUuid(String resourceId, String uuid) { } public Collection<String> getMemberCollectionIds(ContentEntity ce) { return null; } public Collection<String> getMemberResourceIds(ContentEntity ce) { return null; } public String moveResource(ContentResourceEdit thisResource, String new_id) { return null; } public String moveCollection(ContentCollectionEdit thisCollection, String new_folder_id) { return null; } }