http://git-wip-us.apache.org/repos/asf/olingo-odata4/blob/5d7c1287/fit/src/main/java/org/apache/olingo/fit/Services.java ---------------------------------------------------------------------- diff --git a/fit/src/main/java/org/apache/olingo/fit/Services.java b/fit/src/main/java/org/apache/olingo/fit/Services.java index 45f11a5..698938e 100644 --- a/fit/src/main/java/org/apache/olingo/fit/Services.java +++ b/fit/src/main/java/org/apache/olingo/fit/Services.java @@ -25,15 +25,22 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; import java.net.URI; +import java.util.AbstractMap; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.ConcurrentModificationException; +import java.util.Enumeration; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; +import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.mail.Header; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMultipart; import javax.ws.rs.BadRequestException; @@ -51,6 +58,8 @@ import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.UriInfo; @@ -59,63 +68,140 @@ import org.apache.commons.codec.binary.Base64; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.math.NumberUtils; +import org.apache.commons.lang3.tuple.Pair; import org.apache.cxf.interceptor.InInterceptors; +import org.apache.cxf.jaxrs.client.WebClient; import org.apache.cxf.jaxrs.ext.multipart.Attachment; import org.apache.cxf.jaxrs.ext.multipart.Multipart; import org.apache.cxf.jaxrs.ext.multipart.MultipartBody; import org.apache.olingo.client.api.data.ResWrap; +import org.apache.olingo.client.api.serialization.ODataDeserializer; +import org.apache.olingo.client.api.serialization.ODataSerializer; +import org.apache.olingo.client.core.serialization.AtomSerializer; +import org.apache.olingo.client.core.serialization.JsonDeserializer; +import org.apache.olingo.client.core.serialization.JsonSerializer; +import org.apache.olingo.commons.api.data.ComplexValue; import org.apache.olingo.commons.api.data.Entity; import org.apache.olingo.commons.api.data.EntityCollection; import org.apache.olingo.commons.api.data.Link; import org.apache.olingo.commons.api.data.Property; import org.apache.olingo.commons.api.data.ValueType; -import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeException; +import org.apache.olingo.commons.api.edm.EdmPrimitiveTypeKind; import org.apache.olingo.commons.api.format.ContentType; import org.apache.olingo.commons.core.edm.EdmTypeInfo; +import org.apache.olingo.commons.core.edm.primitivetype.EdmPrimitiveTypeFactory; +import org.apache.olingo.fit.metadata.EntityType; import org.apache.olingo.fit.metadata.Metadata; +import org.apache.olingo.fit.metadata.NavigationProperty; import org.apache.olingo.fit.methods.PATCH; import org.apache.olingo.fit.rest.ResolvingReferencesInterceptor; import org.apache.olingo.fit.rest.XHTTPMethodInterceptor; +import org.apache.olingo.fit.serializer.FITAtomDeserializer; import org.apache.olingo.fit.utils.AbstractUtilities; import org.apache.olingo.fit.utils.Accept; import org.apache.olingo.fit.utils.Commons; import org.apache.olingo.fit.utils.ConstantKey; import org.apache.olingo.fit.utils.Constants; import org.apache.olingo.fit.utils.FSManager; +import org.apache.olingo.fit.utils.JSONUtilities; import org.apache.olingo.fit.utils.LinkInfo; +import org.apache.olingo.fit.utils.XMLUtilities; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Service; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; + @Service @Path("/V40/Static.svc") @InInterceptors(classes = { XHTTPMethodInterceptor.class, ResolvingReferencesInterceptor.class }) -public class Services extends AbstractServices { +public class Services { /** - * CR/LF. + * Logger. */ - protected static final byte[] CRLF = { 13, 10 }; - - protected static final Pattern RELENTITY_SELECT_PATTERN = Pattern.compile("^.*\\(\\$select=.*\\)$"); + protected static final Logger LOG = LoggerFactory.getLogger(Services.class); - protected static final Pattern CROSSJOIN_PATTERN = Pattern.compile( + private static final Pattern REQUEST_PATTERN = Pattern.compile("(.*) (http://.*) HTTP/.*"); + private static final Pattern BATCH_REQUEST_REF_PATTERN = Pattern.compile("(.*) ([$]\\d+)(.*) HTTP/.*"); + private static final Pattern REF_PATTERN = Pattern.compile("([$]\\d+)"); + private static final Pattern RELENTITY_SELECT_PATTERN = Pattern.compile("^.*\\(\\$select=.*\\)$"); + private static final Pattern CROSSJOIN_PATTERN = Pattern.compile( "^\\$crossjoin\\(.*\\)\\?\\$filter=\\([a-zA-Z/]+ eq [a-zA-Z/]+\\)$"); + protected static final String BOUNDARY = "batch_243234_25424_ef_892u748"; + protected static final String MULTIPART_MIXED = "multipart/mixed"; + protected static final String APPLICATION_OCTET_STREAM = "application/octet-stream"; private final Map<String, String> providedAsync = new HashMap<String, String>(); + protected final ODataDeserializer atomDeserializer = new FITAtomDeserializer(); + protected final ODataDeserializer jsonDeserializer = new JsonDeserializer(true); + protected final ODataSerializer atomSerializer = new AtomSerializer(true); + protected final ODataSerializer jsonSerializer = new JsonSerializer(true, ContentType.JSON_FULL_METADATA); + + protected final Metadata metadata; + protected final XMLUtilities xml; + protected final JSONUtilities json; + public Services() throws IOException { - super(ODataServiceVersion.V40, Commons.getMetadata(ODataServiceVersion.V40)); + this(Commons.getMetadata()); } protected Services(final Metadata metadata) throws IOException { - super(ODataServiceVersion.V40, metadata); + this.metadata = metadata; + xml = new XMLUtilities(metadata); + json = new JSONUtilities(metadata); + } + + /** + * Provide sample services. + * + * @param accept Accept header. + * @return OData services. + */ + @GET + public Response getServices(@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept) { + try { + final Accept acceptType = Accept.parse(accept); + + if (acceptType == Accept.ATOM) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + return xml.createResponse( + null, + FSManager.instance().readFile(Constants.get(ConstantKey.SERVICES), acceptType), + null, acceptType); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + /** + * Provide sample getMetadata(). + * + * @return getMetadata(). + */ + @GET + @Path("/$metadata") + @Produces(MediaType.APPLICATION_XML) + public Response getMetadata() { + return getMetadata(Constants.get(ConstantKey.METADATA)); + } + + protected Response getMetadata(final String filename) { + try { + return xml.createResponse(null, FSManager.instance().readRes(filename, Accept.XML), null, Accept.XML); + } catch (Exception e) { + return xml.createFaultResponse(Accept.XML.toString(), e); + } } @GET @Path("/redirect/{name}({id})") - public Response conformanceRedirect( - @Context final UriInfo uriInfo, - @PathParam("name") final String name, - @PathParam("id") final String id) { + public Response conformanceRedirect(@Context final UriInfo uriInfo) { return Response.temporaryRedirect( URI.create(uriInfo.getRequestUri().toASCIIString().replace("/redirect", ""))).build(); } @@ -128,7 +214,7 @@ public class Services extends AbstractServices { try { if (CROSSJOIN_PATTERN.matcher("$crossjoin(" + elements + ")?$filter=" + filter).matches()) { - final InputStream feed = FSManager.instance(version).readFile("crossjoin", Accept.JSON); + final InputStream feed = FSManager.instance().readFile("crossjoin", Accept.JSON); return xml.createResponse(feed, null, Accept.JSON_FULLMETA); } else { @@ -141,9 +227,7 @@ public class Services extends AbstractServices { @GET @Path("/relatedEntitySelect/{path:.*}") - public Response relatedEntitySelect( - @PathParam("path") final String path, - @QueryParam("$expand") final String expand) { + public Response relatedEntitySelect(@QueryParam("$expand") final String expand) { if (RELENTITY_SELECT_PATTERN.matcher(expand).matches()) { return xml.createResponse(null, null, Accept.JSON_FULLMETA); @@ -177,7 +261,6 @@ public class Services extends AbstractServices { @PUT @Path("/People(1)/Parent") public Response changeSingleValuedNavigationPropertyReference( - @Context final UriInfo uriInfo, @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, final String content) { @@ -196,39 +279,95 @@ public class Services extends AbstractServices { } @POST - @Path("/async/$batch") - public Response async( - @Context final UriInfo uriInfo, + @Path("/$batch") + @Consumes(MULTIPART_MIXED) + @Produces(APPLICATION_OCTET_STREAM + ";boundary=" + BOUNDARY) + public Response batch( @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, final @Multipart MultipartBody attachment) { + try { + final boolean continueOnError = prefer.contains("odata.continue-on-error"); + return xml.createBatchResponse( + exploreMultipart(attachment.getAllAttachments(), BOUNDARY, continueOnError)); + } catch (IOException e) { + return xml.createFaultResponse(Accept.XML.toString(), e); + } + } + + // ---------------------------------------------- + // just for non nullable property test into PropertyTestITCase + // ---------------------------------------------- + @PATCH + @Path("/Driver('2')") + public Response patchDriver() { + return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), new Exception("Non nullable properties")); + } + + @GET + @Path("/StoredPIs(1000)") + public Response getStoredPI(@Context final UriInfo uriInfo) { + final Entity entity = new Entity(); + entity.setType("Microsoft.Test.OData.Services.ODataWCFService.StoredPI"); + final Property id = new Property(); + id.setType("Edm.Int32"); + id.setName("StoredPIID"); + id.setValue(ValueType.PRIMITIVE, 1000); + entity.getProperties().add(id); + final Link edit = new Link(); + edit.setHref(uriInfo.getRequestUri().toASCIIString()); + edit.setRel("edit"); + edit.setTitle("StoredPI"); + entity.setEditLink(edit); + + final ByteArrayOutputStream content = new ByteArrayOutputStream(); + final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING); + try { + jsonSerializer.write(writer, new ResWrap<Entity>((URI) null, null, entity)); + return xml.createResponse(new ByteArrayInputStream(content.toByteArray()), null, Accept.JSON_FULLMETA); + } catch (Exception e) { + LOG.error("While creating StoredPI", e); + return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), e); + } + } + + @PATCH + @Path("/StoredPIs(1000)") + public Response patchStoredPI() { + // just for non nullable property test into PropertyTestITCase + return xml.createFaultResponse(Accept.JSON_FULLMETA.toString(), new Exception("Non nullable properties")); + } + + @POST + @Path("/async/$batch") + public Response async(@Context final UriInfo uriInfo) { try { final ByteArrayOutputStream bos = new ByteArrayOutputStream(); bos.write("HTTP/1.1 200 Ok".getBytes()); - bos.write(CRLF); + bos.write(Constants.CRLF); bos.write("OData-Version: 4.0".getBytes()); - bos.write(CRLF); + bos.write(Constants.CRLF); bos.write(("Content-Type: " + ContentType.APPLICATION_OCTET_STREAM + ";boundary=" + BOUNDARY).getBytes()); - bos.write(CRLF); - bos.write(CRLF); + bos.write(Constants.CRLF); + bos.write(Constants.CRLF); bos.write(("--" + BOUNDARY).getBytes()); - bos.write(CRLF); + bos.write(Constants.CRLF); bos.write("Content-Type: application/http".getBytes()); - bos.write(CRLF); + bos.write(Constants.CRLF); bos.write("Content-Transfer-Encoding: binary".getBytes()); - bos.write(CRLF); - bos.write(CRLF); + bos.write(Constants.CRLF); + bos.write(Constants.CRLF); bos.write("HTTP/1.1 202 Accepted".getBytes()); - bos.write(CRLF); + bos.write(Constants.CRLF); bos.write("Location: http://service-root/async-monitor".getBytes()); - bos.write(CRLF); + bos.write(Constants.CRLF); bos.write("Retry-After: 10".getBytes()); - bos.write(CRLF); - bos.write(CRLF); + bos.write(Constants.CRLF); + bos.write(Constants.CRLF); bos.write(("--" + BOUNDARY + "--").getBytes()); - bos.write(CRLF); + bos.write(Constants.CRLF); final UUID uuid = UUID.randomUUID(); providedAsync.put(uuid.toString(), bos.toString(Constants.ENCODING.toString())); @@ -263,11 +402,13 @@ public class Services extends AbstractServices { ? Constants.get(ConstantKey.ENTITY) : Constants.get(ConstantKey.FEED)); - final InputStream feed = FSManager.instance(version).readFile(path.toString(), acceptType); + final InputStream feed = FSManager.instance().readFile(path.toString(), acceptType); final StringBuilder builder = new StringBuilder(); - builder.append("HTTP/1.1 200 Ok").append(new String(CRLF)); - builder.append("Content-Type: ").append(accept).append(new String(CRLF)).append(new String(CRLF)); + builder.append("HTTP/1.1 200 Ok").append(new String(Constants.CRLF)); + builder.append("Content-Type: ").append(accept) + .append(new String(Constants.CRLF)) + .append(new String(Constants.CRLF)); builder.append(IOUtils.toString(feed)); IOUtils.closeQuietly(feed); @@ -281,15 +422,80 @@ public class Services extends AbstractServices { } } - @Override - protected void setInlineCount(final EntityCollection entitySet, final String count) { + private void setInlineCount(final EntityCollection entitySet, final String count) { if ("true".equals(count)) { entitySet.setCount(entitySet.getEntities().size()); } } - @Override - public InputStream exploreMultipart( + private Response bodyPartRequest(final MimeBodyPart body, final Map<String, String> references) throws Exception { + @SuppressWarnings("unchecked") + final Enumeration<Header> en = body.getAllHeaders(); + + Header header = en.nextElement(); + final String request = + header.getName() + (StringUtils.isNotBlank(header.getValue()) ? ":" + header.getValue() : ""); + + final Matcher matcher = REQUEST_PATTERN.matcher(request); + final Matcher matcherRef = BATCH_REQUEST_REF_PATTERN.matcher(request); + + final MultivaluedMap<String, String> headers = new MultivaluedHashMap<String, String>(); + + while (en.hasMoreElements()) { + header = en.nextElement(); + headers.putSingle(header.getName(), header.getValue()); + } + + final Response res; + final String url; + final String method; + + if (matcher.find()) { + url = matcher.group(2); + method = matcher.group(1); + } else if (matcherRef.find()) { + url = references.get(matcherRef.group(2)) + matcherRef.group(3); + method = matcherRef.group(1); + } else { + url = null; + method = null; + } + + if (url == null) { + res = null; + } else { + final WebClient client = WebClient.create(url, "odatajclient", "odatajclient", null); + client.headers(headers); + + if ("DELETE".equals(method)) { + res = client.delete(); + } else { + final InputStream is = body.getDataHandler().getInputStream(); + String content = IOUtils.toString(is); + IOUtils.closeQuietly(is); + + final Matcher refs = REF_PATTERN.matcher(content); + + while (refs.find()) { + content = content.replace(refs.group(1), references.get(refs.group(1))); + } + + if ("PATCH".equals(method) || "MERGE".equals(method)) { + client.header("X-HTTP-METHOD", method); + res = client.invoke("POST", IOUtils.toInputStream(content)); + } else { + res = client.invoke(method, IOUtils.toInputStream(content)); + } + } + + // When updating to CXF 3.0.1, uncomment the following line, see CXF-5865 + // client.close(); + } + + return res; + } + + private InputStream exploreMultipart( final List<Attachment> attachments, final String boundary, final boolean continueOnError) throws IOException { @@ -349,9 +555,10 @@ public class Services extends AbstractServices { goon = continueOnError; } } else { - addItemIntro(bos); + addItemIntro(bos, null); - res = bodyPartRequest(new MimeBodyPart(obj.getDataHandler().getInputStream())); + res = bodyPartRequest(new MimeBodyPart(obj.getDataHandler().getInputStream()), + Collections.<String, String> emptyMap()); if (res.getStatus() >= 400) { goon = continueOnError; @@ -374,121 +581,131 @@ public class Services extends AbstractServices { return new ByteArrayInputStream(bos.toByteArray()); } - @GET - @Path("/People/{type:.*}") - public Response getPeople( - @Context final UriInfo uriInfo, - @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @PathParam("type") final String type, - @QueryParam("$top") @DefaultValue(StringUtils.EMPTY) final String top, - @QueryParam("$skip") @DefaultValue(StringUtils.EMPTY) final String skip, - @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, - @QueryParam("$count") @DefaultValue(StringUtils.EMPTY) final String count, - @QueryParam("$filter") @DefaultValue(StringUtils.EMPTY) final String filter, - @QueryParam("$search") @DefaultValue(StringUtils.EMPTY) final String search, - @QueryParam("$orderby") @DefaultValue(StringUtils.EMPTY) final String orderby, - @QueryParam("$skiptoken") @DefaultValue(StringUtils.EMPTY) final String skiptoken) { + private void addItemIntro(final ByteArrayOutputStream bos, final String contentId) throws IOException { + bos.write("Content-Type: application/http".getBytes()); + bos.write(Constants.CRLF); + bos.write("Content-Transfer-Encoding: binary".getBytes()); + bos.write(Constants.CRLF); - return StringUtils.isBlank(filter) && StringUtils.isBlank(search) - ? NumberUtils.isNumber(type) - ? super.getEntityInternal( - uriInfo.getRequestUri().toASCIIString(), accept, "People", type, format, null, null) - : super.getEntitySet(accept, "People", type) - : super.getEntitySet(uriInfo, accept, "People", top, skip, format, count, filter, orderby, skiptoken, type); + if (StringUtils.isNotBlank(contentId)) { + bos.write(("Content-ID: " + contentId).getBytes()); + bos.write(Constants.CRLF); + } + + bos.write(Constants.CRLF); } - @GET - @Path("/Boss") - public Response getSingletonBoss( - @Context final UriInfo uriInfo, - @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + private void addChangesetItemIntro( + final ByteArrayOutputStream bos, final String contentId, final String cboundary) throws IOException { + bos.write(("--" + cboundary).getBytes()); + bos.write(Constants.CRLF); + bos.write(("Content-ID: " + contentId).getBytes()); + bos.write(Constants.CRLF); + addItemIntro(bos, null); + } - return getEntityInternal( - uriInfo.getRequestUri().toASCIIString(), accept, "Boss", StringUtils.EMPTY, format, null, null); + private void addSingleBatchResponse( + final Response response, final ByteArrayOutputStream bos) throws IOException { + addSingleBatchResponse(response, null, bos); } - @GET - @Path("/Company") - public Response getSingletonCompany( - @Context final UriInfo uriInfo, - @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + private void addSingleBatchResponse( + final Response response, final String contentId, final ByteArrayOutputStream bos) throws IOException { + bos.write("HTTP/1.1 ".getBytes()); + bos.write(String.valueOf(response.getStatusInfo().getStatusCode()).getBytes()); + bos.write(" ".getBytes()); + bos.write(response.getStatusInfo().getReasonPhrase().getBytes()); + bos.write(Constants.CRLF); - return getEntityInternal( - uriInfo.getRequestUri().toASCIIString(), accept, "Company", StringUtils.EMPTY, format, null, null); + for (Map.Entry<String, List<Object>> header : response.getHeaders().entrySet()) { + final StringBuilder builder = new StringBuilder(); + for (Object value : header.getValue()) { + if (builder.length() > 0) { + builder.append(", "); + } + builder.append(value.toString()); + } + builder.insert(0, ": ").insert(0, header.getKey()); + bos.write(builder.toString().getBytes()); + bos.write(Constants.CRLF); + } + + if (StringUtils.isNotBlank(contentId)) { + bos.write(("Content-ID: " + contentId).getBytes()); + bos.write(Constants.CRLF); + } + + bos.write(Constants.CRLF); + + final Object entity = response.getEntity(); + if (entity != null) { + bos.write(IOUtils.toByteArray((InputStream) entity)); + bos.write(Constants.CRLF); + } + + bos.write(Constants.CRLF); } - @PATCH - @Path("/Company") - @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) - @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) - public Response patchSingletonCompany( - @Context final UriInfo uriInfo, - @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, - @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, - @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) final String ifMatch, - final String changes) { + protected void addErrorBatchResponse(final Exception e, final ByteArrayOutputStream bos) + throws IOException { + addErrorBatchResponse(e, null, bos); + } - return super.patchEntity(uriInfo, accept, contentType, prefer, ifMatch, "Company", StringUtils.EMPTY, changes); + protected void addErrorBatchResponse(final Exception e, final String contentId, final ByteArrayOutputStream bos) + throws IOException { + addSingleBatchResponse(xml.createFaultResponse(Accept.XML.toString(), e), contentId, bos); } + /** + * Retrieve entities from the given entity set and the given type. + * + * @param accept Accept header. + * @param name entity set. + * @param type entity type. + * @return entity set. + */ @GET - @Path("/Customers") - public Response getCustomers( - @Context final UriInfo uriInfo, + @Path("/{name}/{type:[a-zA-Z].*}") + public Response getEntitySet( @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, - @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, - @QueryParam("$deltatoken") @DefaultValue(StringUtils.EMPTY) final String deltatoken) { + @PathParam("name") final String name, + @PathParam("type") final String type) { try { - final Accept acceptType; - if (StringUtils.isNotBlank(format)) { - acceptType = Accept.valueOf(format.toUpperCase()); - } else { - acceptType = Accept.parse(accept); + final Accept acceptType = Accept.parse(accept); + if (acceptType == Accept.XML || acceptType == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); } - final InputStream output; - if (StringUtils.isBlank(deltatoken)) { - final InputStream input = (InputStream) getEntitySet( - uriInfo, accept, "Customers", null, null, format, null, null, null, null).getEntity(); - final EntityCollection entitySet = xml.readEntitySet(acceptType, input); - - boolean trackChanges = prefer.contains("odata.track-changes"); - if (trackChanges) { - entitySet.setDeltaLink(URI.create("Customers?$deltatoken=8015")); - } + final String basePath = name + File.separatorChar; + final StringBuilder path = new StringBuilder(name). + append(File.separatorChar).append(type). + append(File.separatorChar); - output = xml.writeEntitySet(acceptType, new ResWrap<EntityCollection>( - URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + "Customers"), - null, - entitySet)); - } else { - output = FSManager.instance(version).readFile("delta", acceptType); - } + path.append(metadata.getEntitySet(name).isSingleton() + ? Constants.get(ConstantKey.ENTITY) + : Constants.get(ConstantKey.FEED)); - final Response response = xml.createResponse( - null, - output, - null, - acceptType); - if (StringUtils.isNotBlank(prefer)) { - response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer)); - } - return response; + final InputStream feed = FSManager.instance().readFile(path.toString(), acceptType); + return xml.createResponse(null, feed, Commons.getETag(basePath), acceptType); } catch (Exception e) { return xml.createFaultResponse(accept, e); } } @GET - @Path("/Company/Microsoft.Test.OData.Services.ODataWCFService.GetEmployeesCount{paren:[\\(\\)]*}") - public Response functionGetEmployeesCount( + @Path("/{name}/{type:[a-zA-Z].*}") + public Response getEntitySet(@Context final UriInfo uriInfo, @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + @PathParam("name") final String name, + @QueryParam("$top") @DefaultValue(StringUtils.EMPTY) final String top, + @QueryParam("$skip") @DefaultValue(StringUtils.EMPTY) final String skip, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + @QueryParam("$count") @DefaultValue(StringUtils.EMPTY) final String count, + @QueryParam("$filter") @DefaultValue(StringUtils.EMPTY) final String filter, + @QueryParam("$orderby") @DefaultValue(StringUtils.EMPTY) final String orderby, + @QueryParam("$skiptoken") @DefaultValue(StringUtils.EMPTY) final String skiptoken, + @PathParam("type") final String type) { try { final Accept acceptType; @@ -498,111 +715,450 @@ public class Services extends AbstractServices { acceptType = Accept.parse(accept); } - final Property property = new Property(); - property.setType("Edm.Int32"); - property.setValue(ValueType.PRIMITIVE, 2); - final ResWrap<Property> container = new ResWrap<Property>( - URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), null, - property); + final String location = uriInfo.getRequestUri().toASCIIString(); + try { + // search for function ... + final InputStream func = FSManager.instance().readFile(name, acceptType); + return xml.createResponse(location, func, null, acceptType); + } catch (NotFoundException e) { + if (acceptType == Accept.XML || acceptType == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } - return xml.createResponse( - null, - xml.writeProperty(acceptType, container), - null, - acceptType); - } catch (Exception e) { - return xml.createFaultResponse(accept, e); - } - } + // search for entitySet ... + final String basePath = name + File.separatorChar; - @POST - @Path("/Company/Microsoft.Test.OData.Services.ODataWCFService.IncreaseRevenue{paren:[\\(\\)]*}") - public Response actionIncreaseRevenue( - @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, - @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, - final String param) { + final StringBuilder builder = new StringBuilder(); + builder.append(basePath); - try { - final Accept acceptType; - if (StringUtils.isNotBlank(format)) { - acceptType = Accept.valueOf(format.toUpperCase()); - } else { - acceptType = Accept.parse(accept); - } + if (type != null) { + builder.append(type).append(File.separatorChar); + } - final Accept contentTypeValue = Accept.parse(contentType); - final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING)); + if (StringUtils.isNotBlank(orderby)) { + builder.append(Constants.get(ConstantKey.ORDERBY)).append(File.separatorChar). + append(orderby).append(File.separatorChar); + } - return xml.createResponse( - null, - xml.writeProperty(acceptType, entry.getProperty("IncreaseValue")), - null, - acceptType); + if (StringUtils.isNotBlank(filter)) { + builder.append(Constants.get(ConstantKey.FILTER)).append(File.separatorChar). + append(filter.replaceAll("/", ".")); + } else if (StringUtils.isNotBlank(skiptoken)) { + builder.append(Constants.get(ConstantKey.SKIP_TOKEN)).append(File.separatorChar). + append(skiptoken); + } else { + builder.append(metadata.getEntitySet(name).isSingleton() + ? Constants.get(ConstantKey.ENTITY) + : Constants.get(ConstantKey.FEED)); + } + + final InputStream feed = FSManager.instance().readFile(builder.toString(), Accept.ATOM); + + final ResWrap<EntityCollection> container = atomDeserializer.toEntitySet(feed); + + setInlineCount(container.getPayload(), count); + + final ByteArrayOutputStream content = new ByteArrayOutputStream(); + final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING); + + // ----------------------------------------------- + // Evaluate $skip and $top + // ----------------------------------------------- + List<Entity> entries = new ArrayList<Entity>(container.getPayload().getEntities()); + + if (StringUtils.isNotBlank(skip)) { + entries = entries.subList(Integer.valueOf(skip), entries.size()); + } + + if (StringUtils.isNotBlank(top)) { + entries = entries.subList(0, Integer.valueOf(top)); + } + + container.getPayload().getEntities().clear(); + container.getPayload().getEntities().addAll(entries); + // ----------------------------------------------- + + if (acceptType == Accept.ATOM) { + atomSerializer.write(writer, container); + } else { + jsonSerializer.write(writer, container); + } + writer.flush(); + writer.close(); + + return xml.createResponse( + location, + new ByteArrayInputStream(content.toByteArray()), + Commons.getETag(basePath), + acceptType); + } } catch (Exception e) { return xml.createFaultResponse(accept, e); } } + /** + * Retrieve entity set or function execution sample. + * + * @param accept Accept header. + * @param name entity set or function name. + * @param format format query option. + * @param count count query option. + * @param filter filter query option. + * @param orderby orderby query option. + * @param skiptoken skiptoken query option. + * @return entity set or function result. + */ @GET - @Path("/Products({entityId})/Microsoft.Test.OData.Services.ODataWCFService.GetProductDetails({param:.*})") - public Response functionGetProductDetails( + @Path("/{name}") + public Response getEntitySet( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("name") final String name, + @QueryParam("$top") @DefaultValue(StringUtils.EMPTY) final String top, + @QueryParam("$skip") @DefaultValue(StringUtils.EMPTY) final String skip, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + @QueryParam("$count") @DefaultValue(StringUtils.EMPTY) final String count, + @QueryParam("$filter") @DefaultValue(StringUtils.EMPTY) final String filter, + @QueryParam("$orderby") @DefaultValue(StringUtils.EMPTY) final String orderby, + @QueryParam("$skiptoken") @DefaultValue(StringUtils.EMPTY) final String skiptoken) { + + return getEntitySet(uriInfo, accept, name, top, skip, format, count, filter, orderby, skiptoken, null); + } + + @GET + @Path("/Person({entityId})") + public Response getPerson( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + final Response internal = getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, "Person", entityId, format, null, null); + if (internal.getStatus() == 200) { + InputStream entity = (InputStream) internal.getEntity(); + try { + if (utils.getKey() == Accept.JSON_FULLMETA || utils.getKey() == Accept.ATOM) { + entity = utils.getValue().addOperation(entity, "Sack", "#DefaultContainer.Sack", + uriInfo.getAbsolutePath().toASCIIString() + + "/Microsoft.Test.OData.Services.AstoriaDefaultService.SpecialEmployee/Sack"); + } + + return utils.getValue().createResponse( + uriInfo.getRequestUri().toASCIIString(), + entity, + internal.getHeaderString("ETag"), + utils.getKey()); + } catch (Exception e) { + LOG.error("Error retrieving entity", e); + return xml.createFaultResponse(accept, e); + } + } else { + return internal; + } + } + + @GET + @Path("/Product({entityId})") + public Response getProduct( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + final Response internal = getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, "Product", entityId, format, null, null); + if (internal.getStatus() == 200) { + InputStream entity = (InputStream) internal.getEntity(); + try { + if (utils.getKey() == Accept.JSON_FULLMETA || utils.getKey() == Accept.ATOM) { + entity = utils.getValue().addOperation(entity, + "ChangeProductDimensions", "#DefaultContainer.ChangeProductDimensions", + uriInfo.getAbsolutePath().toASCIIString() + "/ChangeProductDimensions"); + } + + return utils.getValue().createResponse( + uriInfo.getRequestUri().toASCIIString(), + entity, + internal.getHeaderString("ETag"), + utils.getKey()); + } catch (Exception e) { + LOG.error("Error retrieving entity", e); + return xml.createFaultResponse(accept, e); + } + } else { + return internal; + } + } + + @GET + @Path("/ComputerDetail({entityId})") + public Response getComputerDetail( + @Context final UriInfo uriInfo, @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, @PathParam("entityId") final String entityId, @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + final Response internal = getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, "ComputerDetail", entityId, format, null, null); + if (internal.getStatus() == 200) { + InputStream entity = (InputStream) internal.getEntity(); + try { + if (utils.getKey() == Accept.JSON_FULLMETA || utils.getKey() == Accept.ATOM) { + entity = utils.getValue().addOperation(entity, + "ResetComputerDetailsSpecifications", "#DefaultContainer.ResetComputerDetailsSpecifications", + uriInfo.getAbsolutePath().toASCIIString() + "/ResetComputerDetailsSpecifications"); + } + + return utils.getValue().createResponse( + uriInfo.getRequestUri().toASCIIString(), + entity, + internal.getHeaderString("ETag"), + utils.getKey()); + } catch (Exception e) { + LOG.error("Error retrieving entity", e); + return xml.createFaultResponse(accept, e); + } + } else { + return internal; + } + } + + /** + * Retrieve entity sample. + * + * @param accept Accept header. + * @param entitySetName Entity set name. + * @param entityId entity id. + * @param format format query option. + * @param expand expand query option. + * @param select select query option. + * @return entity. + */ + @GET + @Path("/{entitySetName}({entityId})") + public Response getEntity( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entitySetName") final String entitySetName, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + @QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) final String expand, + @QueryParam("$select") @DefaultValue(StringUtils.EMPTY) final String select) { + + return getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, entitySetName, entityId, format, expand, select); + } + + protected Response getEntityInternal( + final String location, + final String accept, + final String entitySetName, + final String entityId, + final String format, + final String expand, + final String select) { + try { - final Accept acceptType; - if (StringUtils.isNotBlank(format)) { - acceptType = Accept.valueOf(format.toUpperCase()); - } else { - acceptType = Accept.parse(accept); + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); } - final Entity entry = new Entity(); - entry.setType("Microsoft.Test.OData.Services.ODataWCFService.ProductDetail"); - final Property productId = new Property(); - productId.setName("ProductID"); - productId.setType("Edm.Int32"); - productId.setValue(ValueType.PRIMITIVE, Integer.valueOf(entityId)); - entry.getProperties().add(productId); - final Property productDetailId = new Property(); - productDetailId.setName("ProductDetailID"); - productDetailId.setType("Edm.Int32"); - productDetailId.setValue(ValueType.PRIMITIVE, 2); - entry.getProperties().add(productDetailId); + final Map.Entry<String, InputStream> entityInfo = + utils.getValue().readEntity(entitySetName, entityId, Accept.ATOM); - final Link link = new Link(); - link.setRel("edit"); - link.setHref(URI.create( - Constants.get(ConstantKey.DEFAULT_SERVICE_URL) - + "ProductDetails(ProductID=6,ProductDetailID=1)").toASCIIString()); - entry.setEditLink(link); + final InputStream entity = entityInfo.getValue(); - final EntityCollection feed = new EntityCollection(); - feed.getEntities().add(entry); + ResWrap<Entity> container = atomDeserializer.toEntity(entity); + if (container.getContextURL() == null) { + container = new ResWrap<Entity>(URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + + entitySetName + Constants.get(ConstantKey.ODATA_METADATA_ENTITY_SUFFIX)), + container.getMetadataETag(), container.getPayload()); + } + final Entity entry = container.getPayload(); - final ResWrap<EntityCollection> container = new ResWrap<EntityCollection>( - URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + "ProductDetail"), null, - feed); + if ((this instanceof KeyAsSegment)) { + final Link editLink = new Link(); + editLink.setRel("edit"); + editLink.setTitle(entitySetName); + editLink.setHref(Constants.get(ConstantKey.DEFAULT_SERVICE_URL) + entitySetName + "/" + entityId); + + entry.setEditLink(editLink); + } + + if (StringUtils.isNotBlank(select)) { + final List<String> properties = Arrays.asList(select.split(",")); + final Set<Property> toBeRemoved = new HashSet<Property>(); + + for (Property property : entry.getProperties()) { + if (!properties.contains(property.getName())) { + toBeRemoved.add(property); + } + } + + entry.getProperties().removeAll(toBeRemoved); + + final Set<Link> linkToBeRemoved = new HashSet<Link>(); + + for (Link link : entry.getNavigationLinks()) { + if (!properties.contains(link.getTitle().replaceAll("@.*$", "")) && !properties.contains(link.getTitle())) { + linkToBeRemoved.add(link); + } + } + + entry.getNavigationLinks().removeAll(linkToBeRemoved); + } + + String tempExpand = expand; + if (StringUtils.isNotBlank(tempExpand)) { + tempExpand = StringUtils.substringBefore(tempExpand, "("); + final List<String> links = Arrays.asList(tempExpand.split(",")); + + final Map<Link, Link> replace = new HashMap<Link, Link>(); + + for (Link link : entry.getNavigationLinks()) { + if (links.contains(link.getTitle())) { + // expand link + final Link rep = new Link(); + rep.setHref(link.getHref()); + rep.setRel(link.getRel()); + rep.setTitle(link.getTitle()); + rep.setType(link.getType()); + if (link.getType().equals(Constants.get(ConstantKey.ATOM_LINK_ENTRY))) { + // inline entry + final Entity inline = atomDeserializer.toEntity( + xml.expandEntity(entitySetName, entityId, link.getTitle())).getPayload(); + rep.setInlineEntity(inline); + } else if (link.getType().equals(Constants.get(ConstantKey.ATOM_LINK_FEED))) { + // inline feed + final EntityCollection inline = atomDeserializer.toEntitySet( + xml.expandEntity(entitySetName, entityId, link.getTitle())).getPayload(); + rep.setInlineEntitySet(inline); + } + replace.put(link, rep); + } + } + + for (Map.Entry<Link, Link> link : replace.entrySet()) { + entry.getNavigationLinks().remove(link.getKey()); + entry.getNavigationLinks().add(link.getValue()); + } + } return xml.createResponse( - null, - xml.writeEntitySet(acceptType, container), - null, - acceptType); + location, + xml.writeEntity(utils.getKey(), container), + Commons.getETag(entityInfo.getKey()), + utils.getKey()); } catch (Exception e) { + LOG.error("Error retrieving entity", e); return xml.createFaultResponse(accept, e); } } - @POST - @Path("/Products({entityId})/Microsoft.Test.OData.Services.ODataWCFService.AddAccessRight{paren:[\\(\\)]*}") - public Response actionAddAccessRight( + @GET + @Path("/{entitySetName}({entityId})/$value") + public Response getMediaEntity( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entitySetName") final String entitySetName, + @PathParam("entityId") final String entityId) { + + try { + if (!accept.contains("*/*") && !accept.contains("application/octet-stream")) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + final AbstractUtilities utils = getUtilities(null); + final Map.Entry<String, InputStream> entityInfo = utils.readMediaEntity(entitySetName, entityId); + return utils.createResponse( + uriInfo.getRequestUri().toASCIIString(), + entityInfo.getValue(), + Commons.getETag(entityInfo.getKey()), + null); + + } catch (Exception e) { + LOG.error("Error retrieving entity", e); + return xml.createFaultResponse(accept, e); + } + } + + @GET + @Path("/People/{type:.*}") + public Response getPeople( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("type") final String type, + @QueryParam("$top") @DefaultValue(StringUtils.EMPTY) final String top, + @QueryParam("$skip") @DefaultValue(StringUtils.EMPTY) final String skip, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + @QueryParam("$count") @DefaultValue(StringUtils.EMPTY) final String count, + @QueryParam("$filter") @DefaultValue(StringUtils.EMPTY) final String filter, + @QueryParam("$search") @DefaultValue(StringUtils.EMPTY) final String search, + @QueryParam("$orderby") @DefaultValue(StringUtils.EMPTY) final String orderby, + @QueryParam("$skiptoken") @DefaultValue(StringUtils.EMPTY) final String skiptoken) { + + return StringUtils.isBlank(filter) && StringUtils.isBlank(search) ? + NumberUtils.isNumber(type) ? + getEntityInternal(uriInfo.getRequestUri().toASCIIString(), accept, "People", type, format, null, null) : + getEntitySet(accept, "People", type) : + getEntitySet(uriInfo, accept, "People", top, skip, format, count, filter, orderby, skiptoken, type); + } + + @GET + @Path("/Boss") + public Response getSingletonBoss( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + return getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, "Boss", StringUtils.EMPTY, format, null, null); + } + + @GET + @Path("/Company") + public Response getSingletonCompany( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + return getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, "Company", StringUtils.EMPTY, format, null, null); + } + + @PATCH + @Path("/Company") + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + public Response patchSingletonCompany( + @Context final UriInfo uriInfo, @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, + @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) final String ifMatch, + final String changes) { + + return patchEntityInternal(uriInfo, accept, contentType, prefer, ifMatch, "Company", StringUtils.EMPTY, changes); + } + + @GET + @Path("/Customers") + public Response getCustomers( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, - final String param) { + @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, + @QueryParam("$deltatoken") @DefaultValue(StringUtils.EMPTY) final String deltatoken) { try { final Accept acceptType; @@ -612,236 +1168,1448 @@ public class Services extends AbstractServices { acceptType = Accept.parse(accept); } - final Accept contentTypeValue = Accept.parse(contentType); - final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING)); - - assert 1 == entry.getProperties().size(); - assert entry.getProperty("accessRight") != null; + final InputStream output; + if (StringUtils.isBlank(deltatoken)) { + final InputStream input = (InputStream) getEntitySet( + uriInfo, accept, "Customers", null, null, format, null, null, null, null).getEntity(); + final EntityCollection entitySet = xml.readEntitySet(acceptType, input); - final Property property = entry.getProperty("accessRight"); - property.setType("Microsoft.Test.OData.Services.ODataWCFService.AccessLevel"); + boolean trackChanges = prefer.contains("odata.track-changes"); + if (trackChanges) { + entitySet.setDeltaLink(URI.create("Customers?$deltatoken=8015")); + } - final ResWrap<Property> result = new ResWrap<Property>( - URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), - null, property); + output = xml.writeEntitySet(acceptType, new ResWrap<EntityCollection>( + URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + "Customers"), + null, + entitySet)); + } else { + output = FSManager.instance().readFile("delta", acceptType); + } - return xml.createResponse( + final Response response = xml.createResponse( null, - xml.writeProperty(acceptType, result), + output, null, acceptType); + if (StringUtils.isNotBlank(prefer)) { + response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer)); + } + return response; } catch (Exception e) { return xml.createFaultResponse(accept, e); } } @POST - @Path("/Customers({personId})/Microsoft.Test.OData.Services.ODataWCFService.ResetAddress{paren:[\\(\\)]*}") - public Response actionResetAddress( + @Path("/{entitySetName}") + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM }) + public Response postNewEntity( @Context final UriInfo uriInfo, @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, - @PathParam("personId") final String personId, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, + @PathParam("entitySetName") final String entitySetName, + final String entity) { + + try { + final Accept acceptType = Accept.parse(accept); + if (acceptType == Accept.XML || acceptType == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + final ResWrap<Entity> container; + + final org.apache.olingo.fit.metadata.EntitySet entitySet = metadata.getEntitySet(entitySetName); + + final Entity entry; + final String entityKey; + if (xml.isMediaContent(entitySetName)) { + entry = new Entity(); + entry.setMediaContentType(ContentType.APPLICATION_OCTET_STREAM.toContentTypeString()); + entry.setType(entitySet.getType()); + + entityKey = xml.getDefaultEntryKey(entitySetName, entry); + + xml.addMediaEntityValue(entitySetName, entityKey, IOUtils.toInputStream(entity, Constants.ENCODING)); + + final Pair<String, EdmPrimitiveTypeKind> id = Commons.getMediaContent().get(entitySetName); + if (id != null) { + final Property prop = new Property(); + prop.setName(id.getKey()); + prop.setType(id.getValue().toString()); + prop.setValue(ValueType.PRIMITIVE, + id.getValue() == EdmPrimitiveTypeKind.Int32 + ? Integer.parseInt(entityKey) + : id.getValue() == EdmPrimitiveTypeKind.Guid + ? UUID.fromString(entityKey) + : entityKey); + entry.getProperties().add(prop); + } + + final Link editLink = new Link(); + editLink.setHref(Commons.getEntityURI(entitySetName, entityKey)); + editLink.setRel("edit"); + editLink.setTitle(entitySetName); + entry.setEditLink(editLink); + + entry.setMediaContentSource(URI.create(editLink.getHref() + "/$value")); + + container = new ResWrap<Entity>((URI) null, null, entry); + } else { + final Accept contentTypeValue = Accept.parse(contentType); + if (Accept.ATOM == contentTypeValue) { + container = atomDeserializer.toEntity(IOUtils.toInputStream(entity, Constants.ENCODING)); + } else { + container = jsonDeserializer.toEntity(IOUtils.toInputStream(entity, Constants.ENCODING)); + } + entry = container.getPayload(); + updateInlineEntities(entry); + + entityKey = xml.getDefaultEntryKey(entitySetName, entry); + } + + normalizeAtomEntry(entry, entitySetName, entityKey); + + final ByteArrayOutputStream content = new ByteArrayOutputStream(); + final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING); + atomSerializer.write(writer, container); + writer.flush(); + writer.close(); + + final InputStream serialization = + xml.addOrReplaceEntity(entityKey, entitySetName, new ByteArrayInputStream(content.toByteArray()), entry); + + ResWrap<Entity> result = atomDeserializer.toEntity(serialization); + result = new ResWrap<Entity>( + URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + + entitySetName + Constants.get(ConstantKey.ODATA_METADATA_ENTITY_SUFFIX)), + null, result.getPayload()); + + final String path = Commons.getEntityBasePath(entitySetName, entityKey); + FSManager.instance().putInMemory(result, path + Constants.get(ConstantKey.ENTITY)); + + final String location; + + if ((this instanceof KeyAsSegment)) { + location = uriInfo.getRequestUri().toASCIIString() + "/" + entityKey; + + final Link editLink = new Link(); + editLink.setRel("edit"); + editLink.setTitle(entitySetName); + editLink.setHref(location); + + result.getPayload().setEditLink(editLink); + } else { + location = uriInfo.getRequestUri().toASCIIString() + "(" + entityKey + ")"; + } + + final Response response; + if ("return-no-content".equalsIgnoreCase(prefer)) { + response = xml.createResponse( + location, + null, + null, + acceptType, + Response.Status.NO_CONTENT); + } else { + response = xml.createResponse( + location, + xml.writeEntity(acceptType, result), + null, + acceptType, + Response.Status.CREATED); + } + + if (StringUtils.isNotBlank(prefer)) { + response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer)); + } + + return response; + } catch (Exception e) { + LOG.error("While creating new entity", e); + return xml.createFaultResponse(accept, e); + } + } + + private void updateInlineEntities(final Entity entity) { + final String type = entity.getType(); + EntityType entityType; + Map<String, NavigationProperty> navProperties = Collections.emptyMap(); + if (type != null && type.length() > 0) { + entityType = metadata.getEntityOrComplexType(type); + navProperties = entityType.getNavigationPropertyMap(); + } + + for (Property property : entity.getProperties()) { + if (navProperties.containsKey(property.getName())) { + Link alink = new Link(); + alink.setTitle(property.getName()); + alink.getAnnotations().addAll(property.getAnnotations()); + + alink.setType(navProperties.get(property.getName()).isEntitySet() + ? Constants.get(ConstantKey.ATOM_LINK_FEED) + : Constants.get(ConstantKey.ATOM_LINK_ENTRY)); + + alink.setRel(Constants.get(ConstantKey.ATOM_LINK_REL) + property.getName()); + + if (property.isCollection()) { + EntityCollection inline = new EntityCollection(); + for (Object value : property.asCollection()) { + Entity inlineEntity = new Entity(); + inlineEntity.setType(navProperties.get(property.getName()).getType()); + for (Property prop : ((ComplexValue) value).getValue()) { + inlineEntity.getProperties().add(prop); + } + inline.getEntities().add(inlineEntity); + } + alink.setInlineEntitySet(inline); + } else if (property.isComplex()) { + Entity inline = new Entity(); + inline.setType(navProperties.get(property.getName()).getType()); + for (Property prop : property.asComplex().getValue()) { + inline.getProperties().add(prop); + } + alink.setInlineEntity(inline); + + } else { + throw new IllegalStateException("Invalid navigation property " + property); + } + entity.getNavigationLinks().add(alink); + } + } + } + + private void normalizeAtomEntry(final Entity entry, final String entitySetName, final String entityKey) { + final org.apache.olingo.fit.metadata.EntitySet entitySet = metadata.getEntitySet(entitySetName); + final EntityType entityType = metadata.getEntityOrComplexType(entitySet.getType()); + for (Map.Entry<String, org.apache.olingo.fit.metadata.Property> property : entityType.getPropertyMap().entrySet()) { + if (entry.getProperty(property.getKey()) == null && property.getValue().isNullable()) { + final Property prop = new Property(); + prop.setName(property.getKey()); + prop.setValue(ValueType.PRIMITIVE, null); + entry.getProperties().add(prop); + } + } + + for (Map.Entry<String, NavigationProperty> property : entityType.getNavigationPropertyMap().entrySet()) { + boolean found = false; + for (Link link : entry.getNavigationLinks()) { + if (link.getTitle().equals(property.getKey())) { + found = true; + } + } + + if (!found) { + final Link link = new Link(); + link.setTitle(property.getKey()); + link.setType(property.getValue().isEntitySet() + ? Constants.get(ConstantKey.ATOM_LINK_FEED) + : Constants.get(ConstantKey.ATOM_LINK_ENTRY)); + link.setRel(Constants.get(ConstantKey.ATOM_LINK_REL) + property.getKey()); + link.setHref(entitySetName + "(" + entityKey + ")/" + property.getKey()); + entry.getNavigationLinks().add(link); + } + } + } + + @GET + @Path("/Company/Microsoft.Test.OData.Services.ODataWCFService.GetEmployeesCount{paren:[\\(\\)]*}") + public Response functionGetEmployeesCount( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + try { + final Accept acceptType; + if (StringUtils.isNotBlank(format)) { + acceptType = Accept.valueOf(format.toUpperCase()); + } else { + acceptType = Accept.parse(accept); + } + + final Property property = new Property(); + property.setType("Edm.Int32"); + property.setValue(ValueType.PRIMITIVE, 2); + final ResWrap<Property> container = new ResWrap<Property>( + URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), null, + property); + + return xml.createResponse( + null, + xml.writeProperty(acceptType, container), + null, + acceptType); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/Person({entityId})/{type:.*}/Sack") + public Response actionSack( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + try { + final Map.Entry<String, InputStream> entityInfo = xml.readEntity("Person", entityId, Accept.ATOM); + + final InputStream entity = entityInfo.getValue(); + final ResWrap<Entity> container = atomDeserializer.toEntity(entity); + + container.getPayload().getProperty("Salary").setValue(ValueType.PRIMITIVE, 0); + container.getPayload().getProperty("Title").setValue(ValueType.PRIMITIVE, "[Sacked]"); + + final FSManager fsManager = FSManager.instance(); + fsManager.putInMemory(xml.writeEntity(Accept.ATOM, container), + fsManager.getAbsolutePath(Commons.getEntityBasePath("Person", entityId) + Constants.get( + ConstantKey.ENTITY), Accept.ATOM)); + + return utils.getValue().createResponse(null, null, null, utils.getKey(), Response.Status.NO_CONTENT); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/Person/{type:.*}/IncreaseSalaries") + public Response actionIncreaseSalaries( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("type") final String type, + final String body) { + + final String name = "Person"; + try { + final Accept acceptType = Accept.parse(accept); + if (acceptType == Accept.XML || acceptType == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + final JsonNode tree = new ObjectMapper().readTree(body); + if (!tree.has("n")) { + throw new Exception("Missing parameter: n"); + } + final int n = tree.get("n").asInt(); + + final StringBuilder path = new StringBuilder(name). + append(File.separatorChar).append(type). + append(File.separatorChar); + + path.append(metadata.getEntitySet(name).isSingleton() + ? Constants.get(ConstantKey.ENTITY) + : Constants.get(ConstantKey.FEED)); + + final InputStream feed = FSManager.instance().readFile(path.toString(), acceptType); + + final ByteArrayOutputStream copy = new ByteArrayOutputStream(); + IOUtils.copy(feed, copy); + IOUtils.closeQuietly(feed); + + String newContent = new String(copy.toByteArray(), "UTF-8"); + final Pattern salary = Pattern.compile(acceptType == Accept.ATOM + ? "\\<d:Salary m:type=\"Edm.Int32\"\\>(-?\\d+)\\</d:Salary\\>" + : "\"Salary\":(-?\\d+),"); + final Matcher salaryMatcher = salary.matcher(newContent); + while (salaryMatcher.find()) { + final Long newSalary = Long.valueOf(salaryMatcher.group(1)) + n; + newContent = newContent. + replaceAll("\"Salary\":" + salaryMatcher.group(1) + ",", + "\"Salary\":" + newSalary + ","). + replaceAll("\\<d:Salary m:type=\"Edm.Int32\"\\>" + salaryMatcher.group(1) + "</d:Salary\\>", + "<d:Salary m:type=\"Edm.Int32\">" + newSalary + "</d:Salary>"); + } + + FSManager.instance().putInMemory(IOUtils.toInputStream(newContent, Constants.ENCODING), + FSManager.instance().getAbsolutePath(path.toString(), acceptType)); + + return xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/Product({entityId})/ChangeProductDimensions") + public Response actionChangeProductDimensions( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + final String argument) { + + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + try { + final Map.Entry<String, InputStream> entityInfo = xml.readEntity("Product", entityId, Accept.ATOM); + + final InputStream entity = entityInfo.getValue(); + final ResWrap<Entity> container = atomDeserializer.toEntity(entity); + + final Entity param = xml.readEntity(utils.getKey(), IOUtils.toInputStream(argument, Constants.ENCODING)); + + final Property property = param.getProperty("dimensions"); + container.getPayload().getProperty("Dimensions").setValue(property.getValueType(), property.getValue()); + + final FSManager fsManager = FSManager.instance(); + fsManager.putInMemory(xml.writeEntity(Accept.ATOM, container), + fsManager.getAbsolutePath(Commons.getEntityBasePath("Product", entityId) + Constants.get( + ConstantKey.ENTITY), Accept.ATOM)); + + return utils.getValue().createResponse(null, null, null, utils.getKey(), Response.Status.NO_CONTENT); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/ComputerDetail({entityId})/ResetComputerDetailsSpecifications") + public Response actionResetComputerDetailsSpecifications( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + final String argument) { + + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + if (utils.getKey() == Accept.XML || utils.getKey() == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + try { + final Map.Entry<String, InputStream> entityInfo = xml.readEntity("ComputerDetail", entityId, Accept.ATOM); + + final InputStream entity = entityInfo.getValue(); + final ResWrap<Entity> container = atomDeserializer.toEntity(entity); + + final Entity param = xml.readEntity(utils.getKey(), IOUtils.toInputStream(argument, Constants.ENCODING)); + + Property property = param.getProperty("specifications"); + container.getPayload().getProperty("SpecificationsBag").setValue(property.getValueType(), property.getValue()); + property = param.getProperty("purchaseTime"); + container.getPayload().getProperty("PurchaseDate").setValue(property.getValueType(), property.getValue()); + + final FSManager fsManager = FSManager.instance(); + fsManager.putInMemory(xml.writeEntity(Accept.ATOM, container), + fsManager.getAbsolutePath(Commons.getEntityBasePath("ComputerDetail", entityId) + Constants.get( + ConstantKey.ENTITY), Accept.ATOM)); + + return utils.getValue().createResponse(null, null, null, utils.getKey(), Response.Status.NO_CONTENT); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/Company/Microsoft.Test.OData.Services.ODataWCFService.IncreaseRevenue{paren:[\\(\\)]*}") + public Response actionIncreaseRevenue( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, final String param) { try { + final Accept acceptType; + if (StringUtils.isNotBlank(format)) { + acceptType = Accept.valueOf(format.toUpperCase()); + } else { + acceptType = Accept.parse(accept); + } + final Accept contentTypeValue = Accept.parse(contentType); final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING)); - assert 2 == entry.getProperties().size(); - assert entry.getProperty("addresses") != null; - assert entry.getProperty("index") != null; + return xml.createResponse( + null, + xml.writeProperty(acceptType, entry.getProperty("IncreaseValue")), + null, + acceptType); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @GET + @Path("/Products({entityId})/Microsoft.Test.OData.Services.ODataWCFService.GetProductDetails({param:.*})") + public Response functionGetProductDetails( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + try { + final Accept acceptType; + if (StringUtils.isNotBlank(format)) { + acceptType = Accept.valueOf(format.toUpperCase()); + } else { + acceptType = Accept.parse(accept); + } + + final Entity entry = new Entity(); + entry.setType("Microsoft.Test.OData.Services.ODataWCFService.ProductDetail"); + final Property productId = new Property(); + productId.setName("ProductID"); + productId.setType("Edm.Int32"); + productId.setValue(ValueType.PRIMITIVE, Integer.valueOf(entityId)); + entry.getProperties().add(productId); + final Property productDetailId = new Property(); + productDetailId.setName("ProductDetailID"); + productDetailId.setType("Edm.Int32"); + productDetailId.setValue(ValueType.PRIMITIVE, 2); + entry.getProperties().add(productDetailId); + + final Link link = new Link(); + link.setRel("edit"); + link.setHref(URI.create( + Constants.get(ConstantKey.DEFAULT_SERVICE_URL) + + "ProductDetails(ProductID=6,ProductDetailID=1)").toASCIIString()); + entry.setEditLink(link); + + final EntityCollection feed = new EntityCollection(); + feed.getEntities().add(entry); + + final ResWrap<EntityCollection> container = new ResWrap<EntityCollection>( + URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + "ProductDetail"), null, + feed); + + return xml.createResponse( + null, + xml.writeEntitySet(acceptType, container), + null, + acceptType); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/Products({entityId})/Microsoft.Test.OData.Services.ODataWCFService.AddAccessRight{paren:[\\(\\)]*}") + public Response actionAddAccessRight( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + final String param) { + + try { + final Accept acceptType; + if (StringUtils.isNotBlank(format)) { + acceptType = Accept.valueOf(format.toUpperCase()); + } else { + acceptType = Accept.parse(accept); + } + + final Accept contentTypeValue = Accept.parse(contentType); + final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING)); + + assert 1 == entry.getProperties().size(); + assert entry.getProperty("accessRight") != null; + + final Property property = entry.getProperty("accessRight"); + property.setType("Microsoft.Test.OData.Services.ODataWCFService.AccessLevel"); + + final ResWrap<Property> result = new ResWrap<Property>( + URI.create(Constants.get(ConstantKey.ODATA_METADATA_PREFIX) + property.getType()), + null, property); + + return xml.createResponse( + null, + xml.writeProperty(acceptType, result), + null, + acceptType); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/Customers({personId})/Microsoft.Test.OData.Services.ODataWCFService.ResetAddress{paren:[\\(\\)]*}") + public Response actionResetAddress( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("personId") final String personId, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + final String param) { + + try { + final Accept contentTypeValue = Accept.parse(contentType); + final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING)); + + assert 2 == entry.getProperties().size(); + assert entry.getProperty("addresses") != null; + assert entry.getProperty("index") != null; + + return getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, "Customers", personId, format, null, null); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @GET + @Path("/ProductDetails(ProductID={productId},ProductDetailID={productDetailId})" + + "/Microsoft.Test.OData.Services.ODataWCFService.GetRelatedProduct{paren:[\\(\\)]*}") + public Response functionGetRelatedProduct( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("productId") final String productId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + return getEntityInternal( + uriInfo.getRequestUri().toASCIIString(), accept, "Products", productId, format, null, null); + } + + @POST + @Path("/Accounts({entityId})/Microsoft.Test.OData.Services.ODataWCFService.RefreshDefaultPI{paren:[\\(\\)]*}") + public Response actionRefreshDefaultPI( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format, + final String param) { + + try { + final Accept contentTypeValue = Accept.parse(contentType); + final Entity entry = xml.readEntity(contentTypeValue, IOUtils.toInputStream(param, Constants.ENCODING)); + + assert 1 == entry.getProperties().size(); + assert entry.getProperty("newDate") != null; + + return functionGetDefaultPI(accept, entityId, format); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @GET + @Path("/Accounts({entityId})/Microsoft.Test.OData.Services.ODataWCFService.GetDefaultPI{paren:[\\(\\)]*}") + public Response functionGetDefaultPI( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + return getContainedEntity(accept, entityId, "MyPaymentInstruments", entityId + "901", format); + } + + @GET + @Path("/Accounts({entityId})/Microsoft.Test.OData.Services.ODataWCFService.GetAccountInfo{paren:[\\(\\)]*}") + public Response functionGetAccountInfo( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("entityId") final String entityId, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + return getPath(accept, "Accounts", entityId, "AccountInfo", format); + } + + @GET + @Path("/Accounts({entityId})/MyGiftCard/Microsoft.Test.OData.Services.ODataWCFService.GetActualAmount({param:.*})") + public Response functionGetActualAmount( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + try { + final Accept acceptType; + if (StringUtils.isNotBlank(format)) { + acceptType = Accept.valueOf(format.toUpperCase()); + } else { + acceptType = Accept.parse(accept); + } + + final Property property = new Property(); + property.setType("Edm.Double"); + property.setValue(ValueType.PRIMITIVE, 41.79); + + final ResWrap<Property> container = new ResWrap<Property>((URI) null, null, property); + + return xml.createResponse( + null, + xml.writeProperty(acceptType, container), + null, + acceptType); + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + /** + * Retrieve entity reference sample. + * + * @param accept Accept header. + * @param path path. + * @param format format query option. + * @return entity reference or feed of entity reference. + */ + @GET + @Path("/{path:.*}/$ref") + public Response getEntityReference( + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @PathParam("path") final String path, + @QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) { + + try { + final Map.Entry<Accept, AbstractUtilities> utils = getUtilities(accept, format); + + if (utils.getKey() == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + final String filename = Base64.encodeBase64String(path.getBytes("UTF-8")); + + return utils.getValue().createResponse( + FSManager.instance().readFile(Constants.get(ConstantKey.REF) + + File.separatorChar + filename, utils.getKey()), + null, + utils.getKey()); + } catch (Exception e) { + LOG.error("Error retrieving entity", e); + return xml.createFaultResponse(accept, e); + } + } + + @POST + @Path("/People") + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON, MediaType.APPLICATION_OCTET_STREAM }) + public Response postPeople( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, + final String entity) { + + if ("{\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.Person\"}".equals(entity)) { + return xml.createFaultResponse(accept, new BadRequestException()); + } + + return postNewEntity(uriInfo, accept, contentType, prefer, "People", entity); + } + + private Response patchEntityInternal(final UriInfo uriInfo, + final String accept, final String contentType, final String prefer, final String ifMatch, + final String entitySetName, final String entityId, final String changes) { + + try { + final Accept acceptType = Accept.parse(accept); + + if (acceptType == Accept.XML || acceptType == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + final Map.Entry<String, InputStream> entityInfo = xml.readEntity(entitySetName, entityId, Accept.ATOM); + + final String etag = Commons.getETag(entityInfo.getKey()); + if (StringUtils.isNotBlank(ifMatch) && !ifMatch.equals(etag)) { + throw new ConcurrentModificationException("Concurrent modification"); + } + + final Accept contentTypeValue = Accept.parse(contentType); + + final Entity entryChanges; + + if (contentTypeValue == Accept.XML || contentTypeValue == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } else if (contentTypeValue == Accept.ATOM) { + entryChanges = atomDeserializer.toEntity( + IOUtils.toInputStream(changes, Constants.ENCODING)).getPayload(); + } else { + final ResWrap<Entity> jcont = jsonDeserializer.toEntity(IOUtils.toInputStream(changes, Constants.ENCODING)); + entryChanges = jcont.getPayload(); + } + + final ResWrap<Entity> container = atomDeserializer.toEntity(entityInfo.getValue()); + + for (Property property : entryChanges.getProperties()) { + final Property _property = container.getPayload().getProperty(property.getName()); + if (_property == null) { + container.getPayload().getProperties().add(property); + } else { + _property.setValue(property.getValueType(), property.getValue()); + } + } + + for (Link link : entryChanges.getNavigationLinks()) { + container.getPayload().getNavigationLinks().add(link); + } + + final ByteArrayOutputStream content = new ByteArrayOutputStream(); + final OutputStreamWriter writer = new OutputStreamWriter(content, Constants.ENCODING); + atomSerializer.write(writer, container); + writer.flush(); + writer.close(); + + final InputStream res = xml.addOrReplaceEntity( + entityId, entitySetName, new ByteArrayInputStream(content.toByteArray()), container.getPayload()); + + final ResWrap<Entity> cres = atomDeserializer.toEntity(res); + + normalizeAtomEntry(cres.getPayload(), entitySetName, entityId); + + final String path = Commons.getEntityBasePath(entitySetName, entityId); + FSManager.instance().putInMemory( + cres, path + File.separatorChar + Constants.get(ConstantKey.ENTITY)); + + final Response response; + if ("return-content".equalsIgnoreCase(prefer)) { + response = xml.createResponse( + uriInfo.getRequestUri().toASCIIString(), + xml.readEntity(entitySetName, entityId, acceptType).getValue(), + null, acceptType, Response.Status.OK); + } else { + res.close(); + response = xml.createResponse( + uriInfo.getRequestUri().toASCIIString(), + null, + null, + acceptType, Response.Status.NO_CONTENT); + } + + if (StringUtils.isNotBlank(prefer)) { + response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer)); + } + + return response; + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @PATCH + @Path("/{entitySetName}({entityId})") + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + public Response patchEntity( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, + @HeaderParam("If-Match") @DefaultValue(StringUtils.EMPTY) final String ifMatch, + @PathParam("entitySetName") final String entitySetName, + @PathParam("entityId") final String entityId, + final String changes) { + + final Response response = + getEntityInternal(uriInfo.getRequestUri().toASCIIString(), + accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY); + return response.getStatus() >= 400 ? + postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, changes) : + patchEntityInternal(uriInfo, accept, contentType, prefer, ifMatch, entitySetName, entityId, changes); + } + + private Response replaceEntity(final UriInfo uriInfo, + final String accept, final String prefer, + final String entitySetName, final String entityId, final String entity) { + + try { + final Accept acceptType = Accept.parse(accept); + + if (acceptType == Accept.XML || acceptType == Accept.TEXT) { + throw new UnsupportedMediaTypeException("Unsupported media type"); + } + + final InputStream res = getUtilities(acceptType).addOrReplaceEntity(entityId, entitySetName, + IOUtils.toInputStream(entity, Constants.ENCODING), + xml.readEntity(acceptType, IOUtils.toInputStream(entity, Constants.ENCODING))); + + final ResWrap<Entity> cres; + if (acceptType == Accept.ATOM) { + cres = atomDeserializer.toEntity(res); + } else { + cres = jsonDeserializer.toEntity(res); + } + + final String path = Commons.getEntityBasePath(entitySetName, entityId); + FSManager.instance().putInMemory( + cres, path + File.separatorChar + Constants.get(ConstantKey.ENTITY)); + + final Response response; + if ("return-content".equalsIgnoreCase(prefer)) { + response = xml.createResponse( + uriInfo.getRequestUri().toASCIIString(), + xml.readEntity(entitySetName, entityId, acceptType).getValue(), + null, + acceptType, + Response.Status.OK); + } else { + res.close(); + response = xml.createResponse( + uriInfo.getRequestUri().toASCIIString(), + null, + null, + acceptType, + Response.Status.NO_CONTENT); + } + + if (StringUtils.isNotBlank(prefer)) { + response.getHeaders().put("Preference-Applied", Collections.<Object> singletonList(prefer)); + } + + return response; + } catch (Exception e) { + return xml.createFaultResponse(accept, e); + } + } + + @PUT + @Path("/{entitySetName}({entityId})") + @Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + @Consumes({ MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON }) + public Response replaceEntity( + @Context final UriInfo uriInfo, + @HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept, + @HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) final String contentType, + @HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) final String prefer, + @PathParam("entitySetName") final String entitySetName, + @PathParam("entityId") final String entityId, + final String entity) { + + try { + getEntityInternal(uriInfo.getRequestUri().toASCIIString(), + accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY); + return replaceEntity(uriInfo, accept, prefer, entitySetName, entityId, entity); + } catch (NotFoundException e) { + return postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, entityId); + } + } + + private StringBuilder containedPath(final String entityId, final String containedEntitySetName) { + return new StringBuilder("Accounts").append(File.separatorChar). + append(entityId).append(File.separatorChar). + appen
<TRUNCATED>
