io.personium.core.rs.odata.ODataBatchResource.java Source code

Java tutorial

Introduction

Here is the source code for io.personium.core.rs.odata.ODataBatchResource.java

Source

/**
 * 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);
        }

    }
}