package org.easysoa.registry;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.easysoa.registry.inheritance.ChildrenModelSelector;
import org.easysoa.registry.inheritance.InheritedFacetDescriptor;
import org.easysoa.registry.inheritance.InheritedFacetDescriptor.TransferLogic;
import org.easysoa.registry.inheritance.InheritedFacetModelSelector;
import org.easysoa.registry.inheritance.UuidInSourceSelector;
import org.easysoa.registry.inheritance.UuidInTargetSelector;
import org.easysoa.registry.types.SoaNode;
import org.easysoa.registry.utils.SimpleELEvaluator;
import org.nuxeo.ecm.core.api.ClientException;
import org.nuxeo.ecm.core.api.CoreSession;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.schema.SchemaManager;
import org.nuxeo.ecm.core.schema.types.CompositeType;
import org.nuxeo.ecm.core.schema.types.Field;
import org.nuxeo.ecm.core.schema.types.Schema;
import org.nuxeo.ecm.platform.types.TypeManager;
import org.nuxeo.runtime.api.Framework;
import org.nuxeo.runtime.model.ComponentInstance;
import org.nuxeo.runtime.model.DefaultComponent;
import edu.uci.ics.jung.algorithms.shortestpath.ShortestPath;
import edu.uci.ics.jung.algorithms.shortestpath.UnweightedShortestPath;
import edu.uci.ics.jung.graph.DirectedSparseGraph;
import edu.uci.ics.jung.graph.Graph;
import edu.uci.ics.jung.graph.util.Pair;
/**
*
* @author mkalam-alami
*
*/
public class SoaMetamodelServiceImpl extends DefaultComponent implements SoaMetamodelService {
private static Logger logger = Logger.getLogger(SoaMetamodelServiceImpl.class);
private Graph<String, String> graph = new DirectedSparseGraph<String, String>();
private UnweightedShortestPath<String, String> shortestPath
= new UnweightedShortestPath<String, String>(graph);
private Map<String, InheritedFacetDescriptor> inheritedFacets
= new HashMap<String, InheritedFacetDescriptor>();
private Map<String, InheritedFacetModelSelector> facetTransferSelectors
= new HashMap<String, InheritedFacetModelSelector>();
private Map<String, SoaNodeTypeDescriptor> soaNodeTypes
= new HashMap<String, SoaNodeTypeDescriptor>();
public SoaMetamodelServiceImpl() {
facetTransferSelectors.put(ChildrenModelSelector.NAME, new ChildrenModelSelector());
facetTransferSelectors.put(UuidInSourceSelector.NAME, new UuidInSourceSelector());
facetTransferSelectors.put(UuidInTargetSelector.NAME, new UuidInTargetSelector());
}
@Override
public void registerContribution(Object contribution, String extensionPoint,
ComponentInstance contributor) throws Exception {
if (EXTENSIONPOINT_TYPES.equals(extensionPoint)) {
SoaNodeTypeDescriptor descriptor = (SoaNodeTypeDescriptor) contribution;
soaNodeTypes.put(descriptor.name, descriptor);
graph.addVertex(descriptor.name);
for (String subtype : descriptor.subtypes) {
graph.addVertex(subtype);
graph.addEdge(descriptor.name + " contains " + subtype, descriptor.name, subtype);
}
}
else if (EXTENSIONPOINT_INHERITEDFACETS.equals(extensionPoint)) {
InheritedFacetDescriptor descriptor = (InheritedFacetDescriptor) contribution;
if (descriptor.facetName != null && !descriptor.transferLogicList.isEmpty()) {
inheritedFacets.put(descriptor.facetName, descriptor);
}
else {
logger.error("Invalid " + EXTENSIONPOINT_INHERITEDFACETS + " contribution");
}
}
}
public Collection<String> getChildren(String type) {
return graph.getSuccessors(type);
}
public List<String> getPath(String fromType, String toType) {
return getPath(graph, shortestPath, fromType, toType);
}
// Modified version of ShortestPathUtils.getPath()
private static <V, E> List<V> getPath(Graph<V, E> graph, ShortestPath<V, E> sp, V source, V target) {
LinkedList<V> path = new LinkedList<V>();
Map<V, E> incomingEdges = sp.getIncomingEdgeMap(source);
if (incomingEdges.isEmpty() || incomingEdges.get(target) == null) {
return null;
}
V current = target;
while (!current.equals(source)) {
E incoming = incomingEdges.get(current);
path.addFirst(current);
Pair<V> endpoints = graph.getEndpoints(incoming);
if (endpoints.getFirst().equals(current)) {
current = endpoints.getSecond();
} else {
current = endpoints.getFirst();
}
}
return path;
}
public Set<String> getInheritedFacets(Set<String> facetsToTest) {
Set<String> facetsSubset = new HashSet<String>(inheritedFacets.keySet());
facetsSubset.retainAll(facetsToTest);
return facetsSubset;
}
@Override
public boolean applyFacetInheritance(CoreSession documentManager,
DocumentModel model, boolean isFacetSource) throws Exception {
SchemaManager schemaManager = Framework.getService(SchemaManager.class);
boolean documentModified = false;
for (String facet : getInheritedFacets(model.getFacets())) {
InheritedFacetDescriptor inheritance = inheritedFacets.get(facet);
for (TransferLogic transferLogic : inheritance.transferLogicList) {
if (isFacetSource && isAssignable(model.getType(), transferLogic.from)) { // OLD model.getType().equals(transferLogic.from))
DocumentModelList modelsToWriteOn = facetTransferSelectors
.get(transferLogic.selectorType)
.findTargets(documentManager, model, transferLogic.to, transferLogic.parameters);
if (modelsToWriteOn != null) {
for (DocumentModel modelToWriteOn : modelsToWriteOn) {
if (!modelToWriteOn.isCheckedOut()) {
// ex. on proxy when tree snapshotting ; can't & don't update it
logger.warn("SoaMetamodelService : skipping because trying to copy inherited facets to checked out (maybe proxy when tree snapshotting) " + modelToWriteOn);
continue;
}
CompositeType facetToCopy = schemaManager.getFacet(facet);
boolean changed = false;
for (String schemaToCopy : facetToCopy.getSchemaNames()) {
// only copy property if it is different
changed = DiscoveryServiceImpl.setPropertiesIfChanged(modelToWriteOn,
model.getProperties(schemaToCopy), false) || changed;
//modelToWriteOn.setProperties(schemaToCopy,
// model.getProperties(schemaToCopy));
//documentManager.saveDocument(modelToWriteOn);
}
if (changed) { // TODO or isDirty ?!
if (!modelToWriteOn.isDirty()) {
logger.error("SoaMetamodelServiceImpl : SAVING CHANGED BUT NOT DIRTY " + modelToWriteOn);
}
documentManager.saveDocument(modelToWriteOn);
}
}
}
}
if (!isFacetSource && isAssignable(model.getType(), transferLogic.to)) { // OLD model.getType().equals(transferLogic.to)
DocumentModel modelToReadFrom = facetTransferSelectors
.get(transferLogic.selectorType)
.findSource(documentManager, model, transferLogic.from, transferLogic.parameters);
if (modelToReadFrom != null) {
CompositeType facetToCopy = schemaManager.getFacet(facet);
for (String schemaToCopy : facetToCopy.getSchemaNames()) {
model.setProperties(schemaToCopy,
modelToReadFrom.getProperties(schemaToCopy));
documentModified = true;
}
}
}
}
}
return documentModified;
}
/**
* Reset metadata if document is target of metadata inheritance (transfer.to)
*/
public boolean resetInheritedFacets(DocumentModel model) throws Exception {
SchemaManager schemaManager = Framework.getService(SchemaManager.class);
boolean documentModified = false;
for (String inheritedFacet : getInheritedFacets(model.getFacets())) {
CompositeType facetToReset = schemaManager.getFacet(inheritedFacet);
InheritedFacetDescriptor inheritedFacetDesc = inheritedFacets.get(inheritedFacet);
boolean isFacetInherited = false;
for (TransferLogic transferLogic : inheritedFacetDesc.transferLogicList) {
if (model.getType().equals(transferLogic.to)) {
isFacetInherited = true;
break;
}
}
if (isFacetInherited) {
for (Schema schemaToReset : facetToReset.getSchemas()) {
for (Field fieldToReset : schemaToReset.getFields()) {
model.setPropertyValue(fieldToReset.getName().toString(), null);
documentModified = true;
}
}
}
}
return documentModified;
}
public String validateIntegrity(DocumentModel model, boolean returnExpectedNameIfNull) throws ModelIntegrityException, ClientException {
SoaNodeTypeDescriptor soaNodeTypeDescriptor = soaNodeTypes.get(model.getType());
if (soaNodeTypeDescriptor != null) {
SimpleELEvaluator elEvaluator = new SimpleELEvaluator();
String expectedSoaName = soaNodeTypeDescriptor.evaluateSoaName(elEvaluator, model);
Serializable actualSoaName = model.getPropertyValue(SoaNode.XPATH_SOANAME);
if (actualSoaName == null) {
if (returnExpectedNameIfNull) {
if (expectedSoaName != null) {
return expectedSoaName;
}
else {
return (String) model.getPropertyValue(SoaNode.XPATH_TITLE);
}
}
else {
throw new ModelIntegrityException("Null soaname for " + model.getPathAsString()
+ " - did happen when : doc was created when it already existed "
+ "because it was queried for using erroneously safeName(), "
+ "doc was created before setting soaname");
}
}
if (!actualSoaName.equals(expectedSoaName)) {
throw new ModelIntegrityException("Invalid SoaName, found '"
+ actualSoaName + "' instead of '" + expectedSoaName + "'");
}
}
return null;
}
public void validateWriteRightsOnProperties(DocumentModel model, Map<String, Serializable> properties) throws ModelIntegrityException, ClientException {
SoaNodeTypeDescriptor soaNodeTypeDescriptor = soaNodeTypes.get(model.getType());
if (properties != null && soaNodeTypeDescriptor != null) {
List<String> immutableProperties = new ArrayList<String>();
immutableProperties.add(SoaNode.XPATH_SOANAME);
List<String> doctypeImmutableProperties = soaNodeTypeDescriptor.idProperties;
if (doctypeImmutableProperties != null) {
immutableProperties.addAll(doctypeImmutableProperties);
}
for (String immutableProperty : immutableProperties) {
if (properties.containsKey(immutableProperty)
&& !properties.get(immutableProperty).equals(model.getPropertyValue(immutableProperty))) {
throw new ModelIntegrityException("Property '" + immutableProperty + "' cannot be changed");
}
}
}
}
public boolean isAssignable(String from, String to) {
if (from.equals(to))
return true;
TypeManager typeManager;
try {
typeManager = Framework.getService(TypeManager.class);
} catch (Exception e) {
throw new RuntimeException("Unable to get type service", e);
}
// NB. using example of inheritance of ServiceImplementationData facet
// from JavaServiceImplementation to Endpoint :
Set<String> superTypes = new HashSet<String>();
superTypes.add(from); // ex. JavaServiceImplementation
Collections.addAll(superTypes, typeManager.getSuperTypes(from));
// ex. ServiceImplementation, SoaNode, Document
return superTypes.contains(to); // ex. ServiceImplementation
}
}