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

Java tutorial

Introduction

Here is the source code for io.personium.core.rs.odata.AbstractODataResource.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.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.ResponseBuilder;
import javax.ws.rs.core.UriInfo;

import org.apache.http.HttpStatus;
import org.odata4j.core.NamedValue;
import org.odata4j.core.NamespacedAnnotation;
import org.odata4j.core.OCollection;
import org.odata4j.core.OCollections;
import org.odata4j.core.OComplexObject;
import org.odata4j.core.OComplexObjects;
import org.odata4j.core.ODataConstants;
import org.odata4j.core.ODataVersion;
import org.odata4j.core.OEntities;
import org.odata4j.core.OEntity;
import org.odata4j.core.OEntityKey;
import org.odata4j.core.OLink;
import org.odata4j.core.OObject;
import org.odata4j.core.OProperties;
import org.odata4j.core.OProperty;
import org.odata4j.edm.EdmCollectionType;
import org.odata4j.edm.EdmComplexType;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmProperty;
import org.odata4j.edm.EdmProperty.CollectionKind;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.edm.EdmType;
import org.odata4j.format.Entry;
import org.odata4j.format.FormatParser;
import org.odata4j.format.FormatWriter;
import org.odata4j.format.Settings;
import org.odata4j.producer.EntityResponse;

import io.personium.common.es.util.PersoniumUUID;
import io.personium.core.PersoniumCoreException;
import io.personium.core.model.ctl.Common;
import io.personium.core.model.ctl.Property;
import io.personium.core.model.impl.es.odata.PropertyLimitChecker;
import io.personium.core.model.impl.es.odata.PropertyLimitChecker.CheckError;
import io.personium.core.odata.OEntityWrapper;
import io.personium.core.odata.PersoniumFormatParserFactory;
import io.personium.core.odata.PersoniumFormatWriterFactory;
import io.personium.core.odata.PersoniumODataProducer;
import io.personium.core.utils.EscapeControlCode;
import io.personium.core.utils.ODataUtils;

/**
 * OData??.
 */
public abstract class AbstractODataResource {

    /**
     * ??.
     */
    private String entitySetName;

    /**
     * ??.
     */
    public static final String DUMMY_KEY = "key_dummy@";

    /**
     * ODataProducer.
     */
    private PersoniumODataProducer odataProducer;

    /** $format?JSON. */
    public static final String FORMAT_JSON = "json";
    /** $format?atom. */
    public static final String FORMAT_ATOM = "atom";

    /** ??(?"SYSUTCDATETIME()"?????PersoniumJsonFromatParser?). */
    private long currentTimeMillis = System.currentTimeMillis();

    /**
     * entitySetName?.
     * @param entitySetName ??
     */
    public void setEntitySetName(String entitySetName) {
        this.entitySetName = entitySetName;
    }

    /**
     * odataProducer?.
     * @return odataProducer
     */
    public PersoniumODataProducer getOdataProducer() {
        return this.odataProducer;
    }

    /**
     * odataProducer?.
     * @param odataProducer odataProducer
     */
    public void setOdataProducer(PersoniumODataProducer odataProducer) {
        this.odataProducer = odataProducer;
    }

    /**
     * entitySetName?.
     * @return ??
     */
    public String getEntitySetName() {
        return this.entitySetName;
    }

    /**
     * ????ContentType???.
     * @param accept Accept ?
     * @param format $format 
     * @return ????Content-Type
     */
    public final MediaType decideOutputFormat(final String accept, final String format) {
        MediaType mediaType = null;
        if (format != null) {
            mediaType = decideOutputFormatFromQueryValue(format);
        } else if (accept != null) {
            mediaType = decideOutputFormatFromHeaderValues(accept);
        }
        if (mediaType == null) {
            // set default.
            mediaType = MediaType.APPLICATION_ATOM_XML_TYPE;
        }
        return mediaType;
    }

    /**
     * ??($format)??.
     * @param format $format?
     * @return ("application/json" or "application/atom+xml")
     */
    private MediaType decideOutputFormatFromQueryValue(String format) {
        MediaType mediaType = null;

        if (format.equals(FORMAT_ATOM)) {
            // $format??atom???
            mediaType = MediaType.APPLICATION_ATOM_XML_TYPE;
        } else if (format.equals(FORMAT_JSON)) {
            mediaType = MediaType.APPLICATION_JSON_TYPE;
        } else {
            throw PersoniumCoreException.OData.FORMAT_INVALID_ERROR.params(format);
        }

        return mediaType;
    }

    /**
     * Accept???.
     * @param format Accept?
     * @return ("application/json" or "application/atom+xml")
     */
    private MediaType decideOutputFormatFromHeaderValues(String acceptHeaderValue) {
        MediaType mediaType = null;
        StringTokenizer st = new StringTokenizer(acceptHeaderValue, ",");
        while (st.hasMoreTokens()) {
            String accept = truncateAfterSemicolon(st.nextToken());
            if (isAcceptXml(accept)) {
                mediaType = MediaType.APPLICATION_ATOM_XML_TYPE;
            } else if (isAcceptJson(accept)) {
                if (mediaType == null) {
                    mediaType = MediaType.APPLICATION_JSON_TYPE;
                }
            } else {
                throw PersoniumCoreException.OData.UNSUPPORTED_MEDIA_TYPE.params(acceptHeaderValue);
            }
        }
        return mediaType;
    }

    /**
     * ????.
     * @param source Accept????
     * @return ???
     */
    private String truncateAfterSemicolon(String source) {
        String result = source;
        int index = source.indexOf(";");
        if (index >= 0) {
            result = source.substring(0, index);
        }
        return result;
    }

    private boolean isAcceptXml(String accept) {
        return accept.equals(MediaType.APPLICATION_ATOM_XML) || accept.equals(MediaType.APPLICATION_XML)
                || accept.equals(MediaType.WILDCARD);
    }

    private boolean isAcceptJson(String accept) {
        return accept.equals(MediaType.APPLICATION_JSON);
    }

    /**
     * Entity?? Producer??.
     * @param reader 
     * @param odataResource OData
     * @return EntityResponse
     */
    protected EntityResponse createEntity(final Reader reader, ODataCtlResource odataResource) {
        OEntityWrapper oew = getOEntityWrapper(reader, odataResource, null);

        // Entity?? Producer??.????????????
        EntityResponse res = getOdataProducer().createEntity(getEntitySetName(), oew);
        return res;
    }

    /**
     * ?OEntityWrapper??.
     * @param reader 
     * @param odataResource OData
     * @param metadata 
     * @return OEntityWrapper
     */
    public OEntityWrapper getOEntityWrapper(final Reader reader, ODataCtlResource odataResource,
            EdmDataServices metadata) {
        // ????OEntity?
        OEntity newEnt;
        if (metadata == null) {
            newEnt = createRequestEntity(reader, null);
        } else {
            newEnt = createRequestEntity(reader, null, metadata);
        }

        // ???. POST?If-Match ETag??????????etag?null
        String uuid = PersoniumUUID.randomUUID();
        OEntityWrapper oew = new OEntityWrapper(uuid, newEnt, null);
        // ??????
        odataResource.beforeCreate(oew);

        return oew;
    }

    /**
     * ?OEntity??.
     * ????????????????????????
     * @param reader 
     * @param oEntityKey ?entityKey???null?
     * @return OData
     */
    protected OEntity createRequestEntity(final Reader reader, OEntityKey oEntityKey) {
        // ??
        EdmDataServices metadata = this.odataProducer.getMetadata();
        return createRequestEntity(reader, oEntityKey, metadata);
    }

    /**
     * ?OEntity??.
     * ????????????????????????
     * @param reader 
     * @param oEntityKey ?entityKey???null?
     * @param metadata EdmDataServices
     * @return OData
     */
    protected OEntity createRequestEntity(final Reader reader, OEntityKey oEntityKey, EdmDataServices metadata) {
        return createRequestEntity(reader, oEntityKey, metadata, this.entitySetName);
    }

    /**
     * ?OEntity??.
     * ????????????????????????
     * @param reader 
     * @param oEntityKey ?entityKey???null?
     * @param metadata EdmDataServices
     * @param entitySetNameParam EntitySet??
     * @return OData
     */
    protected OEntity createRequestEntity(final Reader reader, OEntityKey oEntityKey, EdmDataServices metadata,
            String entitySetNameParam) {
        // ??
        EdmEntitySet edmEntitySet = metadata.findEdmEntitySet(entitySetNameParam);
        EdmEntityType edmEntityType = edmEntitySet.getType();
        // ????
        List<String> keysDefined = edmEntityType.getKeys();

        // ??????Ontity??
        // ??OEntity?????????????
        OEntity reqEntity = createOEntityFromRequest(keysDefined, metadata, reader, entitySetNameParam);
        List<OLink> links = reqEntity.getLinks();

        // TODO Static????
        List<OProperty<?>> props = new ArrayList<OProperty<?>>();
        List<String> schemaProps = new ArrayList<String>();

        // ??
        if (oEntityKey != null) {
            validatePrimaryKey(oEntityKey, edmEntityType);
        }

        for (EdmProperty ep : edmEntityType.getProperties()) {
            String propName = ep.getName();
            schemaProps.add(propName);
            OProperty<?> op = null;
            try {
                // OEntity??
                op = reqEntity.getProperty(propName);
                // ?__published?__updated??????400??
                if (op != null && (propName.equals(Common.P_PUBLISHED.getName())
                        || propName.equals(Common.P_UPDATED.getName()))) {
                    throw PersoniumCoreException.OData.FIELED_INVALID_ERROR
                            .params(propName + " is management information name. Cannot request.");
                }

                if (ep.getType().isSimple()) {
                    // ??
                    op = getSimpleProperty(ep, propName, op);
                } else {
                    // Complex??
                    op = getComplexProperty(ep, propName, op, metadata);
                }
            } catch (PersoniumCoreException e) {
                throw e;
            } catch (Exception e) {
                op = setDefaultValue(ep, propName, op, metadata);
            }

            // ??????????
            if (op != null && op.getValue() != null) {
                validateProperty(ep, propName, op);
            }

            if (op != null) {
                props.add(op);
            }
        }
        // DynamicProperty??
        int dynamicPropCount = 0;
        for (OProperty<?> property : reqEntity.getProperties()) {

            String req = property.getName();
            if (req.equals("__metadata")) {
                // ?__metadata??????400??
                throw PersoniumCoreException.OData.FIELED_INVALID_ERROR
                        .params(req + " is management information name. Cannot request.");
            }
            // EntityType????dynamicProperty?????????????
            // ?????declaredProperty???????????
            if (!schemaProps.contains(req)) {
                // dynamicProperty??
                dynamicPropCount++;
                validateDynamicProperty(property);
                props.add(property);
            } else {
                // ?????null????null???ES?????????
                // ????????ES??????????????
                // ?????null???2???????
                if (property.getValue() == null && isRegisteredDynamicProperty(edmEntityType, req)) {
                    validateDynamicProperty(property);
                    props.add(property);
                }
            }
        }
        // ?????
        this.collectProperties(props);
        this.validate(props);

        // ???
        if (dynamicPropCount > 0) {
            PropertyLimitChecker checker = new PropertyLimitChecker(metadata, entitySetNameParam, dynamicPropCount);
            List<CheckError> errors = checker.checkPropertyLimits(entitySetNameParam);
            if (errors.size() > 0) {
                throw PersoniumCoreException.OData.ENTITYTYPE_STRUCTUAL_LIMITATION_EXCEEDED;
            }
        }

        // entity ?Immutable ?????????Entity??
        // ??OEntity??????????????????
        OEntity newEnt = OEntities.createRequest(edmEntitySet, props, links);

        // key
        OEntityKey key = null;
        // ????????
        if (keysDefined.size() == 1) {
            // single-unnamed value
            String keyPropName = keysDefined.get(0);
            OProperty<?> keyProp = newEnt.getProperty(keyPropName);
            Object value = keyProp.getValue();
            if (value == null) {
                // ??null???400
                throw PersoniumCoreException.OData.NULL_SINGLE_KEY;
            }
            key = OEntityKey.create(keyProp.getValue());
        } else {
            // multiple-named value?
            Map<String, Object> keyMap = new HashMap<String, Object>();
            for (String keyPropName : keysDefined) {
                OProperty<?> keyProp = newEnt.getProperty(keyPropName);
                Object value = keyProp.getValue();
                if (value == null) {
                    // ?????null??
                    // ?null???OEntityKey????????
                    value = DUMMY_KEY;
                    props.remove(keyProp);
                    props.add(OProperties.string(keyPropName, (String) null));
                }
                keyMap.put(keyPropName, value);
            }
            key = OEntityKey.create(keyMap);
        }
        editProperty(props, key.toKeyString());
        // ????OEntity?
        // ??OEntity??????????????????
        newEnt = OEntities.create(reqEntity.getEntitySet(), key, props, links, key.toKeyStringWithoutParentheses(),
                reqEntity.getEntitySet().getName());
        return newEnt;

    }

    /**
     * ????DynamicProperty???????.
     * @param edmEntityType edmEntityType
     * @param propertyName ??
     * @return true:DynamicProperty, false:DeclaredProperty
     */
    protected boolean isRegisteredDynamicProperty(EdmEntityType edmEntityType, String propertyName) {
        boolean isRegisteredDynamicProperty = false;
        EdmProperty prop = edmEntityType.findDeclaredProperty(propertyName);
        NamespacedAnnotation<?> isDeclared = prop.findAnnotation(Common.P_NAMESPACE.getUri(),
                Property.P_IS_DECLARED.getName());
        // Property/ComplexTypeProperty???IsDeclared??????????
        if (isDeclared != null && isDeclared.getValue().equals("false")) {
            isRegisteredDynamicProperty = true;
        }
        return isRegisteredDynamicProperty;
    }

    /**
     * ??.
     * @param oEntityKey ??Key
     * @param edmEntityType EntityType?
     */
    protected void validatePrimaryKey(OEntityKey oEntityKey, EdmEntityType edmEntityType) {
        for (String key : edmEntityType.getKeys()) {
            EdmType keyEdmType = edmEntityType.findProperty(key).getType();
            if (OEntityKey.KeyType.SINGLE.equals(oEntityKey.getKeyType())) {
                // ???
                if (!(oEntityKey.asSingleValue().getClass().equals(
                        EdmSimpleType.getSimple(keyEdmType.getFullyQualifiedTypeName()).getCanonicalJavaType()))) {
                    throw PersoniumCoreException.OData.ENTITY_KEY_PARSE_ERROR;
                }
            } else {
                // ???
                Set<NamedValue<?>> nvSet = oEntityKey.asComplexValue();
                for (NamedValue<?> nv : nvSet) {
                    if (nv.getName().equals(key) && !(nv.getValue().getClass().equals(EdmSimpleType
                            .getSimple(keyEdmType.getFullyQualifiedTypeName()).getCanonicalJavaType()))) {
                        throw PersoniumCoreException.OData.ENTITY_KEY_PARSE_ERROR;
                    }
                }
            }
        }
    }

    /**
     * ?.
     * @param props 
     * @param value ?
     */
    protected void editProperty(List<OProperty<?>> props, String value) {
    }

    /**
     * ????.
     * @param ep EdmProperty
     * @param propName ??
     * @param op OProperty
     * @return ??
     */
    protected OProperty<?> getSimpleProperty(EdmProperty ep, String propName, OProperty<?> op) {
        // ?????????
        if (op == null || op.getValue() == null) {
            op = setDefaultValue(ep, propName, op);
        }
        return op;
    }

    /**
     * ??Complex??.
     * @param ep EdmProperty
     * @param propName ??
     * @param op OProperty
     * @param metadata 
     * @return ??Complex
     */
    @SuppressWarnings("unchecked")
    protected OProperty<?> getComplexProperty(EdmProperty ep, String propName, OProperty<?> op,
            EdmDataServices metadata) {
        // ComplexType???
        OProperty<?> newProp;
        EdmComplexType edmComplexType = metadata.findEdmComplexType(ep.getType().getFullyQualifiedTypeName());

        // dynamicProperty??
        if (op == null || op.getValue() == null) {
            newProp = setDefaultValue(ep, propName, op, metadata);
        } else {
            if (ep.getCollectionKind().equals(CollectionKind.List)) {
                // ComplexType???????OCollectionBuilder??
                EdmCollectionType collectionType = new EdmCollectionType(CollectionKind.List, ep.getType());
                OCollection.Builder<OObject> builder = OCollections
                        .<OObject>newBuilder(collectionType.getItemType());

                if (op.getValue() instanceof OCollection<?>) {
                    // ComplexType???
                    for (OComplexObject val : (OCollection<OComplexObject>) op.getValue()) {
                        // ComplexType?OProperty??
                        List<OProperty<?>> newComplexProperties = getComplexPropertyList(ep, propName,
                                val.getProperties(), metadata);
                        builder.add(OComplexObjects.create(edmComplexType, newComplexProperties));
                    }

                    // ComplexType???OCollection????
                    newProp = OProperties.collection(ep.getName(), collectionType, builder.build());
                } else {
                    throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
                }
            } else {
                // ComplexType????????ComplexType????
                List<OProperty<?>> newComplexProperties = getComplexPropertyList(ep, propName,
                        (List<OProperty<?>>) op.getValue(), metadata);
                newProp = OProperties.complex(propName, edmComplexType, newComplexProperties);
            }
        }
        return newProp;
    }

    /**
     * ??Complex??.
     * @param ep EdmProperty
     * @param propName ??
     * @param opList OProperty
     * @param metadata 
     * @return ??
     */
    protected List<OProperty<?>> getComplexPropertyList(EdmProperty ep, String propName, List<OProperty<?>> opList,
            EdmDataServices metadata) {
        // ComplexType???
        EdmComplexType edmComplexType = metadata.findEdmComplexType(ep.getType().getFullyQualifiedTypeName());
        Map<String, OProperty<?>> complexProperties = new HashMap<String, OProperty<?>>();

        // ????????????OProperty?Hash????
        for (OProperty<?> cp : opList) {
            complexProperties.put(cp.getName(), cp);
        }

        List<OProperty<?>> newComplexProperties = createNewComplexProperties(metadata, edmComplexType,
                complexProperties);

        // ??ComplexType???
        return newComplexProperties;
    }

    /**
     * ComplexType????????.
     * @param metadata 
     * @param edmComplexType ComplexType?
     * @param complexProperties ComplexTypeProperty?List
     * @return ??ComplexType?
     */
    protected List<OProperty<?>> createNewComplexProperties(EdmDataServices metadata, EdmComplexType edmComplexType,
            Map<String, OProperty<?>> complexProperties) {
        List<OProperty<?>> newComplexProperties = new ArrayList<OProperty<?>>();
        for (EdmProperty ctp : edmComplexType.getProperties()) {
            // ??
            String compPropName = ctp.getName();
            OProperty<?> complexProperty = complexProperties.get(compPropName);
            if (ctp.getType().isSimple()) {
                // ??
                complexProperty = getSimpleProperty(ctp, compPropName, complexProperty);
            } else {
                // Complex??
                complexProperty = getComplexProperty(ctp, compPropName, complexProperty, metadata);
            }
            if (complexProperty != null) {
                newComplexProperties.add(complexProperty);
            }
        }
        return newComplexProperties;
    }

    /**
     * ?.
     * @param ep EdmProperty
     * @param propName ??
     * @param op OProperty
     * @return Oproperty
     */
    protected OProperty<?> setDefaultValue(EdmProperty ep, String propName, OProperty<?> op) {
        return setDefaultValue(ep, propName, op, null);
    }

    /**
     * ?.
     * @param ep EdmProperty
     * @param propName ??
     * @param op OProperty
     * @param metadata EdmDataServices
     * @return Oproperty
     */
    protected OProperty<?> setDefaultValue(EdmProperty ep, String propName, OProperty<?> op,
            EdmDataServices metadata) {
        // ????????? Property
        // ????????
        // ComplexType??????????????????
        NamespacedAnnotation<?> annotation = ep.findAnnotation(Common.P_NAMESPACE.getUri(),
                Property.P_IS_DECLARED.getName());
        if (annotation != null && !(Boolean.valueOf(annotation.getValue().toString()))) {
            return null;
        }
        if (ep.getType().isSimple() && !ep.getCollectionKind().equals(CollectionKind.List)
                && ep.getDefaultValue() != null) {
            op = generateDefautlProperty(ep);
        } else if (ep.isNullable()) {
            // nullable?true???null???
            // TODO ??????
            op = OProperties.null_(propName, ep.getType().getFullyQualifiedTypeName());
        } else {
            // nullable?false?????
            throw PersoniumCoreException.OData.INPUT_REQUIRED_FIELD_MISSING.params(propName);
        }
        return op;
    }

    /**
     * ???.
     * @param ep EdmProperty
     * @param propName ??
     * @param op OProperty
     */
    protected void validateProperty(EdmProperty ep, String propName, OProperty<?> op) {
        for (NamespacedAnnotation<?> annotation : ep.getAnnotations()) {
            if (annotation.getName().equals(Common.P_FORMAT)) {
                String pFormat = annotation.getValue().toString();
                // ?????
                if (pFormat.startsWith(Common.P_FORMAT_PATTERN_REGEX)) {
                    validatePropertyRegEx(propName, op, pFormat);
                } else if (pFormat.equals(Common.P_FORMAT_PATTERN_URI)) {
                    validatePropertyUri(propName, op);
                } else if (pFormat.startsWith(Common.P_FORMAT_PATTERN_SCHEMA_URI)) {
                    validatePropertySchemaUri(propName, op);
                } else if (pFormat.startsWith(Common.P_FORMAT_PATTERN_CELL_URL)) {
                    validatePropertyCellUrl(propName, op);
                } else if (pFormat.startsWith(Common.P_FORMAT_PATTERN_USUSST)) {
                    validatePropertyUsusst(propName, op, pFormat);
                } else if (pFormat.startsWith(Common.P_FORMAT_PATTERN_MESSAGE_REQUEST_RELATION)) {
                    validatePropertyMessageRequestRelation(propName, op);
                }
            }
        }
    }

    /**
     * p:Format???.
     * @param props 
     * @param
     */
    public void validate(List<OProperty<?>> props) {
    }

    /**
     * ??.
     * @param props 
     * @param
     */
    public void collectProperties(List<OProperty<?>> props) {
    }

    /**
     * ???.
     * @param property OProperty
     */
    private void validateDynamicProperty(OProperty<?> property) {
        // key??
        String key = property.getName();
        Pattern pattern = Pattern.compile(Common.PATTERN_USERDATA_KEY);
        Matcher matcher = pattern.matcher(key);
        if (!matcher.matches()) {
            throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(key);
        }

        // ???null??
        if (property.getValue() == null) {
            return;
        }

        // String?value??
        EdmType type = property.getType();
        if (EdmSimpleType.STRING.equals(type)) {
            String value = property.getValue().toString();
            if (!ODataUtils.validateString(value)) {
                throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(key);
            }
        }

        // Double??
        if (EdmSimpleType.DOUBLE.equals(type)) {
            double value = (Double) property.getValue();
            if (!ODataUtils.validateDouble(value)) {
                throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(key);
            }
        }
    }

    /**
     * ??????.
     * @param propName ??
     * @param op OProperty
     * @param pFormat pFormat?
     */
    protected void validatePropertyRegEx(String propName, OProperty<?> op, String pFormat) {
        // Extract regular expressions from('regular expression')
        Pattern formatPattern = Pattern.compile(Common.P_FORMAT_PATTERN_REGEX + "\\('(.+)'\\)");
        Matcher formatMatcher = formatPattern.matcher(pFormat);
        formatMatcher.matches();
        pFormat = formatMatcher.group(1);

        if (!ODataUtils.validateRegEx(op.getValue().toString(), pFormat)) {
            throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
        }
    }

    /**
     * ?URI???.
     * @param propName ??
     * @param op OProperty
     */
    protected void validatePropertyUri(String propName, OProperty<?> op) {
        if (!ODataUtils.isValidUri(op.getValue().toString())) {
            throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
        }
    }

    /**
     * Schema URI Format Check.
     * @param propName Property name
     * @param op OProperty
     */
    protected void validatePropertySchemaUri(String propName, OProperty<?> op) {
        if (!ODataUtils.isValidSchemaUri(op.getValue().toString())) {
            throw PersoniumCoreException.OData.SCHEMA_URI_FORMAT_ERROR.params(propName);
        }
    }

    /**
     * Cell URL Format Check.
     * @param propName Property name
     * @param op OProperty
     */
    protected void validatePropertyCellUrl(String propName, OProperty<?> op) {
        if (!ODataUtils.isValidCellUrl(op.getValue().toString())) {
            throw PersoniumCoreException.OData.CELL_URL_FORMAT_ERROR.params(propName);
        }
    }

    /**
     * ??1????????.
     * @param propName ??
     * @param op OProperty
     * @param pFormat pFormat?
     */
    protected void validatePropertyUsusst(String propName, OProperty<?> op, String pFormat) {
        // pFormat????.
        Pattern formatPattern = Pattern.compile(Common.P_FORMAT_PATTERN_USUSST + "\\((.+)\\)");
        Matcher formatMatcher = formatPattern.matcher(pFormat);
        formatMatcher.matches();
        pFormat = formatMatcher.group(1);

        String[] allowedTokens = pFormat.split(", ");
        for (int i = 0; i < allowedTokens.length; i++) {
            //remove single quotations.
            allowedTokens[i] = allowedTokens[i].replaceAll("\'(.+)\'", "$1");
        }
        List<String> allowedTokenList = Arrays.asList(allowedTokens);

        // ????
        String value = op.getValue().toString();
        if (value.indexOf("  ") > -1) {
            throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
        }
        String[] tokens = value.split(" ");
        Set<String> overlapChk = new HashSet<>();

        // ?????????
        // 1????????????
        for (String token : tokens) {
            if (!allowedTokenList.contains(token)) {
                throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
            }
            //??
            if (overlapChk.contains(token)) {
                throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
            } else {
                overlapChk.add(token);
            }
        }
    }

    /**
     * Message RequestRelation Format Check.
     * @param propName Property name
     * @param op OProperty
     */
    protected void validatePropertyMessageRequestRelation(String propName, OProperty<?> op) {
        if (!ODataUtils.validateClassUrl(op.getValue().toString(), Common.PATTERN_RELATION_CLASS_URL)
                && !ODataUtils.validateRegEx(op.getValue().toString(), Common.PATTERN_RELATION_NAME)
                && !ODataUtils.validateClassUrl(op.getValue().toString(), Common.PATTERN_ROLE_CLASS_URL)
                && !ODataUtils.validateRegEx(op.getValue().toString(), Common.PATTERN_NAME)) {
            throw PersoniumCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
        }
    }

    /**
     * OEntityKey???.
     * ??OEntityKeytoKeyString??????????
     * @param oEntityKey ??OEntityKey
     * @param edmEntitySet EdmEntitySet
     * @return OEntityKey ???OEntityKey
     */
    public static OEntityKey normalizeOEntityKey(OEntityKey oEntityKey, EdmEntitySet edmEntitySet) {
        EdmEntityType edmEntityType = edmEntitySet.getType();
        // ????
        List<String> keysDefined = edmEntityType.getKeys();

        // key
        OEntityKey key = null;
        // ????????
        if (keysDefined.size() == 1) {
            key = oEntityKey;
        } else {
            Map<String, Object> keyMap = new HashMap<String, Object>();
            if (OEntityKey.KeyType.COMPLEX == oEntityKey.getKeyType()) {
                // ?????
                Set<NamedValue<?>> nvSet = oEntityKey.asComplexValue();
                // multiple-named value?
                for (String keyName : keysDefined) {
                    for (NamedValue<?> nv : nvSet) {
                        if (nv.getName().equals(keyName)) {
                            // Map???
                            Object value = nv.getValue();
                            if (value == null) {
                                // ?????null??
                                // ?null???OEntityKey????????
                                value = DUMMY_KEY;
                            }
                            keyMap.put(keyName, value);
                        }
                    }
                }
            } else {
                // ?????
                Object keyValue = oEntityKey.asSingleValue();
                for (String keyName : keysDefined) {
                    EdmProperty eProp = edmEntityType.findProperty(keyName);
                    Object value = null;
                    if (eProp.isNullable()) {
                        // Nullable??null?????
                        // ?null???OEntityKey????????
                        value = DUMMY_KEY;
                    } else {
                        value = keyValue;
                    }
                    keyMap.put(keyName, value);
                }
            }
            // ??Map?OEntityKey?
            key = OEntityKey.create(keyMap);
        }
        return key;
    }

    /**
     * Reader?OEntity??.
     * @param keyPropNames ??????????
     * @param reader 
     * @return OEntity
     */
    private OEntity createOEntityFromRequest(List<String> keyPropNames, EdmDataServices metadata, Reader reader,
            String entitySetNameParam) {
        OEntityKey keyDummy = null;

        if (keyPropNames.size() == 1) {
            // single-unnamed value
            keyDummy = OEntityKey.create("");
        }
        // TODO multiple-named value?

        OEntity entity = null;

        // ODataVersion.V2????????????JSON????????V1????
        entity = convertFromString(reader, MediaType.APPLICATION_JSON_TYPE, ODataVersion.V1, metadata,
                entitySetNameParam, keyDummy);

        return entity;
    }

    /**
     * POST????.
     * @param ent OEntity
     * @param outputFormat Content-Type
     * @param responseStr ?
     * @param resUriInfo ??UriInfo
     * @param key ??
     * @return ?
     */
    protected ResponseBuilder getPostResponseBuilder(OEntity ent, MediaType outputFormat, String responseStr,
            UriInfo resUriInfo, String key) {
        ResponseBuilder rb = Response.status(HttpStatus.SC_CREATED).entity(responseStr).type(outputFormat)
                .header(HttpHeaders.LOCATION, resUriInfo.getBaseUri().toASCIIString() + getEntitySetName() + key)
                .header(ODataConstants.Headers.DATA_SERVICE_VERSION, ODataVersion.V2.asString);

        // ?ETAG
        if (ent instanceof OEntityWrapper) {
            OEntityWrapper oew2 = (OEntityWrapper) ent;
            String etag = oew2.getEtag();
            if (etag != null) {
                rb = rb.header(HttpHeaders.ETAG, "W/\"" + etag + "\"");
            }
        }
        return rb;
    }

    /**
     * ???.
     * @param uriInfo UriInfo
     * @param resp ?
     * @param format ??
     * @param acceptableMediaTypes ??MediaType?
     * @return ?
     */
    protected String renderEntityResponse(final UriInfo uriInfo, final EntityResponse resp, final String format,
            final List<MediaType> acceptableMediaTypes) {
        StringWriter w = new StringWriter();
        try {
            FormatWriter<EntityResponse> fw = PersoniumFormatWriterFactory.getFormatWriter(EntityResponse.class,
                    acceptableMediaTypes, format, null);
            // UriInfo uriInfo2 = PersoniumCoreUtils.createUriInfo(uriInfo, 1);
            fw.write(uriInfo, w, resp);
        } catch (UnsupportedOperationException e) {
            throw PersoniumCoreException.OData.FORMAT_INVALID_ERROR.params(format);
        }

        String responseStr = w.toString();

        return responseStr;

    }

    /**
     * Entity Data Model??OData???.
     * @param ep Entity Data Model?
     * @return ???OData?
     */
    private OProperty<?> generateDefautlProperty(EdmProperty ep) {
        EdmType edmType = ep.getType();
        OProperty<?> op = null;

        // ?Default??
        String defaultValue = ep.getDefaultValue();
        String propName = ep.getName();

        // Default?????????
        if (EdmSimpleType.STRING.equals(edmType)) {
            // Type??Default?CELLID()????
            if (defaultValue.equals("UUID()")) {
                // Type??Default?UUID()????
                String newUuid = UUID.randomUUID().toString().replaceAll("-", "");
                op = OProperties.string(propName, newUuid);
            } else if (defaultValue.equals("null")) {
                // Type??Default?null????
                op = OProperties.null_(propName, EdmSimpleType.STRING);
            } else {
                // Type??Default????????
                op = OProperties.string(propName, defaultValue);
            }
        } else if (EdmSimpleType.DATETIME.equals(edmType)) {
            // Edm.DateTime
            if (null == defaultValue || defaultValue.equals("null")) {
                // defaultValue?null???"null"????null?
                op = OProperties.null_(propName, EdmSimpleType.DATETIME);
            } else {
                // ?"\/Date(...)\/"?????
                // ?"SYSUTCDATETIME()"??????
                // TODO ???? Atom ?Default TimeZone??????
                op = OProperties.datetime(propName, new Date(getTimeMillis(defaultValue)));
            }

        } else if (EdmSimpleType.SINGLE.equals(edmType)) {
            // Type?SINGLE?Default?????
            op = OProperties.single(propName, Float.valueOf(defaultValue));
        } else if (EdmSimpleType.INT64.equals(edmType)) {
            // Type?INT64?Default?????
            op = OProperties.int64(propName, Long.valueOf(defaultValue));
        } else if (EdmSimpleType.INT32.equals(edmType)) {
            // Type?INT32?Default?????
            op = OProperties.int32(propName, Integer.valueOf(defaultValue));
        } else if (EdmSimpleType.BOOLEAN.equals(edmType)) {
            // Type?Boolean?Default?????
            op = OProperties.boolean_(propName, Boolean.parseBoolean(defaultValue));
        } else if (EdmSimpleType.DOUBLE.equals(edmType)) {
            // Type?Double?Default?????
            op = OProperties.double_(propName, Double.parseDouble(defaultValue));
        }

        return op;
    }

    private static OEntity convertFromString(final Reader body, final MediaType type, final ODataVersion version,
            final EdmDataServices metadata, final String entitySetName, final OEntityKey entityKey) {
        FormatParser<Entry> parser = PersoniumFormatParserFactory.getParser(Entry.class, type,
                new Settings(version, metadata, entitySetName, entityKey, null, false));
        Entry entry = null;
        try {
            entry = parser.parse(body);
        } catch (PersoniumCoreException e) {
            throw e;
        } catch (Exception e) {
            throw PersoniumCoreException.OData.JSON_PARSE_ERROR.reason(e);
        }
        return entry.getEntity();
    }

    /**
     * ?.
     * @param value ??
     * @return true: false:
     */
    public static boolean isDummy(Object value) {
        boolean flag = false;
        if (value.equals(DUMMY_KEY)) {
            flag = true;
        }
        return flag;
    }

    /**
     * null????????.
     * @param value ?
     * @return 
     */
    public static String replaceDummyKeyToNull(String value) {
        return value.replaceAll("'" + DUMMY_KEY + "'", "null");
    }

    /**
     * null????????(???).
     * @param value ?
     * @return 
     */
    public static String replaceNullToDummyKeyWithParenthesis(String value) {
        return replaceNullToDummyKey("(" + value + ")");
    }

    /**
     * null????????.
     * @param value ?
     * @return 
     */
    public static String replaceNullToDummyKey(String value) {
        Pattern pattern = Pattern.compile("=null([,|\\)])");
        Matcher m = pattern.matcher(value);
        return m.replaceAll("='" + DUMMY_KEY + "'$1");
    }

    /**
     * NavigationTargetKeyProperty?EntityType?PropertyName??.
     * @param propertyName ??
     * @return EntityType?PropertyName
     */
    public static HashMap<String, String> convertNTKP(String propertyName) {
        HashMap<String, String> ntkp = null;
        Pattern pattern = Pattern.compile("_([^.]+)\\.(.+)");
        Matcher m = pattern.matcher(propertyName);
        if (m.matches()) {
            ntkp = new HashMap<String, String>();
            ntkp.put("entityType", m.group(1));
            ntkp.put("propName", m.group(2));
        }
        return ntkp;
    }

    /**
     * ???TimeMillis??????.
     * @param timeStr TimeMillis??(ex."/Data(...)/", "SYSUTCDATETIME()")
     * @return TimeMillis?
     */
    private long getTimeMillis(String timeStr) {
        long timeMillis = 0;
        if (timeStr.equals(Common.SYSUTCDATETIME)) {
            timeMillis = currentTimeMillis;
        } else {
            try {
                Pattern pattern = Pattern.compile("^/Date\\((.+)\\)/$");
                Matcher match = pattern.matcher(timeStr);
                if (match.matches()) {
                    String date = match.replaceAll("$1");
                    timeMillis = Long.parseLong(date);
                }
            } catch (NumberFormatException e) {
                throw PersoniumCoreException.OData.JSON_PARSE_ERROR.reason(e);
            }
        }
        return timeMillis;
    }

    /**
     * ???.
     * @param response ?
     * @return ???
     */
    public String escapeResponsebody(String response) {
        return EscapeControlCode.escape(response);
    }
}