Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Support POST/PUT requests with a Content-Location header instead of a…
… request body
  • Loading branch information
cbeer committed May 27, 2014
1 parent 9afee37 commit a094465
Show file tree
Hide file tree
Showing 22 changed files with 444 additions and 8 deletions.
Expand Up @@ -23,4 +23,6 @@

<bean class="org.modeshape.jcr.JcrRepositoryFactory" />

<bean id="connectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" />

</beans>
2 changes: 2 additions & 0 deletions fcrepo-auth-common/src/test/resources/spring-test/repo.xml
Expand Up @@ -23,4 +23,6 @@

<bean class="org.modeshape.jcr.JcrRepositoryFactory" />

<bean id="connectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" />

</beans>
Expand Up @@ -24,4 +24,6 @@

<bean class="org.modeshape.jcr.JcrRepositoryFactory" />

<bean id="connectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" />

</beans>
Expand Up @@ -17,4 +17,6 @@

<bean class="org.modeshape.jcr.JcrRepositoryFactory" />

<bean id="connectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" />

</beans>
2 changes: 2 additions & 0 deletions fcrepo-connector-file/src/test/resources/spring-test/repo.xml
Expand Up @@ -20,4 +20,6 @@

<bean class="org.modeshape.jcr.JcrRepositoryFactory"/>

<bean id="connectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" />

</beans>
2 changes: 2 additions & 0 deletions fcrepo-generator-dc/src/test/resources/spring-test/repo.xml
Expand Up @@ -27,4 +27,6 @@

<bean class="org.modeshape.jcr.JcrRepositoryFactory"/>

<bean id="connectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager" />

</beans>
Expand Up @@ -18,6 +18,7 @@
import com.codahale.metrics.annotation.Timed;
import com.sun.jersey.core.header.ContentDisposition;
import org.fcrepo.http.commons.api.rdf.HttpIdentifierTranslator;
import org.fcrepo.http.commons.domain.ContentLocation;
import org.fcrepo.http.commons.session.InjectedSession;
import org.fcrepo.kernel.Datastream;
import org.fcrepo.kernel.exception.InvalidChecksumException;
Expand Down Expand Up @@ -86,7 +87,7 @@ public Response create(@PathParam("path")
@HeaderParam("Content-Disposition") final String contentDisposition,
@QueryParam("checksum") final String checksum,
@HeaderParam("Content-Type") final MediaType requestContentType,
final InputStream requestBodyStream, @Context final HttpServletResponse servletResponse)
@ContentLocation final InputStream requestBodyStream, @Context final HttpServletResponse servletResponse)
throws InvalidChecksumException, RepositoryException, URISyntaxException, ParseException {
final MediaType contentType = getSimpleContentType(requestContentType);

Expand Down Expand Up @@ -181,7 +182,7 @@ public Response modifyContent(@PathParam("path") final List<PathSegment> pathLis
@QueryParam("checksum") final String checksum,
@HeaderParam("Content-Disposition") final String contentDisposition,
@HeaderParam("Content-Type") final MediaType requestContentType,
final InputStream requestBodyStream,
@ContentLocation final InputStream requestBodyStream,
@Context final Request request, @Context final HttpServletResponse servletResponse)
throws RepositoryException, InvalidChecksumException, URISyntaxException, ParseException {

Expand Down
Expand Up @@ -39,6 +39,7 @@

import org.fcrepo.http.commons.AbstractResource;
import org.fcrepo.http.commons.api.rdf.HttpIdentifierTranslator;
import org.fcrepo.http.commons.domain.ContentLocation;
import org.fcrepo.http.commons.session.InjectedSession;
import org.fcrepo.kernel.exception.InvalidChecksumException;
import org.fcrepo.serialization.SerializerUtil;
Expand Down Expand Up @@ -72,7 +73,7 @@ public class FedoraImport extends AbstractResource {
*
* @param pathList
* @param format
* @param stream
* @param requestBodyStream
* @return 201 with Location header to the path of the imported resource
* @throws IOException
* @throws RepositoryException
Expand All @@ -82,7 +83,7 @@ public class FedoraImport extends AbstractResource {
@POST
public Response importObject(@PathParam("path") final List<PathSegment> pathList,
@QueryParam("format") @DefaultValue("jcr/xml") final String format,
final InputStream stream)
@ContentLocation final InputStream requestBodyStream)
throws IOException, RepositoryException, InvalidChecksumException,
URISyntaxException {

Expand All @@ -94,7 +95,7 @@ public Response importObject(@PathParam("path") final List<PathSegment> pathList

try {
serializers.getSerializer(format)
.deserialize(session, path, stream);
.deserialize(session, path, requestBodyStream);
session.save();
return created(new URI(subjects.getSubject(path).getURI())).build();
} catch ( ItemExistsException ex ) {
Expand Down
Expand Up @@ -99,6 +99,7 @@
import org.apache.jena.riot.Lang;
import org.fcrepo.http.commons.AbstractResource;
import org.fcrepo.http.commons.api.rdf.HttpIdentifierTranslator;
import org.fcrepo.http.commons.domain.ContentLocation;
import org.fcrepo.http.commons.domain.MOVE;
import org.fcrepo.http.commons.domain.PATCH;
import org.fcrepo.http.commons.domain.COPY;
Expand Down Expand Up @@ -346,7 +347,7 @@ public Response updateSparql(@PathParam("path")
final List<PathSegment> pathList,
@Context
final UriInfo uriInfo,
final InputStream requestBodyStream,
@ContentLocation final InputStream requestBodyStream,
@Context final Request request, @Context final HttpServletResponse servletResponse)
throws RepositoryException, IOException {

Expand Down Expand Up @@ -409,7 +410,7 @@ public Response createOrReplaceObjectRdf(
@Context final UriInfo uriInfo,
@HeaderParam("Content-Type")
final MediaType requestContentType,
final InputStream requestBodyStream,
@ContentLocation final InputStream requestBodyStream,
@Context final Request request,
@Context final HttpServletResponse servletResponse) throws RepositoryException, ParseException,
IOException, InvalidChecksumException, URISyntaxException {
Expand Down Expand Up @@ -492,7 +493,8 @@ public Response createObject(@PathParam("path")
final String slug,
@Context final HttpServletResponse servletResponse,
@Context
final UriInfo uriInfo, final InputStream requestBodyStream)
final UriInfo uriInfo,
@ContentLocation final InputStream requestBodyStream)
throws RepositoryException, ParseException, IOException,
InvalidChecksumException, URISyntaxException {

Expand Down
Expand Up @@ -16,6 +16,7 @@
package org.fcrepo.integration.http.api;

import static java.util.TimeZone.getTimeZone;
import static javax.ws.rs.core.Response.Status.CREATED;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
Expand All @@ -25,6 +26,7 @@
import java.util.Date;
import java.util.Locale;

import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
Expand Down Expand Up @@ -69,6 +71,35 @@ public void testAddDatastream() throws Exception {
assertNotEquals("Last-Modified should not be blank for new nodes", lastmod.trim(), "");
}

@Test
public void testAddDatastreamByContentLocation() throws Exception {
final String pid = getRandomUniquePid();
createObject(pid);

final HttpPost httpPost = postDSMethod(pid, "zxc", "");
httpPost.addHeader("Content-Location", serverAddress);
final HttpResponse response =
client.execute(httpPost);
assertEquals(CREATED.getStatusCode(), response.getStatusLine().getStatusCode());

assertTrue("Didn't find Last-Modified header!", response.containsHeader("Last-Modified"));
assertTrue("Didn't find ETag header!", response.containsHeader("ETag"));

final String location = response.getFirstHeader("Location").getValue();
assertEquals(
"Got wrong URI in Location header for datastream creation!",
serverAddress + pid + "/zxc/fcr:content", location);

final HttpGet httpGet = new HttpGet(location);

final HttpResponse contentResponse = client.execute(httpGet);

final String body = IOUtils.toString(contentResponse.getEntity().getContent());

assertTrue(body.contains(pid));


}
@Test
public void testAddDeepDatastream() throws Exception {
final String uuid = getRandomUniquePid();
Expand Down
2 changes: 2 additions & 0 deletions fcrepo-http-api/src/test/resources/spring-test/repo.xml
Expand Up @@ -17,4 +17,6 @@

<bean class="org.modeshape.jcr.JcrRepositoryFactory"/>

<bean id="connectionManager" class="org.apache.http.impl.conn.PoolingHttpClientConnectionManager">
</bean>
</beans>
@@ -0,0 +1,31 @@
/**
* Copyright 2014 DuraSpace, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.http.commons.domain;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Also check the Content-Location header
*
* @author cbeer
*/
@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface ContentLocation {
}
@@ -0,0 +1,95 @@
/**
* Copyright 2014 DuraSpace, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.http.commons.domain;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import org.fcrepo.kernel.services.ExternalContentService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.Provider;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Arrays;

import static javax.ws.rs.core.Response.Status.BAD_REQUEST;

/**
* Provide an InputStream either from the POST/PUT body, or by resolving a Content-Location URI
* @author cabeer
*/
@Provider
@Component
public class ContentLocationMessageBodyReader implements MessageBodyReader<InputStream> {

public static final Predicate<Annotation> HAS_CONTENT_LOCATION_PREDICATE = new Predicate<Annotation>() {
@Override
public boolean apply(final Annotation annotation) {
return annotation.annotationType().equals(ContentLocation.class);
}
};
/**
* The fcrepo node service
*/
@Autowired
private ExternalContentService contentService;

@Override
public boolean isReadable(final Class<?> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType) {
return InputStream.class.isAssignableFrom(type) &&
Iterables.any(Arrays.asList(annotations), HAS_CONTENT_LOCATION_PREDICATE);
}

@Override
public InputStream readFrom(final Class<InputStream> type,
final Type genericType,
final Annotation[] annotations,
final MediaType mediaType,
final MultivaluedMap<String, String> httpHeaders,
final InputStream entityStream) throws IOException, WebApplicationException {

if (httpHeaders.containsKey("Content-Location")) {
final String location = httpHeaders.getFirst("Content-Location");

try {
return contentService.retrieveExternalContent(new URI(location));
} catch (URISyntaxException e) {
throw new WebApplicationException(e, BAD_REQUEST);
}

} else {
return entityStream;
}
}

@VisibleForTesting
protected void setContentService(final ExternalContentService externalContentService) {
this.contentService = externalContentService;
}
}
@@ -0,0 +1,72 @@
/**
* Copyright 2014 DuraSpace, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.fcrepo.http.commons.domain;

import com.sun.jersey.core.header.InBoundHeaders;
import org.fcrepo.kernel.services.ExternalContentService;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;

import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;

import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks;

/**
* @author cabeer
*/
public class ContentLocationMessageBodyReaderTest {


private ContentLocationMessageBodyReader testObj;

@Mock
private InputStream mockInputStream;

@Mock
private ExternalContentService mockContentService;

@Before
public void setUp() throws URISyntaxException, IOException {
initMocks(this);
testObj = new ContentLocationMessageBodyReader();
testObj.setContentService(mockContentService);
}

@Test
public void testReadFromURI() throws Exception {
final InBoundHeaders headers = new InBoundHeaders();
headers.putSingle("Content-Location", "http://localhost:8080/xyz");
when(mockContentService.retrieveExternalContent(new URI("http://localhost:8080/xyz")))
.thenReturn(mockInputStream);
final InputStream actual = testObj.readFrom(InputStream.class, null, null, null, headers, null);
assertEquals(mockInputStream, actual);
}

@Test
public void testReadFromRequestBody() throws Exception {

final InBoundHeaders headers = new InBoundHeaders();
final InputStream actual = testObj.readFrom(InputStream.class, null, null, null, headers, mockInputStream);
assertEquals(mockInputStream, actual);

}
}

0 comments on commit a094465

Please sign in to comment.