Index: jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/JcrConstants.java =================================================================== --- jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/JcrConstants.java (revision 700037) +++ jackrabbit-jcr-commons/src/main/java/org/apache/jackrabbit/JcrConstants.java (working copy) @@ -241,6 +241,10 @@ */ public static final String MIX_VERSIONABLE = "mix:versionable"; /** + * mix:shareable + */ + public static final String MIX_SHAREABLE = "mix:shareable"; + /** * nt:base */ public static final String NT_BASE = "nt:base"; Index: jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/server/BindTest.java =================================================================== --- jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/server/BindTest.java (revision 700037) +++ jackrabbit-webdav/src/test/java/org/apache/jackrabbit/webdav/server/BindTest.java (working copy) @@ -22,6 +22,12 @@ import java.util.HashSet; import java.util.Set; import java.util.StringTokenizer; +import java.util.Arrays; +import java.util.List; +import java.util.Collection; +import java.util.Iterator; +import java.util.ArrayList; +import java.util.Collections; import junit.framework.TestCase; @@ -32,16 +38,30 @@ import org.apache.commons.httpclient.auth.AuthScope; import org.apache.commons.httpclient.methods.PutMethod; import org.apache.commons.httpclient.methods.StringRequestEntity; +import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.methods.HeadMethod; import org.apache.jackrabbit.webdav.DavException; import org.apache.jackrabbit.webdav.MultiStatus; import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.bind.BindConstants; +import org.apache.jackrabbit.webdav.bind.BindInfo; +import org.apache.jackrabbit.webdav.bind.RebindInfo; +import org.apache.jackrabbit.webdav.bind.UnbindInfo; +import org.apache.jackrabbit.webdav.bind.ParentSet; +import org.apache.jackrabbit.webdav.bind.ParentElement; import org.apache.jackrabbit.webdav.client.methods.DavMethod; import org.apache.jackrabbit.webdav.client.methods.MoveMethod; import org.apache.jackrabbit.webdav.client.methods.OptionsMethod; import org.apache.jackrabbit.webdav.client.methods.PropFindMethod; import org.apache.jackrabbit.webdav.client.methods.VersionControlMethod; +import org.apache.jackrabbit.webdav.client.methods.MkColMethod; +import org.apache.jackrabbit.webdav.client.methods.DeleteMethod; +import org.apache.jackrabbit.webdav.client.methods.DavMethodBase; +import org.apache.jackrabbit.webdav.client.methods.BindMethod; +import org.apache.jackrabbit.webdav.client.methods.RebindMethod; +import org.apache.jackrabbit.webdav.client.methods.UnbindMethod; import org.apache.jackrabbit.webdav.property.DavProperty; -import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.DavPropertyNameSet; import org.w3c.dom.Element; import org.w3c.dom.Node; @@ -59,12 +79,17 @@ public class BindTest extends TestCase { + private String root; private URI uri; private String username, password; private HttpClient client; protected void setUp() throws Exception { this.uri = URI.create(System.getProperty("webdav.test.url")); + this.root = this.uri.toASCIIString(); + if (!this.root.endsWith("/")) { + this.root += "/"; + } this.username = System.getProperty(("webdav.test.username"), ""); this.password = System.getProperty(("webdav.test.password"), ""); this.client = new HttpClient(); @@ -84,33 +109,49 @@ int status = this.client.executeMethod(options); assertEquals(200, status); Set features = getDavFeatures(options); + List allow = Arrays.asList(options.getAllowedMethods()); assertTrue("DAV header should include 'bind' feature: " + features, features.contains("bind")); + assertTrue("Allow header should include BIND method", allow.contains("BIND")); + assertTrue("Allow header should include REBIND method", allow.contains("REBIND")); + assertTrue("Allow header should include UNBIND method", allow.contains("UNBIND")); } // create test resource, make it referenceable, check resource id, move resource, check again public void testResourceId() throws HttpException, IOException, DavException, URISyntaxException { - String testuri = this.uri.toASCIIString() + (this.uri.toASCIIString().endsWith("/") ? "" : "/") + "bindtest"; - String testuri2 = this.uri.toASCIIString() + (this.uri.toASCIIString().endsWith("/") ? "" : "/") + "bindtest2"; - - PutMethod put = new PutMethod(testuri); - put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); - int status = this.client.executeMethod(put); - assertTrue(status == 200 || status == 201 || status == 204); - - // enabling version control always makes the resource referenceable - VersionControlMethod versioncontrol = new VersionControlMethod(testuri); - status = this.client.executeMethod(versioncontrol); - assertTrue(status == 200 || status == 201); - - URI resourceId = getResourceId(testuri); - - MoveMethod move = new MoveMethod(testuri, testuri2, true); - status = this.client.executeMethod(move); - String s = move.getResponseBodyAsString(); - assertTrue(status == 204); - - URI resourceId2 = getResourceId(testuri2); - assertEquals(resourceId, resourceId2); + + String testcol = this.root + "testResourceId/"; + String testuri1 = testcol + "bindtest1"; + String testuri2 = testcol + "bindtest2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + PutMethod put = new PutMethod(testuri1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + // enabling version control always makes the resource referenceable + VersionControlMethod versioncontrol = new VersionControlMethod(testuri1); + status = this.client.executeMethod(versioncontrol); + assertTrue("status: " + status, status == 200 || status == 201); + + URI resourceId = getResourceId(testuri1); + + MoveMethod move = new MoveMethod(testuri1, testuri2, true); + status = this.client.executeMethod(move); + move.getResponseBodyAsString(); + assertEquals(201, status); + + URI resourceId2 = getResourceId(testuri2); + assertEquals(resourceId, resourceId2); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } } // utility methods @@ -118,14 +159,14 @@ // see http://greenbytes.de/tech/webdav/draft-ietf-webdav-bind-20.html#rfc.section.3.1 private URI getResourceId(String uri) throws IOException, DavException, URISyntaxException { DavPropertyNameSet names = new DavPropertyNameSet(); - names.add(DavPropertyName.RESOURCEID); + names.add(BindConstants.RESOURCEID); PropFindMethod propfind = new PropFindMethod(uri, names, 0); int status = this.client.executeMethod(propfind); - assertTrue(status == 207); + assertEquals(207, status); MultiStatus multistatus = propfind.getResponseBodyAsMultiStatus(); MultiStatusResponse[] responses = multistatus.getResponses(); assertEquals(1, responses.length); - DavProperty resourceId = responses[0].getProperties(200).get(DavPropertyName.RESOURCEID); + DavProperty resourceId = responses[0].getProperties(200).get(BindConstants.RESOURCEID); assertNotNull(resourceId); assertTrue(resourceId.getValue() instanceof Element); Element href = (Element)resourceId.getValue(); @@ -134,7 +175,472 @@ URI resid = new URI(text); return resid; } - + + private DavProperty getParentSet(String uri) throws IOException, DavException, URISyntaxException { + DavPropertyNameSet names = new DavPropertyNameSet(); + names.add(BindConstants.PARENTSET); + PropFindMethod propfind = new PropFindMethod(uri, names, 0); + int status = this.client.executeMethod(propfind); + assertEquals(207, status); + MultiStatus multistatus = propfind.getResponseBodyAsMultiStatus(); + MultiStatusResponse[] responses = multistatus.getResponses(); + assertEquals(1, responses.length); + DavProperty parentset = responses[0].getProperties(200).get(BindConstants.PARENTSET); + assertNotNull(parentset); + return parentset; + } + + public void testSimpleBind() throws Exception { + String testcol = this.root + "testSimpleBind/"; + String subcol1 = testcol + "bindtest1/"; + String testres1 = subcol1 + "res1"; + String subcol2 = testcol + "bindtest2/"; + String testres2 = subcol2 + "res2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create new resource R with path bindtest1/res1 + PutMethod put = new PutMethod(testres1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + //create new binding of R with path bindtest2/res2 + DavMethodBase bind = new BindMethod(subcol2, new BindInfo(testres1, "res2")); + status = this.client.executeMethod(bind); + assertEquals(201, status); + //check if both bindings report the same DAV:resource-id + assertEquals(this.getResourceId(testres1), this.getResourceId(testres2)); + + //compare representations retrieved with both paths + GetMethod get = new GetMethod(testres1); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("foo", get.getResponseBodyAsString()); + get = new GetMethod(testres2); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("foo", get.getResponseBodyAsString()); + + //modify R using the new path + put = new PutMethod(testres2); + put.setRequestEntity(new StringRequestEntity("bar", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertTrue("status: " + status, status == 200 || status == 204); + + //compare representations retrieved with both paths + get = new GetMethod(testres1); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("bar", get.getResponseBodyAsString()); + get = new GetMethod(testres2); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("bar", get.getResponseBodyAsString()); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } + } + + public void testRebind() throws Exception { + String testcol = this.root + "testRebind/"; + String subcol1 = testcol + "bindtest1/"; + String testres1 = subcol1 + "res1"; + String subcol2 = testcol + "bindtest2/"; + String testres2 = subcol2 + "res2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create new resource R with path bindtest1/res1 + PutMethod put = new PutMethod(testres1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + // enabling version control always makes the resource referenceable + VersionControlMethod versioncontrol = new VersionControlMethod(testres1); + status = this.client.executeMethod(versioncontrol); + assertTrue("status: " + status, status == 200 || status == 201); + + URI r1 = this.getResourceId(testres1); + + GetMethod get = new GetMethod(testres1); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("foo", get.getResponseBodyAsString()); + + //rebind R with path bindtest2/res2 + DavMethodBase rebind = new RebindMethod(subcol2, new RebindInfo(testres1, "res2")); + status = this.client.executeMethod(rebind); + assertEquals(201, status); + + URI r2 = this.getResourceId(testres2); + + get = new GetMethod(testres2); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("foo", get.getResponseBodyAsString()); + + //make sure that rebind did not change the resource-id + assertEquals(r1, r2); + + //verify that the initial binding is gone + HeadMethod head = new HeadMethod(testres1); + status = this.client.executeMethod(head); + assertEquals(404, status); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } + } + + public void testBindOverwrite() throws Exception { + String testcol = this.root + "testSimpleBind/"; + String subcol1 = testcol + "bindtest1/"; + String testres1 = subcol1 + "res1"; + String subcol2 = testcol + "bindtest2/"; + String testres2 = subcol2 + "res2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create new resource R with path bindtest1/res1 + PutMethod put = new PutMethod(testres1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + //create new resource R' with path bindtest2/res2 + put = new PutMethod(testres2); + put.setRequestEntity(new StringRequestEntity("bar", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + //try to create new binding of R with path bindtest2/res2 and Overwrite:F + DavMethodBase bind = new BindMethod(subcol2, new BindInfo(testres1, "res2")); + bind.addRequestHeader(new Header("Overwrite", "F")); + status = this.client.executeMethod(bind); + assertEquals(412, status); + + //verify that bindtest2/res2 still points to R' + GetMethod get = new GetMethod(testres2); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("bar", get.getResponseBodyAsString()); + + //create new binding of R with path bindtest2/res2 + bind = new BindMethod(subcol2, new BindInfo(testres1, "res2")); + status = this.client.executeMethod(bind); + assertTrue("status: " + status, status == 200 || status == 204); + + //verify that bindtest2/res2 now points to R + get = new GetMethod(testres2); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("foo", get.getResponseBodyAsString()); + + //verify that the initial binding is still there + HeadMethod head = new HeadMethod(testres1); + status = this.client.executeMethod(head); + assertEquals(200, status); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } + } + + public void testRebindOverwrite() throws Exception { + String testcol = this.root + "testSimpleBind/"; + String subcol1 = testcol + "bindtest1/"; + String testres1 = subcol1 + "res1"; + String subcol2 = testcol + "bindtest2/"; + String testres2 = subcol2 + "res2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create new resource R with path testSimpleBind/bindtest1/res1 + PutMethod put = new PutMethod(testres1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + // enabling version control always makes the resource referenceable + VersionControlMethod versioncontrol = new VersionControlMethod(testres1); + status = this.client.executeMethod(versioncontrol); + assertTrue("status: " + status, status == 200 || status == 201); + + //create new resource R' with path testSimpleBind/bindtest2/res2 + put = new PutMethod(testres2); + put.setRequestEntity(new StringRequestEntity("bar", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + //try rebind R with path testSimpleBind/bindtest2/res2 and Overwrite:F + RebindMethod rebind = new RebindMethod(subcol2, new RebindInfo(testres1, "res2")); + rebind.addRequestHeader(new Header("Overwrite", "F")); + status = this.client.executeMethod(rebind); + assertEquals(412, status); + + //verify that testSimpleBind/bindtest2/res2 still points to R' + GetMethod get = new GetMethod(testres2); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("bar", get.getResponseBodyAsString()); + + //rebind R with path testSimpleBind/bindtest2/res2 + rebind = new RebindMethod(subcol2, new RebindInfo(testres1, "res2")); + status = this.client.executeMethod(rebind); + assertTrue("status: " + status, status == 200 || status == 204); + + //verify that testSimpleBind/bindtest2/res2 now points to R + get = new GetMethod(testres2); + status = this.client.executeMethod(get); + assertEquals(200, status); + assertEquals("foo", get.getResponseBodyAsString()); + + //verify that the initial binding is gone + HeadMethod head = new HeadMethod(testres1); + status = this.client.executeMethod(head); + assertEquals(404, status); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } + } + + public void testParentSet() throws Exception { + String testcol = this.root + "testParentSet/"; + String subcol1 = testcol + "bindtest1/"; + String testres1 = subcol1 + "res1"; + String subcol2 = testcol + "bindtest2/"; + String testres2 = subcol2 + "res2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create new resource R with path testSimpleBind/bindtest1/res1 + PutMethod put = new PutMethod(testres1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + //create new binding of R with path testSimpleBind/bindtest2/res2 + DavMethodBase bind = new BindMethod(subcol2, new BindInfo(testres1, "res2")); + status = this.client.executeMethod(bind); + assertEquals(201, status); + //check if both bindings report the same DAV:resource-id + assertEquals(this.getResourceId(testres1), this.getResourceId(testres2)); + + //verify values of parent-set properties + List hrefs1 = new ArrayList(); + List segments1 = new ArrayList(); + List hrefs2 = new ArrayList(); + List segments2 = new ArrayList(); + Object ps1 = this.getParentSet(testres1).getValue(); + Object ps2 = this.getParentSet(testres2).getValue(); + assertTrue(ps1 instanceof List); + assertTrue(ps2 instanceof List); + List plist1 = (List) ps1; + List plist2 = (List) ps2; + assertEquals(2, plist1.size()); + assertEquals(2, plist2.size()); + for (int k = 0; k < 2; k++) { + Object pObj1 = plist1.get(k); + Object pObj2 = plist2.get(k); + assertTrue(pObj1 instanceof Element); + assertTrue(pObj2 instanceof Element); + ParentElement p1 = ParentElement.createFromXml((Element) pObj1); + ParentElement p2 = ParentElement.createFromXml((Element) pObj2); + hrefs1.add(p1.getHref()); + hrefs2.add(p2.getHref()); + segments1.add(p1.getSegment()); + segments2.add(p2.getSegment()); + } + Collections.sort(hrefs1); + Collections.sort(hrefs2); + Collections.sort(segments1); + Collections.sort(segments2); + assertEquals(hrefs1, hrefs2); + assertEquals(segments1, segments2); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } + } + + public void testBindCollections() throws Exception { + String testcol = this.root + "testBindCollections/"; + String a1 = testcol + "a1/"; + String b1 = a1 + "b1/"; + String c1 = b1 + "c1/"; + String x1 = c1 + "x1"; + String a2 = testcol + "a2/"; + String b2 = a2 + "b2/"; + String c2 = b2 + "c2/"; + String x2 = c2 + "x2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(a1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(a2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create collection resource C + mkcol = new MkColMethod(b1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(c1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create plain resource R + PutMethod put = new PutMethod(x1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + //create new binding of C with path a2/b2 + DavMethodBase bind = new BindMethod(a2, new BindInfo(b1, "b2")); + status = this.client.executeMethod(bind); + assertEquals(201, status); + //check if both bindings report the same DAV:resource-id + assertEquals(this.getResourceId(b1), this.getResourceId(b2)); + + mkcol = new MkColMethod(c2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create new binding of R with path a2/b2/c2/r2 + bind = new BindMethod(c2, new BindInfo(x1, "x2")); + status = this.client.executeMethod(bind); + assertEquals(201, status); + //check if both bindings report the same DAV:resource-id + assertEquals(this.getResourceId(x1), this.getResourceId(x2)); + + //verify different path alternatives + URI rid = this.getResourceId(x1); + assertEquals(rid, this.getResourceId(x2)); + assertEquals(rid, this.getResourceId(testcol + "a2/b2/c1/x1")); + assertEquals(rid, this.getResourceId(testcol + "a1/b1/c2/x2")); + Object ps = this.getParentSet(x1).getValue(); + assertTrue(ps instanceof List); + assertEquals(2, ((List) ps).size()); + ps = this.getParentSet(x2).getValue(); + assertTrue(ps instanceof List); + assertEquals(2, ((List) ps).size()); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } + } + + public void testUnbind() throws Exception { + String testcol = this.root + "testUnbind/"; + String subcol1 = testcol + "bindtest1/"; + String testres1 = subcol1 + "res1"; + String subcol2 = testcol + "bindtest2/"; + String testres2 = subcol2 + "res2"; + int status; + try { + MkColMethod mkcol = new MkColMethod(testcol); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol1); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + mkcol = new MkColMethod(subcol2); + status = this.client.executeMethod(mkcol); + assertEquals(201, status); + + //create new resource R with path testSimpleBind/bindtest1/res1 + PutMethod put = new PutMethod(testres1); + put.setRequestEntity(new StringRequestEntity("foo", "text/plain", "UTF-8")); + status = this.client.executeMethod(put); + assertEquals(201, status); + + //create new binding of R with path testSimpleBind/bindtest2/res2 + DavMethodBase bind = new BindMethod(subcol2, new BindInfo(testres1, "res2")); + status = this.client.executeMethod(bind); + assertEquals(201, status); + //check if both bindings report the same DAV:resource-id + assertEquals(this.getResourceId(testres1), this.getResourceId(testres2)); + + //remove new path + UnbindMethod unbind = new UnbindMethod(subcol2, new UnbindInfo("res2")); + status = this.client.executeMethod(unbind); + assertTrue("status: " + status, status == 200 || status == 204); + + //verify that the new binding is gone + HeadMethod head = new HeadMethod(testres2); + status = this.client.executeMethod(head); + assertEquals(404, status); + + //verify that the initial binding is still there + head = new HeadMethod(testres1); + status = this.client.executeMethod(head); + assertEquals(200, status); + } finally { + DeleteMethod delete = new DeleteMethod(testcol); + status = this.client.executeMethod(delete); + assertTrue("status: " + status, status == 200 || status == 204); + } + } + private String getUri(Element href) { String s = ""; for (Node c = href.getFirstChild(); c != null; c = c.getNextSibling()) { Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequestImpl.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequestImpl.java (revision 700037) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequestImpl.java (working copy) @@ -45,6 +45,9 @@ import org.apache.jackrabbit.webdav.version.report.ReportInfo; import org.apache.jackrabbit.webdav.xml.DomUtil; import org.apache.jackrabbit.webdav.xml.ElementIterator; +import org.apache.jackrabbit.webdav.bind.RebindInfo; +import org.apache.jackrabbit.webdav.bind.UnbindInfo; +import org.apache.jackrabbit.webdav.bind.BindInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Document; @@ -189,6 +192,59 @@ } /** + * Parse a href and return the path of the resource. + * + * @return path of the resource identified by the href. + * @see org.apache.jackrabbit.webdav.bind.BindServletRequest#getHrefLocator + */ + public DavResourceLocator getHrefLocator(String href) throws DavException { + String ref = href; + if (ref != null) { + //href should be a Simple-ref production as defined in RFC4918, so it is either an absolute URI + //or an absoltute path + try { + URI uri = new URI(ref); + String auth = uri.getAuthority(); + ref = uri.getRawPath(); + if (auth == null) { + //verify that href is an absolute path + if (ref.startsWith("//") || !ref.startsWith("/")) { + log.warn("expected absolute path but found " + ref); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else if (!auth.equals(httpRequest.getHeader("Host"))) { + //this looks like an unsupported cross-server operation, but of course a reverse-proxy + //might have rewritten the Host header. Since we can't find out, we have to reject anyway. + //Better use absolute paths in DAV:href elements! + throw new DavException(DavServletResponse.SC_FORBIDDEN); + } + } catch (URISyntaxException e) { + log.warn("malformed uri: " + href, e); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + // cut off the context path + String contextPath = httpRequest.getContextPath(); + if (ref.startsWith(contextPath)) { + ref = ref.substring(contextPath.length()); + } else { + //absolute path has to start with contextpath + throw new DavException(DavServletResponse.SC_FORBIDDEN); + } + } + return factory.createResourceLocator(hrefPrefix, ref); + } + + /** + * Returns the path of the member resource of the request resource which is identified by the segment parameter. + * + * @return path of internal member resource. + */ + public DavResourceLocator getMemberLocator(String segment) { + String path = (this.getRequestLocator().getHref(true) + segment).substring(hrefPrefix.length()); + return factory.createResourceLocator(hrefPrefix, path); + } + + /** * Return true if the overwrite header does not inhibit overwriting. * * @return true if the overwrite header requests 'overwriting' @@ -726,6 +782,42 @@ return info; } + /** + * @see org.apache.jackrabbit.webdav.bind.BindServletRequest#getRebindInfo() + */ + public RebindInfo getRebindInfo() throws DavException { + RebindInfo info = null; + Document requestDocument = getRequestDocument(); + if (requestDocument != null) { + info = RebindInfo.createFromXml(requestDocument.getDocumentElement()); + } + return info; + } + + /** + * @see org.apache.jackrabbit.webdav.bind.BindServletRequest#getUnbindInfo() + */ + public UnbindInfo getUnbindInfo() throws DavException { + UnbindInfo info = null; + Document requestDocument = getRequestDocument(); + if (requestDocument != null) { + info = UnbindInfo.createFromXml(requestDocument.getDocumentElement()); + } + return info; + } + + /** + * @see org.apache.jackrabbit.webdav.bind.BindServletRequest#getBindInfo() + */ + public BindInfo getBindInfo() throws DavException { + BindInfo info = null; + Document requestDocument = getRequestDocument(); + if (requestDocument != null) { + info = BindInfo.createFromXml(requestDocument.getDocumentElement()); + } + return info; + } + //---------------------------------------< HttpServletRequest interface >--- public String getAuthType() { return httpRequest.getAuthType(); Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/DavPropertyName.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/DavPropertyName.java (revision 700037) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/property/DavPropertyName.java (working copy) @@ -49,9 +49,6 @@ public static final DavPropertyName SOURCE = DavPropertyName.create(PROPERTY_SOURCE); public static final DavPropertyName SUPPORTEDLOCK = DavPropertyName.create(PROPERTY_SUPPORTEDLOCK); - /* webdav properties defined by the BIND specification */ - public static final DavPropertyName RESOURCEID = DavPropertyName.create(PROPERTY_RESOURCEID); - /* property use by microsoft that are not specified in the RFC 2518 */ public static final DavPropertyName ISCOLLECTION = DavPropertyName.create("iscollection"); Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequest.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequest.java (revision 700037) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/WebdavRequest.java (working copy) @@ -20,6 +20,7 @@ import org.apache.jackrabbit.webdav.ordering.OrderingDavServletRequest; import org.apache.jackrabbit.webdav.transaction.TransactionDavServletRequest; import org.apache.jackrabbit.webdav.version.DeltaVServletRequest; +import org.apache.jackrabbit.webdav.bind.BindServletRequest; /** * The empty WebdavRequest interface collects the functionality @@ -30,5 +31,6 @@ */ public interface WebdavRequest extends DavServletRequest, ObservationDavServletRequest, OrderingDavServletRequest, - TransactionDavServletRequest, DeltaVServletRequest { + TransactionDavServletRequest, DeltaVServletRequest, + BindServletRequest { } \ No newline at end of file Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java (revision 700037) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/server/AbstractWebdavServlet.java (working copy) @@ -32,6 +32,10 @@ import org.apache.jackrabbit.webdav.WebdavRequestImpl; import org.apache.jackrabbit.webdav.WebdavResponse; import org.apache.jackrabbit.webdav.WebdavResponseImpl; +import org.apache.jackrabbit.webdav.bind.RebindInfo; +import org.apache.jackrabbit.webdav.bind.UnbindInfo; +import org.apache.jackrabbit.webdav.bind.BindableResource; +import org.apache.jackrabbit.webdav.bind.BindInfo; import org.apache.jackrabbit.webdav.io.InputContext; import org.apache.jackrabbit.webdav.io.InputContextImpl; import org.apache.jackrabbit.webdav.io.OutputContext; @@ -311,6 +315,15 @@ case DavMethods.DAV_ACL: doAcl(request, response, resource); break; + case DavMethods.DAV_REBIND: + doRebind(request, response, resource); + break; + case DavMethods.DAV_UNBIND: + doUnbind(request, response, resource); + break; + case DavMethods.DAV_BIND: + doBind(request, response, resource); + break; default: // any other method return false; @@ -575,7 +588,7 @@ } DavResource destResource = getResourceFactory().createResource(request.getDestinationLocator(), request, response); - int status = validateDestination(destResource, request); + int status = validateDestination(destResource, request, true); if (status > DavServletResponse.SC_NO_CONTENT) { response.sendError(status); return; @@ -598,7 +611,7 @@ DavResource resource) throws IOException, DavException { DavResource destResource = getResourceFactory().createResource(request.getDestinationLocator(), request, response); - int status = validateDestination(destResource, request); + int status = validateDestination(destResource, request, true); if (status > DavServletResponse.SC_NO_CONTENT) { response.sendError(status); return; @@ -609,6 +622,85 @@ } /** + * The BIND method + * + * @param request + * @param response + * @param resource the collection resource to which a new member will be added + * @throws IOException + * @throws DavException + */ + protected void doBind(WebdavRequest request, WebdavResponse response, + DavResource resource) throws IOException, DavException { + + if (!resource.exists()) { + response.sendError(DavServletResponse.SC_NOT_FOUND); + } + BindInfo bindInfo = request.getBindInfo(); + DavResource oldBinding = getResourceFactory().createResource(request.getHrefLocator(bindInfo.getHref()), request, response); + if (!(oldBinding instanceof BindableResource)) { + response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + DavResource newBinding = getResourceFactory().createResource(request.getMemberLocator(bindInfo.getSegment()), request, response); + int status = validateDestination(newBinding, request, false); + if (status > DavServletResponse.SC_NO_CONTENT) { + response.sendError(status); + return; + } + ((BindableResource) oldBinding).bind(resource, newBinding); + response.setStatus(status); + } + + /** + * The REBIND method + * + * @param request + * @param response + * @param resource the collection resource to which a new member will be added + * @throws IOException + * @throws DavException + */ + protected void doRebind(WebdavRequest request, WebdavResponse response, + DavResource resource) throws IOException, DavException { + + if (!resource.exists()) { + response.sendError(DavServletResponse.SC_NOT_FOUND); + } + RebindInfo rebindInfo = request.getRebindInfo(); + DavResource oldBinding = getResourceFactory().createResource(request.getHrefLocator(rebindInfo.getHref()), request, response); + if (!(oldBinding instanceof BindableResource)) { + response.sendError(DavServletResponse.SC_METHOD_NOT_ALLOWED); + return; + } + DavResource newBinding = getResourceFactory().createResource(request.getMemberLocator(rebindInfo.getSegment()), request, response); + int status = validateDestination(newBinding, request, false); + if (status > DavServletResponse.SC_NO_CONTENT) { + response.sendError(status); + return; + } + ((BindableResource) oldBinding).rebind(resource, newBinding); + response.setStatus(status); + } + + /** + * The UNBIND method + * + * @param request + * @param response + * @param resource the collection resource from which a member will be removed + * @throws IOException + * @throws DavException + */ + protected void doUnbind(WebdavRequest request, WebdavResponse response, + DavResource resource) throws IOException, DavException { + + UnbindInfo unbindInfo = request.getUnbindInfo(); + DavResource srcResource = getResourceFactory().createResource(request.getMemberLocator(unbindInfo.getSegment()), request, response); + resource.removeMember(srcResource); + } + + /** * Validate the given destination resource and return the proper status * code: Any return value greater/equal than {@link DavServletResponse#SC_NO_CONTENT} * indicates an error. @@ -617,12 +709,14 @@ * @param request * @return status code indicating whether the destination is valid. */ - private int validateDestination(DavResource destResource, WebdavRequest request) + private int validateDestination(DavResource destResource, WebdavRequest request, boolean checkHeader) throws DavException { - String destHeader = request.getHeader(HEADER_DESTINATION); - if (destHeader == null || "".equals(destHeader)) { - return DavServletResponse.SC_BAD_REQUEST; + if (checkHeader) { + String destHeader = request.getHeader(HEADER_DESTINATION); + if (destHeader == null || "".equals(destHeader)) { + return DavServletResponse.SC_BAD_REQUEST; + } } if (destResource.getLocator().equals(request.getRequestLocator())) { return DavServletResponse.SC_FORBIDDEN; Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/RebindInfo.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/RebindInfo.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/RebindInfo.java (revision 0) @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.apache.jackrabbit.webdav.xml.ElementIterator; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.w3c.dom.Element; +import org.w3c.dom.Document; + +public class RebindInfo implements XmlSerializable { + + private static Logger log = LoggerFactory.getLogger(RebindInfo.class); + + private String segment; + private String href; + + public RebindInfo(String href, String segment) { + this.href = href; + this.segment = segment; + } + + public String getHref() { + return this.href; + } + + public String getSegment() { + return this.segment; + } + + /** + * Build an RebindInfo object from the root element present + * in the request body. + * + * @param root the root element of the request body + * @return a RebindInfo object containing segment and href + * @throws org.apache.jackrabbit.webdav.DavException if the REBIND request is malformed + */ + public static RebindInfo createFromXml(Element root) throws DavException { + if (!DomUtil.matches(root, BindConstants.XML_REBIND, BindConstants.NAMESPACE)) { + log.warn("DAV:rebind element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + String href = null; + String segment = null; + ElementIterator it = DomUtil.getChildren(root); + while (it.hasNext()) { + Element elt = it.nextElement(); + if (DomUtil.matches(elt, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE)) { + if (segment == null) { + segment = DomUtil.getText(elt); + } else { + log.warn("unexpected multiple occurence of DAV:segment element"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else if (DomUtil.matches(elt, BindConstants.XML_HREF, BindConstants.NAMESPACE)) { + if (href == null) { + href = DomUtil.getText(elt); + } else { + log.warn("unexpected multiple occurence of DAV:href element"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else { + log.warn("unexpected element " + elt.getLocalName()); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } + if (href == null) { + log.warn("DAV:href element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + if (segment == null) { + log.warn("DAV:segment element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + return new RebindInfo(href, segment); + } + + /** + * @see org.apache.jackrabbit.webdav.xml.XmlSerializable#toXml(org.w3c.dom.Document) + */ + public Element toXml(Document document) { + Element rebindElt = DomUtil.createElement(document, BindConstants.XML_REBIND, BindConstants.NAMESPACE); + Element hrefElt = DomUtil.createElement(document, BindConstants.XML_HREF, BindConstants.NAMESPACE, this.href); + Element segElt = DomUtil.createElement(document, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE, this.segment); + rebindElt.appendChild(hrefElt); + rebindElt.appendChild(segElt); + return rebindElt; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/ParentElement.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/ParentElement.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/ParentElement.java (revision 0) @@ -0,0 +1,111 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.apache.jackrabbit.webdav.xml.ElementIterator; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.w3c.dom.Element; +import org.w3c.dom.Document; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ParentElement wraps en element of the parent set of a resource. A java.util.Set of + * ParentElement objects may serve as the value object of the ParentSet DavProperty. + */ +public class ParentElement implements XmlSerializable { + + private static Logger log = LoggerFactory.getLogger(ParentElement.class); + + private final String href; + private final String segment; + + public ParentElement(String href, String segment) { + this.href = href; + this.segment = segment; + } + + public String getHref() { + return this.href; + } + + public String getSegment() { + return this.segment; + } + + /** + * Build an ParentElement object from an XML element DAV:parent + * + * @param root the DAV:parent element + * @return a ParentElement object + * @throws org.apache.jackrabbit.webdav.DavException if the DAV:parent element is malformed + */ + public static ParentElement createFromXml(Element root) throws DavException { + if (!DomUtil.matches(root, BindConstants.XML_PARENT, BindConstants.NAMESPACE)) { + log.warn("DAV:paret element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + String href = null; + String segment = null; + ElementIterator it = DomUtil.getChildren(root); + while (it.hasNext()) { + Element elt = it.nextElement(); + if (DomUtil.matches(elt, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE)) { + if (segment == null) { + segment = DomUtil.getText(elt); + } else { + log.warn("unexpected multiple occurence of DAV:segment element"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else if (DomUtil.matches(elt, BindConstants.XML_HREF, BindConstants.NAMESPACE)) { + if (href == null) { + href = DomUtil.getText(elt); + } else { + log.warn("unexpected multiple occurence of DAV:href element"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else { + log.warn("unexpected element " + elt.getLocalName()); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } + if (href == null) { + log.warn("DAV:href element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + if (segment == null) { + log.warn("DAV:segment element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + return new ParentElement(href, segment); + } + + /** + * @see org.apache.jackrabbit.webdav.xml.XmlSerializable#toXml(org.w3c.dom.Document) + */ + public Element toXml(Document document) { + Element parentElt = DomUtil.createElement(document, BindConstants.XML_PARENT, BindConstants.NAMESPACE); + Element hrefElt = DomUtil.createElement(document, BindConstants.XML_HREF, BindConstants.NAMESPACE, this.href); + Element segElt = DomUtil.createElement(document, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE, this.segment); + parentElt.appendChild(hrefElt); + parentElt.appendChild(segElt); + return parentElt; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/ParentSet.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/ParentSet.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/ParentSet.java (revision 0) @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.apache.jackrabbit.webdav.property.AbstractDavProperty; +import org.apache.jackrabbit.webdav.property.DavPropertyName; +import org.w3c.dom.Element; +import org.w3c.dom.Document; +import org.w3c.dom.Node; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +/** + * ParentSet represents a DAV:parent-set property. + */ +public class ParentSet extends AbstractDavProperty { + + private final Collection parents; + + /** + * Creates a new ParentSet from a collection of ParentElement objects. + * @param parents + */ + public ParentSet(Collection parents) { + super(BindConstants.PARENTSET, true); + this.parents = parents; + } + + /** + * @see org.apache.jackrabbit.webdav.property.AbstractDavProperty#getValue() + */ + public Object getValue() { + return this.parents; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindInfo.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindInfo.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindInfo.java (revision 0) @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.apache.jackrabbit.webdav.xml.ElementIterator; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.w3c.dom.Element; +import org.w3c.dom.Document; + +public class BindInfo implements XmlSerializable { + + private static Logger log = LoggerFactory.getLogger(BindInfo.class); + + private String segment; + private String href; + + public BindInfo(String href, String segment) { + this.href = href; + this.segment = segment; + } + + public String getHref() { + return this.href; + } + + public String getSegment() { + return this.segment; + } + + /** + * Build an BindInfo object from the root element present + * in the request body. + * + * @param root the root element of the request body + * @return a BindInfo object containing segment and href + * @throws org.apache.jackrabbit.webdav.DavException if the BIND request is malformed + */ + public static BindInfo createFromXml(Element root) throws DavException { + if (!DomUtil.matches(root, BindConstants.XML_BIND, BindConstants.NAMESPACE)) { + log.warn("DAV:bind element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + String href = null; + String segment = null; + ElementIterator it = DomUtil.getChildren(root); + while (it.hasNext()) { + Element elt = it.nextElement(); + if (DomUtil.matches(elt, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE)) { + if (segment == null) { + segment = DomUtil.getText(elt); + } else { + log.warn("unexpected multiple occurence of DAV:segment element"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else if (DomUtil.matches(elt, BindConstants.XML_HREF, BindConstants.NAMESPACE)) { + if (href == null) { + href = DomUtil.getText(elt); + } else { + log.warn("unexpected multiple occurence of DAV:href element"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else { + log.warn("unexpected element " + elt.getLocalName()); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } + if (href == null) { + log.warn("DAV:href element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + if (segment == null) { + log.warn("DAV:segment element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + return new BindInfo(href, segment); + } + + /** + * @see org.apache.jackrabbit.webdav.xml.XmlSerializable#toXml(org.w3c.dom.Document) + */ + public Element toXml(Document document) { + Element bindElt = DomUtil.createElement(document, BindConstants.XML_BIND, BindConstants.NAMESPACE); + Element hrefElt = DomUtil.createElement(document, BindConstants.XML_HREF, BindConstants.NAMESPACE, this.href); + Element segElt = DomUtil.createElement(document, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE, this.segment); + bindElt.appendChild(hrefElt); + bindElt.appendChild(segElt); + return bindElt; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindableResource.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindableResource.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindableResource.java (revision 0) @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavResource; + +import java.util.Collection; +import java.util.Set; + +public interface BindableResource { + + /** + * Will add a new binding to the given collection referencing this resource. + * + * @param collection the collection to create the new binding in. + * @param newBinding the new binding + */ + public void bind(DavResource collection, DavResource newBinding) throws DavException; + + /** + * Will rebind the resource to the given collection. By definition, this is an atomic move operation. + * + * @param collection the collection to create the new binding in. + * @param newBinding the new binding + */ + public void rebind(DavResource collection, DavResource newBinding) throws DavException; + + /** + * Will retrieve a collection of parent elements of the bindable resource representing the parent set. + * + * @return newBinding the new binding + */ + public Set getParentElements(); +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindConstants.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindConstants.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindConstants.java (revision 0) @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.apache.jackrabbit.webdav.xml.Namespace; +import org.apache.jackrabbit.webdav.DavConstants; +import org.apache.jackrabbit.webdav.property.DavPropertyName; + +/** + * BindConstants provide constants for request and response + * headers, Xml elements and property names defined by + * the BIND specification. + */ +public interface BindConstants { + + /** + * The namespace + */ + public static final Namespace NAMESPACE = DavConstants.NAMESPACE; + + /** + * local names of XML elements used in the request bodies of the methods BIND, REBIND and UNBIND. + */ + public static final String XML_BIND = "bind"; + public static final String XML_REBIND = "rebind"; + public static final String XML_UNBIND = "unbind"; + public static final String XML_SEGMENT = "segment"; + public static final String XML_HREF = "href"; + public static final String XML_PARENT = "parent"; + + public static final String METHODS = "BIND, REBIND, UNBIND"; + + public static final String COMPLIANCE_CLASS = "bind"; + + /* + * Webdav properties defined by the BIND specification. + */ + public static final DavPropertyName RESOURCEID = DavPropertyName.create("resource-id"); + public static final DavPropertyName PARENTSET = DavPropertyName.create("parent-set"); +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindServletRequest.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindServletRequest.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/BindServletRequest.java (revision 0) @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.DavResourceLocator; + +/** + * BindServletRequest provides extension useful for functionality + * related to BIND specification. + */ +public interface BindServletRequest { + + /** + * Returns the {@link RebindInfo} present with the request + * + * @return {@link RebindInfo} object + * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body + */ + public RebindInfo getRebindInfo() throws DavException; + + /** + * Returns the {@link UnbindInfo} present with the request + * + * @return {@link UnbindInfo} object + * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body + */ + public UnbindInfo getUnbindInfo() throws DavException; + + /** + * Returns the {@link BindInfo} present with the request + * + * @return {@link BindInfo} object + * @throws org.apache.jackrabbit.webdav.DavException in case of an invalid or missing request body + */ + public BindInfo getBindInfo() throws DavException; + + /** + * Parses a href and returns the path of the resource. + * + * @return path of the resource identified by the href. + */ + public DavResourceLocator getHrefLocator(String href) throws DavException; + + /** + * Returns the path of the member resource of the request resource which is identified by the segment parameter. + * + * @return path of internal member resource. + */ + public DavResourceLocator getMemberLocator(String segment); +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/UnbindInfo.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/UnbindInfo.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/bind/UnbindInfo.java (revision 0) @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.bind; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.jackrabbit.webdav.DavServletResponse; +import org.apache.jackrabbit.webdav.DavException; +import org.apache.jackrabbit.webdav.xml.DomUtil; +import org.apache.jackrabbit.webdav.xml.ElementIterator; +import org.apache.jackrabbit.webdav.xml.XmlSerializable; +import org.w3c.dom.Element; +import org.w3c.dom.Document; + +public class UnbindInfo implements XmlSerializable { + + private static Logger log = LoggerFactory.getLogger(UnbindInfo.class); + + private String segment; + + private UnbindInfo() {} + + public UnbindInfo(String segment) { + this.segment = segment; + } + + public String getSegment() { + return this.segment; + } + + /** + * Build an UnbindInfo object from the root element present + * in the request body. + * + * @param root the root element of the request body + * @return a UnbindInfo object containing a segment identifier + * @throws org.apache.jackrabbit.webdav.DavException if the UNBIND request is malformed + */ + public static UnbindInfo createFromXml(Element root) throws DavException { + if (!DomUtil.matches(root, BindConstants.XML_UNBIND, BindConstants.NAMESPACE)) { + log.warn("DAV:unbind element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + String segment = null; + ElementIterator it = DomUtil.getChildren(root); + while (it.hasNext()) { + Element elt = it.nextElement(); + if (DomUtil.matches(elt, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE)) { + if (segment == null) { + segment = DomUtil.getText(elt); + } else { + log.warn("unexpected multiple occurence of DAV:segment element"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } else { + log.warn("unexpected element " + elt.getLocalName()); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + } + if (segment == null) { + log.warn("DAV:segment element expected"); + throw new DavException(DavServletResponse.SC_BAD_REQUEST); + } + return new UnbindInfo(segment); + } + + /** + * @see org.apache.jackrabbit.webdav.xml.XmlSerializable#toXml(org.w3c.dom.Document) + */ + public Element toXml(Document document) { + Element unbindElt = DomUtil.createElement(document, BindConstants.XML_UNBIND, BindConstants.NAMESPACE); + Element segElt = DomUtil.createElement(document, BindConstants.XML_SEGMENT, BindConstants.NAMESPACE, this.segment); + unbindElt.appendChild(segElt); + return unbindElt; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/UnbindMethod.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/UnbindMethod.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/UnbindMethod.java (revision 0) @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.client.methods; + +import org.apache.jackrabbit.webdav.bind.UnbindInfo; +import org.apache.jackrabbit.webdav.DavServletResponse; + +import java.io.IOException; + +/** + * UnbindMethod removes a binding to a resource (semantically equivalent to delete). + */ +public class UnbindMethod extends DavMethodBase { + + public UnbindMethod(String uri, UnbindInfo info) throws IOException { + super(uri); + setRequestBody(info); + } + + //---------------------------------------------------------< HttpMethod >--- + /** + * @see org.apache.commons.httpclient.HttpMethod#getName() + */ + public String getName() { + return "UNBIND"; + } + + //------------------------------------------------------< DavMethodBase >--- + /** + * + * @param statusCode + * @return true if status code is 200 (binding was removed). + */ + protected boolean isSuccess(int statusCode) { + return statusCode == DavServletResponse.SC_OK; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/RebindMethod.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/RebindMethod.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/RebindMethod.java (revision 0) @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.client.methods; + +import org.apache.jackrabbit.webdav.bind.RebindInfo; +import org.apache.jackrabbit.webdav.DavServletResponse; + +import java.io.IOException; + +/** + * RebindMethod replaces a binding to a resource (atomic version of move). + */ +public class RebindMethod extends DavMethodBase { + + public RebindMethod(String uri, RebindInfo info) throws IOException { + super(uri); + setRequestBody(info); + } + + //---------------------------------------------------------< HttpMethod >--- + /** + * @see org.apache.commons.httpclient.HttpMethod#getName() + */ + public String getName() { + return "REBIND"; + } + + //------------------------------------------------------< DavMethodBase >--- + /** + * + * @param statusCode + * @return true if status code is 200 (existing binding was overwritten) or 201 (new binding created). + */ + protected boolean isSuccess(int statusCode) { + return statusCode == DavServletResponse.SC_CREATED || statusCode == DavServletResponse.SC_OK; + } +} + Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BindMethod.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BindMethod.java (revision 0) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/client/methods/BindMethod.java (revision 0) @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.jackrabbit.webdav.client.methods; + +import org.apache.jackrabbit.webdav.bind.BindInfo; +import org.apache.jackrabbit.webdav.DavServletResponse; + +import java.io.IOException; + +/** + * BindMethod creates a new binding to a resource. + */ +public class BindMethod extends DavMethodBase { + + public BindMethod(String uri, BindInfo info) throws IOException { + super(uri); + setRequestBody(info); + } + + //---------------------------------------------------------< HttpMethod >--- + /** + * @see org.apache.commons.httpclient.HttpMethod#getName() + */ + public String getName() { + return "BIND"; + } + + //------------------------------------------------------< DavMethodBase >--- + /** + * + * @param statusCode + * @return true if status code is 200 (existing binding was overwritten) or 201 (new binding created). + */ + protected boolean isSuccess(int statusCode) { + return statusCode == DavServletResponse.SC_CREATED || statusCode == DavServletResponse.SC_OK; + } +} Index: jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavMethods.java =================================================================== --- jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavMethods.java (revision 700037) +++ jackrabbit-webdav/src/main/java/org/apache/jackrabbit/webdav/DavMethods.java (working copy) @@ -262,6 +262,27 @@ public static final String METHOD_ACL = "ACL"; /** + * The webdav REBIND method and public constant defined by + * the BIND specification + */ + public static final int DAV_REBIND = DAV_ACL + 1; + public static final String METHOD_REBIND = "REBIND"; + + /** + * The webdav UNBIND method and public constant defined by + * the BIND specification + */ + public static final int DAV_UNBIND = DAV_REBIND + 1; + public static final String METHOD_UNBIND = "UNBIND"; + + /** + * The webdav BIND method and public constant defined by + * the BIND specification + */ + public static final int DAV_BIND = DAV_UNBIND + 1; + public static final String METHOD_BIND = "BIND"; + + /** * Returns webdav method type code, error result <= 0 * Valid type codes > 0 */ @@ -314,6 +335,9 @@ addMethodCode(METHOD_BASELINE_CONTROL, DAV_BASELINE_CONTROL); addMethodCode(METHOD_MKACTIVITY, DAV_MKACTIVITY); addMethodCode(METHOD_ACL, DAV_ACL); + addMethodCode(METHOD_REBIND, DAV_REBIND); + addMethodCode(METHOD_UNBIND, DAV_UNBIND); + addMethodCode(METHOD_BIND, DAV_BIND); labelMethods = new int[] { DAV_GET, DAV_HEAD, DAV_OPTIONS, DAV_PROPFIND, DAV_LABEL, DAV_COPY }; Index: jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DavResourceImpl.java =================================================================== --- jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DavResourceImpl.java (revision 700037) +++ jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DavResourceImpl.java (working copy) @@ -39,6 +39,10 @@ import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.MultiStatusResponse; +import org.apache.jackrabbit.webdav.bind.BindConstants; +import org.apache.jackrabbit.webdav.bind.BindableResource; +import org.apache.jackrabbit.webdav.bind.ParentSet; +import org.apache.jackrabbit.webdav.bind.ParentElement; import org.apache.jackrabbit.webdav.io.InputContext; import org.apache.jackrabbit.webdav.io.OutputContext; import org.apache.jackrabbit.webdav.jcr.JcrDavException; @@ -69,6 +73,7 @@ import javax.jcr.PathNotFoundException; import javax.jcr.RepositoryException; import javax.jcr.Session; +import javax.jcr.Workspace; import javax.jcr.lock.Lock; import java.io.IOException; import java.io.OutputStream; @@ -78,17 +83,22 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.HashSet; /** * DavResourceImpl implements a DavResource. */ -public class DavResourceImpl implements DavResource, JcrConstants { +public class DavResourceImpl implements DavResource, BindableResource, JcrConstants { /** * the default logger */ private static final Logger log = LoggerFactory.getLogger(DavResourceImpl.class); + public static final String METHODS = DavResource.METHODS + ", " + BindConstants.METHODS; + public static final String COMPLIANCE_CLASS = DavResource.COMPLIANCE_CLASS + ", " + BindConstants.COMPLIANCE_CLASS; + private DavResourceFactory factory; private LockManager lockManager; private JcrDavSession session; @@ -196,7 +206,7 @@ */ private void initRfc4122Uri() { try { - if (node.isNodeType("mix:referenceable")) { + if (node.isNodeType(MIX_REFERENCEABLE)) { String uuid = node.getUUID(); try { UUID.fromString(uuid); @@ -215,7 +225,7 @@ * @see org.apache.jackrabbit.webdav.DavResource#getComplianceClass() */ public String getComplianceClass() { - return DavResource.COMPLIANCE_CLASS; + return COMPLIANCE_CLASS; } /** @@ -223,7 +233,7 @@ * @see org.apache.jackrabbit.webdav.DavResource#getSupportedMethods() */ public String getSupportedMethods() { - return DavResource.METHODS; + return METHODS; } /** @@ -369,9 +379,14 @@ } if (rfc4122Uri != null) { - properties.add(new HrefProperty(DavPropertyName.RESOURCEID, rfc4122Uri, true)); + properties.add(new HrefProperty(BindConstants.RESOURCEID, rfc4122Uri, true)); } + Set parentElements = this.getParentElements(); + if (!parentElements.isEmpty()) { + properties.add(new ParentSet(parentElements)); + } + /* set current lock information. If no lock is set to this resource, an empty lockdiscovery will be returned in the response. */ properties.add(new LockDiscovery(getLock(Type.WRITE, Scope.EXCLUSIVE))); @@ -612,7 +627,13 @@ try { String itemPath = member.getLocator().getRepositoryPath(); Item memItem = getJcrSession().getItem(itemPath); - memItem.remove(); + //TODO once jcr2 is out: simply call removeShare() + if (memItem instanceof org.apache.jackrabbit.api.jsr283.Node) { + org.apache.jackrabbit.api.jsr283.Node n = (org.apache.jackrabbit.api.jsr283.Node) memItem; + n.removeShare(); + } else { + memItem.remove(); + } getJcrSession().save(); // make sure, non-jcr locks are removed, once the removal is completed @@ -836,7 +857,99 @@ return session; } + /** + * @see BindableResource#rebind(DavResource, DavResource) + */ + public void bind(DavResource collection, DavResource newBinding) throws DavException { + if (!exists()) { + //DAV:bind-source-exists + throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); + } + if (isLocked(collection)) { + //DAV:locked-update-allowed? + throw new DavException(DavServletResponse.SC_LOCKED); + } + if (isFilteredResource(newBinding)) { + throw new DavException(DavServletResponse.SC_FORBIDDEN); + } + checkSameWorkspace(collection.getLocator()); + try { + if (!this.node.isNodeType(MIX_SHAREABLE)) { + if (!this.node.canAddMixin(MIX_SHAREABLE)) { + //DAV:binding-allowed + throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); + } + this.node.addMixin(MIX_SHAREABLE); + this.node.save(); + } + Workspace workspace = this.session.getRepositorySession().getWorkspace(); + workspace.clone(workspace.getName(), this.node.getPath(), newBinding.getLocator().getRepositoryPath(), false); + + } catch (RepositoryException e) { + throw new JcrDavException(e); + } + + } + + /** + * @see BindableResource#rebind(DavResource, DavResource) + */ + public void rebind(DavResource collection, DavResource newBinding) throws DavException { + if (!exists()) { + //DAV:rebind-source-exists + throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); + } + if (isLocked(this)) { + //DAV:protected-source-url-deletion.allowed + throw new DavException(DavServletResponse.SC_PRECONDITION_FAILED); + } + if (isLocked(collection)) { + //DAV:locked-update-allowed? + throw new DavException(DavServletResponse.SC_LOCKED); + } + if (isFilteredResource(newBinding)) { + throw new DavException(DavServletResponse.SC_FORBIDDEN); + } + checkSameWorkspace(collection.getLocator()); + try { + if (!this.node.isNodeType(MIX_REFERENCEABLE)) { + throw new DavException(this.node.canAddMixin(MIX_REFERENCEABLE)? + DavServletResponse.SC_CONFLICT : DavServletResponse.SC_METHOD_NOT_ALLOWED); + } + getJcrSession().getWorkspace().move(locator.getRepositoryPath(), newBinding.getLocator().getRepositoryPath()); + } catch (RepositoryException e) { + throw new JcrDavException(e); + } + } + + /** + * @see org.apache.jackrabbit.webdav.bind.BindableResource#getParentElements() + */ + public Set getParentElements() { + try { + //TODO remove this check once jcr2 is out + if (!(this.node instanceof org.apache.jackrabbit.api.jsr283.Node)) { + DavResourceLocator loc = this.locator.getFactory().createResourceLocator( + this.locator.getPrefix(), this.locator.getWorkspacePath(), this.node.getParent().getPath(), false); + return Collections.singleton(new ParentElement(loc.getHref(true), this.node.getName())); + } + Set ps = new HashSet(); + NodeIterator sharedSetIterator = ((org.apache.jackrabbit.api.jsr283.Node) this.node).getSharedSet(); + while (sharedSetIterator.hasNext()) { + Node sharednode = sharedSetIterator.nextNode(); + DavResourceLocator loc = this.locator.getFactory().createResourceLocator( + this.locator.getPrefix(), this.locator.getWorkspacePath(), sharednode.getParent().getPath(), false); + ps.add(new ParentElement(loc.getHref(true), sharednode.getName())); + } + return ps; + } catch (RepositoryException e) { + log.warn("unable to calculate parent set", e); + return Collections.EMPTY_SET; + } + } + + /** * Returns the node that is wrapped by this resource. * * @return Index: jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DeltaVResourceImpl.java =================================================================== --- jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DeltaVResourceImpl.java (revision 700037) +++ jackrabbit-jcr-server/src/main/java/org/apache/jackrabbit/webdav/simple/DeltaVResourceImpl.java (working copy) @@ -32,6 +32,7 @@ import org.apache.jackrabbit.webdav.DavServletResponse; import org.apache.jackrabbit.webdav.DavSession; import org.apache.jackrabbit.webdav.DavCompliance; +import org.apache.jackrabbit.webdav.bind.BindConstants; import org.apache.jackrabbit.webdav.property.DavProperty; import org.apache.jackrabbit.webdav.property.DavPropertyName; import org.apache.jackrabbit.webdav.property.HrefProperty; @@ -77,7 +78,8 @@ DavCompliance._2_, DavCompliance.VERSION_CONTROL, DavCompliance.VERSION_HISTORY, - DavCompliance.LABEL + DavCompliance.LABEL, + BindConstants.COMPLIANCE_CLASS, }); } Index: jackrabbit-jcr-server/pom.xml =================================================================== --- jackrabbit-jcr-server/pom.xml (revision 700037) +++ jackrabbit-jcr-server/pom.xml (working copy) @@ -66,7 +66,12 @@ jcr + org.apache.jackrabbit + jackrabbit-api + + + org.apache.jackrabbit jackrabbit-jcr-commons