/** * 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.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.odata4j.core.NamespacedAnnotation; import org.odata4j.core.OEntityId; import org.odata4j.core.OEntityKey; import org.odata4j.core.OProperty; import org.odata4j.edm.EdmProperty; import org.odata4j.producer.QueryInfo; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.fujitsu.dc.common.es.response.DcSearchHits; import com.fujitsu.dc.common.utils.DcCoreUtils; import com.fujitsu.dc.core.DcCoreConfig; import com.fujitsu.dc.core.DcCoreException; import com.fujitsu.dc.core.auth.AuthUtils; import com.fujitsu.dc.core.model.impl.es.accessor.EntitySetAccessor; import com.fujitsu.dc.core.model.impl.es.doc.EntitySetDocHandler; import com.fujitsu.dc.core.odata.OEntityWrapper; import com.fujitsu.dc.core.rs.odata.AbstractODataResource; import com.fujitsu.dc.core.utils.ODataUtils; /** * ODataProducerUtils. */ public final class ODataProducerUtils { /** * ログ. */ static Logger log = LoggerFactory.getLogger(ODataProducerUtils.class); private ODataProducerUtils() { } /** * Entity登録・更新時のデータの一意性チェックを行う. * @param producer * @param newEntity 新しく登録・更新するEntity * @param originalEntity もとのEntity * @param originalKey 更新リクエストで指定されたキー名 */ static void checkUniqueness(EsODataProducer producer, OEntityWrapper newEntity, OEntityWrapper originalEntity, OEntityKey originalKey) { boolean needsPkCheck = false; if (originalEntity == null) { needsPkCheck = true; } else { // originalKeyからoewのキーが変更になっているときのみ、キー変更による影響確認をする。 OEntityKey normNewKey = AbstractODataResource.normalizeOEntityKey(newEntity.getEntityKey(), newEntity.getEntitySet()); if (null == originalKey) { originalKey = originalEntity.getEntityKey(); } OEntityKey normOrgKey = AbstractODataResource.normalizeOEntityKey( originalKey, newEntity.getEntitySet()); String newKeyStr = normNewKey.toKeyStringWithoutParentheses(); String orgKeyStr = normOrgKey.toKeyStringWithoutParentheses(); // KEYを正規化した上で比較しなくてはならない。 log.debug("NWKEY:" + newKeyStr); log.debug("ORKEY:" + orgKeyStr); if (!newKeyStr.equals(orgKeyStr)) { needsPkCheck = true; } } if (needsPkCheck) { // 主キーでの検索を行う。 EntitySetDocHandler hit = producer.retrieveWithKey(newEntity.getEntitySet(), newEntity.getEntityKey()); if (hit != null) { // データが存在したら CONFLICT エラーとする throw DcCoreException.OData.ENTITY_ALREADY_EXISTS; } } // UK 制約による一意性チェック // UK 制約の抽出処理 // TODO スキーマ情報と共にキャッシュ(別メソッド化) Map<String, List<String>> uks = new HashMap<String, List<String>>(); List<EdmProperty> listEdmProperties = newEntity.getEntityType().getProperties().toList(); for (EdmProperty edmProp : listEdmProperties) { Iterable<? extends NamespacedAnnotation<?>> anots = edmProp.getAnnotations(); for (NamespacedAnnotation<?> anot : anots) { if ("Unique".equals(anot.getName()) && DcCoreUtils.XmlConst.NS_DC1.equals(anot.getNamespace().getUri())) { String ukName = (String) anot.getValue(); List<String> ukProps = uks.get(ukName); if (ukProps == null) { ukProps = new ArrayList<String>(); } ukProps.add(edmProp.getName()); uks.put(ukName, ukProps); } } } // ここですべてのユニークキーでの検索を行い、データが存在しないことを確認する for (Map.Entry<String, List<String>> uk : uks.entrySet()) { log.debug("checking uk : [" + uk.getKey() + "] = "); List<String> ukProps = uk.getValue(); Set<OProperty<?>> ukSet = new HashSet<OProperty<?>>(); // UKは非null項目の一意性確保なので、全項目がnullであるものは、いくつあってもよい。 boolean allNull = true; // UKを構成する全項目に変更がなかったときは、チェックを入れる必要がない。 boolean changed = false; for (String k : ukProps) { log.debug(" - [" + k + "]"); OProperty<?> oProp = newEntity.getProperty(k); if (oProp.getValue() != null) { allNull = false; if (originalEntity != null) { OProperty<?> origProp = originalEntity.getProperty(k); if (!oProp.getValue().equals(origProp.getValue())) { changed = true; } } } ukSet.add(oProp); } // UKを構成する全項目がNullであるときはチェック必要なし boolean needsUkCheck = !allNull; if (originalEntity != null && !changed) { // 変更で、UKを構成する全項目の変更がないときはチェックの必要なし。 needsUkCheck = false; } // 変更後キーがAllNullでなく、変更があったときのみ、チェック。 if (needsUkCheck) { EntitySetDocHandler edh = producer.retrieveWithKey(newEntity.getEntitySet(), ukSet, null); if (edh != null) { // データが存在したら CONFLICT エラーとする throw DcCoreException.OData.ENTITY_ALREADY_EXISTS; } } } } /** * N:NのLinks情報を検索する. * @param src リクエストURLにて指定されたEntity * @param targetSetName リクエストURLにて指定されたNavPropのEntitySet名 * @param idvals idvals * @param tgtEsType tgtEsType * @param queryInfo リクエストで指定されたクエリ情報 * @return Links情報の検索結果 */ public static DcSearchHits searchLinksNN(EntitySetDocHandler src, String targetSetName, List<String> idvals, EntitySetAccessor tgtEsType, QueryInfo queryInfo) { if (idvals.size() == 0) { return null; } // リクエストでクエリが指定されていない場合は、デフォルト値を設定する // linksの一覧取得で設定できるクエリは、$top,$skipのみのため、$top,$skipのみデフォルト値を設定している Integer size = DcCoreConfig.getTopQueryDefaultSize(); Integer from = 0; if (queryInfo != null) { if (queryInfo.top != null) { size = queryInfo.top; } if (queryInfo.skip != null) { from = queryInfo.skip; } } // ターゲットUUID列から、ターゲット主キー列の取得 Map<String, Object> source = new HashMap<String, Object>(); Map<String, Object> filter = new HashMap<String, Object>(); Map<String, Object> ids = new HashMap<String, Object>(); source.put("filter", filter); source.put("size", size); source.put("from", from); filter.put("ids", ids); ids.put("values", idvals); List<Map<String, Object>> sort = new ArrayList<Map<String, Object>>(); Map<String, Object> orderByName = new HashMap<String, Object>(); Map<String, Object> orderById = new HashMap<String, Object>(); Map<String, Object> order = new HashMap<String, Object>(); source.put("sort", sort); sort.add(orderByName); sort.add(orderById); order.put("order", "asc"); order.put("ignore_unmapped", true); orderByName.put("s.Name.untouched", order); orderById.put("s.__id.untouched", order); DcSearchHits sHits = tgtEsType.search(source).hits(); return sHits; } /** * linksKeyと親キーが等しいか比較する. * @param entity entity * @param linksKey linksKey * @return boolean */ public static boolean isParentEntity(OEntityId entity, String linksKey) { return entity.getEntitySetName().equals(linksKey); } /** * ESへのパスワード変更リクエストを生成する. * @param oedhNew oedhNew * @param dcCredHeader dcCredHeader */ public static void createRequestPassword(EntitySetDocHandler oedhNew, String dcCredHeader) { // 更新前処理(Hash文字列化したパスワードを取得) String hPassStr = AuthUtils.checkValidatePassword(dcCredHeader, oedhNew.getType()); // 変更するパスワードをHashedCredentialへ上書きする Map<String, Object> hiddenFields = oedhNew.getHiddenFields(); // X-Dc-Credentialの値をHashedCredentialのキーへputする // 指定がない場合400エラーを返却する if (hPassStr != null) { hiddenFields.put("HashedCredential", hPassStr); } else { throw DcCoreException.Auth.DC_CREDENTIAL_REQUIRED; } oedhNew.setHiddenFields(hiddenFields); // 現在時刻を取得して__updatedを上書きする long nowTimeMillis = System.currentTimeMillis(); oedhNew.setUpdated(nowTimeMillis); } /** * 引数で指定されたOEDHのdynamic fieldsとstatic fieldsの値をマージしてoedhNewを更新する. * @param oedhExisting ベースにするOEDH * @param oedhNew 追加するOEDH */ public static void mergeFields(EntitySetDocHandler oedhExisting, EntitySetDocHandler oedhNew) { // static fieldsに登録済みのプロパティを追加 oedhNew.setStaticFields(ODataUtils.getMergeFields(oedhExisting.getStaticFields(), oedhNew.getStaticFields())); } }