/** * personium.io * Modifications 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. * -------------------------------------------------- * This code is based on JsonFormatParser.java of odata4j-core, and some modifications * for personium.io are applied by us. * -------------------------------------------------- * The copyright and the license text of the original code is as follows: */ /**************************************************************************** * Copyright (c) 2010 odata4j * * 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 com.fujitsu.dc.core.odata; import java.util.ArrayList; import java.util.List; import org.odata4j.core.NamespacedAnnotation; import org.odata4j.core.OCollection; import org.odata4j.core.OComplexObject; 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.Settings; import org.odata4j.format.json.JsonTypeConverter; import org.odata4j.producer.edm.Edm; import com.fujitsu.dc.core.DcCoreException; import com.fujitsu.dc.core.model.ctl.Common; import com.fujitsu.dc.core.odata.DcJsonFeedFormatParser.JsonEntry; import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamReader; import com.fujitsu.dc.core.odata.DcJsonStreamReaderFactory.JsonStreamReader.JsonEvent; import com.fujitsu.dc.core.utils.ODataUtils; /** * JsonFormatParser. */ public class DcJsonFormatParser { /** __metadata. */ protected static final String METADATA_PROPERTY = "__metadata"; /** __deferred. */ protected static final String DEFERRED_PROPERTY = "__deferred"; /** __next. */ protected static final String NEXT_PROPERTY = "__next"; /** __count. */ protected static final String COUNT_PROPERTY = "__count"; /** uri. */ protected static final String URI_PROPERTY = "uri"; /** type. */ protected static final String TYPE_PROPERTY = "type"; /** etag. */ protected static final String ETAG_PROPERTY = "etag"; /** results. */ protected static final String RESULTS_PROPERTY = "results"; /** d. */ protected static final String DATA_PROPERTY = "d"; /** version. */ private ODataVersion version; /** metadata. */ private EdmDataServices metadata; /** entitySetName. */ private String entitySetName; /** entityKey. */ private OEntityKey entityKey; /** リクエスト時の時刻. */ private long currentTimeMillis = System.currentTimeMillis(); /** * ODataVersionのゲッター. * @return ODataVersion */ public ODataVersion getVersion() { return version; } /** * Metadataのゲッター. * @return EdmDataServices */ public EdmDataServices getMetadata() { return metadata; } /** * Metadataのセッター. * @param metadata スキーマ情報 */ public void setMetadata(EdmDataServices metadata) { this.metadata = metadata; } /** * entitySetNameのゲッター. * @return String */ public String getEntitySetName() { return entitySetName; } /** * entityKeyのゲッター. * @return OEntityKey */ public OEntityKey getEntityKey() { return entityKey; } /** * コンストラクタ. * @param settings セッティング情報 */ protected DcJsonFormatParser(Settings settings) { if (settings != null) { this.version = settings.version; this.metadata = settings.metadata; this.entitySetName = settings.entitySetName; this.entityKey = settings.entityKey; } } /** * ネストデータオブジェクト. */ static class JsonObjectPropertyValue { OComplexObject complexObject; OCollection<? extends OObject> collection; EdmCollectionType collectionType; } /** * JsonEntryをパースする. * @param ees EdmEntitySet * @param jsr JsonStreamReader * @return JsonEntry */ protected JsonEntry parseEntry(EdmEntitySet ees, JsonStreamReader jsr) { JsonEntry entry = new JsonEntry(ees); entry.properties = new ArrayList<OProperty<?>>(); entry.links = new ArrayList<OLink>(); String name = ""; while (jsr.hasNext()) { JsonEvent event = jsr.nextEvent(); if (event.isStartProperty()) { try { name = event.asStartProperty().getName(); ees = addProperty(entry, ees, name, jsr); } catch (IllegalArgumentException e) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name).reason(e); } } else if (event.isEndObject()) { break; } } entry.oentity = toOEntity(ees, entry.getEntityType(), entry.getEntityKey(), entry.properties, entry.links); return entry; } /** * OEntityへ変換する. * @param entitySet エンティティセット * @param entityType エンティティタイプ * @param key キー * @param properties プロパティ * @param links リンク情報 * @return OEntity */ private OEntity toOEntity(EdmEntitySet entitySet, EdmEntityType entityType, OEntityKey key, List<OProperty<?>> properties, List<OLink> links) { // key is what we pulled out of the _metadata, use it first. if (key != null) { return OEntities.create(entitySet, entityType, key, properties, links); } if (entityKey != null) { return OEntities.create(entitySet, entityType, entityKey, properties, links); } return OEntities.createRequest(entitySet, properties, links); } /** * adds the property. This property can be a navigation property too. In this * case a link will be added. If it's the meta data the information will be * added to the entry too. * @param entry JsonEntry * @param ees EdmEntitySet * @param name PropertyName * @param jsr JsonStreamReader * @return EdmEntitySet */ protected EdmEntitySet addProperty(JsonEntry entry, EdmEntitySet ees, String name, JsonStreamReader jsr) { JsonEvent event = jsr.nextEvent(); if (event.isEndProperty()) { // scalar property EdmProperty ep = entry.getEntityType().findProperty(name); if (ep == null) { // OpenEntityTypeの場合は、プロパティを追加する NamespacedAnnotation<?> openType = findAnnotation(ees.getType(), null, Edm.EntityType.OpenType); if (openType != null && openType.getValue() == "true") { Object propValue = null; try { propValue = event.asEndProperty().getObject(); } catch (NumberFormatException e) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name).reason(e); } // 型によって登録するEntityPropertyを変更する if (propValue instanceof Boolean) { entry.properties.add(JsonTypeConverter.parse(name, (EdmSimpleType<?>) EdmSimpleType.BOOLEAN, propValue.toString())); } else if (propValue instanceof Double) { entry.properties.add(JsonTypeConverter.parse(name, (EdmSimpleType<?>) EdmSimpleType.DOUBLE, propValue.toString())); } else { if (propValue == null) { entry.properties.add(JsonTypeConverter.parse(name, (EdmSimpleType<?>) EdmSimpleType.STRING, null)); } else { entry.properties.add(JsonTypeConverter.parse(name, (EdmSimpleType<?>) EdmSimpleType.STRING, propValue.toString())); } } } else { throw DcCoreException.OData.FIELED_INVALID_ERROR.params("unknown property " + name + " for " + entry.getEntityType().getName()); } } else { // StaticPropertyの値チェック String propValue = event.asEndProperty().getValue(); if (propValue != null) { EdmType type = ep.getType(); if (type.equals(EdmSimpleType.BOOLEAN) && !ODataUtils.validateBoolean(propValue)) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } else if (type.equals(EdmSimpleType.STRING) && !ODataUtils.validateString(propValue)) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } else if (type.equals(EdmSimpleType.DATETIME)) { if (!ODataUtils.validateDateTime(propValue)) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } if (Common.SYSUTCDATETIME.equals(propValue)) { String crrTime = String.valueOf(getCurrentTimeMillis()); propValue = String.format("/Date(%s)/", crrTime); } } else if (type.equals(EdmSimpleType.SINGLE) && !ODataUtils.validateSingle(propValue)) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } else if (type.equals(EdmSimpleType.INT32) && !ODataUtils.validateInt32(propValue)) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } else if (type.equals(EdmSimpleType.DOUBLE) && !ODataUtils.validateDouble(propValue)) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } } if (ep.getType().isSimple()) { // シンプル型(文字列や数値など)であればプロパティに追加する entry.properties.add(JsonTypeConverter.parse(name, (EdmSimpleType<?>) ep.getType(), propValue)); } else { if (propValue == null) { // ComplexType型で、値がnullの場合はエラーにしない entry.properties.add(JsonTypeConverter.parse(name, (EdmSimpleType<?>) EdmSimpleType.STRING, null)); } else { // ComplexType型で、ComplexType型以外の値が指定された場合("aaa")はエラーとする throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } } } } else if (event.isStartObject()) { // JSONオブジェクトの場合は値を取得する JsonObjectPropertyValue val = getValue(event, ees, name, jsr, entry); if (val.complexObject != null) { // ComplexTypeデータであればプロパティに追加する entry.properties.add(OProperties.complex(name, (EdmComplexType) val.complexObject.getType(), val.complexObject.getProperties())); } else { // ComplexTypeデータ以外はエラーとする throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } } else if (event.isStartArray()) { // 配列オブジェクトの場合 JsonObjectPropertyValue val = new JsonObjectPropertyValue(); // スキーマ定義が存在してCollectionKindがNoneでなければ、配列としてパースする EdmProperty eprop = entry.getEntityType().findProperty(name); if (null != eprop && eprop.getCollectionKind() != CollectionKind.NONE) { val.collectionType = new EdmCollectionType(eprop.getCollectionKind(), eprop.getType()); DcJsonCollectionFormatParser cfp = new DcJsonCollectionFormatParser(val.collectionType, this.metadata, name); val.collection = cfp.parseCollection(jsr); } // パースに成功した場合は、プロパティに追加する if (val.collectionType != null && val.collection != null) { entry.properties.add(OProperties.collection(name, val.collectionType, val.collection)); } else { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } } else { throw DcCoreException.OData.INVALID_TYPE_ERROR.params(name); } return ees; } /** * JSONオブジェクトの値を取得する. * @param event JsonEvent * @param ees エンティティセット型 * @param name プロパティ名 * @param jsr JsonStreamReader * @param entry JsonEntry * @return JsonObjectPropertyValue */ protected JsonObjectPropertyValue getValue(JsonEvent event, EdmEntitySet ees, String name, JsonStreamReader jsr, JsonEntry entry) { JsonObjectPropertyValue rt = new JsonObjectPropertyValue(); ensureStartObject(event); event = jsr.nextEvent(); ensureStartProperty(event); // ComplexObjectであればエンティティタイプ定義からプロパティ定義を取得する EdmProperty eprop = entry.getEntityType().findProperty(name); if (eprop == null) { // プロパティがスキーマ定義上に存在しなければエラーとする throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } else { // スキーマ定義からComplexType定義を取得する EdmComplexType ct = metadata.findEdmComplexType(eprop.getType().getFullyQualifiedTypeName()); if (null != ct) { // ComplexTypeが存在する場合は、パースを実施してComplexTypeObjectを取得する Settings s = new Settings(version, metadata, entitySetName, entityKey, null, false, ct); DcJsonComplexObjectFormatParser cofp = new DcJsonComplexObjectFormatParser(s); rt.complexObject = cofp.parseSingleObject(jsr, event); } else { // ComplexTypeがスキーマ定義上に存在しなければエラーとする throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(name); } } ensureEndProperty(jsr.nextEvent()); return rt; } /** * 指定したアノテーションを取得する. * @param type EdmType * @param namespaceUri 名前空間 * @param localName 取得対象名 * @return namespaceUri */ private NamespacedAnnotation<?> findAnnotation(EdmType type, String namespaceUri, String localName) { if (type != null) { for (NamespacedAnnotation<?> annotation : type.getAnnotations()) { if (localName.equals(annotation.getName())) { String uri = annotation.getNamespace().getUri(); if ((namespaceUri == null && uri == null) || (namespaceUri != null && namespaceUri.equals(uri))) { return annotation; } } } } return null; } /** * ensureNext. * @param jsr JsonStreamReader */ protected void ensureNext(JsonStreamReader jsr) { if (!jsr.hasNext()) { throw new IllegalArgumentException("no valid JSON format exepected at least one more event"); } } /** * ensureStartObject. * @param event JsonEvent */ protected void ensureStartObject(JsonEvent event) { if (!event.isStartObject()) { throw new IllegalArgumentException("no valid OData JSON format expected StartObject got " + event + ")"); } } /** * ensureStartProperty. * @param event JsonEvent */ protected void ensureStartProperty(JsonEvent event) { if (!event.isStartProperty()) { throw new IllegalArgumentException("no valid OData JSON format (expected StartProperty got " + event + ")"); } } /** * ensureEndProperty. * @param event JsonEvent */ protected void ensureEndProperty(JsonEvent event) { if (!event.isEndProperty()) { throw new IllegalArgumentException("no valid OData JSON format (expected EndProperty got " + event + ")"); } } /** * ensureEndArray. * @param event JsonEvent */ protected void ensureEndArray(JsonEvent event) { if (!event.isEndArray()) { throw new IllegalArgumentException("no valid OData JSON format expected EndArray got " + event + ")"); } } /** * 現在時刻を取得する. * @return the currentTimeMillis */ public long getCurrentTimeMillis() { return currentTimeMillis; } }