/* * Copyright (c) 2010-2017 Evolveum * * 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.evolveum.midpoint.model.impl.dataModel.dot; import com.evolveum.midpoint.common.refinery.RefinedAssociationDefinition; import com.evolveum.midpoint.common.refinery.RefinedObjectClassDefinition; import com.evolveum.midpoint.common.refinery.RefinedResourceSchema; import com.evolveum.midpoint.model.impl.dataModel.DataModel; import com.evolveum.midpoint.model.impl.dataModel.DataModelVisualizerImpl; import com.evolveum.midpoint.model.impl.dataModel.model.*; import com.evolveum.midpoint.prism.PrismObject; import com.evolveum.midpoint.prism.path.ItemPath; import com.evolveum.midpoint.prism.polystring.PolyString; import com.evolveum.midpoint.schema.processor.ResourceAttributeDefinition; import com.evolveum.midpoint.schema.util.ResourceTypeUtil; import com.evolveum.midpoint.util.logging.Trace; import com.evolveum.midpoint.util.logging.TraceManager; import com.evolveum.midpoint.xml.ns._public.common.common_3.ActivationType; import com.evolveum.midpoint.xml.ns._public.common.common_3.CredentialsType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ResourceType; import com.evolveum.midpoint.xml.ns._public.common.common_3.ShadowType; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.NotNull; import javax.xml.namespace.QName; import java.util.*; /** * @author mederly */ public class DotModel { private static final Trace LOGGER = TraceManager.getTrace(DotModel.class); public static final String LF = " "; private final DataModel dataModel; @NotNull private final Map<DataItem, DotDataItem> dataItemsMap = new HashMap<>(); @NotNull private final Map<Relation, DotRelation> relationsMap = new HashMap<>(); public DotModel(DataModel dataModel) { this.dataModel = dataModel; for (DataItem dataItem : dataModel.getDataItems()) { DotDataItem ddi; if (dataItem instanceof RepositoryDataItem) { ddi = new DotRepositoryDataItem((RepositoryDataItem) dataItem); } else if (dataItem instanceof ResourceDataItem) { ddi = new DotResourceDataItem((ResourceDataItem) dataItem, this); } else if (dataItem instanceof AdHocDataItem) { ddi = new DotAdHocDataItem((AdHocDataItem) dataItem); } else { throw new AssertionError("Wrong data item: " + dataItem); } dataItemsMap.put(dataItem, ddi); } for (Relation relation : dataModel.getRelations()) { DotRelation dr; if (relation instanceof MappingRelation) { dr = new DotMappingRelation((MappingRelation) relation); } else { dr = new DotOtherRelation(relation); } relationsMap.put(relation, dr); } } private boolean subgraphsForResources = false; private boolean showUnusedItems = false; public String exportDot() { StringBuilder sb = new StringBuilder(); sb.append("digraph G {\n"); Set<DataItem> itemsShown = new HashSet<>(); int clusterNumber = 1; int indent = 1; for (PrismObject<ResourceType> resource : dataModel.getResources().values()) { if (subgraphsForResources) { sb.append(indent(indent)).append("subgraph cluster_").append(clusterNumber++).append(" {\n"); sb.append(indent(indent+1)).append("label=\"").append(resource.getName()).append("\";\n"); // TODO style for resource label indent++; } RefinedResourceSchema schema = dataModel.getRefinedResourceSchema(resource.getOid()); for (RefinedObjectClassDefinition def : schema.getRefinedDefinitions()) { StringBuilder sb1 = new StringBuilder(); sb1.append(indent(indent)).append("subgraph cluster_").append(clusterNumber++).append(" {\n"); String typeName = ""; if (!subgraphsForResources && dataModel.getResources().size() > 1) { typeName = PolyString.getOrig(resource.getName()) + LF; } typeName += getObjectTypeName(def, true); sb1.append(indent(indent + 1)).append("label=\"").append(typeName).append("\";\n"); sb1.append(indent(indent + 1)).append("fontname=\"times-bold\";\n\n"); String previousNodeName = null; indent++; for (ResourceAttributeDefinition attrDef : def.getAttributeDefinitions()) { if (attrDef.isIgnored()) { continue; } ResourceDataItem item = dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(attrDef.getName())); previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, item); } for (RefinedAssociationDefinition assocDef : def.getAssociationDefinitions()) { if (assocDef.isIgnored()) { continue; } ResourceDataItem item = dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(assocDef.getName())); previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, item); } previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_ADMINISTRATIVE_STATUS))); previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(ShadowType.F_ACTIVATION, DataModelVisualizerImpl.ACTIVATION_EXISTENCE))); previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_VALID_FROM))); previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_VALID_TO))); previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(ShadowType.F_ACTIVATION, ActivationType.F_LOCKOUT_STATUS))); previousNodeName = addResourceItem(itemsShown, indent, sb1, previousNodeName, dataModel.findResourceItem(resource.getOid(), def.getKind(), def.getIntent(), getObjectClassName(def), new ItemPath(ShadowType.F_CREDENTIALS, CredentialsType.F_PASSWORD))); indent--; sb1.append(indent(indent)).append("}\n"); if (previousNodeName != null) { sb.append(sb1.toString()); } } if (subgraphsForResources) { sb.append(indent(indent)).append("}\n"); indent--; } } sb.append("\n"); int mappingNode = 1; for (Relation relation : dataModel.getRelations()) { showNodesIfNeeded(sb, indent, itemsShown, relation.getSources()); showNodeIfNeeded(sb, indent, itemsShown, relation.getTarget()); DotRelation dotRelation = getDotRelation(relation); if (relation.getSources().size() == 1 && relation.getTarget() != null) { DataItem sourceItem = relation.getSources().get(0); DataItem targetItem = relation.getTarget(); DotDataItem dotSourceItem = getDotDataItem(sourceItem); DotDataItem dotTargetItem = getDotDataItem(targetItem); sb.append(indent(indent)).append(dotSourceItem.getNodeName()); sb.append(" -> ").append(dotTargetItem.getNodeName()); sb.append(" [label=\"").append(dotRelation.getEdgeLabel()).append("\""); sb.append(", style=").append(dotRelation.getEdgeStyle()); sb.append(", tooltip=\"").append(dotRelation.getEdgeTooltip()).append("\""); sb.append(", labeltooltip=\"").append(dotRelation.getEdgeTooltip()).append("\""); sb.append("];").append("\n"); } else { String mappingName = "m" + (mappingNode++); String nodeLabel = dotRelation.getNodeLabel(mappingName); if (nodeLabel != null) { sb.append(indent(indent)).append(mappingName).append(" [label=\"").append(nodeLabel).append("\""); String styles = dotRelation.getNodeStyleAttributes(); if (StringUtils.isNotEmpty(styles)) { sb.append(", ").append(styles); } sb.append(", tooltip=\"").append(dotRelation.getNodeTooltip()).append("\""); sb.append("];\n"); } for (DataItem src : relation.getSources()) { DotDataItem dotSrc = getDotDataItem(src); sb.append(indent(indent)).append(dotSrc.getNodeName()).append(" -> ").append(mappingName) .append(" [style=").append(dotRelation.getEdgeStyle()).append("]\n"); } if (relation.getTarget() != null) { DotDataItem dotTarget = getDotDataItem(relation.getTarget()); sb.append(indent(indent)).append(mappingName).append(" -> ").append(dotTarget.getNodeName()) .append(" [style=").append(dotRelation.getEdgeStyle()).append("]\n"); } } } sb.append("}"); String dot = sb.toString(); LOGGER.debug("Resulting DOT:\n{}", dot); return dot; } private QName getObjectClassName(RefinedObjectClassDefinition def) { return def != null ? def.getTypeName() : null; } private String addResourceItem(Set<DataItem> itemsShown, int indent, StringBuilder sb1, String previousNodeName, ResourceDataItem item) { if (showUnusedItems || isUsed(item)) { showNodeIfNeeded(sb1, indent, itemsShown, item); String nodeName = getDotDataItem(item).getNodeName(); addHiddenEdge(sb1, indent, previousNodeName, nodeName); previousNodeName = nodeName; } return previousNodeName; } private void showNodesIfNeeded(StringBuilder sb, int indent, Set<DataItem> itemsShown, List<DataItem> items) { for (DataItem item : items) { showNodeIfNeeded(sb, indent, itemsShown, item); } } private void showNodeIfNeeded(StringBuilder sb, int indent, Set<DataItem> itemsShown, DataItem item) { if (itemsShown.add(item)) { DotDataItem dotDataItem = getDotDataItem(item); sb.append(indent(indent)).append(dotDataItem.getNodeName()).append(" ["); sb.append("label=\"").append(dotDataItem.getNodeLabel()).append("\" ").append(dotDataItem.getNodeStyleAttributes()); sb.append("]\n"); } } private boolean isUsed(DataItem item) { for (Relation relation : dataModel.getRelations()) { if (relation.getSources().contains(item) || relation.getTarget() == item) { return true; } } return false; } private void addHiddenEdge(StringBuilder sb, int indent, String previousNodeName, String nodeName) { if (previousNodeName == null) { return; } sb.append(indent(indent)).append(previousNodeName).append(" -> ").append(nodeName).append(" [style=invis];\n"); } @NotNull public String getObjectTypeName(RefinedObjectClassDefinition refinedObjectClassDefinition, boolean formatted) { if (refinedObjectClassDefinition == null) { return "?"; } StringBuilder sb = new StringBuilder(); if (refinedObjectClassDefinition.getDisplayName() != null) { sb.append(refinedObjectClassDefinition.getDisplayName()); sb.append(formatted ? LF : "/"); } sb.append(ResourceTypeUtil.fillDefault(refinedObjectClassDefinition.getKind())); sb.append(formatted ? LF : "/"); sb.append(ResourceTypeUtil.fillDefault(refinedObjectClassDefinition.getIntent())); sb.append(formatted ? LF : "/"); sb.append("("); sb.append(refinedObjectClassDefinition.getObjectClassDefinition().getTypeName().getLocalPart()); sb.append(")"); return sb.toString(); } public String indent(int i) { return StringUtils.repeat(" ", i); } public DataModel getDataModel() { return dataModel; } public DotRelation getDotRelation(Relation relation) { DotRelation rv = relationsMap.get(relation); if (rv != null) { return rv; } else { throw new IllegalStateException("No dot relation for " + relation); } } public DotDataItem getDotDataItem(DataItem dataItem) { DotDataItem rv = dataItemsMap.get(dataItem); if (rv != null) { return rv; } else { throw new IllegalStateException("No dot data item for " + dataItem); } } }