/** * 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.HashMap; import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.odata4j.core.OEntity; import org.odata4j.core.OEntityId; import org.odata4j.core.OEntityIds; import org.odata4j.core.OEntityKey; import org.odata4j.edm.EdmDataServices; import org.odata4j.edm.EdmEntitySet; import org.odata4j.producer.EntityQueryInfo; import org.odata4j.producer.EntityResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fujitsu.dc.common.es.response.DcIndexResponse; import com.fujitsu.dc.common.es.util.DcUUID; import com.fujitsu.dc.core.DcCoreException; import com.fujitsu.dc.core.model.Box; import com.fujitsu.dc.core.model.BoxCmp; import com.fujitsu.dc.core.model.Cell; import com.fujitsu.dc.core.model.ModelFactory; import com.fujitsu.dc.core.model.ctl.CtlSchema; import com.fujitsu.dc.core.model.ctl.ExtCell; import com.fujitsu.dc.core.model.ctl.ReceivedMessage; import com.fujitsu.dc.core.model.ctl.ReceivedMessagePort; import com.fujitsu.dc.core.model.ctl.Relation; import com.fujitsu.dc.core.model.impl.es.EsModel; import com.fujitsu.dc.core.model.impl.es.accessor.DataSourceAccessor; import com.fujitsu.dc.core.model.impl.es.accessor.EntitySetAccessor; import com.fujitsu.dc.core.model.impl.es.accessor.ODataLinkAccessor; import com.fujitsu.dc.core.model.impl.es.cache.BoxCache; import com.fujitsu.dc.core.model.impl.es.doc.EntitySetDocHandler; import com.fujitsu.dc.core.model.impl.es.doc.OEntityDocHandler; import com.fujitsu.dc.core.model.lock.Lock; import com.fujitsu.dc.core.odata.OEntityWrapper; /** * Cell管理オブジェクトの ODataProducer. */ public class CellCtlODataProducer extends EsODataProducer { Cell cell; Logger log = LoggerFactory.getLogger(CellCtlODataProducer.class); /** * Constructor. * @param cell Cell */ public CellCtlODataProducer(final Cell cell) { this.cell = cell; } /** * Obtains the service metadata for this producer. * @return a fully-constructed metadata object */ @Override public EdmDataServices getMetadata() { return edmDataServices.build(); } // スキーマ情報 private static EdmDataServices.Builder edmDataServices = CtlSchema.getEdmDataServicesForCellCtl(); @Override public DataSourceAccessor getAccessorForIndex(final String entitySetName) { return null; // 必要時に実装すること } @Override public EntitySetAccessor getAccessorForEntitySet(final String entitySetName) { return EsModel.cellCtl(this.cell, entitySetName); } @Override public ODataLinkAccessor getAccessorForLink() { return EsModel.cellCtlLink(this.cell); } @Override public DataSourceAccessor getAccessorForLog() { return null; } @Override public DataSourceAccessor getAccessorForBatch() { return EsModel.batch(this.cell); } /** * CellのIdを返すよう実装. * @see com.fujitsu.dc.core.model.impl.es.odata.EsODataProducer#getCellId() * @return cell id */ @Override public String getCellId() { return this.cell.getId(); } /** * 登録前処理. * @param entitySetName エンティティセット名 * @param oEntity 登録対象のエンティティ * @param docHandler 登録対象のエンティティドックハンドラ */ @Override public void beforeCreate(final String entitySetName, final OEntity oEntity, final EntitySetDocHandler docHandler) { if (entitySetName.equals(ReceivedMessagePort.EDM_TYPE_NAME)) { // 受信メッセージの場合は登録データから「_Box.Name」を削除 Map<String, Object> dynamic = docHandler.getDynamicFields(); dynamic.remove(ReceivedMessagePort.P_BOX_NAME.getName()); docHandler.setDynamicFields(dynamic); } } @Override public void beforeDelete(final String entitySetName, final OEntityKey oEntityKey, final EntitySetDocHandler docHandler) { if (!Box.EDM_TYPE_NAME.equals(entitySetName)) { return; } // Boxの削除時のみ、Dav管理データを削除 // entitySetがBoxの場合のみの処理 EntityResponse er = this.getEntity(entitySetName, oEntityKey, new EntityQueryInfo.Builder().build()); OEntityWrapper oew = (OEntityWrapper) er.getEntity(); Box box = new Box(this.cell, oew); // このBoxが存在するときのみBoxCmpが必要 BoxCmp davCmp = ModelFactory.boxCmp(box); if (!davCmp.isEmpty()) { throw DcCoreException.OData.CONFLICT_HAS_RELATED; } davCmp.delete(null, false); // BoxのCacheクリア BoxCache.clear(oEntityKey.asSingleValue().toString(), this.cell); } @Override public void beforeUpdate(final String entitySetName, final OEntityKey oEntityKey, final EntitySetDocHandler docHandler) { if (!Box.EDM_TYPE_NAME.equals(entitySetName)) { return; } // BoxのCacheクリア BoxCache.clear(oEntityKey.asSingleValue().toString(), this.cell); } /** * 関係登録/削除、及びメッセージ受信のステータスを変更する. * @param entitySet entitySetName * @param originalKey 更新対象キー * @param status メッセージステータス * @return ETag */ public String changeStatusAndUpdateRelation(final EdmEntitySet entitySet, final OEntityKey originalKey, final String status) { Lock lock = lock(); try { // ESから変更する受信メッセージ情報を取得する EntitySetDocHandler entitySetDocHandler = this.retrieveWithKey(entitySet, originalKey); if (entitySetDocHandler == null) { throw DcCoreException.OData.NO_SUCH_ENTITY; } // TypeとStatusのチェック String type = (String) entitySetDocHandler.getStaticFields().get(ReceivedMessage.P_TYPE.getName()); String currentStatus = (String) entitySetDocHandler.getStaticFields() .get(ReceivedMessage.P_STATUS.getName()); if (!isValidMessageStatus(type, status) || !isValidRelationStatus(type, status) || !isValidCurrentStatus(type, currentStatus)) { throw DcCoreException.OData.REQUEST_FIELD_FORMAT_ERROR.params(ReceivedMessage.MESSAGE_COMMAND); } // 関係登録/削除 updateRelation(entitySetDocHandler, status); // 取得した受信メッセージのステータスと更新日を上書きする updateStatusOfEntitySetDocHandler(entitySetDocHandler, status); // ESに保存する EntitySetAccessor esType = this.getAccessorForEntitySet(entitySet.getName()); Long version = entitySetDocHandler.getVersion(); DcIndexResponse idxRes; idxRes = esType.update(entitySetDocHandler.getId(), entitySetDocHandler, version); entitySetDocHandler.setVersion(idxRes.version()); return entitySetDocHandler.createEtag(); } finally { log.debug("unlock"); lock.release(); } } /** * 関係登録/削除を行う. * @param entitySetDocHandler 受信メッセージ * @param status 変更するStatus */ private void updateRelation(EntitySetDocHandler entitySetDocHandler, String status) { String type = (String) entitySetDocHandler.getStaticFields().get(ReceivedMessage.P_TYPE.getName()); if (ReceivedMessage.STATUS_APPROVED.equals(status)) { // approvedの場合、Relation/ExtCellの登録/削除を行う if (ReceivedMessage.TYPE_REQ_RELATION_BUILD.equals(type)) { buildRelation(entitySetDocHandler); } else if (ReceivedMessage.TYPE_REQ_RELATION_BREAK.equals(type)) { breakRelation(entitySetDocHandler); } } } /** * 関係登録を行う. <br /> * 現状はRequestRelationで指定されたURLのRelation名のみ取得して、対象の受信メッセージのCellに <br /> * Boxと紐付かないRelationを登録する. * @param entitySetDocHandler 受信メッセージ */ private void buildRelation(EntitySetDocHandler entitySetDocHandler) { // 登録対象のRelation名取得 String requestRelation = (String) entitySetDocHandler.getStaticFields().get( ReceivedMessage.P_REQUEST_RELATION.getName()); String[] partRequestRelation = requestRelation.split("/"); String relationKey = partRequestRelation[partRequestRelation.length - 1]; EntitySetDocHandler relation = getRelation(relationKey); if (relation == null) { // データが存在しない場合はRelationを新規に登録 createRelationEntity(relationKey); } // 関係を結ぶセルURL取得 String requestExtCell = (String) entitySetDocHandler.getStaticFields().get( ReceivedMessage.P_REQUEST_RELATION_TARGET.getName()); if (!requestExtCell.endsWith("/")) { requestExtCell += "/"; } if (getExtCell(requestExtCell) == null) { // データが存在しない場合はExtCellを新規に登録 createExtCellEntity(requestExtCell); } // RelationとExtCellの$linksを作成 createRelationExtCellLinks(relationKey, requestExtCell); } /** * RelationとExtCellの$links作成. * @param relationKey * @param requestExtCell */ private void createRelationExtCellLinks(String relationKey, String requestExtCell) { try { OEntityKey relationOEntityKey = createRelationOEntityKey(relationKey); OEntityId relationEntityId = OEntityIds.create(Relation.EDM_TYPE_NAME, relationOEntityKey); OEntityKey extCellOEntityKey = createExtCellOEntityKey(requestExtCell); OEntityId extCellEntityId = OEntityIds.create(ExtCell.EDM_TYPE_NAME, extCellOEntityKey); // n:nの場合 createLinks(relationEntityId, extCellEntityId); } catch (DcCoreException e) { if (DcCoreException.OData.CONFLICT_LINKS.getCode().equals(e.getCode())) { // $linksが既に存在する場合 throw DcCoreException.ReceiveMessage.REQUEST_RELATION_EXISTS_ERROR; } throw e; } } private OEntityKey createExtCellOEntityKey(String requestExtCell) { OEntityKey extCellOEntityKey; try { extCellOEntityKey = OEntityKey.parse("('" + requestExtCell + "')"); } catch (IllegalArgumentException e) { throw DcCoreException.ReceiveMessage.REQUEST_RELATION_TARGET_PARSE_ERROR.reason(e); } return extCellOEntityKey; } private OEntityKey createRelationOEntityKey(String relationKey) { OEntityKey relationOEntityKey; try { relationOEntityKey = OEntityKey.parse("('" + relationKey + "')"); } catch (IllegalArgumentException e) { throw DcCoreException.ReceiveMessage.REQUEST_RELATION_PARSE_ERROR.reason(e); } return relationOEntityKey; } /** * Relationを取得する. * @param key キー * @return EntitySetDocHandler */ protected EntitySetDocHandler getRelation(String key) { EdmEntitySet edmEntitySet = getMetadata().getEdmEntitySet(Relation.EDM_TYPE_NAME); OEntityKey oEntityKey = createRelationOEntityKey(key); return retrieveWithKey(edmEntitySet, oEntityKey); } /** * ExtCellを取得する. * @param key キー * @return EntitySetDocHandler */ protected EntitySetDocHandler getExtCell(String key) { EdmEntitySet edmEntitySet = getMetadata().getEdmEntitySet(ExtCell.EDM_TYPE_NAME); OEntityKey oEntityKey = createExtCellOEntityKey(key); return retrieveWithKey(edmEntitySet, oEntityKey); } /** * RelationをESに保存. * @param key RelationのNameの値 */ private void createRelationEntity(String key) { // staticFields Map<String, Object> staticFields = new HashMap<String, Object>(); staticFields.put(Relation.P_ROLE_NAME.getName(), key); createEntity(Relation.EDM_TYPE_NAME, staticFields); } /** * ExtCellをESに保存. * @param key ExtCellのUrlの値 */ private void createExtCellEntity(String key) { // staticFields Map<String, Object> staticFields = new HashMap<String, Object>(); staticFields.put(ExtCell.P_URL.getName(), key); createEntity(ExtCell.EDM_TYPE_NAME, staticFields); } /** * ESに保存. * @param typeName 登録するデータのType名 * @param staticFields 登録するstaticFieldsの値 */ private void createEntity(String typeName, Map<String, Object> staticFields) { EntitySetAccessor esType = this.getAccessorForEntitySet(typeName); // EntitySetDocHandlerの作成 EntitySetDocHandler oedh = new OEntityDocHandler(); oedh.setType(typeName); oedh.setId(DcUUID.randomUUID()); // staticFields oedh.setStaticFields(staticFields); // Cell, Box, Nodeの紐付 oedh.setCellId(this.getCellId()); oedh.setBoxId(null); oedh.setNodeId(null); // published, updated long crrTime = System.currentTimeMillis(); oedh.setPublished(crrTime); oedh.setUpdated(crrTime); // TODO 複合キーでNTKPの項目(ex. _EntityType.Name)があれば、リンク情報を設定する // if (KeyType.COMPLEX.equals(oEntityKey.getKeyType())) { // try { // setLinksFromOEntity(getMetadata().findEdmEntitySet(Relation.EDM_TYPE_NAME).getType(), // oEntityKey, entitySetDocHandler); // } catch (NTKPNotFoundException e) { // throw DcCoreException.OData.BODY_NTKP_NOT_FOUND_ERROR.params(e.getMessage()); // } // } // 登録前処理 this.beforeCreate(typeName, null, oedh); // ESに保存する esType.create(oedh.getId(), oedh); // 登録後処理 this.afterCreate(typeName, null, oedh); } /** * 関係削除を行う. * @param entitySetDocHandler 受信メッセージ */ protected void breakRelation(EntitySetDocHandler entitySetDocHandler) { log.debug("breakRelation start."); // RequestRelationからRelation名を取得する String reqRelation = entitySetDocHandler.getStaticFields() .get(ReceivedMessagePort.P_REQUEST_RELATION.getName()).toString(); String relationName = getRelationFromRelationClassUrl(reqRelation); if (relationName == null) { throw DcCoreException.ReceiveMessage.REQUEST_RELATION_PARSE_ERROR; } // 対象のRelationが存在することを確認 EntitySetDocHandler relation = getRelation(relationName); if (relation == null) { log.debug(String.format("RequestRelation does not exists. [%s]", relationName)); throw DcCoreException.ReceiveMessage.REQUEST_RELATION_DOES_NOT_EXISTS.params(relationName); } // 対象のExtCell(RequestRelationTarget)が存在することを確認 String extCellUrl = entitySetDocHandler.getStaticFields() .get(ReceivedMessagePort.P_REQUEST_RELATION_TARGET.getName()).toString(); EntitySetDocHandler extCell = getExtCell(extCellUrl); if (extCell == null) { log.debug(String.format("RequestRelationTarget does not exists. [%s]", extCellUrl)); throw DcCoreException.ReceiveMessage.REQUEST_RELATION_TARGET_DOES_NOT_EXISTS.params(extCellUrl); } // RelationとExtCellの関連を削除する if (!deleteLinkEntity(relation, extCell)) { log.debug(String.format("RequestRelation and RequestRelationTarget does not related. [%s] - [%s]", relationName, extCellUrl)); throw DcCoreException.ReceiveMessage.LINK_DOES_NOT_EXISTS.params(relationName, extCellUrl); } log.debug("breakRelation success."); } /** * リレーションクラスURLからリレーション名を取得する. * @param relationClassUrl リレーションクラスURL * @return リレーション名 */ protected String getRelationFromRelationClassUrl(String relationClassUrl) { String relationName = null; log.debug(String.format("RequestRelation URI = [%s]", relationClassUrl)); Pattern pattern = Pattern.compile(".+/([^/]+)/__relation/([^/]+)/([^/]+)/?"); Matcher m = pattern.matcher(relationClassUrl); if (!m.matches()) { log.debug(String.format("RequestRelation URI if not relationClassUrl format. [%s]", relationClassUrl)); return relationName; } relationName = m.replaceAll("$3"); log.debug(String.format("RequestRelation URI Path = [%s]", relationName)); return relationName; } /** * Messageのステータスバリデート. * @param type メッセージタイプ * @param status メッセージステータス * @return boolean */ protected boolean isValidMessageStatus(String type, String status) { // messageの場合のみバリデートをして、read / unread であればtrueを返却する if (type.equals(ReceivedMessage.TYPE_MESSAGE)) { return ReceivedMessage.STATUS_UNREAD.equals(status) || ReceivedMessage.STATUS_READ.equals(status); } return true; } /** * Relationのステータスバリデート. * @param type メッセージタイプ * @param status メッセージステータス * @return boolean */ protected boolean isValidRelationStatus(String type, String status) { // build or breakの場合のみバリデートをして、approved / rejectedであればtrueを返却する if (type.equals(ReceivedMessage.TYPE_REQ_RELATION_BUILD) || type.equals(ReceivedMessage.TYPE_REQ_RELATION_BREAK)) { return ReceivedMessage.STATUS_APPROVED.equals(status) || ReceivedMessage.STATUS_REJECTED.equals(status); } return true; } /** * 受信メッセージのステータスバリデート. * @param type メッセージタイプ * @param status メッセージステータス * @return boolean */ protected boolean isValidCurrentStatus(String type, String status) { // build or breakの場合のみバリデートをして、none であればtrueを返却する if (type.equals(ReceivedMessage.TYPE_REQ_RELATION_BUILD) || type.equals(ReceivedMessage.TYPE_REQ_RELATION_BREAK)) { return ReceivedMessage.STATUS_NONE.equals(status); } return true; } /** * 受信メッセージのステータスと更新日を上書きする. * @param entitySetDocHandler DocHandler * @param status メッセージステータス */ private void updateStatusOfEntitySetDocHandler(EntitySetDocHandler entitySetDocHandler, String status) { Map<String, Object> staticFields = entitySetDocHandler.getStaticFields(); // 変更するメッセージステータスをHashedCredentialへ上書きする staticFields.put(ReceivedMessage.P_STATUS.getName(), status); entitySetDocHandler.setStaticFields(staticFields); // 現在時刻を取得して__updatedを上書きする long nowTimeMillis = System.currentTimeMillis(); entitySetDocHandler.setUpdated(nowTimeMillis); } /** * 不正なLink情報のチェックを行う. * @param sourceEntity ソース側Entity * @param targetEntity ターゲット側Entity */ @Override protected void checkInvalidLinks(EntitySetDocHandler sourceEntity, EntitySetDocHandler targetEntity) { } /** * 不正なLink情報のチェックを行う. * @param sourceDocHandler ソース側Entity * @param entity ターゲット側Entity * @param targetEntitySetName ターゲットのEntitySet名 */ @Override protected void checkInvalidLinks(EntitySetDocHandler sourceDocHandler, OEntity entity, String targetEntitySetName) { } @Override public void onChange(String entitySetName) { } }