All Downloads are FREE. Search and download functionalities are using the official Maven repository.

tbrugz.sqldump.graph.Schema2GraphML Maven / Gradle / Ivy

There is a newer version: 0.9.17
Show newest version
package tbrugz.sqldump.graph;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import tbrugz.graphml.model.Edge;
import tbrugz.graphml.model.Root;
import tbrugz.graphml.model.Stereotyped;
import tbrugz.sqldump.dbmodel.Column;
import tbrugz.sqldump.dbmodel.Constraint;
import tbrugz.sqldump.dbmodel.FK;
import tbrugz.sqldump.dbmodel.Index;
import tbrugz.sqldump.dbmodel.Relation;
import tbrugz.sqldump.dbmodel.SchemaModel;
import tbrugz.sqldump.dbmodel.Table;
import tbrugz.sqldump.dbmodel.TableType;
import tbrugz.sqldump.dbmodel.View;
import tbrugz.sqldump.def.AbstractFailable;
import tbrugz.sqldump.def.Defs;
import tbrugz.sqldump.def.ProcessingException;
import tbrugz.sqldump.def.SchemaModelDumper;
import tbrugz.sqldump.util.Utils;
import tbrugz.xml.AbstractDump;

enum EdgeLabelType {
	FK, FKANDCOLUMNS, COLUMNS, FKCOLUMNS, PKCOLUMNS, NONE;
}

/*
 * TODO!: node contents: show PK columns, FK columns, IDX columns (at column side) - optional: order cols by name 
 * TODOne: add constraints (PK, UK, (check?,) indexes) inside 
 * TODOne: stereotype may include 'otherschema' or table type (table, view, synonym(?), external table)
 * XXX: stereotype may be 'temporary table' 
 * XXX: node color by row count?
 * XXXdone: stereotypes: FK-source-only nodes (roots), FK-target-only nodes (leafs)
 * XXXdone: height by col size?
 * XXXdone: prop for toggling on/off the adjustment of node height?
 * TODOne: prop for setting 'graphml-snippets.properties' location
 * TODO: merge with existing graphml (then layout is not lost) 
 *  - update node contents, update edge label, add new nodes and edges
 *  - specific stereotype for 'new' nodes & edges
 * XXXdone: FK stereotype: composite?
 * XXXdone: schema_name as node stereotype?
 * XXXdone: table name pattern as node stereotype
 * XXXdone: option to show only table names - see/use: /sqldump/src_graphml/graphml-snippets-simple.properties
 * XXX: node label: show table type (VIEW, EXTERNAL_TABLE, ...)?
 * XXX: option to map one stereotype to another (schema@XXX -> schema@YYY; type@EXTERNAL_TABLE -> type@VIEW)
 */
public class Schema2GraphML extends AbstractFailable implements SchemaModelDumper {
	
	static final Log log = LogFactory.getLog(Schema2GraphML.class);
	
	public static final String PREFIX_SCHEMA2GRAPHML = "sqldump.graphmldump";
	public static final String SUFFIX_DUMPFORMATCLASS = ".dumpformatclass";
	
	public static final String PROP_OUTPUTFILE = "sqldump.graphmldump.outputfile";
	public static final String PROP_SHOWSCHEMANAME = "sqldump.graphmldump.showschemaname";
	public static final String PROP_SHOWCONSTRAINTS = "sqldump.graphmldump.showconstraints";
	public static final String PROP_SHOWINDEXES = "sqldump.graphmldump.showindexes";
	public static final String PROP_EDGELABEL = "sqldump.graphmldump.edgelabel";
	public static final String PROP_NODEHEIGHTBYCOLSNUMBER = "sqldump.graphmldump.nodeheightbycolsnumber";
	public static final String PROP_SNIPPETS_FILE = "sqldump.graphmldump.snippetsfile";
	public static final String PROP_STEREOTYPE_ADD_TABLETYPE = "sqldump.graphmldump.addtabletypestereotype";
	public static final String PROP_STEREOTYPE_ADD_SCHEMA = "sqldump.graphmldump.addschemastereotype";
	public static final String PROP_STEREOTYPE_ADD_VERTEXTYPE = "sqldump.graphmldump.addvertextypestereotype";
	
	public static final String PROP_NODESTEREOTYPEREGEX_PREFIX = "sqldump.graphmldump.nodestereotyperegex.";
	public static final String PROP_NODESTEREOTYPECONTAINS_PREFIX = "sqldump.graphmldump.nodestereotypecontains.";
	
	static final String DEFAULT_SNIPPETS = "/graphml-snippets-sqld.properties";
	static final Class DEFAULT_DUMPFORMAT_CLASS = DumpSchemaGraphMLModel.class;

	Class dumpFormatClass = null;
	String snippetsFile;
	
	File output;
	PrintStream outputStream;
	List schemaNamesList = new ArrayList();
	EdgeLabelType edgeLabel = EdgeLabelType.NONE;
	boolean showSchemaName = true;
	boolean showConstraints = false;
	boolean showIndexes = false;
	
	boolean addViewsAsNodes = true; //XXX prop for showing (or not) views as nodes
	boolean addTableTypeStereotype = true;
	boolean addSchemaStereotype = false;
	boolean addVertexTypeStereotype = false;
	
	Map> stereotypeRegexes = new HashMap>();
	Map> stereotypeNodenames = new HashMap>();
	
	//String defaultSchemaName;
	
	public Root getGraphMlModel(SchemaModel schemaModel) {
		Root graphModel = new Root();
		
		Set tableIds = new HashSet();
		
		for(Table t: schemaModel.getTables()) {
			TableNode n = processRelation(schemaModel, t);
			tableIds.add(n.getId());
			graphModel.getChildren().add(n);
		}

		for(View v: schemaModel.getViews()) {
			TableNode n = processRelation(schemaModel, v);
			tableIds.add(n.getId());
			graphModel.getChildren().add(n);
		}
		
		for(FK fk: schemaModel.getForeignKeys()) {
			Edge l = fkToLink(fk);
			if(tableIds.contains(l.getSource())) {
				if(tableIds.contains(l.getTarget())) {
					graphModel.getChildren().add(l);
				}
				else {
					log.warn("Link end ["+l.getTarget()+"] not found");
				}
			}
			else {
				log.warn("Link end ["+l.getSource()+"] not found");
			}
		}
		
		return graphModel;
	}
	
	TableNode processRelation(SchemaModel schemaModel, Relation t) {
		TableNode n = new TableNode();
		String id = t.getSchemaName()+"."+t.getName();
		n.setId(id);
		
		//label
		if(showSchemaName) {
			n.setLabel(id);
		}
		else {
			n.setLabel(t.getName());
		}
		
		//columns
		int colCount = setTableNodeAttributes(n, schemaModel, t);
		/*StringBuffer sbCols = new StringBuffer();
		for(Column c: t.getColumns()) {
			sbCols.append(Column.getColumnDesc(c, null, null, null)+"\n");
		}
		n.setColumnsDesc(sbCols.toString());*/

		//constraints + indexes
		int methodCount = setTableNodeMethods(n, schemaModel, t);
		/*n.setConstraintsDesc("");
		int constrCount = 0;
		if(showConstraints) {
			StringBuffer sbConstraints = new StringBuffer();
			for(Constraint cons: t.getConstraints()) {
				switch(cons.type) {
					case PK: 
					case CHECK:
					case UNIQUE:
						sbConstraints.append(cons.getDefinition(false)+"\n");
						constrCount++;
				}
			}
			n.setConstraintsDesc(n.getConstraintsDesc()+sbConstraints.toString());
		}
		//indexes
		int indexCount = 0;
		if(showIndexes) {
			StringBuffer sbIndexes = new StringBuffer();
			for(Index idx: schemaModel.getIndexes()) {
				//log.debug("idx: "+idx+" / t: "+t.name);
				if(idx.tableName.equals(t.name)) {
					sbIndexes.append(idx.getDefinition(false)+"\n");
					indexCount++;
				}
			}
			n.setConstraintsDesc(n.getConstraintsDesc()+sbIndexes.toString());
		}*/

		//root, leaf
		boolean isRoot = true;
		boolean isLeaf = true;
		for(FK fk: schemaModel.getForeignKeys()) {
			if(fk.getPkTable().equals(t.getName())) {
				isRoot = false;
			}
			if(fk.getFkTable().equals(t.getName())) {
				isLeaf = false;
			}
		}
		n.setRoot(isRoot);
		n.setLeaf(isLeaf);

		//column number
		n.setColumnNumber(colCount+methodCount);
		
		//node stereotype
		if(addTableTypeStereotype) {
			if(t instanceof Table) {
				Table tt = (Table) t;
				switch (tt.getType()) {
					case SYSTEM_TABLE: 
					case VIEW:
					case MATERIALIZED_VIEW:
					case EXTERNAL_TABLE:
					case SYNONYM:
						addStereotype(n, "type@"+tt.getType()); break;
					case TABLE:
					default:
						break;
				}
			}
			else {
				addStereotype(n, "type@"+TableType.VIEW);
			}
		}
		
		for(String key: stereotypeNodenames.keySet()) {
			//List patterns = stereotypeRegexes.get(key);
			for(String nodename: stereotypeNodenames.get(key)) {
				if(nodename.equals(t.getName())) {
					addStereotype(n, "nodename@"+key);
				}
			}
		}

		for(String key: stereotypeRegexes.keySet()) {
			//List patterns = stereotypeRegexes.get(key);
			for(Pattern pat: stereotypeRegexes.get(key)) {
				if(pat.matcher(t.getName()).matches()) {
					addStereotype(n, "regex@"+key);
				}
			}
		}
		
		if(t.getSchemaName()!=null && schemaNamesList.contains(t.getSchemaName())) {
			if(addSchemaStereotype) {
				addStereotype(n, "schema@"+t.getSchemaName());
			}
		}
		else {
			if(addSchemaStereotype) {
				addStereotype(n, "otherschema.schema@"+t.getSchemaName());
			}
			else {
				addStereotype(n, "otherschema");
			}
		}
		
		if(addVertexTypeStereotype) {
			if(n.isRoot() && n.isLeaf()) {
				//log.debug("table '"+t.getName()+"' is disconnected");
				addStereotype(n, "disconnected");
			}
			else if(n.isRoot()) {
				//log.debug("table '"+t.getName()+"' is root");
				addStereotype(n, "root");
			}
			else if(n.isLeaf()) {
				//log.debug("table '"+t.getName()+"' is leaf");
				addStereotype(n, "leaf");
			}
			else {
				//is in the middle
				addStereotype(n, "connected");
			}
		}
		if(n.getStereotype()!=null) {
			log.debug("node '"+t.getName()+"' has stereotype: "+n.getStereotype());
		}
		else {
			log.debug("node '"+t.getName()+"' has no stereotype");
		}
		//end stereotype
		
		//tableIds.add(id);
		return n;
		//graphModel.getChildren().add(n);
			
	}
	
	int setTableNodeAttributes(TableNode n, SchemaModel schemaModel, Relation t) {
		//columns
		StringBuilder sbCols = new StringBuilder();
		int colCount = 0;
		if(t instanceof Table) {
			Table tt = (Table) t;
			for(Column c: tt.getColumns()) {
				sbCols.append(c.getDefinition()+"\n");
				colCount++;
			}
		}
		else {
			if(t.getColumnNames()!=null) {
			for(String c: t.getColumnNames()) {
				sbCols.append(c+"\n");
				colCount++;
			}
			}
			else {
				log.warn("getColumnNames is null... TableNode: "+n.getId()+" relation: "+t);
			}
		}
		n.setColumnsDesc(sbCols.toString());
		return colCount;
	}

	int setTableNodeMethods(TableNode n, SchemaModel schemaModel, Relation t) {
		//constraints
		n.setConstraintsDesc("");
		int constrCount = 0;
		if(showConstraints) {
			StringBuilder sbConstraints = new StringBuilder();
			for(Constraint cons: t.getConstraints()) {
				switch(cons.getType()) {
					case PK: 
					case CHECK:
					case UNIQUE:
						sbConstraints.append(cons.getDefinition(false)+"\n");
						constrCount++;
				}
			}
			n.setConstraintsDesc(n.getConstraintsDesc()+sbConstraints.toString());
		}
		//indexes
		int indexCount = 0;
		if(showIndexes) {
			StringBuilder sbIndexes = new StringBuilder();
			for(Index idx: schemaModel.getIndexes()) {
				//log.debug("idx: "+idx+" / t: "+t.name);
				if(idx.getTableName().equals(t.getName())) {
					sbIndexes.append(idx.getDefinition(false)+"\n");
					indexCount++;
				}
			}
			n.setConstraintsDesc(n.getConstraintsDesc()+sbIndexes.toString());
		}
		return constrCount+indexCount;
	}
	
	Edge fkToLink(FK fk) {
		FKEdge l = new FKEdge();
		switch(edgeLabel) {
			case FK: 
				l.setName(fk.getName()); break;
			case COLUMNS: 
				l.setName("("+fk.getFkColumns()+" -> "+fk.getPkColumns()+")"); break;
			case FKANDCOLUMNS:
				//fk label: fk name + columns involved
				l.setName(fk.getName()+" ("+fk.getFkColumns()+" -> "+fk.getPkColumns()+")"); break;
			case FKCOLUMNS:
				l.setName(Utils.join(fk.getFkColumns(),", ")); break;
			case PKCOLUMNS:
				l.setName(Utils.join(fk.getPkColumns(),", ")); break;
			case NONE:
			default:
				l.setName(""); break;
		}
		l.referencesPK = fk.getFkReferencesPK();
		l.composite = fk.getFkColumns().size()>1;
		l.setSource(getSourceId(fk));
		l.setTarget(getTargetId(fk));
		
		return l;
	}

	@Override
	public void dumpSchema(SchemaModel schemaModel) {
		log.info("dumping graphML: translating model");
		if(schemaModel==null) {
			log.error("schemaModel is null!");
			if(failonerror) { throw new ProcessingException("schemaModel is null!"); }
			return;
		}
		Root r = getGraphMlModel(schemaModel);
		log.info("dumping model... [size = "+r.getChildren().size()+"]");

		AbstractDump dg = (AbstractDump) Utils.getClassInstance(dumpFormatClass);
		
		try {
			if(outputStream==null) {
				if(output==null) {
					String message = "graphml output file [prop '"+PROP_OUTPUTFILE+"'] not defined. won't dump";
					log.error(message);
					if(failonerror) { throw new ProcessingException(message); }
					return;
				}
				Utils.prepareDir(output);
				outputStream = new PrintStream(output);
			}
				
			dg.loadSnippets(DEFAULT_SNIPPETS);
			if(snippetsFile!=null) { dg.loadSnippets(snippetsFile); }
			
			dg.dumpModel(r, outputStream);
			
			if(output!=null) {
				log.info("graphML dumped to: "+output.getAbsolutePath());
			}
			else {
				log.info("graphML dumped");
			}
		} catch (FileNotFoundException e) {
			log.error("error dumping schema: "+e);
			log.debug("error dumping schema", e);
			if(failonerror) { throw new ProcessingException(e); }
		}
	}

	public void setOutput(File output) {
		this.output = output;
	}
	
	@Override
	public void setProperties(Properties prop) {
		String outFileStr = prop.getProperty(PROP_OUTPUTFILE);
		if(outFileStr==null && output==null) {
			log.debug("graphml output file ["+PROP_OUTPUTFILE+"] not defined");
		}
		else {
			setOutput(new File(outFileStr));
		}
		
		dumpFormatClass = getDumpFormatClass(prop, PREFIX_SCHEMA2GRAPHML+SUFFIX_DUMPFORMATCLASS);
		if(dumpFormatClass!=null) {
			log.info("dump format class: "+dumpFormatClass.getName());
		}
		else {
			dumpFormatClass = DEFAULT_DUMPFORMAT_CLASS;
		}
		
		@SuppressWarnings("deprecation")
		String schemaPattern = Utils.getPropWithDeprecated(prop, Defs.PROP_SCHEMAGRAB_SCHEMANAMES, Defs.PROP_DUMPSCHEMAPATTERN, null);
		
		schemaNamesList = new ArrayList();
		
		if(schemaPattern!=null) { 
			String[] schemasArr = schemaPattern.split(",");
			for(String schemaName: schemasArr) {
				schemaNamesList.add(schemaName.trim());
			}
		}
		
		//sqldump.graphmldump.edgelabel=FK|FKANDCOLUMNS|COLUMNS|NONE
		String edgeLabelStr = prop.getProperty(PROP_EDGELABEL, EdgeLabelType.NONE.name());
		try {
			edgeLabel = EdgeLabelType.valueOf(edgeLabelStr);
		}
		catch(IllegalArgumentException e) {
			log.warn("Illegal value for prop '"+PROP_EDGELABEL+"': "+edgeLabelStr);
		}
		
		//node stereotype regex
		for(String key: Utils.getKeysStartingWith(prop, PROP_NODESTEREOTYPEREGEX_PREFIX)) {
			String stereotype = key.substring(PROP_NODESTEREOTYPEREGEX_PREFIX.length());
			String[] regexes = prop.getProperty(key).split("\\|");
			for(String regex: regexes) {
				regex = regex.trim();
				log.debug("added stereotype table pattern: stereotype: '"+stereotype+"', pattern: '"+regex+"'");
				List patterns = stereotypeRegexes.get(stereotype);
				if(patterns==null) {
					patterns = new ArrayList();
					stereotypeRegexes.put(stereotype, patterns);
				}
				patterns.add(Pattern.compile(regex));
			}
		}

		//node stereotype contains
		for(String key: Utils.getKeysStartingWith(prop, PROP_NODESTEREOTYPECONTAINS_PREFIX)) {
			String stereotype = key.substring(PROP_NODESTEREOTYPECONTAINS_PREFIX.length());
			String[] nodenames = prop.getProperty(key).split(",");
			for(String nodename: nodenames) {
				nodename = nodename.trim();
				log.debug("added stereotype table name: stereotype: '"+stereotype+"', name: '"+nodename+"'");
				List patterns = stereotypeNodenames.get(stereotype);
				if(patterns==null) {
					patterns = new ArrayList();
					stereotypeNodenames.put(stereotype, patterns);
				}
				patterns.add(nodename);
			}
		}
		
		showSchemaName = Utils.getPropBool(prop, PROP_SHOWSCHEMANAME, showSchemaName);
		showConstraints = Utils.getPropBool(prop, PROP_SHOWCONSTRAINTS, showConstraints);
		showIndexes = Utils.getPropBool(prop, PROP_SHOWINDEXES, showIndexes);
		//XXX: remove static reference to DumpSchemaGraphMLModel.nodeHeightByColsNumber
		DumpSchemaGraphMLModel.nodeHeightByColsNumber = Utils.getPropBool(prop, PROP_NODEHEIGHTBYCOLSNUMBER, true);
		
		String snippetsFile = prop.getProperty(PROP_SNIPPETS_FILE);
		if(snippetsFile!=null) {
			this.snippetsFile = snippetsFile;
			log.info("snippets-file is '"+this.snippetsFile+"'");
		}
		
		addTableTypeStereotype = Utils.getPropBool(prop, PROP_STEREOTYPE_ADD_TABLETYPE, addTableTypeStereotype);
		addSchemaStereotype = Utils.getPropBool(prop, PROP_STEREOTYPE_ADD_SCHEMA, addSchemaStereotype);
		addVertexTypeStereotype = Utils.getPropBool(prop, PROP_STEREOTYPE_ADD_VERTEXTYPE, addVertexTypeStereotype);
	}
	
	
	static void addStereotype(Stereotyped stereo, String str){
		if(stereo.getStereotype()!=null) {
			stereo.setStereotype(stereo.getStereotype()+"."+str);
		}
		else {
			stereo.setStereotype(str);
		}
	}
	
	public static String getSourceId(FK fk) {
		return fk.getPkTableSchemaName()+"."+fk.getPkTable();
	}

	public static String getTargetId(FK fk) {
		return fk.getFkTableSchemaName()+"."+fk.getFkTable();
	}
	
	@Override
	public void setPropertiesPrefix(String propertiesPrefix) {
		// TODO: properties-prefix setting
	}
	
	/*static Class getDumpFormatClass(Properties prop, String key, Class defaultFormatClass) {
		String dumpFormatClassStr = prop.getProperty(key);
		if(dumpFormatClassStr!=null) {
			Class c = Utils.getClassWithinPackages(dumpFormatClassStr, "tbrugz.graphml", "tbrugz.sqldump.graph", null);
			if(c!=null) {
				return c;
			}
			else {
				log.warn("dump format class '"+dumpFormatClassStr+"' not found. defaulting to '"+DEFAULT_DUMPFORMAT_CLASS.getName()+"'");
			}
		}
		return defaultFormatClass;
	}*/

	static Class getDumpFormatClass(Properties prop, String key) {
		String dumpFormatClassStr = prop.getProperty(key);
		if(dumpFormatClassStr!=null) {
			Class c = Utils.getClassWithinPackages(dumpFormatClassStr, "tbrugz.graphml", "tbrugz.sqldump.graph", null);
			if(c!=null) {
				return c;
			}
			else {
				log.warn("dump format class '"+dumpFormatClassStr+"' not found. defaulting to '"+DEFAULT_DUMPFORMAT_CLASS.getName()+"'");
			}
		}
		return null;
	}

	@Override
	public String getMimeType() {
		return "application/xml";
	}
	
	@Override
	public boolean acceptsOutputStream() {
		return true;
	}
	
	@Override
	public void setOutputStream(OutputStream out) {
		if(out instanceof PrintStream) {
			this.outputStream = (PrintStream) out;
		}
		else {
			this.outputStream = new PrintStream(out);
		}
	}
	
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy