Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@
package org.apache.jackrabbit.spi2dav;

import java.lang.reflect.Constructor;
import java.util.HashSet;
import java.util.Set;

import javax.jcr.InvalidItemStateException;
import javax.jcr.ItemNotFoundException;
Expand All @@ -35,23 +37,88 @@
import org.w3c.dom.Element;

/**
* <code>ExceptionConverter</code>...
* Converter utility for mapping WebDAV protocol errors ({@link DavException}) into
* corresponding JCR API exceptions ({@link RepositoryException}).
* <p>
* This class translates WebDAV server responses back into native JCR exception types so that
* remote repository client operations maintain standard JCR API error behaviors.
* </p>
*/
public class ExceptionConverter {

// these exception classes are allowed to be instantiated
private static final Set<String> SAFE_EXCEPTION_NAMES = new HashSet<>();
static {
SAFE_EXCEPTION_NAMES.add(javax.jcr.AccessDeniedException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.InvalidItemStateException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.InvalidLifecycleTransitionException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.InvalidSerializedDataException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.ItemExistsException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.ItemNotFoundException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.LoginException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.MergeException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.NamespaceException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.NoSuchWorkspaceException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.PathNotFoundException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.ReferentialIntegrityException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.RepositoryException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.UnsupportedRepositoryOperationException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.ValueFormatException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.lock.LockException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.nodetype.ConstraintViolationException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.nodetype.InvalidNodeTypeDefinitionException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.nodetype.NoSuchNodeTypeException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.nodetype.NodeTypeExistsException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.query.InvalidQueryException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.security.AccessControlException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.version.ActivityViolationException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.version.LabelExistsVersionException.class.getName());
SAFE_EXCEPTION_NAMES.add(javax.jcr.version.VersionException.class.getName());
}

// avoid instantiation
private ExceptionConverter() {}

/**
* @param davExc the WebDAV exception to convert
* @return the mapped JCR {@link RepositoryException}
* @see #generate(DavException, HttpRequestBase)
*/
public static RepositoryException generate(DavException davExc) {
return generate(davExc, null);
}

/**
* @param davExc the WebDAV exception to convert
* @param request the HTTP request that triggered the WebDAV exception, or {@code null} if unavailable
* @return the mapped JCR {@link RepositoryException}
* @see #generate(DavException, int, String)
*/
public static RepositoryException generate(DavException davExc, HttpRequestBase request) {
String name = (request == null) ? "_undefined_" : request.getMethod();
int code = DavMethods.getMethodCode(name);
return generate(davExc, code, name);
}

/**
* Generates a JCR {@link RepositoryException} from a {@link DavException}, HTTP method code,
* and method name string.
* <p>
* The method first attempts to extract explicit exception information (class name and message)
* from the WebDAV XML error response. If the class name matches an entry in {@link #SAFE_EXCEPTION_NAMES},
* it reflectively instantiates that specific JCR exception.
* </p>
* <p>
* If no XML error details exist or the exception class is not in the allow-list, it converts
* the HTTP status code (and method context where appropriate) into a standard JCR exception type
* such as {@link ItemNotFoundException}, {@link LockException}, or {@link ConstraintViolationException}.
* </p>
*
* @param davExc the WebDAV exception to convert
* @param methodCode the numeric code of the WebDAV HTTP method (from {@link DavMethods})
* @param name the HTTP method name string (e.g., "GET", "POST", "MKCOL")
* @return the resulting JCR {@link RepositoryException}
*/
public static RepositoryException generate(DavException davExc, int methodCode, String name) {
String msg = davExc.getMessage();
if (davExc.hasErrorCondition()) {
Expand All @@ -64,18 +131,16 @@ public static RepositoryException generate(DavException davExc, int methodCode,
msg = DomUtil.getChildText(exc, "message", null);
}
if (DomUtil.hasChildElement(exc, "class", null)) {
Class<?> cl = Class.forName(DomUtil.getChildText(exc, "class", null));
Constructor<?> excConstr = cl.getConstructor(String.class);
if (excConstr != null) {
Object o = excConstr.newInstance(msg);
if (o instanceof PathNotFoundException && methodCode == DavMethods.DAV_POST) {
// see JCR-2536
return new InvalidItemStateException(msg);
} else if (o instanceof RepositoryException) {
return (RepositoryException) o;
} else if (o instanceof Exception) {
return new RepositoryException(msg, (Exception)o);
}
String className = DomUtil.getChildText(exc, "class", null);
// never Class.forName or construct on wire data:
// only the standard JCR exception types are
// reconstructed; any other class name falls
// through to the status-code based mapping below.
if (className != null && SAFE_EXCEPTION_NAMES.contains(className)) {
return getRepositoryException(methodCode, className, msg);
} else {
throw new InvalidItemStateException(
String.format("Class '%s' not in allow list for exception conversion", className));
}
}
}
Expand Down Expand Up @@ -112,7 +177,7 @@ public static RepositoryException generate(DavException davExc, int methodCode,
if (methodCode > 0 && name != null) {
return new UnsupportedRepositoryOperationException(
"Missing implementation: Method "
+ name + " could not be executed", davExc);
+ name + " could not be executed", davExc);
} else {
return new UnsupportedRepositoryOperationException(
"Missing implementation", davExc);
Expand All @@ -121,4 +186,37 @@ public static RepositoryException generate(DavException davExc, int methodCode,
return new RepositoryException(msg, davExc);
}
}

/**
* Reflectively instantiates a {@link RepositoryException} sub-class for the given class name and error message.
* <p>
* Performs a type-check to confirm that the requested class extends {@link RepositoryException}.
* Special handling is applied for {@link PathNotFoundException} when encountered during a {@code POST} request,
* translating it into an {@link InvalidItemStateException}.
* </p>
*
* @param methodCode the numeric code of the WebDAV HTTP method
* @param className the fully qualified class name of the target JCR exception
* @param msg the error detail message
* @return the reflectively created {@link RepositoryException}
* @throws ReflectiveOperationException if class loading, constructor resolution, or instantiation fails
* @throws InvalidItemStateException if the target class is not a subclass of {@link RepositoryException}
*/
private static RepositoryException getRepositoryException(int methodCode, String className, String msg) throws ReflectiveOperationException, InvalidItemStateException {
Class<?> cl = Class.forName(className);
// type check before construction
if (RepositoryException.class.isAssignableFrom(cl)) {
Constructor<?> excConstr = cl.getConstructor(String.class);
Object o = excConstr.newInstance(msg);
if (o instanceof PathNotFoundException && methodCode == DavMethods.DAV_POST) {
// see JCR-2536
return new InvalidItemStateException(msg);
} else {
return (RepositoryException) o;
}
} else {
throw new InvalidItemStateException(
String.format("Class '%s' not assignable from '%s'", RepositoryException.class.getName(), className));
}
}
}
Loading