/* * LNISoapServlet.java * * Version: $Revision: 3705 $ * * Date: $Date: 2009-04-11 17:02:24 +0000 (Sat, 11 Apr 2009) $ * * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts * Institute of Technology. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the Hewlett-Packard Company nor the name of the * Massachusetts Institute of Technology nor the names of their * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.app.dav; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.sql.SQLException; import javax.servlet.GenericServlet; import javax.servlet.ServletConfig; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.axis.MessageContext; import org.apache.axis.transport.http.AxisServlet; import org.apache.log4j.Logger; import org.dspace.authenticate.AuthenticationManager; import org.dspace.authenticate.AuthenticationMethod; import org.dspace.authorize.AuthorizeException; import org.dspace.core.Context; import org.dspace.eperson.EPerson; import org.jdom.Document; import org.jdom.output.Format; import org.jdom.output.XMLOutputter; /** * Servlet implementing SOAP services of DSpace Lightweight Network Interface * <P> * This is implemented as a subclass of the AxisServlet that processes SOAP * requests, so it can pick out the requests it handles and pass on the rest to * the Axis Engine. * <p> * Note that it also handles WebDAV GET and PUT requests, so the SOAP client can * use the same URL as a SOAP endpoint and WebDAV resource root. * * @author Larry Stone * @version $Revision: 3705 $ */ public class LNISoapServlet extends AxisServlet { /** log4j category. */ private static Logger log = Logger.getLogger(LNISoapServlet.class); /** The output pretty. */ private static XMLOutputter outputPretty = new XMLOutputter(Format .getPrettyFormat()); // state of this transaction /** The request. */ private HttpServletRequest request = null; /** The response. */ private HttpServletResponse response = null; // last servlet instance when put into service, set by init() /** The servlet instance. */ private static GenericServlet servletInstance = null; /* (non-Javadoc) * @see javax.servlet.GenericServlet#init(javax.servlet.ServletConfig) */ @Override public void init(ServletConfig sc) throws ServletException { super.init(sc); servletInstance = this; } /** * Gets the servlet instance. * * @return the servlet instance */ public static GenericServlet getServletInstance() { return servletInstance; } /** * Pass a GET request directly to the WebDAV implementation. It handles * authentication. * * @param request the request * @param response the response * * @throws ServletException the servlet exception * @throws IOException Signals that an I/O exception has occurred. */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DAVServlet.serviceInternal("GET", request, response); } /** * Pass a PUT request directly to the WebDAV implementation. It handles * authentication. * * @param request the request * @param response the response * * @throws ServletException the servlet exception * @throws IOException Signals that an I/O exception has occurred. */ @Override public void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { DAVServlet.serviceInternal("PUT", request, response); } /** * Authenticate and return the filled-in DSpace context This is the prologue * to all calls. * * @return the context * * @throws SQLException the SQL exception * @throws IOException Signals that an I/O exception has occurred. */ private Context prologue() throws SQLException, IOException { MessageContext mc = MessageContext.getCurrentContext(); String username = null, password = null; if (mc.getUsername() != null) { username = DAVServlet.URLDecode(mc.getUsername()); } if (mc.getPassword() != null) { password = DAVServlet.URLDecode(mc.getPassword()); } /*********************************************************************** * ** XXX TEMPORARY *** Instrumentation to explore the guts of Axis at * runtime, *** leave this commented-out. java.util.Iterator pi = * mc.getPropertyNames(); while (pi.hasNext()) log.debug("SOAP: request * has property named \""+((String)pi.next())+"\""); log.debug("SOAP: * getSOAPActionURI = \""+mc.getSOAPActionURI()+"\""); log.debug("SOAP: * property(servletEndpointContext) = = * \""+mc.getProperty("servletEndpointContext").toString()+"\""); * log.debug("SOAP: property(realpath) = = * \""+mc.getProperty("realpath").toString()+"\""); log.debug("SOAP: * property(transport.http.servletLocation) = = * \""+mc.getProperty("transport.http.servletLocation").toString()+"\""); * *** end TEMPORARY INSTRUMENTATION **********************************************************************/ this.request = (HttpServletRequest) mc .getProperty("transport.http.servletRequest"); this.response = (HttpServletResponse) mc .getProperty("transport.http.servletResponse"); Context context = new Context(); // try cookie shortcut if (DAVServlet.getAuthFromCookie(context, this.request)) { DAVServlet.putAuthCookie(context, this.request, this.response, false); log.debug("SOAP service " + this.getClass().getName() + " authenticated with cookie."); return context; } int status = AuthenticationManager.authenticate(context, username, password, null, this.request); if (status == AuthenticationMethod.SUCCESS) { EPerson cu = context.getCurrentUser(); log.debug("SOAP service " + this.getClass().getName() + " authenticated as " + cu.getEmail() + " (" + cu.getFirstName() + " " + cu.getLastName() + ")"); DAVServlet.putAuthCookie(context, this.request, this.response, true); return context; } else if (status == AuthenticationMethod.BAD_CREDENTIALS) { context.abort(); throw new LNIRemoteException( "Authentication failed: Bad Credentials."); } else if (status == AuthenticationMethod.CERT_REQUIRED) { context.abort(); throw new LNIRemoteException( "Authentication failed: This user may only login with X.509 certificate."); } else if (status == AuthenticationMethod.NO_SUCH_USER) { context.abort(); throw new LNIRemoteException("Authentication failed: No such user."); } else { context.abort(); /** AuthenticationMethod.BAD_ARGS and etc * */ throw new LNIRemoteException( "Authentication failed: Cannot authenticate."); } } /** * Propfind. * * @param uri the uri * @param doc the doc * @param depth the depth * @param types the types * * @return the string * * @throws LNIRemoteException the LNI remote exception */ public String propfind(String uri, String doc, int depth, String types) throws LNIRemoteException { // break up path into elements. if (uri.startsWith("/")) { uri = uri.substring(1); } String pathElt[] = uri.split("/"); Context context = null; try { context = prologue(); // return properties only for resources of these types, comma-sep // list String aTypes[] = (types == null) ? null : types.split(","); int typeMask = DAVResource.typesToMask(aTypes); DAVResource resource = DAVResource.findResource(context, null, null, pathElt); if (resource == null) { throw new LNIRemoteException("Resource not found."); } else { Document outdoc = resource.propfindDriver(depth, new ByteArrayInputStream(doc.getBytes()), typeMask); if (outdoc == null) { // this should never happen, it should throw an error // before returning null throw new LNIRemoteException( "propfind failed, no document returned."); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); outputPretty.output(outdoc, baos); context.complete(); return baos.toString(); } } catch (IOException ie) { throw new LNIRemoteException("Exception executing PROPFIND", ie); } catch (SQLException e) { throw new LNIRemoteException("Failure accessing database", e); } catch (DAVStatusException e) { throw new LNIRemoteException("PROPFIND request failed: " + e.getStatusLine()); } catch (AuthorizeException e) { throw new LNIRemoteException( "You are not authorized for the requested operation.", e); } finally { if (context != null && context.isValid()) { context.abort(); } } } /** * Proppatch. * * @param uri the uri * @param doc the doc * * @return the string * * @throws LNIRemoteException the LNI remote exception */ public String proppatch(String uri, String doc) throws LNIRemoteException { // break up path into elements. if (uri.startsWith("/")) { uri = uri.substring(1); } String pathElt[] = uri.split("/"); Context context = null; try { context = prologue(); DAVResource resource = DAVResource.findResource(context, null, null, pathElt); if (resource == null) { throw new LNIRemoteException("Resource not found."); } else { Document outdoc = resource .proppatchDriver(new ByteArrayInputStream(doc .getBytes())); if (outdoc == null) { // this should never happen, it should throw an error // before returning null throw new LNIRemoteException( "proppatch failed, no document returned."); } ByteArrayOutputStream baos = new ByteArrayOutputStream(); outputPretty.output(outdoc, baos); context.complete(); return baos.toString(); } } catch (IOException ie) { throw new LNIRemoteException("Exception executing PROPPATCH", ie); } catch (SQLException e) { throw new LNIRemoteException("Failure accessing database", e); } catch (DAVStatusException e) { throw new LNIRemoteException("PROPPATCH request failed: " + e.getStatusLine()); } catch (AuthorizeException e) { throw new LNIRemoteException( "You are not authorized for the requested operation.", e); } finally { if (context != null && context.isValid()) { context.abort(); } } } /** The lookup path elt. */ private static String lookupPathElt[] = { "lookup", "handle" }; /** * Return "absolute" DAV URI for the given handle (and optional bitstream * persistent identifier). Always returns a valid URI; if resource is not * found it throws an exception. * * @param handle the handle * @param bitstreamPid the bitstream pid * * @return the string * * @throws LNIRemoteException the LNI remote exception */ public String lookup(String handle, String bitstreamPid) throws LNIRemoteException { Context context = null; try { context = prologue(); // trim leading scheme if any: if (handle.startsWith("hdl:")) { handle = handle.substring(4); } DAVLookup resource = new DAVLookup(context, this.request, this.response, lookupPathElt); String result = resource.makeURI(handle, bitstreamPid); if (result == null) { throw new LNIRemoteException("Resource not found."); } context.complete(); return result; } catch (IOException ie) { throw new LNIRemoteException("Exception executing LOOKUP", ie); } catch (SQLException e) { throw new LNIRemoteException("Failure accessing database", e); } finally { if (context != null && context.isValid()) { context.abort(); } } } /** * Copy. * * @param source the source * @param destination the destination * @param depth the depth * @param overwrite the overwrite * @param keepProperties the keep properties * * @return the int * * @throws LNIRemoteException the LNI remote exception */ public int copy(String source, String destination, int depth, boolean overwrite, boolean keepProperties) throws LNIRemoteException { // break up path into elements. if (source.startsWith("/")) { source = source.substring(1); } String pathElt[] = source.split("/"); Context context = null; try { context = prologue(); DAVResource resource = DAVResource.findResource(context, null, null, pathElt); if (resource == null) { throw new LNIRemoteException("Resource not found."); } int status = resource.copyDriver(destination, depth, overwrite, keepProperties); context.complete(); return status; } catch (IOException ie) { throw new LNIRemoteException("IOException while executing COPY", ie); } catch (SQLException e) { throw new LNIRemoteException("Failure accessing database", e); } catch (DAVStatusException e) { throw new LNIRemoteException("COPY request failed: " + e.getStatusLine()); } catch (AuthorizeException e) { throw new LNIRemoteException( "You are not authorized for the requested operation.", e); } finally { if (context != null && context.isValid()) { context.abort(); } } } }