/** * 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.odata; import java.io.Reader; import java.io.StringWriter; import java.util.ArrayList; import java.util.List; import javax.ws.rs.DefaultValue; import javax.ws.rs.GET; import javax.ws.rs.HeaderParam; import javax.ws.rs.HttpMethod; import javax.ws.rs.OPTIONS; import javax.ws.rs.POST; import javax.ws.rs.Produces; 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.Response.ResponseBuilder; import javax.ws.rs.core.UriInfo; import org.apache.http.HttpStatus; import org.odata4j.core.ODataConstants; import org.odata4j.core.ODataVersion; import org.odata4j.core.OEntity; import org.odata4j.core.OEntityId; import org.odata4j.core.OEntityKey; import org.odata4j.edm.EdmEntitySet; import org.odata4j.edm.EdmEntityType; import org.odata4j.edm.EdmNavigationProperty; import org.odata4j.format.FormatWriter; import org.odata4j.producer.BaseResponse; import org.odata4j.producer.EntitiesResponse; import org.odata4j.producer.EntityResponse; import org.odata4j.producer.QueryInfo; import com.fujitsu.dc.common.es.util.DcUUID; 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.odata.DcFormatWriterFactory; import com.fujitsu.dc.core.odata.OEntityWrapper; /** * Navigationプロパティを扱うリソース. * Jax-RS Resource. */ public class ODataPropertyResource extends AbstractODataResource { private final ODataResource sourceOData; private final OEntityId sourceEntityId; private final String targetNavProp; private final EdmEntitySet targetEntitySet; private final AccessContext accessContext; private final ODataResource odataResource; /** * コンストラクタ. * @param entityResource 親リソース * @param targetNavProp Navigation Property */ public ODataPropertyResource( final ODataEntityResource entityResource, final String targetNavProp) { this.targetNavProp = targetNavProp; this.sourceOData = entityResource.getOdataResource(); this.sourceEntityId = entityResource.getOEntityId(); setOdataProducer(entityResource.getOdataProducer()); this.accessContext = entityResource.getAccessContext(); this.odataResource = entityResource.getOdataResource(); // Navigationプロパティのスキーマ上の存在確認 EdmEntitySet eSet = getOdataProducer().getMetadata().findEdmEntitySet(this.sourceEntityId.getEntitySetName()); EdmNavigationProperty enp = eSet.getType().findNavigationProperty(this.targetNavProp); if (enp == null) { throw DcCoreException.OData.NOT_SUCH_NAVPROP; } // TargetのEntityKey, EdmEntitySetを準備 EdmEntityType tgtType = enp.getToRole().getType(); this.targetEntitySet = getOdataProducer().getMetadata().findEdmEntitySet(tgtType.getName()); } /** * POST メソッドへの処理. * @param uriInfo UriInfo * @param accept アクセプトヘッダ * @param format $formatクエリ * @param reader リクエストボディ * @return JAX-RS Response */ @POST public final Response postEntity( @Context final UriInfo uriInfo, @HeaderParam(HttpHeaders.ACCEPT) final String accept, @DefaultValue(FORMAT_JSON) @QueryParam("$format") final String format, final Reader reader) { // アクセス制御 this.checkWriteAccessContext(); OEntityWrapper oew = createEntityFromInputStream(reader); EntityResponse res = getOdataProducer().createNp(this.sourceEntityId, this.targetNavProp, oew, getEntitySetName()); if (res == null || res.getEntity() == null) { return Response.status(HttpStatus.SC_PRECONDITION_FAILED).entity("conflict").build(); } OEntity ent = res.getEntity(); String etag = oew.getEtag(); // バージョンが更新された場合、etagを更新する if (etag != null && ent instanceof OEntityWrapper) { ((OEntityWrapper) ent).setEtag(etag); } // 現状は、ContentTypeはJSON固定 MediaType outputFormat = this.decideOutputFormat(accept, format); // Entity Responseをレンダー List<MediaType> contentTypes = new ArrayList<MediaType>(); contentTypes.add(outputFormat); UriInfo resUriInfo = DcCoreUtils.createUriInfo(uriInfo, 2); String key = AbstractODataResource.replaceDummyKeyToNull(ent.getEntityKey().toKeyString()); String responseStr = renderEntityResponse(resUriInfo, res, format, contentTypes); // 制御コードのエスケープ処理 responseStr = escapeResponsebody(responseStr); ResponseBuilder rb = getPostResponseBuilder(ent, outputFormat, responseStr, resUriInfo, key); return rb.build(); } /** * NavigationProperty経由で登録するEntityデータを入力ストリームから生成する. * @param reader 入力ストリーム * @return 入力ストリームから生成したOEntityWrapperオブジェクト */ OEntityWrapper createEntityFromInputStream(final Reader reader) { EdmEntityType edmEntityType = getOdataProducer().getMetadata() .findEdmEntitySet(this.sourceEntityId.getEntitySetName()).getType(); return createEntityFromInputStream( reader, edmEntityType, this.sourceEntityId.getEntityKey(), this.targetEntitySet.getName()); } /** * NavigationProperty経由で登録するEntityデータを入力ストリームから生成する. * @param reader 入力ストリーム * @return 入力ストリームから生成したOEntityWrapperオブジェクト */ OEntityWrapper createEntityFromInputStream( final Reader reader, EdmEntityType sourceEdmEntityType, OEntityKey sourceEntityKey, String targetEntitySetName) { // 主キーのバリデート validatePrimaryKey(sourceEntityKey, sourceEdmEntityType); // 登録すべきOEntityを作成 setEntitySetName(targetEntitySetName); OEntity newEnt = createRequestEntity(reader, null); // ラッパにくるむ. POSTでIf-Match等 ETagを受け取ることはないのでetagはnull。 String uuid = DcUUID.randomUUID(); OEntityWrapper oew = new OEntityWrapper(uuid, newEnt, null); return oew; } /** * NavigationProperty経由でEntityを登録する. * @param oew 登録用OEntityWrapperオブジェクト * @return 登録した内容から生成したEntityレスポンス */ EntityResponse createEntity(OEntityWrapper oew) { // 必要ならばメタ情報をつける処理 this.sourceOData.beforeCreate(oew); // Entityの作成を Producerに依頼.この中であわせて、存在確認もしてもらう。 EntityResponse res = getOdataProducer().createEntity(getEntitySetName(), oew); return res; } /** * NavPropに対するGETメソッドによる検索処理. * @param uriInfo UriInfo * @param accept Acceptヘッダ * @param callback ?? なんだこれは?JSONP? * @param skipToken ?? なんだこれは? * @param q 全文検索パラメタ * @return JAX-RS Response */ @GET @Produces({ODataConstants.APPLICATION_ATOM_XML_CHARSET_UTF8, ODataConstants.TEXT_JAVASCRIPT_CHARSET_UTF8, ODataConstants.APPLICATION_JAVASCRIPT_CHARSET_UTF8 }) public final Response getNavProperty( @Context final UriInfo uriInfo, @HeaderParam(HttpHeaders.ACCEPT) final String accept, @QueryParam("$callback") final String callback, @QueryParam("$skiptoken") final String skipToken, @QueryParam("q") final String q) { // アクセス制御 this.checkReadAccessContext(); // queryのパース UriInfo uriInfo2 = DcCoreUtils.createUriInfo(uriInfo, 2); QueryInfo queryInfo = ODataEntitiesResource.queryInfo(uriInfo); // NavigationProperty経由の一覧取得を実行する BaseResponse response = getOdataProducer().getNavProperty( this.sourceEntityId.getEntitySetName(), this.sourceEntityId.getEntityKey(), this.targetNavProp, queryInfo); 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 = DcFormatWriterFactory.getFormatWriter(EntitiesResponse.class, acceptableMediaTypes, null, callback); fw.write(uriInfo2, sw, (EntitiesResponse) response); String entity = sw.toString(); // 制御コードのエスケープ処理 entity = escapeResponsebody(entity); ODataVersion version = ODataVersion.V2; return Response.ok(entity, fw.getContentType()) .header(ODataConstants.Headers.DATA_SERVICE_VERSION, version.asString).build(); } /** * OPTIONSメソッド. * @return JAX-RS Response */ @OPTIONS public Response options() { // アクセス制御 this.odataResource.checkAccessContext(this.accessContext, this.odataResource.getNecessaryOptionsPrivilege()); return DcCoreUtils.responseBuilderForOptions( HttpMethod.GET, HttpMethod.POST ).build(); } private void checkWriteAccessContext() { // アクセス制御 // TODO BOXレベルの場合に同じ処理が2回走る。無駄なのでcheckAccessContextにPrivilegeを配列で渡す等の工夫が必要 this.odataResource.checkAccessContext(this.accessContext, this.odataResource.getNecessaryWritePrivilege(this.sourceEntityId.getEntitySetName())); this.odataResource.checkAccessContext(this.accessContext, this.odataResource.getNecessaryWritePrivilege(targetNavProp.substring(1))); } private void checkReadAccessContext() { // アクセス制御 // TODO BOXレベルの場合に同じ処理が2回走る。無駄なのでcheckAccessContextにPrivilegeを配列で渡す等の工夫が必要 this.odataResource.checkAccessContext(this.accessContext, this.odataResource.getNecessaryReadPrivilege(this.sourceEntityId.getEntitySetName())); this.odataResource.checkAccessContext(this.accessContext, this.odataResource.getNecessaryReadPrivilege(targetNavProp.substring(1))); } }