/* * Lokomo OneCMDB - An Open Source Software for Configuration * Management of Datacenter Resources * * Copyright (C) 2006 Lokomo Systems AB * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or (at * your option) any later version. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA. * * Lokomo Systems AB can be contacted via e-mail: info@lokomo.com or via * paper mail: Lokomo Systems AB, Sv�rdv�gen 27, SE-182 33 * Danderyd, Sweden. * */ package org.onecmdb.web; import java.awt.Color; import java.awt.FontMetrics; import java.awt.Graphics2D; import java.awt.Point; import java.awt.image.BufferedImage; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.PrintWriter; import java.io.UnsupportedEncodingException; import java.util.Collections; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import org.onecmdb.core.IAttribute; import org.onecmdb.core.ICi; import org.onecmdb.core.IModelService; import org.onecmdb.core.IOneCmdbContext; import org.onecmdb.core.IReference; import org.onecmdb.core.IReferenceService; import org.onecmdb.core.ISession; import org.onecmdb.core.IType; import org.onecmdb.core.internal.model.ItemId; import org.onecmdb.web.graphs.EGraphRelation; import org.onecmdb.web.graphs.PrefuseRenderer; import org.onecmdb.web.tags.SwingImageCreator; import org.springframework.validation.Errors; import org.springframework.web.bind.ServletRequestDataBinder; import org.springframework.web.servlet.ModelAndView; import org.springframework.web.servlet.mvc.multiaction.MultiActionController; /** * A controller used to handle graph rendering * @author nogun * */ public class GraphGenerator extends MultiActionController { /** format to use for the images created */ final static private String MIMETYPE = "image/png"; private SiteController siteController; // {{{ bean support /** * NOTE: Used to satisfy spring only. */ public void setSiteController(SiteController controller) { this.siteController = controller; } public SiteController getSiteController() { return this.siteController; } public void init() { if (getSiteController() == null) { throw new IllegalStateException("Reference to SiteController missing!"); } } // }}} protected Map referenceData(HttpServletRequest request, Object command, Errors errors) throws Exception { HashMap<String, Object> data = new HashMap<String, Object>(); data.put("scratch", new HashMap() ); data.put("time", new Date()); return data; } @Override protected ServletRequestDataBinder createBinder(ServletRequest request, Object command) throws Exception { String n = getCommandName(command); ServletRequestDataBinder binder = super.createBinder(request, command); return binder; } @Override protected void initBinder(ServletRequest request, ServletRequestDataBinder binder) throws Exception { binder.registerCustomEditor(ItemId.class, new ItemIdEditor()); binder.registerCustomEditor(EnumSet.class, new EnumSetEditor()); } private Map<ItemId, ImageMap> getGraphs(HttpServletRequest request) { HttpSession session = request.getSession(); Map<ItemId, ImageMap> graphs = (Map<ItemId, ImageMap>) session.getAttribute("_graphs_"); if (graphs == null) { graphs = Collections.synchronizedMap(new HashMap<ItemId, ImageMap>()); session.setAttribute("_graphs_", graphs); } return graphs; } public ModelAndView imagemapHandler( HttpServletRequest request, HttpServletResponse response, final GraphCommand graphCommand) throws Exception { Map<String,Object> data = new HashMap<String,Object>(); SiteController sitectrl = getSiteController(); SiteCommand sitecmd = sitectrl.getSiteCommand(request); if (sitecmd == null) { return new ModelAndView("main"); } { // mimic ardinary requests Map refs = referenceData(request, graphCommand, null); data.putAll(refs); } ItemId ciid = graphCommand.getCiid(); Map<String,String> params = new HashMap<String,String>(); data.put("params", params); params.put("ci", ciid.toString()); { SiteAction nav = sitecmd.getActionMap().get("viewci"); data.put("action", nav); } String qs = request.getQueryString(); Pattern p = Pattern.compile("^.*\\?(\\d+),(\\d+)$"); Matcher m = p.matcher(qs); if (m.matches()) { int x = Integer.parseInt(m.group(1)); int y = Integer.parseInt(m.group(2)); Point click = new Point(x,y); Map<ItemId, ImageMap> graphs = getGraphs(request); ImageMap imagemap = null; try { synchronized(graphs) { long start = System.currentTimeMillis(); long stop = start; do { imagemap = graphs.get(ciid); if (imagemap == null) { graphs.wait(10000); } stop = System.currentTimeMillis();; } while (imagemap == null && stop-start < 10000); } if (imagemap != null) { synchronized(imagemap) { long start = System.currentTimeMillis(); long stop = start; do { if (imagemap.isFillng()) { imagemap.wait(10000); } stop = System.currentTimeMillis();; } while (imagemap.isFillng() && stop-start < 10000); } if (!imagemap.isFillng()) { Area area = imagemap.getArea(click); if (area != null) { params.put("ci", area.getItemId().toString()); } } //graphs.remove(ciid); } } catch (InterruptedException e) {} } return new ModelAndView("forward", data); } public ModelAndView generateHandler( HttpServletRequest request, HttpServletResponse response, final GraphCommand graphCommand) throws Exception { final ServletOutputStream out = response.getOutputStream(); /** all content produced should follow the declared mime type */ response.setHeader("Cache-Control", "no-cache"); response.setContentType(MIMETYPE); SiteController ctl = getSiteController(); final SiteCommand siteSession = ctl.getSiteCommand(request); final BufferedImage image; final ItemId ciid = graphCommand.getCiid(); if (siteSession == null) { image = createTextImage("NO SESSION"); SwingImageCreator.writeImageToStream(image, MIMETYPE, out); } else { final ISession session = siteSession.getSession(); final IModelService model = (IModelService) session.getService(IModelService.class); final IReferenceService refs = (IReferenceService) session.getService(IReferenceService.class); final ICi base = model.find(ciid); if (base == null) { image = createTextImage("MISSING CI: " + graphCommand.getCiid()); SwingImageCreator.writeImageToStream(image, MIMETYPE, out); } else { final PipedInputStream pins = new PipedInputStream(); final PipedOutputStream pouts = new PipedOutputStream(pins); ImageMap imagemap = new ImageMap(); Map<ItemId, ImageMap> graphs = getGraphs(request); graphs.put(ciid, imagemap); synchronized (graphs) { graphs.notify(); } Thread consumer = conusumeMlInput(base.getId().toString(), pins, out, "PNG", imagemap); boolean userMode = true; SiteCommand sitecmd = ctl.getSiteCommand(request); if (sitecmd != null) { if (sitecmd != null) { if (!sitecmd.getMode().equals("user")) { userMode = false; } } } produceMlInput(session, graphCommand, userMode, base, pouts); pouts.close(); // nothing more to write consumer.join(); pins.close(); } //image = SwingImageCreator.createImage(graph); } out.flush(); return null; } private void produceMlInput(final ISession session, final GraphCommand grapgctl, final boolean userMode, final ICi base, PipedOutputStream pouts) throws UnsupportedEncodingException { final MlGraph<ICi, ICi> graph = new MlGraph<ICi, ICi>(pouts); graph.populate(new GraphWorker<ICi,ICi>() { final IReferenceService refs = (IReferenceService) session.getService(IReferenceService.class); private MlGraph<ICi, ICi> ml; public void run(MlGraph<ICi,ICi> ml) { this.ml = ml; ml.addNode(base, 0); // Center this.advance(0, base); } private void outGoing(int level, final ICi base) { if (level == grapgctl.depth()) { return; } Set<IAttribute> atts = base.getAttributes(); Set<ICi> nextLevel = new HashSet<ICi>(); for (IAttribute a : atts) { ICi v = null; ICi r = null; if (base.isBlueprint()) { IType type = a.getValueType(); if (type instanceof ICi) { v = (ICi)type; } IType refType = a.getReferenceType(); if (refType instanceof ICi) { r = (ICi)refType; } } else { r = a.getReference(); if (r != null) { v = (ICi) a.getValue(); } } if (v != null) { if (!ml.containsNode(v)) { ml.addNode(v, level); // one level nextLevel.add(v); } if (userMode) { ml.addEdge(base, v); } else { ml.addEdge(base, v, r, a); } } } for (ICi v : nextLevel) { this.advance(level, v); } } private void inGoing(int level, ICi base) { if (level == grapgctl.depth()) return; Set<IReference> incoming = refs.getReferrers(base); Set<ICi> nextLevel = new HashSet<ICi>(); for (IReference r : incoming) { for (ICi s: r.getSourceCis()) { if (!ml.containsNode(s)) { ml.addNode(s, level); // nextLevel.add(s); } if (userMode) { ml.addEdge(s, base); } else { ml.addEdge(s, base, r); } } } for (ICi v : nextLevel) { this.advance(level, v); } } private void advance(int level, ICi v) { EGraphRelation rtype = grapgctl.getRelationType(); if (rtype.equals(EGraphRelation.OUTGOING) || rtype.equals(EGraphRelation.ALL)) { outGoing(level + 1, v); } if (rtype.equals(EGraphRelation.INGOING) || rtype.equals(EGraphRelation.ALL)) { inGoing(level + 1, v); } } } ); } /** * <p>Spawn a thread to consume a ML graph input stream, and write the * content into an output stream, using a specified output type.</p> * * <p>This method returns immediately, leaving a running thread.</p> * * @param ml The ML graph input stream to be consumed. * @param out The place to write the image to. * @param outputType Type of output expected, for example "PNG". * @param imagemap An initialized {@link ImageMap} object onto which an * image map will be put. */ protected Thread conusumeMlInput(String id, final InputStream ml, final OutputStream out, final String outputType, final ImageMap imagemap) { Runnable consumer = new Runnable() { public void run() { try { new PrefuseRenderer().render(ml, out, outputType, imagemap); } catch (IOException e) { e.printStackTrace(); } } }; Thread t = new Thread(consumer, "MLCONSUMER-"+id); t.start(); return t; } /** * Create dynamic text as an image * @param text Text to output * @return An image (PNG) representing the text */ private BufferedImage createTextImage(final String text) { final BufferedImage image = new BufferedImage(200,20, BufferedImage.TYPE_INT_RGB); Graphics2D g = image.createGraphics(); g.setBackground(Color.white); g.fillRect(0,0, image.getWidth(), image.getHeight()); g.setColor(Color.red); g.drawRect(0,0,image.getWidth() - 1,image.getHeight() - 1); FontMetrics fm = g.getFontMetrics(); int baseX = 0; int width = fm.stringWidth(text); int asc = fm.getAscent(); int dec = fm.getDescent(); int height = asc + dec; g.drawString(text, (image.getWidth() - width) / 2, (image.getHeight() + asc - dec ) / 2 ); g.dispose(); return image; } } /** * <p>A simple abstraction (on to of an existing output stream) of an MLGraph, * used to add nodes and edges. </p> * * TODO: Use a XML package to write the data instead of simple `println' * @author nogun * */ class MlGraph<V,E> { private final Set<V> passed = new HashSet<V>(); private final PrintWriter pw; /** * Create a new MLGraph abstraction on top of an existing output stream. * @param outs The stream where to put the produced graph. * @throws UnsupportedEncodingException */ MlGraph(OutputStream outs) throws UnsupportedEncodingException { OutputStreamWriter osw = new OutputStreamWriter(outs, "UTF8"); this.pw = new PrintWriter(osw, true); pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); pw.println("<graphml xmlns=\"http://graphml.graphdrawing.org/xmlns\"" + " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" + " xsi:schemaLocation=\"http://graphml.graphdrawing.org/xmlns" + " http://graphml.graphdrawing.org/xmlns/1.0/graphml.xsd\">"); } public boolean containsNode(ICi v) { return this.passed.contains(v); } private void beginGraph() { pw.println("<graph edgedefault=\"directed\">"); pw.println("<!-- data schema -->"); pw.println("<key id=\"id\" for=\"node\" attr.name=\"id\" attr.type=\"string\"/>"); pw.println("<key id=\"label\" for=\"node\" attr.name=\"label\" attr.type=\"string\"/>"); pw.println("<key id=\"distance\" for=\"node\" attr.name=\"distance\" attr.type=\"integer\"/>"); pw.println("<key id=\"label\" for=\"edge\" attr.name=\"label\" attr.type=\"string\"/>"); } private void endGraph() { pw.println("</graph>"); pw.println("</graphml>"); this.passed.clear(); } /** Populate the graph using a simple worker. The method takes care of * <em>opening</em> the graph, as well as <em>closing</em> it. * * @param runnable Worker responsible to populate the graph with nodes, and * edges. */ void populate(GraphWorker runnable) { beginGraph(); runnable.run(this); endGraph(); } void addNode(V v, int distance) { if (passed.contains(v)) { return; } passed.add(v); ICi ci = (ICi) v; if (ci == null || ci.getId()==null) { System.out.println("Not good!"); } String id = ci.getId().toString(); String lab = ci.getDisplayName(); ICi t = ci.getDerivedFrom(); if (t != null) { //� � lab = "«" + t.getAlias() + "»\n" + lab; } String node = "<node id=\""+ id +"\">\n" + "<data key=\"id\">" + id + "</data>" + "<data key=\"label\">" + lab + "</data>" + "<data key=\"distance\">" + distance + "</data>" + "</node>"; pw.println(node); } public void addEdge(ICi source, ICi target, ICi rel, IAttribute a) { String refLabel = ""; if (rel != null) { refLabel = rel.getDisplayName() + " [" + a.getMinOccurs() + ".." + (a.getMaxOccurs() < 0 ? "*" : a.getMaxOccurs()) + "]"; } addEdge(source, target, refLabel); } void addEdge(ICi source, ICi target, ICi rel) { String refLabel = ""; if (rel != null) { refLabel = rel.getDisplayName(); } addEdge(source, target, refLabel); } void addEdge(ICi source, ICi target) { String refLabel = ""; addEdge(source, target, refLabel); } void addEdge(ICi source, ICi target, String label) { String srcId = source.getId().toString(); String tgtId = target.getId().toString(); pw.println("<edge" + " source=\""+ srcId + "\"" + " target=\""+ tgtId +"\">\n" + "<data key=\"label\">" + label + "</data>" + "</edge>"); } } /** * * @author nogun * * @param <V> Underlying type representing vertices * @param <E> Underlying type representing edges */ interface GraphWorker<V,E> { public void run(MlGraph<V,E> graph); }