/*
Copyright [2013-2014] eBay Software Foundation
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.
*/
/**
*
*/
/*
Copyright 2012 eBay Software Foundation
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.ebay.cloud.cms.metadata.mongo;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang.StringUtils;
import com.ebay.cloud.cms.consts.CMSConsts;
import com.ebay.cloud.cms.metadata.exception.IllegalIndexException;
import com.ebay.cloud.cms.metadata.exception.IndexExistsException;
import com.ebay.cloud.cms.metadata.exception.IndexNotExistsException;
import com.ebay.cloud.cms.metadata.exception.IndexOptionOperationException;
import com.ebay.cloud.cms.metadata.model.IMetadataVisistor;
import com.ebay.cloud.cms.metadata.model.IndexInfo;
import com.ebay.cloud.cms.metadata.model.MetaClass;
import com.ebay.cloud.cms.metadata.model.MetaClassGraph;
import com.ebay.cloud.cms.metadata.model.MetaField;
import com.ebay.cloud.cms.metadata.model.MetaField.DataTypeEnum;
import com.ebay.cloud.cms.metadata.model.MetaOption;
import com.ebay.cloud.cms.metadata.model.MetaRelationship;
import com.ebay.cloud.cms.metadata.model.MetaRelationship.RelationTypeEnum;
import com.ebay.cloud.cms.metadata.model.Repository;
import com.ebay.cloud.cms.metadata.model.RepositoryOption;
import com.ebay.cloud.cms.metadata.service.IMetadataService;
import com.ebay.cloud.cms.metadata.service.MetadataContext;
/**
* @author liasu
*
*/
public class MetadataOptionValidator implements IMetadataVisistor {
private final MetadataContext context;
private final MetaOption newOption;
private MetaOption tempOption;
private Set<String> visited;
public MetadataOptionValidator(MetaOption option, MetadataContext context) {
this.context = context;
this.newOption = option;
this.visited = new HashSet<String>();
}
@Override
public void processField(MetaClass metaClass, MetaField field) {
// nothing to do
}
@Override
public void processOption(MetaClass metaClass, MetaOption option, MetaClassGraph tempGraph) {
tempOption = new MetaOption();
Collection<IndexInfo> newIndexes = newOption.getIndexes();
for (IndexInfo ii : newIndexes) {
IndexInfo newIndex = new IndexInfo(ii.getIndexName());
tempOption.addIndex(newIndex);
}
Collection<IndexInfo> oldIndexes = option.getIndexes();
Map<String, IndexInfo> oldIndexMap = new HashMap<String, IndexInfo>();
for (IndexInfo oldi : oldIndexes) {
oldIndexMap.put(oldi.getIndexName(), oldi);
}
StringBuilder sb = new StringBuilder();
switch (context.getOptionChangeMode()) {
case ADD:
checkIndexSize(metaClass, newOption, tempGraph);
for (IndexInfo ii : newIndexes) {
if (ii.isInternal()) {
continue;
}
if (oldIndexMap.containsKey(ii.getIndexName())) {
sb.append(MessageFormat.format("index {0} already exsits on metaclass {1}!", ii.getIndexName(), metaClass.getName())).append("\n");
}
ii.validate();
validateKeyList(metaClass, ii);
}
if (sb.length() > 0) {
throw new IndexExistsException(sb.toString());
}
// check all descendant metaclass
List<MetaClass> descendants = metaClass.getDescendants();
for (MetaClass desc : descendants) {
desc.traverse(this);
}
break;
case UPDATE:
checkIndexSize(metaClass, newOption, tempGraph);
for (IndexInfo ii : newIndexes) {
if (ii.isInternal()) {
continue;
}
ii.validate();
validateKeyList(metaClass, ii);
}
// pass through
case DELETE:
for (IndexInfo ii : newIndexes) {
if (ii.isInternal()) {
continue;
}
if (!oldIndexMap.containsKey(ii.getIndexName())) {
sb.append(MessageFormat.format("index {0} doesn''t exsits", ii.getIndexName())).append("\n");
}
}
if (sb.length() > 0) {
throw new IndexNotExistsException(sb.toString());
}
break;
case VALIDATE:
checkIndexSize(metaClass, newOption, tempGraph);
for (IndexInfo ii : oldIndexes) {
if (ii.isInternal()) {
continue;
}
ii.validate();
}
break;
default:
throw new IndexOptionOperationException("Illegal update mode for metaclass option change");
}
}
private void validateKeyList(MetaClass meta, IndexInfo ii) {
for (String key : ii.getKeyList()) {
if (meta.getFieldByName(key) == null) {
throw new IndexOptionOperationException("MetaClass " + meta.getName() + "'sindex " + ii.getIndexName()
+ " key list contains non-existing field " + key + " in meta " + meta.getName());
}
}
}
private Collection<IndexInfo> getEmbededIndexes(MetaClass metaClass, MetaClassGraph graph) {
Collection<IndexInfo> embedIndexes = new ArrayList<IndexInfo>();
Collection<MetaField> fields = metaClass.getClassFields();
for (MetaField field : fields) {
if (field.getDataType() == DataTypeEnum.RELATIONSHIP
&& ((MetaRelationship) field).getRelationType() == RelationTypeEnum.Embedded) {
MetaRelationship relationship = (MetaRelationship) field;
String refDataType = relationship.getRefDataType();
MetaClass targetMeta = graph.getMetaClass(refDataType);
if (targetMeta != null) {
// add reference indexes
targetMeta.addReferenceIndexes();
// add self indexes
Collection<IndexInfo> targetIndexes = new ArrayList<IndexInfo>(targetMeta.getOptions().getIndexes());
// add nested embed indexes
targetIndexes.addAll(getEmbededIndexes(targetMeta, graph));
for (IndexInfo targetIndex : targetIndexes) {
embedIndexes.add(new IndexInfo(targetIndex, relationship));
}
}
}
}
return embedIndexes;
}
private void checkIndexSize(MetaClass metaClass, MetaOption newOption, MetaClassGraph tempGraph) {
// add indexes from reference fields
metaClass.addReferenceIndexes();
// add indexes from embed fields
Collection<IndexInfo> embedIndexes = getEmbededIndexes(metaClass, tempGraph);
IMetadataService metadataService = metaClass.getMetadataService();
Repository repo = metadataService.getRepository();
RepositoryOption repositoryOption = repo.getOptions();
Integer maxNumOfIndexes = CMSConsts.MAX_INDEXES_PER_META_CLASS;
if (repositoryOption != null && repositoryOption.getMaxNumOfIndexes() != null) {
maxNumOfIndexes = repositoryOption.getMaxNumOfIndexes();
}
Collection<IndexInfo> existedIndexes = metaClass.getIndexes();
Collection<IndexInfo> newIndexes = newOption.getIndexes();
Set<String> mergedIndexNames = new HashSet<String>();
Set<String> existedIndexNames = new HashSet<String>();
for (IndexInfo ii : existedIndexes) {
existedIndexNames.add(ii.getIndexName());
mergedIndexNames.add(ii.getIndexName());
}
for (IndexInfo ii : newIndexes) {
mergedIndexNames.add(ii.getIndexName());
}
for (IndexInfo ii : embedIndexes) {
mergedIndexNames.add(ii.getIndexName());
}
int actualIndexSize = mergedIndexNames.size();
if(actualIndexSize > maxNumOfIndexes) {
String errorMessage = String.format("The number of index on a metaclass %s should NOT be more than %d! Existed index names: %s, while target index names: %s",
metaClass.getName(), maxNumOfIndexes, StringUtils.join(existedIndexNames.iterator(), ", "), StringUtils.join(mergedIndexNames.iterator(), ", "));
throw new IllegalIndexException(errorMessage);
}
// if this is embed class, check container index size also
if (metaClass.isEmbed()) {
List<MetaRelationship> fromRefs = tempGraph.getFromReference(metaClass);
for(MetaRelationship fromRef : fromRefs) {
String containerMetaClassName = fromRef.getSourceDataType();
if (containerMetaClassName != null) {
MetaClass containerMetaClass = tempGraph.getMetaClass(containerMetaClassName);
containerMetaClass.setMetadataService(metadataService);
Collection<IndexInfo> tempIndexes = tempOption.getIndexes();
for (IndexInfo tempIndex : tempIndexes) {
IndexInfo newIndex = new IndexInfo(tempIndex, fromRef);
tempIndex.setIndexName(newIndex.getIndexName());
}
checkIndexSize(containerMetaClass, tempOption, tempGraph);
}
}
} else {
// check descendants violated or not as indexes allow inheritance
List<MetaClass> descendants = tempGraph.getDescendants(metaClass);
for (MetaClass descendant : descendants) {
String metaClassName = descendant.getName();
if (!visited.contains(metaClassName)) {
visited.add(metaClassName);
checkIndexSize(descendant, newOption, tempGraph);
}
}
}
}
@Override
public Collection<String> getVisitFields(MetaClass metaClass) {
return Collections.emptyList();
}
}