/**
* 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 com.fujitsu.dc.core.model.impl.es.odata;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.core4j.Enumerable;
import org.odata4j.edm.EdmComplexType;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmDataServices.Builder;
import org.odata4j.edm.EdmEntitySet;
import org.odata4j.edm.EdmEntityType;
import org.odata4j.edm.EdmProperty;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.edm.EdmType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.fujitsu.dc.core.DcCoreConfig;
import com.fujitsu.dc.core.model.impl.es.doc.ComplexTypePropertyDocHandler;
import com.fujitsu.dc.core.model.impl.es.doc.PropertyDocHandler;
import java.util.Arrays;
/**
* EntityType内の Property数制限数をチェックするためのメソッドを実装しているクラス.
*/
public class PropertyLimitChecker {
static Logger log = LoggerFactory.getLogger(PropertyLimitChecker.class);
/**
* 制限チェックでエラーが発生した場合の通知オブジェクト.
*/
public static class CheckError {
String entityTypeName;
String message;
/**
* コンストラクタ.
* @param entityTypeName EntityType名
* @param message エラー詳細
*/
public CheckError(String entityTypeName, String message) {
this.entityTypeName = entityTypeName;
this.message = message;
}
/**
* EntityType名を返す.
* @return EntityType名
*/
public String getEntityTypeName() {
return entityTypeName;
}
/**
* エラーメッセージを返す.
* @return エラーメッセージ
*/
public String getMessage() {
return message;
}
}
/**
* 内部例外クラス.
*/
@SuppressWarnings("serial")
static class PropertyLimitException extends Exception {
private String entityTypeName = null;
/**
* コンストラクタ.
* @param message メッセージ
*/
PropertyLimitException(String message) {
super(message);
}
/**
* コンストラクタ.
* @param entityTypeName EntityType名
* @param message メッセージ
*/
PropertyLimitException(String entityTypeName, String message) {
super(message);
this.entityTypeName = entityTypeName;
}
/**
* EntityType名を返す.
* @return EntityType名
*/
public String getEntityTypeName() {
return entityTypeName;
}
/**
* EntityType名を設定する.
* @param entityTypeName EntityType名
*/
public void setEntityTypeName(String entityTypeName) {
this.entityTypeName = entityTypeName;
}
}
/**
* EntityTypeの階層の深さが制限を超えた場合の例外.
*/
@SuppressWarnings("serial")
static class EntityTypeDepthExceedException extends PropertyLimitException {
/**
* constructor.
* @param message メッセージ
*/
EntityTypeDepthExceedException(String message) {
super(message);
}
}
EdmDataServices metadata = null;
int maxPropertyLimitInEntityType = 0;
int maxDepth = 0;
int[] simplePropertyLimits = null;
int[] complexPropertyLimits = null;
int simpleMaxForOverAllLayers = 0;
int complexMaxForOverallLayers = 0;
/**
* default constructor.
*/
public PropertyLimitChecker() {
this.maxPropertyLimitInEntityType = DcCoreConfig.getMaxPropertyCountInEntityType();
this.simplePropertyLimits = DcCoreConfig.getUserdataSimpleTypePropertyLimits();
this.complexPropertyLimits = DcCoreConfig.getUserdataComplexTypePropertyLimits();
this.maxDepth = simplePropertyLimits.length;
int[] maxPropLimitCopy = Arrays.copyOf(simplePropertyLimits, simplePropertyLimits.length);
Arrays.sort(maxPropLimitCopy);
this.simpleMaxForOverAllLayers = maxPropLimitCopy[maxPropLimitCopy.length - 1];
maxPropLimitCopy = Arrays.copyOf(complexPropertyLimits, complexPropertyLimits.length);
Arrays.sort(maxPropLimitCopy);
this.complexMaxForOverallLayers = maxPropLimitCopy[maxPropLimitCopy.length - 1];
}
/**
* constructor.
* @param metadata UserDataのメタデータ(プロパティ更新前)
* @param entityTypeName 追加対象のEntityType名
* @param dynamicPropCount 追加されるDynamicProperty数
*/
public PropertyLimitChecker(final EdmDataServices metadata,
final String entityTypeName, final int dynamicPropCount) {
this();
// 引数で受け取る metadataは更新前のものなので、追加後のメタデータを作成する必要がある。
Builder builder = EdmDataServices.newBuilder(metadata);
// 追加対象数分、ダミーでプロパティをスキーマ情報に追加する
String dummyKey = "dummy" + System.currentTimeMillis();
for (int i = 0; i < dynamicPropCount; i++) {
org.odata4j.edm.EdmEntityType.Builder entityTypeBuilder = builder
.findEdmEntityType(UserDataODataProducer.USER_ODATA_NAMESPACE + "." + entityTypeName);
org.odata4j.edm.EdmProperty.Builder propertyBuilder =
EdmProperty.newBuilder(String.format("%s_%03d", dummyKey, i))
.setType(EdmSimpleType.getSimple("Edm.String"));
entityTypeBuilder.addProperties(propertyBuilder);
}
// 変更後のメタデータを作成
this.metadata = builder.build();
}
/**
* constructor.
* @param metadata UserDataのメタデータ(プロパティ更新前)
* @param propHandler 追加するプロパティのハンドラ
*/
public PropertyLimitChecker(final EdmDataServices metadata, PropertyDocHandler propHandler) {
this();
// 引数で受け取る metadataは更新前のものなので、追加後のメタデータを作成する必要がある。
Builder builder = EdmDataServices.newBuilder(metadata);
// ここで新規Property, ComplexTypeProperty等を追加
final String dummyPropertyKey = "DUMMY" + System.currentTimeMillis();
if (propHandler instanceof ComplexTypePropertyDocHandler) {
// ComplexTypePropertyDocHandlerの場合の処理
org.odata4j.edm.EdmComplexType.Builder complexTypeBuilder = builder
.findEdmComplexType(UserDataODataProducer.USER_ODATA_NAMESPACE + "." + propHandler
.getEntityTypeName());
Map<String, Object> staticFields = propHandler.getStaticFields();
String typeName = (String) staticFields.get("Type");
EdmType type = EdmSimpleType.getSimple(typeName);
if (null != type) {
org.odata4j.edm.EdmProperty.Builder propertyBuilder = EdmProperty.newBuilder(dummyPropertyKey).setType(
type);
complexTypeBuilder.addProperties(propertyBuilder);
} else {
EdmComplexType complex = metadata.findEdmComplexType(UserDataODataProducer.USER_ODATA_NAMESPACE + "."
+ typeName);
org.odata4j.edm.EdmProperty.Builder propertyBuilder = EdmProperty.newBuilder(dummyPropertyKey).setType(
complex);
complexTypeBuilder.addProperties(propertyBuilder);
}
} else {
org.odata4j.edm.EdmEntityType.Builder entityTypeBuilder = builder
.findEdmEntityType(UserDataODataProducer.USER_ODATA_NAMESPACE + "." + propHandler
.getEntityTypeName());
Map<String, Object> staticFields = propHandler.getStaticFields();
String typeName = (String) staticFields.get("Type");
EdmType type = EdmSimpleType.getSimple(typeName);
if (null != type) {
org.odata4j.edm.EdmProperty.Builder propertyBuilder = EdmProperty.newBuilder(dummyPropertyKey).setType(
type);
entityTypeBuilder.addProperties(propertyBuilder);
} else {
EdmComplexType complex = metadata.findEdmComplexType(UserDataODataProducer.USER_ODATA_NAMESPACE + "."
+ typeName);
org.odata4j.edm.EdmProperty.Builder propertyBuilder = EdmProperty.newBuilder(dummyPropertyKey).setType(
complex);
entityTypeBuilder.addProperties(propertyBuilder);
}
}
// 変更後のメタデータを作成
this.metadata = builder.build();
}
/**
* プロパティ数、階層制限値をチェックする.
* @return エラー情報のリスト
*/
public List<PropertyLimitChecker.CheckError> checkPropertyLimits() {
List<PropertyLimitChecker.CheckError> result = new ArrayList<PropertyLimitChecker.CheckError>();
if (null == metadata) {
return result;
}
Iterator<EdmEntityType> iter = metadata.getEntityTypes().iterator();
while (iter.hasNext()) {
EdmEntityType target = iter.next();
checkPropertyLimitsForEntityTypeInternal(result, target);
}
// EntityTypeに関連づいていない ComplexType内のプロパティ数の制限チェック
Iterator<EdmComplexType> complexTypeIter = metadata.getComplexTypes().iterator();
// 全 ComplexTypeに対してチェックする。(効率的に行うのであれば、先に EntityType側のチェックを行い、未チェックの ComplexTypeのみ実施する方法もあり)
while (complexTypeIter.hasNext()) {
int simplePropCount = 0;
int complexPropCount = 0;
EdmComplexType complexType = complexTypeIter.next();
for (EdmProperty prop : complexType.getProperties()) {
// 予約語プロパティの除外
if (prop.getName().startsWith("_")) {
continue;
}
if (prop.getType().isSimple()) {
simplePropCount++;
} else {
complexPropCount++;
}
}
if (simpleMaxForOverAllLayers < simplePropCount) {
String message = String.format(
"Total property[%s] count exceeds the limit[%d].", complexType.getName(),
simpleMaxForOverAllLayers);
log.info(message);
result.add(new PropertyLimitChecker.CheckError(complexType.getName(), message));
}
if (complexMaxForOverallLayers < complexPropCount) {
String message = String.format(
"Total property[%s] count exceeds the limit[%d].", complexType.getName(),
complexMaxForOverallLayers);
log.info(message);
result.add(new PropertyLimitChecker.CheckError(complexType.getName(), message));
}
}
return result;
}
/**
* 指定されたエンティティタイプのプロパティ数をチェックする.
* @param entityTypeName エンティティタイプ名
* @return エラー情報のリスト
*/
public List<PropertyLimitChecker.CheckError> checkPropertyLimits(String entityTypeName) {
List<PropertyLimitChecker.CheckError> result = new ArrayList<PropertyLimitChecker.CheckError>();
EdmEntitySet edmEntitySet = this.metadata.findEdmEntitySet(entityTypeName);
EdmEntityType edmEntityType = edmEntitySet.getType();
checkPropertyLimitsForEntityTypeInternal(result, edmEntityType);
return result;
}
private void checkPropertyLimitsForEntityTypeInternal(
List<PropertyLimitChecker.CheckError> result, EdmEntityType target) {
try {
// 予約語プロパティは内部で除外される。
int totalProps = checkPropetyLimitPerLayer(0, target.getName(), target.getProperties());
// 全体プロパティ数チェック
if (maxPropertyLimitInEntityType < totalProps) {
// 制限値を超えた。
String message = String.format("Total property count exceeds the limit[%d].",
maxPropertyLimitInEntityType);
log.info(message);
throw new PropertyLimitException(target.getName(), message);
}
} catch (PropertyLimitException e) {
if (e instanceof EntityTypeDepthExceedException) {
e.setEntityTypeName(target.getName());
}
result.add(new PropertyLimitChecker.CheckError(e.getEntityTypeName(), e.getMessage()));
}
}
/**
* 階層レベルでのプロパティ制限数チェックルーチン.
* @param depth 階層の深さ(第一階層は0, 第二階層は 1,....)
* @param entityTypeName EntityType名
* @param properties EntityType内のプロパティ
* @return 対象EntityType配下の全プロパティ数
* @throws PropertyLimitException プロパティ数の制限を超えたことを通知する例外
*/
private int checkPropetyLimitPerLayer(int depth, String entityTypeName, Enumerable<EdmProperty> properties)
throws PropertyLimitException {
log.debug(String.format("Checking [%s] Depth[%d]", entityTypeName, depth));
if (maxDepth <= depth) {
// 実際にはここは通らないはず。
// ここに来る前に、Property, ComplexTypePropertyの制限値が 0となるチェックが必ず走るため。
String message = String.format("Hiearchy depth exceeds the limit[%d].", maxDepth);
log.info(message);
throw new EntityTypeDepthExceedException(message);
}
int simplePropCount = 0;
int complexPropCount = 0;
for (EdmProperty prop : properties) {
// 予約語プロパティの除外
if (prop.getName().startsWith("_")) {
continue;
}
if (prop.getType().isSimple()) {
simplePropCount++;
} else {
complexPropCount++;
}
}
// 階層毎のプロパティ数制限値のチェック
int currentSimplePropLimits = simplePropertyLimits[depth];
if (currentSimplePropLimits < 0) {
// 1階層目の特殊処理。そもそも1階層目の Limitが "*" と指定された場合 -1がsimplePropertyLimits[0]に
// 代入されている。これをmaxPropertyLimitsInEntityTypeとして認識し、かつ現行の complexPropertyの
// 数を勘案した制限値を計算しておく。
currentSimplePropLimits = maxPropertyLimitInEntityType - complexPropCount;
}
if (currentSimplePropLimits < simplePropCount) {
// 制限値を超えた。
String message = String.format("Property count exceeds the limit[%d].", simplePropertyLimits[depth]);
log.info(message);
throw new PropertyLimitException(entityTypeName, message);
}
if (complexPropertyLimits[depth] < complexPropCount) {
// 制限値を超えた。
String message = String.format("ComplexTypeProperty count exceeds the limit[%d].",
complexPropertyLimits[depth]);
log.info(message);
throw new PropertyLimitException(entityTypeName, message);
}
int totalPropCountOfChildren = 0;
depth++;
for (EdmProperty prop : properties) {
if (!prop.getType().isSimple()) {
// 子階層へ処理を進める。SimpleTypeの場合は対象外
String complexTypeName = prop.getType().getFullyQualifiedTypeName();
EdmComplexType edmComplexType = metadata.findEdmComplexType(complexTypeName);
if (null != edmComplexType) {
log.debug(String.format(" Proceed to child check [%s] Depth[%d]",
edmComplexType.getName(), depth));
totalPropCountOfChildren += checkPropetyLimitPerLayer(depth, edmComplexType.getName(),
edmComplexType.getProperties());
}
}
}
// 子階層を含めた総プロパティ数を返す。
return simplePropCount + complexPropCount + totalPropCountOfChildren;
}
}