[OLINGO-175, OLINGO-205, OLINGO-246] provied V3 integration test for batch features including references in changeset
This commit is contained in:
parent
47f1ec1801
commit
c953fca19d
|
@ -114,6 +114,8 @@ public abstract class AbstractServices {
|
|||
|
||||
private static final Pattern REQUEST_PATTERN = Pattern.compile("(.*) (http://.*) HTTP/.*");
|
||||
|
||||
private static final Pattern BATCH_REQUEST_REF_PATTERN = Pattern.compile("(.*) ([$].*) HTTP/.*");
|
||||
|
||||
private static final String BOUNDARY = "batch_243234_25424_ef_892u748";
|
||||
|
||||
protected final ODataServiceVersion version;
|
||||
|
@ -161,6 +163,7 @@ public abstract class AbstractServices {
|
|||
}
|
||||
|
||||
return xml.createResponse(
|
||||
null,
|
||||
FSManager.instance(version).readFile(Constants.get(version, ConstantKey.SERVICES), acceptType),
|
||||
null, acceptType);
|
||||
} catch (Exception e) {
|
||||
|
@ -182,7 +185,7 @@ public abstract class AbstractServices {
|
|||
|
||||
protected Response getMetadata(final String filename) {
|
||||
try {
|
||||
return xml.createResponse(FSManager.instance(version).readFile(filename, Accept.XML), null, Accept.XML);
|
||||
return xml.createResponse(null, FSManager.instance(version).readFile(filename, Accept.XML), null, Accept.XML);
|
||||
} catch (Exception e) {
|
||||
return xml.createFaultResponse(Accept.XML.toString(version), e);
|
||||
}
|
||||
|
@ -201,15 +204,21 @@ public abstract class AbstractServices {
|
|||
}
|
||||
|
||||
private Response bodyPartRequest(final MimeBodyPart body) throws Exception {
|
||||
return bodyPartRequest(body, Collections.<String, String>emptyMap());
|
||||
}
|
||||
|
||||
private Response bodyPartRequest(final MimeBodyPart body, final Map<String, String> references) throws Exception {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
final Enumeration<Header> en = (Enumeration<Header>) body.getAllHeaders();
|
||||
|
||||
Header header = en.nextElement();
|
||||
final String request = header.getName() + ":" + header.getValue();
|
||||
final Matcher matcher = REQUEST_PATTERN.matcher(request);
|
||||
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);
|
||||
|
||||
if (matcher.find()) {
|
||||
final MultivaluedMap<String, String> headers = new MultivaluedHashMap<String, String>();
|
||||
|
||||
while (en.hasMoreElements()) {
|
||||
|
@ -217,6 +226,7 @@ public abstract class AbstractServices {
|
|||
headers.putSingle(header.getName(), header.getValue());
|
||||
}
|
||||
|
||||
if (matcher.find()) {
|
||||
String method = matcher.group(1);
|
||||
if ("PATCH".equals(method) || "MERGE".equals(method)) {
|
||||
headers.putSingle("X-HTTP-METHOD", method);
|
||||
|
@ -226,7 +236,19 @@ public abstract class AbstractServices {
|
|||
final String url = matcher.group(2);
|
||||
|
||||
final WebClient client = WebClient.create(url);
|
||||
client.headers(headers);
|
||||
|
||||
return client.invoke(method, body.getDataHandler().getInputStream());
|
||||
} else if (matcherRef.find()) {
|
||||
String method = matcherRef.group(1);
|
||||
if ("PATCH".equals(method) || "MERGE".equals(method)) {
|
||||
headers.putSingle("X-HTTP-METHOD", method);
|
||||
method = "POST";
|
||||
}
|
||||
|
||||
final String url = matcherRef.group(2);
|
||||
|
||||
final WebClient client = WebClient.create(references.get(url));
|
||||
client.headers(headers);
|
||||
|
||||
return client.invoke(method, body.getDataHandler().getInputStream());
|
||||
|
@ -246,6 +268,8 @@ public abstract class AbstractServices {
|
|||
|
||||
final Object content = obj.getDataHandler().getContent();
|
||||
if (content instanceof MimeMultipart) {
|
||||
final Map<String, String> references = new HashMap<String, String>();
|
||||
|
||||
final String cboundary = "changeset_" + UUID.randomUUID().toString();
|
||||
bos.write(("Content-Type: multipart/mixed;boundary=" + cboundary).getBytes());
|
||||
bos.write(Constants.CRLF);
|
||||
|
@ -259,12 +283,13 @@ public abstract class AbstractServices {
|
|||
lastContebtID = part.getContentID();
|
||||
addChangesetItemIntro(chbos, lastContebtID, cboundary);
|
||||
|
||||
res = bodyPartRequest(new MimeBodyPart(part.getInputStream()));
|
||||
if (res.getStatus() > 400) {
|
||||
res = bodyPartRequest(new MimeBodyPart(part.getInputStream()), references);
|
||||
if (res.getStatus() >= 400) {
|
||||
throw new Exception("Failure processing changeset");
|
||||
}
|
||||
|
||||
addSingleBatchResponse(res, lastContebtID, chbos);
|
||||
references.put("$" + lastContebtID, res.getHeaderString("Location"));
|
||||
}
|
||||
bos.write(chbos.toByteArray());
|
||||
IOUtils.closeQuietly(chbos);
|
||||
|
@ -290,7 +315,7 @@ public abstract class AbstractServices {
|
|||
|
||||
res = bodyPartRequest(new MimeBodyPart(obj.getDataHandler().getInputStream()));
|
||||
|
||||
if (res.getStatus() > 400) {
|
||||
if (res.getStatus() >= 400) {
|
||||
throw new Exception("Failure processing changeset");
|
||||
}
|
||||
|
||||
|
@ -384,6 +409,7 @@ public abstract class AbstractServices {
|
|||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
@Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
public Response mergeEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
|
@ -392,7 +418,7 @@ public abstract class AbstractServices {
|
|||
@PathParam("entityId") String entityId,
|
||||
final String changes) {
|
||||
|
||||
return patchEntity(accept, contentType, prefer, ifMatch, entitySetName, entityId, changes);
|
||||
return patchEntity(uriInfo, accept, contentType, prefer, ifMatch, entitySetName, entityId, changes);
|
||||
}
|
||||
|
||||
@PATCH
|
||||
|
@ -400,6 +426,7 @@ public abstract class AbstractServices {
|
|||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
@Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
public Response patchEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
|
@ -442,11 +469,16 @@ public abstract class AbstractServices {
|
|||
final Response response;
|
||||
if ("return-content".equalsIgnoreCase(prefer)) {
|
||||
response = xml.createResponse(
|
||||
uriInfo.getRequestUri().toASCIIString(),
|
||||
util.readEntity(entitySetName, entityId, acceptType).getValue(),
|
||||
null, acceptType, Response.Status.OK);
|
||||
} else {
|
||||
res.close();
|
||||
response = xml.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
response = xml.createResponse(
|
||||
uriInfo.getRequestUri().toASCIIString(),
|
||||
null,
|
||||
null,
|
||||
acceptType, Response.Status.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(prefer)) {
|
||||
|
@ -464,6 +496,7 @@ public abstract class AbstractServices {
|
|||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
@Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
public Response replaceEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
|
@ -504,11 +537,19 @@ public abstract class AbstractServices {
|
|||
final Response response;
|
||||
if ("return-content".equalsIgnoreCase(prefer)) {
|
||||
response = xml.createResponse(
|
||||
uriInfo.getRequestUri().toASCIIString(),
|
||||
getUtilities(acceptType).readEntity(entitySetName, entityId, acceptType).getValue(),
|
||||
null, acceptType, Response.Status.OK);
|
||||
null,
|
||||
acceptType,
|
||||
Response.Status.OK);
|
||||
} else {
|
||||
res.close();
|
||||
response = xml.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
response = xml.createResponse(
|
||||
uriInfo.getRequestUri().toASCIIString(),
|
||||
null,
|
||||
null,
|
||||
acceptType,
|
||||
Response.Status.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(prefer)) {
|
||||
|
@ -526,6 +567,7 @@ public abstract class AbstractServices {
|
|||
@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 UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
|
@ -620,12 +662,23 @@ public abstract class AbstractServices {
|
|||
FSManager.instance(version).putInMemory(
|
||||
cres, path + File.separatorChar + Constants.get(version, ConstantKey.ENTITY));
|
||||
|
||||
final String location = uriInfo.getRequestUri().toASCIIString() + "(" + entityKey + ")";
|
||||
|
||||
final Response response;
|
||||
if ("return-no-content".equalsIgnoreCase(prefer)) {
|
||||
response = utils.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
response = utils.createResponse(
|
||||
location,
|
||||
null,
|
||||
null,
|
||||
acceptType,
|
||||
Response.Status.NO_CONTENT);
|
||||
} else {
|
||||
response = utils.createResponse(utils.readEntity(entitySetName, entityKey, acceptType).getValue(),
|
||||
null, acceptType, Response.Status.CREATED);
|
||||
response = utils.createResponse(
|
||||
location,
|
||||
utils.readEntity(entitySetName, entityKey, acceptType).getValue(),
|
||||
null,
|
||||
acceptType,
|
||||
Response.Status.CREATED);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(prefer)) {
|
||||
|
@ -672,7 +725,7 @@ public abstract class AbstractServices {
|
|||
fsManager.getAbsolutePath(Commons.getEntityBasePath("Person", entityId) + Constants.get(version,
|
||||
ConstantKey.ENTITY), utils.getKey()));
|
||||
|
||||
return utils.getValue().createResponse(null, null, utils.getKey(), Response.Status.NO_CONTENT);
|
||||
return utils.getValue().createResponse(null, null, null, utils.getKey(), Response.Status.NO_CONTENT);
|
||||
} catch (Exception e) {
|
||||
return xml.createFaultResponse(accept, e);
|
||||
}
|
||||
|
@ -730,7 +783,7 @@ public abstract class AbstractServices {
|
|||
FSManager.instance(version).putInMemory(IOUtils.toInputStream(newContent, "UTF-8"),
|
||||
FSManager.instance(version).getAbsolutePath(path.toString(), acceptType));
|
||||
|
||||
return xml.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
return xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
} catch (Exception e) {
|
||||
return xml.createFaultResponse(accept, e);
|
||||
}
|
||||
|
@ -767,7 +820,7 @@ public abstract class AbstractServices {
|
|||
: Constants.get(version, ConstantKey.FEED));
|
||||
|
||||
final InputStream feed = FSManager.instance(version).readFile(path.toString(), acceptType);
|
||||
return xml.createResponse(feed, Commons.getETag(basePath, version), acceptType);
|
||||
return xml.createResponse(null, feed, Commons.getETag(basePath, version), acceptType);
|
||||
} catch (Exception e) {
|
||||
return xml.createFaultResponse(accept, e);
|
||||
}
|
||||
|
@ -788,6 +841,7 @@ public abstract class AbstractServices {
|
|||
@GET
|
||||
@Path("/{name}")
|
||||
public Response getEntitySet(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@PathParam("name") String name,
|
||||
@QueryParam("$format") @DefaultValue(StringUtils.EMPTY) String format,
|
||||
|
@ -804,10 +858,11 @@ public abstract class AbstractServices {
|
|||
acceptType = Accept.parse(accept, version);
|
||||
}
|
||||
|
||||
final String location = uriInfo.getRequestUri().toASCIIString();
|
||||
try {
|
||||
// search for function ...
|
||||
final InputStream func = FSManager.instance(version).readFile(name, acceptType);
|
||||
return xml.createResponse(func, null, acceptType);
|
||||
return xml.createResponse(location, func, null, acceptType);
|
||||
} catch (NotFoundException e) {
|
||||
if (acceptType == Accept.XML || acceptType == Accept.TEXT) {
|
||||
throw new UnsupportedMediaTypeException("Unsupported media type");
|
||||
|
@ -859,8 +914,11 @@ public abstract class AbstractServices {
|
|||
new DataBinder(version).getJsonFeed(container.getObject())));
|
||||
}
|
||||
|
||||
return xml.createResponse(new ByteArrayInputStream(content.toByteArray()),
|
||||
Commons.getETag(basePath, version), acceptType);
|
||||
return xml.createResponse(
|
||||
location,
|
||||
new ByteArrayInputStream(content.toByteArray()),
|
||||
Commons.getETag(basePath, version),
|
||||
acceptType);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return xml.createFaultResponse(accept, e);
|
||||
|
@ -880,6 +938,7 @@ public abstract class AbstractServices {
|
|||
@GET
|
||||
@Path("/Person({entityId})")
|
||||
public Response getEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) final String accept,
|
||||
@PathParam("entityId") final String entityId,
|
||||
@QueryParam("$format") @DefaultValue(StringUtils.EMPTY) final String format) {
|
||||
|
@ -901,7 +960,10 @@ public abstract class AbstractServices {
|
|||
}
|
||||
|
||||
return utils.getValue().createResponse(
|
||||
entity, Commons.getETag(entityInfo.getKey(), version), utils.getKey());
|
||||
uriInfo.getRequestUri().toASCIIString(),
|
||||
entity,
|
||||
Commons.getETag(entityInfo.getKey(), version),
|
||||
utils.getKey());
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error retrieving entity", e);
|
||||
return xml.createFaultResponse(accept, e);
|
||||
|
@ -922,6 +984,7 @@ public abstract class AbstractServices {
|
|||
@GET
|
||||
@Path("/{entitySetName}({entityId})")
|
||||
public Response getEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@PathParam("entitySetName") String entitySetName,
|
||||
@PathParam("entityId") String entityId,
|
||||
|
@ -929,10 +992,12 @@ public abstract class AbstractServices {
|
|||
@QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) String expand,
|
||||
@QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
|
||||
|
||||
return getEntityInternal(accept, entitySetName, entityId, format, expand, select, false);
|
||||
return getEntityInternal(
|
||||
uriInfo.getRequestUri().toASCIIString(), accept, entitySetName, entityId, format, expand, select, false);
|
||||
}
|
||||
|
||||
protected Response getEntityInternal(
|
||||
final String location,
|
||||
final String accept,
|
||||
final String entitySetName,
|
||||
final String entityId,
|
||||
|
@ -1041,8 +1106,11 @@ public abstract class AbstractServices {
|
|||
(new DataBinder(version)).getJsonEntry((AtomEntryImpl) container.getObject())));
|
||||
}
|
||||
|
||||
return xml.createResponse(new ByteArrayInputStream(content.toByteArray()),
|
||||
Commons.getETag(entityInfo.getKey(), version), utils.getKey());
|
||||
return xml.createResponse(
|
||||
location,
|
||||
new ByteArrayInputStream(content.toByteArray()),
|
||||
Commons.getETag(entityInfo.getKey(), version),
|
||||
utils.getKey());
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error retrieving entity", e);
|
||||
|
@ -1053,6 +1121,7 @@ public abstract class AbstractServices {
|
|||
@GET
|
||||
@Path("/{entitySetName}({entityId})/$value")
|
||||
public Response getMediaEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@PathParam("entitySetName") String entitySetName,
|
||||
@PathParam("entityId") String entityId) {
|
||||
|
@ -1064,7 +1133,11 @@ public abstract class AbstractServices {
|
|||
|
||||
final AbstractUtilities utils = getUtilities(null);
|
||||
final Map.Entry<String, InputStream> entityInfo = utils.readMediaEntity(entitySetName, entityId);
|
||||
return utils.createResponse(entityInfo.getValue(), Commons.getETag(entityInfo.getKey(), version), null);
|
||||
return utils.createResponse(
|
||||
uriInfo.getRequestUri().toASCIIString(),
|
||||
entityInfo.getValue(),
|
||||
Commons.getETag(entityInfo.getKey(), version),
|
||||
null);
|
||||
|
||||
} catch (Exception e) {
|
||||
LOG.error("Error retrieving entity", e);
|
||||
|
@ -1084,7 +1157,7 @@ public abstract class AbstractServices {
|
|||
|
||||
FSManager.instance(version).deleteFile(basePath + Constants.get(version, ConstantKey.ENTITY));
|
||||
|
||||
return xml.createResponse(null, null, null, Response.Status.NO_CONTENT);
|
||||
return xml.createResponse(null, null, null, null, Response.Status.NO_CONTENT);
|
||||
} catch (Exception e) {
|
||||
return xml.createFaultResponse(Accept.XML.toString(version), e);
|
||||
}
|
||||
|
@ -1122,10 +1195,10 @@ public abstract class AbstractServices {
|
|||
|
||||
final Response response;
|
||||
if ("return-content".equalsIgnoreCase(prefer)) {
|
||||
response = xml.createResponse(changed, null, acceptType, Response.Status.OK);
|
||||
response = xml.createResponse(null, changed, null, acceptType, Response.Status.OK);
|
||||
} else {
|
||||
changed.close();
|
||||
response = xml.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
response = xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(prefer)) {
|
||||
|
@ -1167,10 +1240,10 @@ public abstract class AbstractServices {
|
|||
|
||||
final Response response;
|
||||
if ("return-content".equalsIgnoreCase(prefer)) {
|
||||
response = xml.createResponse(changed, null, acceptType, Response.Status.OK);
|
||||
response = xml.createResponse(null, changed, null, acceptType, Response.Status.OK);
|
||||
} else {
|
||||
changed.close();
|
||||
response = xml.createResponse(null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
response = xml.createResponse(null, null, null, acceptType, Response.Status.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(prefer)) {
|
||||
|
@ -1261,6 +1334,7 @@ public abstract class AbstractServices {
|
|||
@Consumes({MediaType.WILDCARD, MediaType.APPLICATION_OCTET_STREAM})
|
||||
@Path("/{entitySetName}({entityId})/$value")
|
||||
public Response replaceMediaEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
@PathParam("entitySetName") String entitySetName,
|
||||
@PathParam("entityId") String entityId,
|
||||
|
@ -1272,12 +1346,14 @@ public abstract class AbstractServices {
|
|||
|
||||
InputStream res = utils.putMediaInMemory(entitySetName, entityId, IOUtils.toInputStream(value));
|
||||
|
||||
final String location = uriInfo.getRequestUri().toASCIIString().replace("/$value", "");
|
||||
|
||||
final Response response;
|
||||
if ("return-content".equalsIgnoreCase(prefer)) {
|
||||
response = xml.createResponse(res, null, null, Response.Status.OK);
|
||||
response = xml.createResponse(location, res, null, null, Response.Status.OK);
|
||||
} else {
|
||||
res.close();
|
||||
response = xml.createResponse(null, null, null, Response.Status.NO_CONTENT);
|
||||
response = xml.createResponse(location, null, null, null, Response.Status.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(prefer)) {
|
||||
|
@ -1333,10 +1409,10 @@ public abstract class AbstractServices {
|
|||
|
||||
final Response response;
|
||||
if ("return-content".equalsIgnoreCase(prefer)) {
|
||||
response = xml.createResponse(res, null, null, Response.Status.OK);
|
||||
response = xml.createResponse(null, res, null, null, Response.Status.OK);
|
||||
} else {
|
||||
res.close();
|
||||
response = xml.createResponse(null, null, null, Response.Status.NO_CONTENT);
|
||||
response = xml.createResponse(null, null, null, null, Response.Status.NO_CONTENT);
|
||||
}
|
||||
|
||||
if (StringUtils.isNotBlank(prefer)) {
|
||||
|
@ -1462,7 +1538,7 @@ public abstract class AbstractServices {
|
|||
|
||||
final AbstractUtilities utils = getUtilities(null);
|
||||
final Map.Entry<String, InputStream> entityInfo = utils.readMediaEntity(entitySetName, entityId, path);
|
||||
return utils.createResponse(entityInfo.getValue(), Commons.getETag(entityInfo.getKey(), version), null);
|
||||
return utils.createResponse(null, entityInfo.getValue(), Commons.getETag(entityInfo.getKey(), version), null);
|
||||
}
|
||||
|
||||
private Response navigateEntity(
|
||||
|
@ -1511,7 +1587,7 @@ public abstract class AbstractServices {
|
|||
}
|
||||
}
|
||||
final String basePath = Commons.getEntityBasePath(entitySetName, entityId);
|
||||
return xml.createResponse(stream, Commons.getETag(basePath, version), acceptType);
|
||||
return xml.createResponse(null, stream, Commons.getETag(basePath, version), acceptType);
|
||||
}
|
||||
|
||||
private Response navigateProperty(
|
||||
|
@ -1544,7 +1620,7 @@ public abstract class AbstractServices {
|
|||
stream = utils.getProperty(entitySetName, entityId, pathElements, edmType);
|
||||
}
|
||||
|
||||
return xml.createResponse(stream, Commons.getETag(basePath, version), acceptType);
|
||||
return xml.createResponse(null, stream, Commons.getETag(basePath, version), acceptType);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,8 +32,10 @@ import javax.ws.rs.Path;
|
|||
import javax.ws.rs.PathParam;
|
||||
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.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.olingo.fit.methods.MERGE;
|
||||
|
@ -79,6 +81,7 @@ public class V3KeyAsSegment {
|
|||
@GET
|
||||
@Path("/{entitySetName}/{entityId}")
|
||||
public Response getEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@PathParam("entitySetName") String entitySetName,
|
||||
@PathParam("entityId") String entityId,
|
||||
|
@ -86,7 +89,7 @@ public class V3KeyAsSegment {
|
|||
@QueryParam("$expand") @DefaultValue(StringUtils.EMPTY) String expand,
|
||||
@QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
|
||||
|
||||
return replaceServiceName(services.getEntityInternal(
|
||||
return replaceServiceName(services.getEntityInternal(uriInfo.getRequestUri().toASCIIString(),
|
||||
accept, entitySetName, entityId, format, expand, select, true));
|
||||
}
|
||||
|
||||
|
@ -104,6 +107,7 @@ public class V3KeyAsSegment {
|
|||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
@Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
public Response mergeEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
|
@ -113,7 +117,7 @@ public class V3KeyAsSegment {
|
|||
final String changes) {
|
||||
|
||||
return replaceServiceName(
|
||||
services.patchEntity(accept, contentType, prefer, ifMatch, entitySetName, entityId, changes));
|
||||
services.patchEntity(uriInfo, accept, contentType, prefer, ifMatch, entitySetName, entityId, changes));
|
||||
}
|
||||
|
||||
@PATCH
|
||||
|
@ -121,6 +125,7 @@ public class V3KeyAsSegment {
|
|||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
@Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
public Response patchEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
|
@ -130,7 +135,7 @@ public class V3KeyAsSegment {
|
|||
final String changes) {
|
||||
|
||||
return replaceServiceName(
|
||||
services.patchEntity(accept, contentType, prefer, ifMatch, entitySetName, entityId, changes));
|
||||
services.patchEntity(uriInfo, accept, contentType, prefer, ifMatch, entitySetName, entityId, changes));
|
||||
}
|
||||
|
||||
@PUT
|
||||
|
@ -138,6 +143,7 @@ public class V3KeyAsSegment {
|
|||
@Produces({MediaType.APPLICATION_XML, MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
@Consumes({MediaType.APPLICATION_ATOM_XML, MediaType.APPLICATION_JSON})
|
||||
public Response putNewEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
|
@ -146,7 +152,7 @@ public class V3KeyAsSegment {
|
|||
final String entity) {
|
||||
|
||||
return replaceServiceName(
|
||||
services.replaceEntity(accept, contentType, prefer, entitySetName, entityId, entity));
|
||||
services.replaceEntity(uriInfo, accept, contentType, prefer, entitySetName, entityId, entity));
|
||||
}
|
||||
|
||||
@POST
|
||||
|
@ -154,12 +160,13 @@ public class V3KeyAsSegment {
|
|||
@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 UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
@PathParam("entitySetName") String entitySetName,
|
||||
final String entity) {
|
||||
|
||||
return replaceServiceName(services.postNewEntity(accept, contentType, prefer, entitySetName, entity));
|
||||
return replaceServiceName(services.postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, entity));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -33,8 +33,10 @@ import javax.ws.rs.Path;
|
|||
import javax.ws.rs.PathParam;
|
||||
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.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
|
||||
|
@ -110,6 +112,7 @@ public class V3OpenType {
|
|||
@GET
|
||||
@Path("/{entitySetName}({entityId})")
|
||||
public Response getEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@PathParam("entitySetName") String entitySetName,
|
||||
@PathParam("entityId") String entityId,
|
||||
|
@ -118,7 +121,8 @@ public class V3OpenType {
|
|||
@QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
|
||||
|
||||
final Matcher matcher = GUID.matcher(entityId);
|
||||
return replaceServiceName(services.getEntityInternal(accept, entitySetName,
|
||||
return replaceServiceName(services.getEntityInternal(
|
||||
uriInfo.getRequestUri().toASCIIString(), accept, entitySetName,
|
||||
matcher.matches() ? matcher.group(1) : entityId, format, expand, select, false));
|
||||
}
|
||||
|
||||
|
@ -127,13 +131,14 @@ public class V3OpenType {
|
|||
@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 UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
@PathParam("entitySetName") final String entitySetName,
|
||||
final String entity) {
|
||||
|
||||
return replaceServiceName(services.postNewEntity(accept, contentType, prefer, entitySetName, entity));
|
||||
return replaceServiceName(services.postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, entity));
|
||||
}
|
||||
|
||||
@DELETE
|
||||
|
|
|
@ -319,5 +319,4 @@ public class V3Services extends AbstractServices {
|
|||
return xml.createFaultResponse(accept, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -31,8 +31,10 @@ import javax.ws.rs.Path;
|
|||
import javax.ws.rs.PathParam;
|
||||
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.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.olingo.commons.api.edm.constants.ODataServiceVersion;
|
||||
|
@ -56,7 +58,6 @@ public class V4OpenType {
|
|||
readFile("openType" + StringUtils.capitalize(Constants.get(ODataServiceVersion.V40, ConstantKey.METADATA)),
|
||||
Accept.XML));
|
||||
this.services = new V4Services() {
|
||||
|
||||
@Override
|
||||
protected Metadata getMetadataObj() {
|
||||
return openMetadata;
|
||||
|
@ -106,6 +107,7 @@ public class V4OpenType {
|
|||
@GET
|
||||
@Path("/{entitySetName}({entityId})")
|
||||
public Response getEntity(
|
||||
@Context UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@PathParam("entitySetName") String entitySetName,
|
||||
@PathParam("entityId") String entityId,
|
||||
|
@ -114,7 +116,8 @@ public class V4OpenType {
|
|||
@QueryParam("$select") @DefaultValue(StringUtils.EMPTY) String select) {
|
||||
|
||||
return replaceServiceName(
|
||||
services.getEntityInternal(accept, entitySetName, entityId, format, expand, select, false));
|
||||
services.getEntityInternal(
|
||||
uriInfo.getRequestUri().toASCIIString(), accept, entitySetName, entityId, format, expand, select, false));
|
||||
}
|
||||
|
||||
@POST
|
||||
|
@ -122,13 +125,14 @@ public class V4OpenType {
|
|||
@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 UriInfo uriInfo,
|
||||
@HeaderParam("Accept") @DefaultValue(StringUtils.EMPTY) String accept,
|
||||
@HeaderParam("Content-Type") @DefaultValue(StringUtils.EMPTY) String contentType,
|
||||
@HeaderParam("Prefer") @DefaultValue(StringUtils.EMPTY) String prefer,
|
||||
@PathParam("entitySetName") final String entitySetName,
|
||||
final String entity) {
|
||||
|
||||
return replaceServiceName(services.postNewEntity(accept, contentType, prefer, entitySetName, entity));
|
||||
return replaceServiceName(services.postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, entity));
|
||||
}
|
||||
|
||||
@DELETE
|
||||
|
|
|
@ -31,6 +31,7 @@ import javax.ws.rs.Path;
|
|||
import javax.ws.rs.PathParam;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.UriInfo;
|
||||
import org.apache.commons.codec.binary.Base64;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -108,6 +109,7 @@ public class V4Services extends AbstractServices {
|
|||
|
||||
@Override
|
||||
public Response patchEntity(
|
||||
final UriInfo uriInfo,
|
||||
final String accept,
|
||||
final String contentType,
|
||||
final String prefer,
|
||||
|
@ -117,14 +119,16 @@ public class V4Services extends AbstractServices {
|
|||
final String changes) {
|
||||
|
||||
final Response response =
|
||||
getEntityInternal(accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY, false);
|
||||
getEntityInternal(uriInfo.getRequestUri().toASCIIString(),
|
||||
accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY, false);
|
||||
return response.getStatus() >= 400
|
||||
? postNewEntity(accept, contentType, prefer, entitySetName, changes)
|
||||
: super.patchEntity(accept, contentType, prefer, ifMatch, entitySetName, entityId, changes);
|
||||
? postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, changes)
|
||||
: super.patchEntity(uriInfo, accept, contentType, prefer, ifMatch, entitySetName, entityId, changes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response replaceEntity(
|
||||
final UriInfo uriInfo,
|
||||
final String accept,
|
||||
final String contentType,
|
||||
final String prefer,
|
||||
|
@ -133,10 +137,11 @@ public class V4Services extends AbstractServices {
|
|||
final String entity) {
|
||||
|
||||
try {
|
||||
getEntityInternal(accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY, false);
|
||||
return super.replaceEntity(accept, contentType, prefer, entitySetName, entityId, entity);
|
||||
getEntityInternal(uriInfo.getRequestUri().toASCIIString(),
|
||||
accept, entitySetName, entityId, accept, StringUtils.EMPTY, StringUtils.EMPTY, false);
|
||||
return super.replaceEntity(uriInfo, accept, contentType, prefer, entitySetName, entityId, entity);
|
||||
} catch (NotFoundException e) {
|
||||
return postNewEntity(accept, contentType, prefer, entitySetName, entityId);
|
||||
return postNewEntity(uriInfo, accept, contentType, prefer, entitySetName, entityId);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -246,5 +251,4 @@ public class V4Services extends AbstractServices {
|
|||
return xml.createFaultResponse(accept, e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -410,8 +410,12 @@ public abstract class AbstractUtilities {
|
|||
}
|
||||
|
||||
public Response createResponse(
|
||||
final InputStream entity, final String etag, final Accept accept) {
|
||||
return createResponse(entity, etag, accept, null);
|
||||
final String location, final InputStream entity, final String etag, final Accept accept) {
|
||||
return createResponse(location, entity, etag, accept, null);
|
||||
}
|
||||
|
||||
public Response createResponse(final InputStream entity, final String etag, final Accept accept) {
|
||||
return createResponse(null, entity, etag, accept, null);
|
||||
}
|
||||
|
||||
public Response createBatchResponse(final InputStream stream, final String boundary) {
|
||||
|
@ -421,7 +425,18 @@ public abstract class AbstractUtilities {
|
|||
}
|
||||
|
||||
public Response createResponse(
|
||||
final InputStream entity, final String etag, final Accept accept, final Response.Status status) {
|
||||
final InputStream entity,
|
||||
final String etag,
|
||||
final Accept accept,
|
||||
final Response.Status status) {
|
||||
return createResponse(null, entity, etag, accept, status);
|
||||
}
|
||||
public Response createResponse(
|
||||
final String location,
|
||||
final InputStream entity,
|
||||
final String etag,
|
||||
final Accept accept,
|
||||
final Response.Status status) {
|
||||
|
||||
final Response.ResponseBuilder builder = Response.ok();
|
||||
if (version.compareTo(ODataServiceVersion.V30) <= 0) {
|
||||
|
@ -466,6 +481,10 @@ public abstract class AbstractUtilities {
|
|||
builder.header("Content-Length", contentLength);
|
||||
builder.header("Content-Type", (accept == null ? "*/*" : accept.toString(version)) + contentTypeEncoding);
|
||||
|
||||
if (StringUtils.isNotBlank(location)) {
|
||||
builder.header("Location", location);
|
||||
}
|
||||
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"odata.metadata": "http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/$metadata#CustomerInfo/@Element",
|
||||
"odata.type": "Microsoft.Test.OData.Services.AstoriaDefaultService.CustomerInfo",
|
||||
"odata.id": "http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/CustomerInfo(17)",
|
||||
"odata.editLink": "CustomerInfo(17)",
|
||||
"odata.mediaEditLink": "CustomerInfo(17)/$value",
|
||||
"odata.mediaReadLink": "CustomerInfo(17)/$value",
|
||||
"odata.mediaContentType": "*/*",
|
||||
"CustomerInfoId": 17,
|
||||
"Information": "び黑ポ畚ぜマチンハ歹黑zクヲネボァたグヲ黑ソЯ歹ぴせポzゼ弌ぞせぜゼ亜Яクあソ亜ゼそせ珱ァタひグゼ縷яぁゾ黑マミ裹暦ポя"
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!--
|
||||
|
||||
Licensed to the Apache Software Foundation (ASF) under one
|
||||
or more contributor license agreements. See the NOTICE file
|
||||
distributed with this work for additional information
|
||||
regarding copyright ownership. The ASF licenses this file
|
||||
to you under the Apache License, Version 2.0 (the
|
||||
"License"); you may not use this file except in compliance
|
||||
with the License. You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing,
|
||||
software distributed under the License is distributed on an
|
||||
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
KIND, either express or implied. See the License for the
|
||||
specific language governing permissions and limitations
|
||||
under the License.
|
||||
|
||||
-->
|
||||
<entry xml:base="http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/" xmlns="http://www.w3.org/2005/Atom" xmlns:d="http://schemas.microsoft.com/ado/2007/08/dataservices" xmlns:m="http://schemas.microsoft.com/ado/2007/08/dataservices/metadata">
|
||||
<id>http://localhost:${cargo.servlet.port}/StaticService/V30/Static.svc/CustomerInfo(17)</id>
|
||||
<category term="Microsoft.Test.OData.Services.AstoriaDefaultService.CustomerInfo" scheme="http://schemas.microsoft.com/ado/2007/08/dataservices/scheme" />
|
||||
<link rel="edit" title="CustomerInfo" href="CustomerInfo(17)" />
|
||||
<title />
|
||||
<updated>2014-03-04T09:52:15Z</updated>
|
||||
<author>
|
||||
<name />
|
||||
</author>
|
||||
<link rel="edit-media" title="CustomerInfo" href="CustomerInfo(17)/$value" />
|
||||
<content type="*/*" src="CustomerInfo(17)/$value" />
|
||||
<m:properties>
|
||||
<d:CustomerInfoId m:type="Edm.Int32">17</d:CustomerInfoId>
|
||||
<d:Information>び黑ポ畚ぜマチンハ歹黑zクヲネボァたグヲ黑ソЯ歹ぴせポzゼ弌ぞせぜゼ亜Яクあソ亜ゼそせ珱ァタひグゼ縷яぁゾ黑マミ裹暦ポя</d:Information>
|
||||
</m:properties>
|
||||
</entry>
|
|
@ -151,7 +151,6 @@ public class BatchTestITCase extends AbstractTestITCase {
|
|||
}
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
@SuppressWarnings("unchecked")
|
||||
public void changesetWithReference() throws EdmPrimitiveTypeException {
|
||||
// create your request
|
||||
|
@ -161,7 +160,7 @@ public class BatchTestITCase extends AbstractTestITCase {
|
|||
final ODataChangeset changeset = streamManager.addChangeset();
|
||||
ODataEntity customer = getSampleCustomerProfile(20, "sample customer", false);
|
||||
|
||||
URIBuilder uriBuilder = client.getURIBuilder(testAuthServiceRootURL).appendEntitySetSegment("Customer");
|
||||
URIBuilder uriBuilder = client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("Customer");
|
||||
|
||||
// add create request
|
||||
final ODataEntityCreateRequest<ODataEntity> createReq =
|
||||
|
@ -176,10 +175,10 @@ public class BatchTestITCase extends AbstractTestITCase {
|
|||
final ODataEntity customerChanges = client.getObjectFactory().newEntity(customer.getTypeName());
|
||||
customerChanges.addLink(client.getObjectFactory().newEntityNavigationLink(
|
||||
"Info",
|
||||
client.getURIBuilder(testAuthServiceRootURL).appendEntitySetSegment("CustomerInfo").
|
||||
client.getURIBuilder(testStaticServiceRootURL).appendEntitySetSegment("CustomerInfo").
|
||||
appendKeySegment(17).build()));
|
||||
|
||||
final ODataEntityUpdateRequest updateReq = client.getCUDRequestFactory().getEntityUpdateRequest(
|
||||
final ODataEntityUpdateRequest<ODataEntity> updateReq = client.getCUDRequestFactory().getEntityUpdateRequest(
|
||||
URI.create("$" + createRequestRef), UpdateType.PATCH, customerChanges);
|
||||
|
||||
changeset.addRequest(updateReq);
|
||||
|
|
Loading…
Reference in New Issue