Java tutorial
/** * personium.io * Copyright 2014 FUJITSU LIMITED * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.personium.core.rs.odata; import java.io.Reader; import java.io.StringReader; import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.HttpMethod; import javax.ws.rs.POST; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Request; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import org.apache.http.HttpStatus; import org.json.simple.JSONObject; import org.odata4j.core.ODataConstants; import org.odata4j.core.ODataVersion; import org.odata4j.core.OEntity; import org.odata4j.core.OEntityId; import org.odata4j.core.OEntityIds; import org.odata4j.core.OEntityKey; import org.odata4j.edm.EdmDataServices; import org.odata4j.edm.EdmEntitySet; import org.odata4j.edm.EdmEntityType; import org.odata4j.edm.EdmNavigationProperty; import org.odata4j.format.FormatWriter; import org.odata4j.producer.EntitiesResponse; import org.odata4j.producer.EntityResponse; import org.odata4j.producer.QueryInfo; import org.odata4j.producer.resources.ODataBatchProvider; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import io.personium.common.es.util.PersoniumUUID; import io.personium.common.utils.PersoniumCoreUtils; import io.personium.core.PersoniumCoreAuthzException; import io.personium.core.PersoniumCoreException; import io.personium.core.PersoniumReadDeleteModeManager; import io.personium.core.PersoniumUnitConfig; import io.personium.core.auth.AccessContext; import io.personium.core.auth.Privilege; import io.personium.core.exceptions.ODataErrorMessage; import io.personium.core.model.impl.es.doc.EntitySetDocHandler; import io.personium.core.model.impl.es.doc.LinkDocHandler; import io.personium.core.model.impl.es.odata.UserDataODataProducer; import io.personium.core.odata.OEntityWrapper; import io.personium.core.odata.PersoniumFormatWriterFactory; import io.personium.core.rs.PersoniumCoreExceptionMapper; /** * ODataBatchResource. */ public class ODataBatchResource extends AbstractODataResource { private static final String X_PERSONIUM_PRIORITY = "X-Personium-Priority"; /** * Lock????????. */ public enum BatchPriority { /** Lock???. */ HIGH("high"), /** Lock?. */ LOW("low"); private String priority; BatchPriority(String priority) { this.priority = priority; } /** * ???(: LOW). * @param val * @return */ public static BatchPriority fromString(String val) { for (BatchPriority e : BatchPriority.values()) { if (e.priority.equalsIgnoreCase(val)) { return e; } } return LOW; } } Logger logger = LoggerFactory.getLogger(ODataBatchResource.class); ODataResource odataResource; LinkedHashMap<String, BulkRequest> bulkRequests = new LinkedHashMap<String, BulkRequest>(); // Batch?Too Many Concurrent??/? BatchRequestShutter shutter; Map<Privilege, BatchAccess> readAccess = new HashMap<Privilege, BatchAccess>(); Map<Privilege, BatchAccess> writeAccess = new HashMap<Privilege, BatchAccess>(); // EntityType???EntityTypeID? Map<String, String> entityTypeIds; /** * . * @param odataResource ODataResource */ public ODataBatchResource(final ODataResource odataResource) { this.odataResource = odataResource; this.shutter = new BatchRequestShutter(); } /** * ????. * @param uriInfo uriInfo * @param headers headers * @param request request * @param reader reader * @return ? */ @POST public Response batchRequest(@Context UriInfo uriInfo, @Context HttpHeaders headers, @Context Request request, Reader reader) { long startTime = System.currentTimeMillis(); // (personium-unit-config.properties io.personium.core.odata.batch.timeoutInSec?. ???) long batchTimeoutInSec = PersoniumUnitConfig.getOdataBatchRequestTimeoutInMillis(); // Lock???????????? BatchPriority priority = BatchPriority.LOW; List<String> priorityHeaders = headers.getRequestHeader(X_PERSONIUM_PRIORITY); if (priorityHeaders != null) { priority = BatchPriority.fromString(priorityHeaders.get(0)); } timer = new BatchElapsedTimer(startTime, batchTimeoutInSec, priority); checkAccessContext(this.odataResource.getAccessContext()); // TODO ???????? String boundary = headers.getMediaType().getParameters().get("boundary"); // ? BatchBodyParser parser = new BatchBodyParser(); List<BatchBodyPart> bodyParts = parser.parse(boundary, reader, uriInfo.getRequestUri().toString()); if (bodyParts == null || bodyParts.size() == 0) { // ? throw PersoniumCoreException.OData.BATCH_BODY_PARSE_ERROR; } if (bodyParts.size() > Integer.parseInt(PersoniumUnitConfig.getOdataBatchBulkRequestMaxSize())) { // $Batch????? throw PersoniumCoreException.OData.TOO_MANY_REQUESTS.params(bodyParts.size()); } UserDataODataProducer producer = (UserDataODataProducer) this.odataResource.getODataProducer(); entityTypeIds = producer.getEntityTypeIds(); List<NavigationPropertyBulkContext> npBulkContexts = new ArrayList<NavigationPropertyBulkContext>(); StringBuilder responseBody = new StringBuilder(); // ?? for (BatchBodyPart bodyPart : bodyParts) { executePartRequest(responseBody, uriInfo, boundary, npBulkContexts, bodyPart); } // POST?bulk checkAndExecBulk(responseBody, uriInfo, boundary, npBulkContexts); // ? responseBody.append("--" + boundary + "--"); // ?? String contentType = ODataBatchProvider.MULTIPART_MIXED + "; boundary=" + boundary; return Response.status(HttpStatus.SC_ACCEPTED).header(HttpHeaders.CONTENT_TYPE, contentType) .header(ODataConstants.Headers.DATA_SERVICE_VERSION, ODataVersion.V2.asString) .entity(responseBody.toString()).build(); } /** * $batch???(Changeset). */ private void setChangesetTimeoutResponse(StringBuilder builder, String boundary, BatchBodyPart bodyPart) { BatchResponse res = getTimeoutResponse(); builder.append(getChangesetResponseBody(boundary, bodyPart, res)); } /** * $batch???. */ private void setTimeoutResponse(StringBuilder builder, String boundary) { BatchResponse res = getTimeoutResponse(); builder.append(getRetrieveResponseBody(boundary, res)); } /** * ??. */ private BatchResponse getTimeoutResponse() { BatchResponse res = new BatchResponse(); res.setErrorResponse(PersoniumCoreException.Misc.SERVER_REQUEST_TIMEOUT); return res; } private BatchElapsedTimer timer = null; private volatile boolean timedOut = false; /** * timeout?????????.<br /> * timer??API????????????.<br /> * mode?YIELD???timeout?????????Lock?????. * @param mode Lock???????? * @return timeout???????? */ private boolean isTimedOut(BatchElapsedTimer.Lock mode) { if (!timedOut) { timedOut = timer.shouldBreak(mode); if (timedOut) { logger.info("Batch request timed out after " + timer.getElapseTimeToBreak() + " msec."); } } return timedOut; } private void executePartRequest(StringBuilder responseBody, UriInfo uriInfo, String boundary, List<NavigationPropertyBulkContext> npBulkContexts, BatchBodyPart bodyPart) { // ReadDeleteOnlyMode?GET?DELETE?????????? if (!PersoniumReadDeleteModeManager.isAllowedMethod(bodyPart.getHttpMethod())) { BatchResponse res = new BatchResponse(); res.setErrorResponse(PersoniumCoreException.Server.READ_DELETE_ONLY); responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); return; } // Batch?Too Many Concurrent???????. if (!shutter.accept(bodyPart.getHttpMethod())) { setChangesetTooManyConcurrentResponse(responseBody, boundary, bodyPart); return; } if (!isValidNavigationProperty(bodyPart)) { checkAndExecBulk(responseBody, uriInfo, boundary, npBulkContexts); BatchResponse res = new BatchResponse(); res.setErrorResponse(PersoniumCoreException.OData.KEY_FOR_NAVPROP_SHOULD_NOT_BE_SPECIFIED); if (HttpMethod.GET.equals(bodyPart.getHttpMethod())) { responseBody.append(getRetrieveResponseBody(boundary, res)); } else { responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); } return; } if (bodyPart.isLinksRequest()) { checkAndExecBulk(responseBody, uriInfo, boundary, npBulkContexts); BatchResponse res = new BatchResponse(); if (bodyPart.getHttpMethod().equals(HttpMethod.POST)) { if (!isTimedOut(BatchElapsedTimer.Lock.YIELD)) { if (!shutter.isShuttered()) { res = createLinks(uriInfo, bodyPart); responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); } else { setChangesetTooManyConcurrentResponse(responseBody, boundary, bodyPart); } } else { setChangesetTimeoutResponse(responseBody, boundary, bodyPart); } } else if (bodyPart.getHttpMethod().equals(HttpMethod.GET)) { res.setErrorResponse(PersoniumCoreException.Misc.METHOD_NOT_IMPLEMENTED); responseBody.append(getRetrieveResponseBody(boundary, res)); } else { res.setErrorResponse(PersoniumCoreException.Misc.METHOD_NOT_IMPLEMENTED); responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); } } else if (bodyPart.getHttpMethod().equals(HttpMethod.POST)) { if (bodyPart.hasNavigationProperty()) { // NP if (bulkRequests.size() != 0) { if (!isTimedOut(BatchElapsedTimer.Lock.YIELD)) { execBulk(responseBody, uriInfo, boundary); } else { for (Entry<String, BulkRequest> request : bulkRequests.entrySet()) { setChangesetTimeoutResponse(responseBody, boundary, request.getValue().getBodyPart()); BatchResponse res = new BatchResponse(); Exception exception = PersoniumCoreException.Misc.SERVER_REQUEST_TIMEOUT; res.setErrorResponse(exception); responseBody.append( getChangesetResponseBody(boundary, request.getValue().getBodyPart(), res)); } bulkRequests.clear(); } } NavigationPropertyBulkContext navigationPropertyBulkContext = new NavigationPropertyBulkContext( bodyPart); if (shutter.isShuttered()) { setChangesetTooManyConcurrentResponse(responseBody, boundary, bodyPart); } else { try { navigationPropertyBulkContext = createNavigationPropertyBulkContext(bodyPart); npBulkContexts.add(navigationPropertyBulkContext); } catch (Exception e) { navigationPropertyBulkContext.setException(e); npBulkContexts.add(navigationPropertyBulkContext); } } } else { // if (!npBulkContexts.isEmpty()) { if (!isTimedOut(BatchElapsedTimer.Lock.YIELD)) { // NP?bulk execBulkRequestForNavigationProperty(npBulkContexts); createNavigationPropertyBulkResponse(responseBody, uriInfo, boundary, npBulkContexts); } else { for (NavigationPropertyBulkContext npBulkContext : npBulkContexts) { npBulkContext.setException(PersoniumCoreException.Misc.SERVER_REQUEST_TIMEOUT); npBulkContext.getBatchResponse() .setErrorResponse(PersoniumCoreException.Misc.SERVER_REQUEST_TIMEOUT); responseBody.append(getChangesetResponseBody(boundary, npBulkContext.getBodyPart(), npBulkContext.getBatchResponse())); } } npBulkContexts.clear(); } if (!shutter.isShuttered()) { setBulkRequestsForEntity(bodyPart); } else { setChangesetTooManyConcurrentResponse(responseBody, boundary, bodyPart); } } } else if (bodyPart.getHttpMethod().equals(HttpMethod.GET)) { // POST?bulk checkAndExecBulk(responseBody, uriInfo, boundary, npBulkContexts); BatchResponse res = null; if (!isTimedOut(BatchElapsedTimer.Lock.HOLD)) { if (isListRequst(bodyPart)) { res = list(uriInfo, bodyPart); } else { res = retrieve(uriInfo, bodyPart); } responseBody.append(getRetrieveResponseBody(boundary, res)); } else { setTimeoutResponse(responseBody, boundary); } } else if (bodyPart.getHttpMethod().equals(HttpMethod.PUT)) { // POST?bulk checkAndExecBulk(responseBody, uriInfo, boundary, npBulkContexts); if (!isTimedOut(BatchElapsedTimer.Lock.YIELD)) { if (!shutter.isShuttered()) { BatchResponse res = update(bodyPart); responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); } else { setChangesetTooManyConcurrentResponse(responseBody, boundary, bodyPart); } } else { setChangesetTimeoutResponse(responseBody, boundary, bodyPart); } } else if (bodyPart.getHttpMethod().equals(HttpMethod.DELETE)) { // POST?bulk checkAndExecBulk(responseBody, uriInfo, boundary, npBulkContexts); if (!isTimedOut(BatchElapsedTimer.Lock.YIELD)) { if (!shutter.isShuttered()) { BatchResponse res = delete(bodyPart); responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); } else { setChangesetTooManyConcurrentResponse(responseBody, boundary, bodyPart); } } else { setChangesetTimeoutResponse(responseBody, boundary, bodyPart); } } else { BatchResponse res = new BatchResponse(); res.setErrorResponse(PersoniumCoreException.Misc.METHOD_NOT_ALLOWED); responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); } } private void setChangesetTooManyConcurrentResponse(StringBuilder responseBody, String boundary, BatchBodyPart bodyPart) { // ??POST?TooManyConcurrent????????? BatchResponse res = new BatchResponse(); res.setErrorResponse(PersoniumCoreException.Misc.TOO_MANY_CONCURRENT_REQUESTS); responseBody.append(getChangesetResponseBody(boundary, bodyPart, res)); } /** * NP??. * @param npBulkContexts NavigationProperty? */ private void execBulkRequestForNavigationProperty(List<NavigationPropertyBulkContext> npBulkContexts) { // ???BulkRequest? // NP??EntityType??????ID?????? LinkedHashMap<String, BulkRequest> npBulkRequests = new LinkedHashMap<String, BulkRequest>(); for (NavigationPropertyBulkContext npBulkContext : npBulkContexts) { BatchBodyPart bodyPart = npBulkContext.getBodyPart(); BulkRequest bulkRequest = new BulkRequest(bodyPart); String key = PersoniumUUID.randomUUID(); if (npBulkContext.isError()) { bulkRequest.setError(npBulkContext.getException()); npBulkRequests.put(key, bulkRequest); continue; } String targetEntitySetName = bodyPart.getTargetEntitySetName(); bulkRequest = createBulkRequest(bodyPart, targetEntitySetName); // ??ID?? // TODO ?????NTKP if (bulkRequest.getError() == null) { EntitySetDocHandler docHandler = bulkRequest.getDocHandler(); key = docHandler.getEntityTypeId() + ":" + (String) docHandler.getStaticFields().get("__id"); if (npBulkRequests.containsKey(key)) { key = PersoniumUUID.randomUUID(); bulkRequest.setError(PersoniumCoreException.OData.ENTITY_ALREADY_EXISTS); } } npBulkRequests.put(key, bulkRequest); } try { this.odataResource.getODataProducer().bulkCreateEntityViaNavigationProperty(npBulkContexts, npBulkRequests); } catch (PersoniumCoreException e) { // 503??????????shutter? shutter.updateStatus(e); if (!PersoniumCoreException.Misc.TOO_MANY_CONCURRENT_REQUESTS.equals(e)) { throw e; } else { createTooManyConcurrentResponse(npBulkContexts); } } npBulkRequests.clear(); } private void createNavigationPropertyBulkResponse(StringBuilder responseBody, UriInfo uriInfo, String boundary, List<NavigationPropertyBulkContext> npBulkContexts) { for (NavigationPropertyBulkContext npBulkContext : npBulkContexts) { BatchBodyPart bodyPart = npBulkContext.getBodyPart(); if (!npBulkContext.isError()) { try { setNavigationPropertyResponse(uriInfo, bodyPart, npBulkContext); } catch (Exception e) { npBulkContext.getBatchResponse().setErrorResponse(e); npBulkContext.setException(e); } } else { npBulkContext.getBatchResponse().setErrorResponse(npBulkContext.getException()); } responseBody.append(getChangesetResponseBody(boundary, bodyPart, npBulkContext.getBatchResponse())); } } /** * ??Changeset????. * @param boundaryStr ? * @param bodyPart BatchBodyPart * @param res ? * @return ?? */ private String getChangesetResponseBody(String boundaryStr, BatchBodyPart bodyPart, BatchResponse res) { StringBuilder resBody = new StringBuilder(); // ?? // ? if (bodyPart.isChangesetStart()) { // ??? resBody.append("--" + boundaryStr + "\n"); resBody.append(HttpHeaders.CONTENT_TYPE + ": "); resBody.append( ODataBatchProvider.MULTIPART_MIXED + "; boundary=" + bodyPart.getChangesetStr() + "\n\n"); } // changeset resBody.append("--" + bodyPart.getChangesetStr() + "\n"); // ContentType resBody.append(HttpHeaders.CONTENT_TYPE + ": "); resBody.append("application/http\n"); // Content-Transfer-Encoding resBody.append("Content-Transfer-Encoding: binary\n\n"); // HTTP/1.1 {?} {??} resBody.append("HTTP/1.1 "); resBody.append(res.getResponseCode() + " "); resBody.append(res.getResponseMessage() + "\n"); // ? for (String key : res.getHeaders().keySet()) { resBody.append(key + ": " + res.getHeaders().get(key) + "\n"); } resBody.append("\n"); // ? if (res.getBody() != null) { resBody.append(res.getBody() + "\n\n"); } // changeset if (bodyPart.isChangesetEnd()) { resBody.append("--" + bodyPart.getChangesetStr() + "--\n\n"); } return resBody.toString(); } /** * ??GET, LIST????. * @param boundaryStr ? * @param res ? * @return ?? */ private String getRetrieveResponseBody(String boundaryStr, BatchResponse res) { StringBuilder resBody = new StringBuilder(); // ?? // ? // ??? resBody.append("--" + boundaryStr + "\n"); resBody.append(HttpHeaders.CONTENT_TYPE + ": application/http\n\n"); // HTTP/1.1 {?} {??} resBody.append("HTTP/1.1 "); resBody.append(res.getResponseCode() + " "); resBody.append(res.getResponseMessage() + "\n"); // ? for (String key : res.getHeaders().keySet()) { resBody.append(key + ": " + res.getHeaders().get(key) + "\n"); } resBody.append("\n"); // ? if (res.getBody() != null) { resBody.append(res.getBody() + "\n\n"); } return resBody.toString(); } /** * ?????. * @param uri URI * @return true: ?, false:? */ private boolean isListRequst(BatchBodyPart bodyPart) { if (bodyPart.getEntityKeyWithParences() == null || bodyPart.hasNavigationProperty()) { return true; } return false; } /** * ??????. * @param bodyPart BatchBodyPart */ private void setBulkRequestsForEntity(BatchBodyPart bodyPart) { String key = PersoniumUUID.randomUUID(); BulkRequest bulkRequest = createBulkRequest(bodyPart, bodyPart.getEntitySetName()); if (bulkRequest.getError() == null) { // ??ID?? // TODO ?????NTKP EntitySetDocHandler docHandler = bulkRequest.getDocHandler(); key = docHandler.getEntityTypeId() + ":" + (String) docHandler.getStaticFields().get("__id"); if (bulkRequests.containsKey(key)) { key = PersoniumUUID.randomUUID(); bulkRequest.setError(PersoniumCoreException.OData.ENTITY_ALREADY_EXISTS); } } bulkRequests.put(key, bulkRequest); } private BulkRequest createBulkRequest(BatchBodyPart bodyPart, String entitySetName) { BulkRequest bulkRequest = new BulkRequest(bodyPart); try { // checkWriteAccessContext(bodyPart); // ?????????404? EdmEntitySet eSet = this.odataResource.metadata.findEdmEntitySet(entitySetName); if (eSet == null) { throw PersoniumCoreException.OData.NO_SUCH_ENTITY_SET; } // ?? ODataEntitiesResource resource = new ODataEntitiesResource(this.odataResource, entitySetName); OEntity oEntity = resource.getOEntityWrapper(new StringReader(bodyPart.getEntity()), this.odataResource, this.odataResource.metadata); EntitySetDocHandler docHandler = resource.getOdataProducer().getEntitySetDocHandler(entitySetName, oEntity); String docType = UserDataODataProducer.USER_ODATA_NAMESPACE; docHandler.setType(docType); docHandler.setEntityTypeId(entityTypeIds.get(entitySetName)); this.odataResource.metadata = resource.getOdataProducer().getMetadata(); // ID?????UUID?? if (docHandler.getId() == null) { docHandler.setId(PersoniumUUID.randomUUID()); } bulkRequest.setEntitySetName(entitySetName); bulkRequest.setDocHandler(docHandler); } catch (Exception e) { bulkRequest.setError(e); } return bulkRequest; } private void setNavigationPropertyResponse(UriInfo uriInfo, BatchBodyPart bodyPart, NavigationPropertyBulkContext npBulkContext) { OEntity ent = npBulkContext.getEntityResponse().getEntity(); // ???ContentType?JSON String accept = bodyPart.getHttpHeaders().get(org.apache.http.HttpHeaders.ACCEPT); MediaType outputFormat = this.decideOutputFormat(accept, "json"); // Entity Response List<MediaType> contentTypes = new ArrayList<MediaType>(); contentTypes.add(outputFormat); UriInfo resUriInfo = PersoniumCoreUtils.createUriInfo(uriInfo, 1); String key = AbstractODataResource.replaceDummyKeyToNull(ent.getEntityKey().toKeyString()); String responseStr = renderEntityResponse(resUriInfo, npBulkContext.getEntityResponse(), "json", contentTypes); // ? npBulkContext.setResponseHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); String entityName = bodyPart.getTargetNavigationProperty().substring(1) + key; npBulkContext.setResponseHeader(HttpHeaders.LOCATION, resUriInfo.getBaseUri().toASCIIString() + entityName); npBulkContext.setResponseHeader(ODataConstants.Headers.DATA_SERVICE_VERSION, ODataVersion.V2.asString); // ?ETAG if (ent instanceof OEntityWrapper) { OEntityWrapper oew2 = (OEntityWrapper) ent; String etag = oew2.getEtag(); if (etag != null) { npBulkContext.setResponseHeader(HttpHeaders.ETAG, "W/\"" + etag + "\""); } } npBulkContext.setResponseBody(responseStr); npBulkContext.setResponseCode(HttpStatus.SC_CREATED); } private NavigationPropertyBulkContext createNavigationPropertyBulkContext(BatchBodyPart bodyPart) { // checkWriteAccessContext(bodyPart); // ?????????404? EdmEntitySet eSet = this.odataResource.metadata.findEdmEntitySet(bodyPart.getEntitySetName()); if (eSet == null) { throw PersoniumCoreException.OData.NO_SUCH_ENTITY_SET; } // Navigation??? ODataEntityResource entityResource = new ODataEntityResource(this.odataResource, bodyPart.getEntitySetName(), bodyPart.getEntityKey()); OEntityId oEntityId = entityResource.getOEntityId(); EdmNavigationProperty enp = eSet.getType().findNavigationProperty(bodyPart.getTargetNavigationProperty()); if (enp == null) { throw PersoniumCoreException.OData.NOT_SUCH_NAVPROP; } // ? StringReader requestReader = new StringReader(bodyPart.getEntity()); OEntityWrapper oew = createEntityFromInputStream(requestReader, eSet.getType(), oEntityId.getEntityKey(), bodyPart.getTargetEntitySetName()); String navigationPropertyName = bodyPart.getTargetNavigationProperty(); return new NavigationPropertyBulkContext(bodyPart, oew, oEntityId, navigationPropertyName); } /** * NavigationProperty??Entity???. * @param reader * @return ????OEntityWrapper */ OEntityWrapper createEntityFromInputStream(final Reader reader, EdmEntityType sourceEdmEntityType, OEntityKey sourceEntityKey, String targetEntitySetName) { // ?? validatePrimaryKey(sourceEntityKey, sourceEdmEntityType); // ????OEntity? setEntitySetName(targetEntitySetName); EdmDataServices metadata = this.odataResource.getODataProducer().getMetadata(); OEntity newEnt = createRequestEntity(reader, null, metadata, targetEntitySetName); // ???. POST?If-Match ETag??????????etag?null String uuid = PersoniumUUID.randomUUID(); OEntityWrapper oew = new OEntityWrapper(uuid, newEnt, null); return oew; } /** * NavigationProperty?Entity?. * @param oew OEntityWrapper * @return ??????Entity? */ EntityResponse createEntity(OEntityWrapper oew) { // ?????? this.odataResource.beforeCreate(oew); // Entity?? Producer??.???????????? EntityResponse res = this.odataResource.getODataProducer().createEntity(getEntitySetName(), oew); return res; } /** * NP??. */ public enum NavigationPropertyLinkType { /** 1:1 / 0..1:1 / 1:0..1 / 0..1:0..1 . */ oneToOne, /** n:1 / n:0..1 . */ manyToOne, /** 1:n / 0..1:n . */ oneToMany, /** n:n. */ manyToMany } /** * NP. */ public static class NavigationPropertyBulkContext { private OEntityWrapper oew; private EntityResponse entityResponse; private OEntityId srcEntityId; private String tgtNavProp; private BatchBodyPart bodyPart; private NavigationPropertyLinkType linkType; private EntitySetDocHandler sourceDocHandler; private EntitySetDocHandler targetDocHandler; private LinkDocHandler linkDocHandler; private BatchResponse res; private boolean isError; private Exception exception; /** * . * @param bodyPart BatchBodyPart */ public NavigationPropertyBulkContext(BatchBodyPart bodyPart) { this(bodyPart, null, null, null); } /** * . * @param bodyPart ?BatchBodyPart * @param oew ?OEntity * @param srcEntityId OEntityId * @param tgtNavProp NavigationProperty?? */ public NavigationPropertyBulkContext(BatchBodyPart bodyPart, OEntityWrapper oew, OEntityId srcEntityId, String tgtNavProp) { this.oew = oew; this.entityResponse = null; this.srcEntityId = srcEntityId; this.tgtNavProp = tgtNavProp; this.bodyPart = bodyPart; this.res = new BatchResponse(); } /** * ?OEntity?. * @param entity ?OEntity */ public void setOEntityWrapper(OEntityWrapper entity) { this.oew = entity; } /** * ?OEntity??. * @return ?OEntity */ public OEntityWrapper getOEntityWrapper() { return this.oew; } /** * ????EntityResponse??. * @return EntityResponse */ public EntityResponse getEntityResponse() { return this.entityResponse; } /** * ????EntityResponse?. * @param entityResponse ????EntityResponse */ public void setEntityResponse(EntityResponse entityResponse) { this.entityResponse = entityResponse; } /** * OEntityId??. * @return OEntityId */ public OEntityId getSrcEntityId() { return this.srcEntityId; } /** * NavigationProperty????. * @return NavigationProperty?? */ public String getTgtNavProp() { return this.tgtNavProp; } /** * ?BatchBodyPart??. * @return ?BatchBodyPart */ public BatchBodyPart getBodyPart() { return this.bodyPart; } /** * Batch????. * @return Batch?? */ public BatchResponse getBatchResponse() { return this.res; } /** * Batch???. * @param key BatchResponse?? * @param value BatchResponse?? */ public void setResponseHeader(String key, String value) { this.res.setHeader(key, value); } /** * Batch???. * @param body BatchResponse?body */ public void setResponseBody(String body) { this.res.setBody(body); } /** * Batch???. * @param code BatchResponse?? */ public void setResponseCode(int code) { this.res.setResponseCode(code); } /** * ???. * @return ? */ public NavigationPropertyLinkType getLinkType() { return linkType; } /** * ??. * @param linkType ? */ public void setLinkType(NavigationPropertyLinkType linkType) { this.linkType = linkType; } /** * ??. * /EntityType('ID')/_NavigationProperty ?EntityType?? * @return ? */ public EntitySetDocHandler getSourceDocHandler() { return sourceDocHandler; } /** * ??. * /EntityType('ID')/_NavigationProperty ?EntityType?? * @param sourceDocHandler ? */ public void setSourceDocHandler(EntitySetDocHandler sourceDocHandler) { this.sourceDocHandler = sourceDocHandler; } /** * ???. * /EntityType('ID')/_NavigationProperty ?_NavigationProperty?? * @return */ public EntitySetDocHandler getTargetDocHandler() { return targetDocHandler; } /** * ?. * /EntityType('ID')/_NavigationProperty ?_NavigationProperty?? * @param targetDocHandler */ public void setTargetDocHandler(EntitySetDocHandler targetDocHandler) { this.targetDocHandler = targetDocHandler; } /** * ???. * @return ? */ public LinkDocHandler getLinkDocHandler() { return linkDocHandler; } /** * ??. * @param linkDocHandler ? */ public void setLinkDocHandler(LinkDocHandler linkDocHandler) { this.linkDocHandler = linkDocHandler; } /** * ?. * @param ex */ public void setException(Exception ex) { this.exception = ex; this.isError = true; } /** * ??????. * @return true: , false: ?? */ public boolean isError() { return this.isError; } /** * ???????. * @return */ public Exception getException() { return this.exception; } } /** * bulk????. * @param uriInfo uriInfo * @param boundary boundary * @param navigationPropertyBulkContexts ? */ private void checkAndExecBulk(StringBuilder responseBody, UriInfo uriInfo, String boundary, List<NavigationPropertyBulkContext> navigationPropertyBulkContexts) { if (!navigationPropertyBulkContexts.isEmpty()) { if (!isTimedOut(BatchElapsedTimer.Lock.YIELD)) { if (!shutter.isShuttered()) { execBulkRequestForNavigationProperty(navigationPropertyBulkContexts); createNavigationPropertyBulkResponse(responseBody, uriInfo, boundary, navigationPropertyBulkContexts); } else { // ???503????? createTooManyConcurrentResponse(navigationPropertyBulkContexts); } } else { for (NavigationPropertyBulkContext npBulkContext : navigationPropertyBulkContexts) { npBulkContext.setException(PersoniumCoreException.Misc.SERVER_REQUEST_TIMEOUT); npBulkContext.getBatchResponse() .setErrorResponse(PersoniumCoreException.Misc.SERVER_REQUEST_TIMEOUT); BatchBodyPart bodyPart = npBulkContext.getBodyPart(); responseBody .append(getChangesetResponseBody(boundary, bodyPart, npBulkContext.getBatchResponse())); } } navigationPropertyBulkContexts.clear(); } else { if (bulkRequests.size() != 0) { if (!isTimedOut(BatchElapsedTimer.Lock.YIELD)) { if (!shutter.isShuttered()) { execBulk(responseBody, uriInfo, boundary); } else { // ???503????? createTooManyConcurrentResponse(responseBody, boundary); bulkRequests.clear(); } } else { for (Entry<String, BulkRequest> request : bulkRequests.entrySet()) { BatchResponse res = new BatchResponse(); Exception exception = PersoniumCoreException.Misc.SERVER_REQUEST_TIMEOUT; res.setErrorResponse(exception); // ?? responseBody .append(getChangesetResponseBody(boundary, request.getValue().getBodyPart(), res)); } bulkRequests.clear(); } } } } /** * bulk??. * @param responseBody ?? * @param uriInfo uriInfo * @param boundary boundary */ private void execBulk(StringBuilder responseBody, UriInfo uriInfo, String boundary) { EntityResponse entityRes = null; // ? String cellId = this.odataResource.accessContext.getCell().getId(); List<EntityResponse> resultList = null; try { resultList = this.odataResource.getODataProducer().bulkCreateEntity(this.odataResource.metadata, bulkRequests, cellId); } catch (PersoniumCoreException e) { // 503??????????shutter? shutter.updateStatus(e); if (shutter.isShuttered()) { createTooManyConcurrentResponse(responseBody, boundary); bulkRequests.clear(); return; } else { throw e; } } // ??? int index = 0; for (Entry<String, BulkRequest> request : bulkRequests.entrySet()) { BatchResponse res = new BatchResponse(); Exception exception = request.getValue().getError(); if (exception != null) { res.setErrorResponse(exception); } else { // ?? entityRes = resultList.get(index); OEntity oEntity = entityRes.getEntity(); // res.setResponseCode(HttpStatus.SC_CREATED); // String key = oEntity.getEntityKey().toKeyString(); res.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); res.setHeader(HttpHeaders.LOCATION, request.getValue().getBodyPart().getUri() + key); res.setHeader(ODataConstants.Headers.DATA_SERVICE_VERSION, "2.0"); String etag = ((OEntityWrapper) oEntity).getEtag(); if (etag != null) { res.setHeader(HttpHeaders.ETAG, "W/\"" + etag + "\""); } // UriInfo resUriInfo = PersoniumCoreUtils.createUriInfo(uriInfo, 1); String format = AbstractODataResource.FORMAT_JSON; List<MediaType> contentTypes = new ArrayList<MediaType>(); contentTypes.add(MediaType.APPLICATION_JSON_TYPE); String responseStr = renderEntityResponse(resUriInfo, entityRes, format, contentTypes); res.setBody(responseStr); index++; } // ?? responseBody.append(getChangesetResponseBody(boundary, request.getValue().getBodyPart(), res)); } bulkRequests.clear(); } /** * POST?Too Many Concurrent ???. * @param responseBody * @param boundary */ private void createTooManyConcurrentResponse(StringBuilder responseBody, String boundary) { for (Entry<String, BulkRequest> request : bulkRequests.entrySet()) { BatchResponse res = new BatchResponse(); res.setErrorResponse(PersoniumCoreException.Misc.TOO_MANY_CONCURRENT_REQUESTS); // ?? responseBody.append(getChangesetResponseBody(boundary, request.getValue().getBodyPart(), res)); } } /** * NP?POST?Too Many Concurrent ???. * @param responseBody * @param boundary */ private void createTooManyConcurrentResponse(List<NavigationPropertyBulkContext> npBulkContexts) { for (NavigationPropertyBulkContext npBulkContext : npBulkContexts) { npBulkContext.setException(PersoniumCoreException.Misc.TOO_MANY_CONCURRENT_REQUESTS); } } /** * ?????. * @param uriInfo uriInfo * @param bodyPart BatchBodyPart * @return ? */ private BatchResponse list(UriInfo uriInfo, BatchBodyPart bodyPart) { BatchResponse res = new BatchResponse(); EntitiesResponse entitiesResp = null; try { // checkReadAccessContext(bodyPart); // NavigationProperty??? 501 if (bodyPart.hasNavigationProperty()) { throw PersoniumCoreException.Misc.METHOD_NOT_IMPLEMENTED; } ODataEntitiesResource entitiesResource = new ODataEntitiesResource(this.odataResource, bodyPart.getEntitySetName()); // Entity?? String query = bodyPart.getRequestQuery(); QueryInfo queryInfo = QueryParser.createQueryInfo(query); entitiesResp = entitiesResource.getEntities(queryInfo); // ?? res.setResponseCode(HttpStatus.SC_OK); // TODO ???ContentType?JSON res.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); res.setHeader(ODataConstants.Headers.DATA_SERVICE_VERSION, "2.0"); // ? UriInfo resUriInfo = PersoniumCoreUtils.createUriInfo(uriInfo, 1); StringWriter sw = new StringWriter(); // TODO ??Accept???JSON??????JSON?. List<MediaType> acceptableMediaTypes = new ArrayList<MediaType>(); acceptableMediaTypes.add(MediaType.APPLICATION_JSON_TYPE); // TODO ??Query?????null?. FormatWriter<EntitiesResponse> fw = PersoniumFormatWriterFactory.getFormatWriter(EntitiesResponse.class, acceptableMediaTypes, null, null); UriInfo uriInfo2 = PersoniumCoreUtils.createUriInfo(resUriInfo, 1); fw.write(uriInfo2, sw, entitiesResp); String entity = sw.toString(); res.setBody(entity); } catch (Exception e) { res.setErrorResponse(e); } return res; } /** * ?????. * @param uriInfo uriInfo * @param bodyPart BatchBodyPart * @return ? */ private BatchResponse retrieve(UriInfo uriInfo, BatchBodyPart bodyPart) { BatchResponse res = new BatchResponse(); EntityResponse entityResp = null; try { // checkReadAccessContext(bodyPart); ODataEntityResource entityResource = new ODataEntityResource(this.odataResource, bodyPart.getEntitySetName(), bodyPart.getEntityKey()); // Entity?? // TODO entityResp = entityResource.getEntity(null, null, null); // ?? res.setResponseCode(HttpStatus.SC_OK); // TODO ???ContentType?JSON res.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); res.setHeader(ODataConstants.Headers.DATA_SERVICE_VERSION, "2.0"); // ? UriInfo resUriInfo = PersoniumCoreUtils.createUriInfo(uriInfo, 1); // TODO ???ContentType?JSON String format = AbstractODataResource.FORMAT_JSON; List<MediaType> contentTypes = new ArrayList<MediaType>(); contentTypes.add(MediaType.APPLICATION_JSON_TYPE); String responseStr = entityResource.renderEntityResponse(resUriInfo, entityResp, format, null); res.setBody(responseStr); } catch (Exception e) { res.setErrorResponse(e); } return res; } /** * ????. * @param bodyPart BatchBodyPart */ private BatchResponse update(BatchBodyPart bodyPart) { BatchResponse res = new BatchResponse(); try { // checkWriteAccessContext(bodyPart); ODataEntityResource entityResource = new ODataEntityResource(this.odataResource, bodyPart.getEntitySetName(), bodyPart.getEntityKey()); // Entity? Reader reader = new StringReader(bodyPart.getEntity()); String ifMatch = bodyPart.getHttpHeaders().get(HttpHeaders.IF_MATCH); OEntityWrapper oew = entityResource.updateEntity(reader, ifMatch); // ?? // ?????????? // oew?????ETag? String etag = oew.getEtag(); res.setResponseCode(HttpStatus.SC_NO_CONTENT); res.setHeader(ODataConstants.Headers.DATA_SERVICE_VERSION, "2.0"); res.setHeader(HttpHeaders.ETAG, ODataResource.renderEtagHeader(etag)); } catch (Exception e) { res.setErrorResponse(e); shutter.updateStatus(e); } return res; } /** * ????. * @param bodyPart BatchBodyPart * @return BatchResponse */ private BatchResponse delete(BatchBodyPart bodyPart) { BatchResponse res = new BatchResponse(); try { // checkWriteAccessContext(bodyPart); ODataEntityResource entityResource = new ODataEntityResource(this.odataResource, bodyPart.getEntitySetName(), bodyPart.getEntityKey()); // Entity? String ifMatch = bodyPart.getHttpHeaders().get(HttpHeaders.IF_MATCH); entityResource.deleteEntity(ifMatch); // ?? res.setResponseCode(HttpStatus.SC_NO_CONTENT); res.setHeader(ODataConstants.Headers.DATA_SERVICE_VERSION, "2.0"); } catch (Exception e) { res.setErrorResponse(e); shutter.updateStatus(e); } return res; } /** * ???$links?. * @param uriInfo uriInfo * @param bodyPart BatchBodyPart * @return BatchResponse */ private BatchResponse createLinks(UriInfo uriInfo, BatchBodyPart bodyPart) { BatchResponse res = new BatchResponse(); try { // ?????????? EdmEntitySet eSet = this.odataResource.metadata.findEdmEntitySet(bodyPart.getEntitySetName()); if (eSet == null) { throw PersoniumCoreException.OData.NO_SUCH_ENTITY_SET; } // checkWriteAccessContext(bodyPart); // $links ? POST?Nav Prop?????????? if (bodyPart.getTargetEntityKey().length() > 0) { throw PersoniumCoreException.OData.KEY_FOR_NAVPROP_SHOULD_NOT_BE_SPECIFIED; } OEntityKey oeKey = OEntityKey.parse(bodyPart.getEntityKeyWithParences()); OEntityId sourceEntityId = OEntityIds.create(bodyPart.getEntitySetName(), oeKey); StringReader requestReader = new StringReader(bodyPart.getEntity()); OEntityId targetEntityId = ODataLinksResource.parseRequestUri( PersoniumCoreUtils.createUriInfo(uriInfo, 1), requestReader, bodyPart.getEntitySetName(), this.odataResource.metadata); this.odataResource.getODataProducer().createLink(sourceEntityId, bodyPart.getTargetEntitySetName(), targetEntityId); // ?? res.setResponseCode(HttpStatus.SC_NO_CONTENT); res.setHeader(ODataConstants.Headers.DATA_SERVICE_VERSION, ODataVersion.V2.asString); } catch (Exception e) { res.setErrorResponse(e); shutter.updateStatus(e); } return res; } /** * $batch????. * @param ac */ private void checkAccessContext(AccessContext ac) { // ? this.odataResource.checkSchemaAuth(this.odataResource.getAccessContext()); // ? if (ac.isUnitUserToken()) { return; } // Basic?????? this.odataResource.setBasicAuthenticateEnableInBatchRequest(ac); // principal?ALL??????? // ????$batch??MIME????? if (!this.odataResource.hasPrivilegeForBatch(ac)) { // ?? // ?INVALID?ACL?Privilege?all???????????????? if (AccessContext.TYPE_INVALID.equals(ac.getType())) { ac.throwInvalidTokenException(this.odataResource.getAcceptableAuthScheme()); } else if (AccessContext.TYPE_ANONYMOUS.equals(ac.getType())) { throw PersoniumCoreAuthzException.AUTHORIZATION_REQUIRED.realm(ac.getRealm(), this.odataResource.getAcceptableAuthScheme()); } // $batch???????privilege????????????403?? throw PersoniumCoreException.Auth.NECESSARY_PRIVILEGE_LACKING; } } /** * $batch??MIME????. * @param ac * @param privilege ??? */ private void checkAccessContextForMimePart(AccessContext ac, Privilege privilege) { // ? if (ac.isUnitUserToken()) { return; } if (!this.odataResource.hasPrivilege(ac, privilege)) { // $batch????????????????????????? throw PersoniumCoreException.Auth.NECESSARY_PRIVILEGE_LACKING; } } /** * $batchwrite. * @param bodyPart bodyPart */ private void checkWriteAccessContext(BatchBodyPart bodyPart) { // TODO EntitySet?Privilege???? Privilege priv = this.odataResource.getNecessaryWritePrivilege(bodyPart.getEntitySetName()); BatchAccess batchAccess = writeAccess.get(priv); if (batchAccess == null) { batchAccess = new BatchAccess(); writeAccess.put(priv, batchAccess); try { this.checkAccessContextForMimePart(this.odataResource.getAccessContext(), priv); } catch (PersoniumCoreException ex) { batchAccess.setAccessContext(ex); } } batchAccess.checkAccessContext(); } /** * $batchread. * @param bodyPart bodyPart */ private void checkReadAccessContext(BatchBodyPart bodyPart) { // TODO EntitySet?Privilege???? Privilege priv = this.odataResource.getNecessaryReadPrivilege(bodyPart.getEntitySetName()); BatchAccess batchAccess = readAccess.get(priv); if (batchAccess == null) { batchAccess = new BatchAccess(); readAccess.put(priv, batchAccess); try { this.checkAccessContextForMimePart(this.odataResource.getAccessContext(), priv); } catch (PersoniumCoreException ex) { batchAccess.setAccessContext(ex); } } batchAccess.checkAccessContext(); } /** * ?NavigationProperty??????????. * @param bodyPart bodyPart * @return ?????true????false? */ private boolean isValidNavigationProperty(BatchBodyPart bodyPart) { if (bodyPart.hasNavigationProperty() && bodyPart.getTargetNavigationProperty().indexOf("(") >= 0) { // ??NG return false; } return true; } /** * Batch??. */ static class BatchAccess { private PersoniumCoreException exception = null; void checkAccessContext() { if (this.exception != null) { throw this.exception; } } void setAccessContext(PersoniumCoreException ex) { this.exception = ex; } } /** * Batch??. */ static class BatchResponse { private int responseCode; private Map<String, String> headers = new HashMap<String, String>(); private String body = null; /** * BatchResponse????. * @return BatchResponse?? */ public int getResponseCode() { return responseCode; } /** * BatchResponse???. * @param responseCode BatchResponse?? */ public void setResponseCode(int responseCode) { this.responseCode = responseCode; } /** * BatchResponse???. * @return BatchResponse? */ public Map<String, String> getHeaders() { return headers; } /** * ????. * @return ??OK, No Content */ public String getResponseMessage() { String message = null; switch (this.responseCode) { case HttpStatus.SC_NO_CONTENT: message = "No Content"; break; case HttpStatus.SC_CREATED: message = "Created"; break; case HttpStatus.SC_OK: message = "OK"; break; default: message = ""; break; } return message; } /** * BatchResponse??. * @param key BatchResponse?? * @param value BatchResponse?? */ public void setHeader(String key, String value) { this.headers.put(key, value); } /** * BatchResponse???. * @return BatchResponse? */ public String getBody() { return body; } /** * BatchResponse??. * @param body BatchResponse? */ public void setBody(String body) { this.body = body; } /** * . * @param res BatchResponse * @param e PersoniumCoreException */ void setErrorResponse(Exception e) { // PersoniumCoreExceptionMapper mapper = new PersoniumCoreExceptionMapper(); mapper.toResponse(e); if (e instanceof PersoniumCoreException) { // ?? setHeader(org.apache.http.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); setResponseCode(((PersoniumCoreException) e).getStatus()); setBody(createJsonBody((PersoniumCoreException) e)); } else { // ?? setHeader(org.apache.http.HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON); setResponseCode(PersoniumCoreException.Server.UNKNOWN_ERROR.getStatus()); setBody(createJsonBody(PersoniumCoreException.Server.UNKNOWN_ERROR)); } } /** * Json????. * @param exception PersoniumCoreException * @return ?Json? */ private String createJsonBody(PersoniumCoreException exception) { String code = exception.getCode(); String message = exception.getMessage(); LinkedHashMap<String, Object> json = new LinkedHashMap<String, Object>(); LinkedHashMap<String, Object> jsonMessage = new LinkedHashMap<String, Object>(); json.put("code", code); jsonMessage.put("lang", ODataErrorMessage.DEFAULT_LANG_TAG); jsonMessage.put("value", message); json.put("message", jsonMessage); return JSONObject.toJSONString(json); } } }