/**
* 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.rs.box;
import java.io.StringWriter;
import java.util.List;
import javax.ws.rs.GET;
import javax.ws.rs.OPTIONS;
import javax.ws.rs.Path;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang.NotImplementedException;
import org.odata4j.core.ODataConstants;
import org.odata4j.core.OEntityId;
import org.odata4j.core.OEntityKey;
import org.odata4j.core.OProperty;
import org.odata4j.edm.EdmDataServices;
import org.odata4j.edm.EdmProperty.CollectionKind;
import org.odata4j.edm.EdmSimpleType;
import org.odata4j.expression.BoolCommonExpression;
import org.odata4j.format.xml.AtomServiceDocumentFormatWriter;
import org.odata4j.format.xml.EdmxFormatWriter;
import org.odata4j.producer.CountResponse;
import org.odata4j.producer.ODataProducer;
import org.odata4j.producer.QueryInfo;
import com.fujitsu.dc.common.utils.DcCoreUtils;
import com.fujitsu.dc.core.DcCoreException;
import com.fujitsu.dc.core.auth.AccessContext;
import com.fujitsu.dc.core.auth.BoxPrivilege;
import com.fujitsu.dc.core.auth.OAuth2Helper.AcceptableAuthScheme;
import com.fujitsu.dc.core.auth.Privilege;
import com.fujitsu.dc.core.model.DavRsCmp;
import com.fujitsu.dc.core.model.ctl.AssociationEnd;
import com.fujitsu.dc.core.model.ctl.ComplexType;
import com.fujitsu.dc.core.model.ctl.ComplexTypeProperty;
import com.fujitsu.dc.core.model.ctl.CtlSchema;
import com.fujitsu.dc.core.model.ctl.EntityType;
import com.fujitsu.dc.core.model.ctl.Property;
import com.fujitsu.dc.core.odata.DcOptionsQueryParser;
import com.fujitsu.dc.core.odata.OEntityWrapper;
import com.fujitsu.dc.core.rs.odata.ODataResource;
import com.fujitsu.dc.core.utils.ODataUtils;
/**
* ODataSvcSchemaResourceを担当するJAX-RSリソース.
*/
public final class ODataSvcSchemaResource extends ODataResource {
private static final MediaType APPLICATION_ATOMSVC_XML_MEDIATYPE =
MediaType.valueOf(ODataConstants.APPLICATION_ATOMSVC_XML);
ODataSvcCollectionResource odataSvcCollectionReource;
DavRsCmp davRsCmp;
/**
* constructor.
* @param davRsCmp このスキーマが担当するユーザデータのResource
*/
ODataSvcSchemaResource(
final DavRsCmp davRsCmp, final ODataSvcCollectionResource odataSvcCollectionReource) {
super(davRsCmp.getAccessContext(),
davRsCmp.getUrl() + davRsCmp.getDavCmp().getName() + "/",
davRsCmp.getDavCmp().getSchemaODataProducer(davRsCmp.getCell()));
this.odataSvcCollectionReource = odataSvcCollectionReource;
this.davRsCmp = davRsCmp;
}
@Override
public void checkAccessContext(AccessContext ac, Privilege privilege) {
this.davRsCmp.checkAccessContext(ac, privilege);
}
/**
* 認証に使用できるAuth Schemeを取得する.
* @return 認証に使用できるAuth Scheme
*/
@Override
public AcceptableAuthScheme getAcceptableAuthScheme() {
return this.davRsCmp.getAcceptableAuthScheme();
}
@Override
public boolean hasPrivilege(AccessContext ac, Privilege privilege) {
return this.davRsCmp.hasPrivilege(ac, privilege);
}
@Override
public void checkSchemaAuth(AccessContext ac) {
}
/**
* サービスメタデータリクエストに対応する.
* @param uriInfo UriInfo
* @param format String
* @param httpHeaders HttpHeaders
* @return JAX-RS 応答オブジェクト
*/
@Override
@GET
@Path("")
public Response getRoot(@Context final UriInfo uriInfo,
@QueryParam("$format") final String format,
@Context HttpHeaders httpHeaders) {
// アクセス制御
this.checkAccessContext(this.getAccessContext(), BoxPrivilege.READ);
// $format と Acceptヘッダの内容から、
// SchemaのAtom ServiceDocumentを返すべきか
// データのEDMXを返すべきかをを判定する。
if ("atomsvc".equals(format) || isAtomSvcRequest(httpHeaders)) {
// SchemaのAtom ServiceDocumentを返す
EdmDataServices edmDataServices = CtlSchema.getEdmDataServicesForODataSvcSchema().build();
StringWriter w = new StringWriter();
AtomServiceDocumentFormatWriter fw = new AtomServiceDocumentFormatWriter();
fw.write(DcCoreUtils.createUriInfo(uriInfo, 0), w, edmDataServices);
return Response.ok(w.toString(), fw.getContentType())
.header(ODataConstants.Headers.DATA_SERVICE_VERSION, ODataConstants.DATA_SERVICE_VERSION_HEADER)
.build();
}
// データのEDMXを返す
ODataProducer userDataODataProducer = this.odataSvcCollectionReource.getODataProducer();
EdmDataServices dataEdmDataSearvices = userDataODataProducer.getMetadata();
StringWriter w = new StringWriter();
EdmxFormatWriter.write(dataEdmDataSearvices, w);
return Response.ok(w.toString(), ODataConstants.APPLICATION_XML_CHARSET_UTF8)
.header(ODataConstants.Headers.DATA_SERVICE_VERSION, ODataConstants.DATA_SERVICE_VERSION_HEADER)
.build();
}
private boolean isAtomSvcRequest(HttpHeaders h) {
return h.getAcceptableMediaTypes().contains(APPLICATION_ATOMSVC_XML_MEDIATYPE);
}
/**
* サービスメタデータリクエストに対応する.
* @return JAX-RS 応答オブジェクト
*/
@GET
@Path("{first: \\$}metadata")
public Response getMetadata() {
// アクセス制御
this.checkAccessContext(this.getAccessContext(), BoxPrivilege.READ);
// スキーマのEDMXを返す
// Authヘッダチェック
return super.doGetMetadata();
}
/**
* OPTIONS Method.
* @return JAX-RS Response
*/
@Override
@OPTIONS
@Path("")
public Response optionsRoot() {
// アクセス制御
this.checkAccessContext(this.getAccessContext(), BoxPrivilege.READ);
return super.doGetOptionsMetadata();
}
@Override
public Privilege getNecessaryReadPrivilege(String entitySetNameStr) {
return BoxPrivilege.READ;
}
@Override
public Privilege getNecessaryWritePrivilege(String entitySetNameStr) {
return BoxPrivilege.ALTER_SCHEMA;
}
@Override
public Privilege getNecessaryOptionsPrivilege() {
return BoxPrivilege.READ;
}
/**
* 部分更新前処理.
* @param oEntityWrapper OEntityWrapperオブジェクト
* @param oEntityKey 削除対象のentityKey
*/
@Override
public void beforeMerge(final OEntityWrapper oEntityWrapper, final OEntityKey oEntityKey) {
// 未対応のEntityTypeかチェックする
String entityTypeName = oEntityWrapper.getEntitySetName();
// PropertyとComplexTypeとComplexTypePropertyの更新は未対応のため501を返却する
if (entityTypeName.equals(Property.EDM_TYPE_NAME)
|| entityTypeName.equals(ComplexType.EDM_TYPE_NAME)
|| entityTypeName.equals(ComplexTypeProperty.EDM_TYPE_NAME)) {
throw DcCoreException.Misc.METHOD_NOT_IMPLEMENTED;
}
}
/**
* リンク登録前処理.
* @param sourceEntity リンク対象のエンティティ
* @param targetNavProp リンク対象のナビゲーションプロパティ
*/
@Override
public void beforeLinkCreate(OEntityId sourceEntity, String targetNavProp) {
// 未対応のEntityTypeかチェックする
checkNonSupportLinks(sourceEntity.getEntitySetName(), targetNavProp);
}
/**
* リンク取得前処理.
* @param sourceEntity リンク対象のエンティティ
* @param targetNavProp リンク対象のナビゲーションプロパティ
*/
@Override
public void beforeLinkGet(OEntityId sourceEntity, String targetNavProp) {
}
/**
* リンク削除前処理.
* @param sourceEntity リンク対象のエンティティ
* @param targetNavProp リンク対象のナビゲーションプロパティ
*/
@Override
public void beforeLinkDelete(OEntityId sourceEntity, String targetNavProp) {
// 未対応のEntityTypeかチェックする
checkNonSupportLinks(sourceEntity.getEntitySetName(), targetNavProp);
}
/**
* dc:Format以外のチェック処理.
* @param props プロパティ一覧
*/
@Override
public void validate(List<OProperty<?>> props) {
String type = null;
for (OProperty<?> property : props) {
if (property.getValue() == null) {
continue;
}
// プロパティ名と値を取得
String propValue = property.getValue().toString();
String propName = property.getName();
if (propName.equals(Property.P_TYPE.getName())) {
// Typeのバリデート
// Edm.Boolean / Edm.String / Edm.Single / Edm.Int32 / Edm.Double / Edm.DateTime
type = propValue;
if (!propValue.equals(EdmSimpleType.STRING.getFullyQualifiedTypeName())
&& !propValue.equals(EdmSimpleType.BOOLEAN.getFullyQualifiedTypeName())
&& !propValue.equals(EdmSimpleType.SINGLE.getFullyQualifiedTypeName())
&& !propValue.equals(EdmSimpleType.INT32.getFullyQualifiedTypeName())
&& !propValue.equals(EdmSimpleType.DOUBLE.getFullyQualifiedTypeName())
&& !propValue.equals(EdmSimpleType.DATETIME.getFullyQualifiedTypeName())) {
// 登録済みのComplexTypeのチェック
BoolCommonExpression filter = DcOptionsQueryParser.parseFilter("Name eq '" + propValue + "'");
QueryInfo query = new QueryInfo(null, null, null, filter, null, null, null, null, null);
CountResponse reponse = this.getODataProducer().getEntitiesCount(ComplexType.EDM_TYPE_NAME, query);
if (reponse.getCount() == 0) {
throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
}
}
} else if (propName.equals(Property.P_COLLECTION_KIND.getName())) {
// CollectionKindのバリデート
// None / List
if (!propValue.equals(Property.COLLECTION_KIND_NONE)
&& !propValue.equals(CollectionKind.List.toString())) {
throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
}
} else if (propName.equals(Property.P_DEFAULT_VALUE.getName())) {
// DefaultValueのバリデート
// Typeの値によってチェック内容を切り替える
boolean result = false;
if (type.equals(EdmSimpleType.BOOLEAN.getFullyQualifiedTypeName())) {
result = ODataUtils.validateBoolean(propValue);
} else if (type.equals(EdmSimpleType.INT32.getFullyQualifiedTypeName())) {
result = ODataUtils.validateInt32(propValue);
} else if (type.equals(EdmSimpleType.DOUBLE.getFullyQualifiedTypeName())) {
result = ODataUtils.validateDouble(propValue);
} else if (type.equals(EdmSimpleType.SINGLE.getFullyQualifiedTypeName())) {
result = ODataUtils.validateSingle(propValue);
} else if (type.equals(EdmSimpleType.STRING.getFullyQualifiedTypeName())) {
result = ODataUtils.validateString(propValue);
} else if (type.equals(EdmSimpleType.DATETIME.getFullyQualifiedTypeName())) {
result = ODataUtils.validateDateTime(propValue);
}
if (!result) {
throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(propName);
}
}
}
}
private void checkNonSupportLinks(String sourceEntity, String targetNavProp) {
if (targetNavProp.startsWith("_")) {
targetNavProp = targetNavProp.substring(1);
}
// EntityTypeとAssociationEndの$links指定は不可(EntityType:AssociationEndは1:Nの関係だから)
// EntityTypeとPropertyの$links指定は不可(EntityType:Propertyは1:Nの関係だから)
// ComplexTypeとComplexTypePropertyの$links指定は不可(ComplexType:ComplexTypePropertyは1:Nの関係だから)
if ((sourceEntity.equals(EntityType.EDM_TYPE_NAME) && targetNavProp.equals(AssociationEnd.EDM_TYPE_NAME))
|| (sourceEntity.equals(AssociationEnd.EDM_TYPE_NAME) && targetNavProp.equals(EntityType.EDM_TYPE_NAME))
|| (sourceEntity.equals(EntityType.EDM_TYPE_NAME) && targetNavProp.equals(Property.EDM_TYPE_NAME))
|| (sourceEntity.equals(Property.EDM_TYPE_NAME) && targetNavProp.equals(EntityType.EDM_TYPE_NAME))
|| (sourceEntity.equals(ComplexType.EDM_TYPE_NAME)
&& targetNavProp.equals(ComplexTypeProperty.EDM_TYPE_NAME))
|| (sourceEntity.equals(ComplexTypeProperty.EDM_TYPE_NAME)
&& targetNavProp.equals(ComplexType.EDM_TYPE_NAME))) {
throw DcCoreException.OData.NO_SUCH_ASSOCIATION;
}
}
@Override
public void setBasicAuthenticateEnableInBatchRequest(AccessContext ac) {
// スキーマレベルAPIはバッチリクエストに対応していないため、ここでは何もしない
}
/**
* Not Implemented. <br />
* 現状、$batchのアクセス制御でのみ必要なメソッドのため未実装. <br />
* アクセスコンテキストが$batchしてよい権限を持っているかを返す.
* @param ac アクセスコンテキスト
* @return true: アクセスコンテキストが$batchしてよい権限を持っている
*/
@Override
public boolean hasPrivilegeForBatch(AccessContext ac) {
throw new NotImplementedException();
}
}