org.kite9.diagram.server.AbstractKite9Controller.java Source code

Java tutorial

Introduction

Here is the source code for org.kite9.diagram.server.AbstractKite9Controller.java

Source

package org.kite9.diagram.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.net.InetAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.annotation.Resource;
import javax.mail.Message;
import javax.mail.Message.RecipientType;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.servlet.ServletContext;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.sql.DataSource;
import javax.xml.transform.Source;
import javax.xml.transform.sax.SAXSource;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;

import org.kite9.diagram.adl.Diagram;
import org.kite9.diagram.primitives.Connected;
import org.kite9.diagram.primitives.Connection;
import org.kite9.diagram.primitives.DiagramElement;
import org.kite9.diagram.visitors.DiagramElementVisitor;
import org.kite9.diagram.visitors.VisitorAction;
import org.kite9.diagram.visualization.display.java2d.style.Stylesheet;
import org.kite9.diagram.visualization.display.java2d.style.sheets.BasicBlueStylesheet;
import org.kite9.diagram.visualization.display.java2d.style.sheets.BasicStylesheet;
import org.kite9.diagram.visualization.display.java2d.style.sheets.CGWhiteStylesheet;
import org.kite9.diagram.visualization.display.java2d.style.sheets.Designer2012Stylesheet;
import org.kite9.diagram.visualization.display.java2d.style.sheets.DesignerStylesheet;
import org.kite9.diagram.visualization.display.java2d.style.sheets.OutlinerStylesheet;
import org.kite9.diagram.visualization.format.ClientSideMapRenderer;
import org.kite9.framework.common.Kite9ProcessingException;
import org.kite9.framework.common.RepositoryHelp;
import org.kite9.framework.logging.Kite9Log;
import org.kite9.framework.logging.LogicException;
import org.kite9.framework.serialization.XMLHelper;
import org.springframework.dao.DataAccessException;
import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.web.context.ServletContextAware;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

public abstract class AbstractKite9Controller implements ServletContextAware {

    public static final int MAX_UNLICENSED_ELEMENTS = 20;
    public static final int MAX_NO_AUTH_ELEMENTS = 20;
    protected XMLHelper helper = new XMLHelper();
    protected JdbcTemplate template;

    protected static Map<String, Stylesheet> stylesheet;
    public static final String BASIC = "basic";
    public static final String DOCTYPE = "<!DOCTYPE html PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">";
    private static final Project LOCAL_PROJECT = new Project(1, true);
    private static final User LOCAL_USER = new User(999, "Mr Local", true, "", "");
    public static final User NO_USER = new User(-1, "(none)", false, "", "");
    public static final Project NO_PROJECT = new Project(-1, false);

    static {
        stylesheet = new HashMap<String, Stylesheet>();
        stylesheet.put(BASIC, new BasicStylesheet());
        stylesheet.put("cg_white", new CGWhiteStylesheet());
        stylesheet.put("blue", new BasicBlueStylesheet());
        stylesheet.put("outline", new OutlinerStylesheet());
        stylesheet.put("designer", new DesignerStylesheet());
        stylesheet.put("designer2012", new Designer2012Stylesheet());

    }

    public Stylesheet getStylesheet(String style) {
        Stylesheet out = stylesheet.get(style);
        if (out == null) {
            return stylesheet.get(BASIC);
        }

        return out;
    }

    public String getStyle(String style) {
        if (stylesheet.containsKey(style)) {
            return style;
        } else {
            return BASIC;
        }
    }

    public AbstractKite9Controller() {
        super();
        if (!isLocal()) {
            Kite9Log.setLogging(false);
            System.out.println("Setting logging off for diagrams");
        } else {
            System.out.println("Setting logging on for diagrams");
        }
    }

    @Resource(name = "dataSource")
    public void setDataSource(DataSource ds) {
        this.template = new JdbcTemplate(ds);
    }

    public static final String PROJECT_SQL_ROOT = "select ctp.nid as nid, field_key_value, ur.rid = 4 as license "
            + "from content_type_project ctp " + "left join node n  on (n.nid = ctp.nid) "
            + "left join users_roles ur ON ur.uid = n.uid " + "where (ur.rid is null or ur.rid = 4) ";

    public Project getProject(int projectId, String projectKey) {
        if (isLocal()) {
            return LOCAL_PROJECT;
        }

        try {
            Project out = (Project) template.queryForObject(
                    PROJECT_SQL_ROOT + " and n.nid=? and ctp.field_key_value=?",
                    new Object[] { projectId, projectKey }, createProjectRowMapper());

            return out;
        } catch (EmptyResultDataAccessException e) {
            return NO_PROJECT;
        } catch (DataAccessException e) {
            throw new InvalidProjectException("The Project with ID " + projectId + " and key " + projectKey
                    + " could not be retrieved from the server.", e);

        }
    }

    public static class User {

        public User(int i, String string, boolean b, String ip, String host) {
            this.number = i;
            this.userName = string;
            this.isLicensed = b;
            this.ip = ip;
            this.host = host;
        }

        public User() {
        }

        int number;
        String userName;
        boolean isLicensed;
        String ip;
        String host;

        public String toString() {
            return number + "," + userName + ",l=" + isLicensed + "," + ip + "," + host;
        }

        public int getDiagramSaveQuota() {
            return 50;
        }
    }

    /**
     * Retrieves user info from cookie
     */
    public User getUser(HttpServletRequest req) {
        if (isLocal()) {
            return LOCAL_USER;
        }

        Cookie[] cookies = req.getCookies();
        String wpCookieName = null;
        String wpCookieValue = null;
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (cookie.getName().startsWith("wordpress_logged_in")) {
                    wpCookieName = cookie.getName();
                    wpCookieValue = cookie.getValue();
                }
            }
        }

        final String ip = req.getRemoteAddr();
        final String host = req.getRemoteHost();

        System.out.println("Session : " + wpCookieName + " " + wpCookieValue);

        if (wpCookieName == null) {
            return NO_USER;
        }

        try {
            URL u = new URL(URL_ROOT + "/kite9_user_info");
            URLConnection conn = u.openConnection();
            conn.setRequestProperty("Cookie", wpCookieName + "=" + wpCookieValue);
            conn.connect();
            BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line = br.readLine();
            br.close();
            if (line.contains("<none>")) {
                return NO_USER;
            } else {
                String parts[] = line.split(",");
                int id = Integer.parseInt(parts[1]);
                return new User(id, parts[0], false, ip, host);
            }
        } catch (IOException e) {
            throw new Kite9ProcessingException("Couldn't handle user log-in", e);
        }
    }

    protected boolean isLocal() {
        try {
            if (InetAddress.getLocalHost().toString().contains("kite9")) {
                return false;
            }
        } catch (UnknownHostException e) {
            return true;
        }

        return true;
    }

    private RowMapper<Project> createProjectRowMapper() {
        return new RowMapper<Project>() {

            public Project mapRow(ResultSet rs, int arg1) throws SQLException {
                Project p = new Project();
                p.setId(rs.getInt("nid"));
                p.setLicensed(rs.getInt("license") == 1);
                return p;
            }

        };
    }

    public Project getProject(String domain) {
        if (isLocal()) {
            return LOCAL_PROJECT;
        }
        try {
            Project out = (Project) template.queryForObject(PROJECT_SQL_ROOT + " and field_domain_name_value=?",
                    new Object[] { domain }, createProjectRowMapper());

            return out;
        } catch (EmptyResultDataAccessException e) {
            return null;
        } catch (DataAccessException e) {
            throw new InvalidProjectException(
                    "The Project with domain " + domain + " could not be retrieved from the server.", e);

        }
    }

    protected void storeDiagramDetails(Project p, User u, String fileName, long currentTime, boolean failed,
            DiagramSize mva, Action a, String xml) {
        try {
            String stamp = new SimpleDateFormat("HH:mm:ss").format(new Date());
            PrintWriter pw = new PrintWriter(new FileWriter(getDiagramLog(), true), false);
            pw.write(u + "," + p + "," + currentTime + "," + (failed ? "FAIL" : "GOOD") + "," + mva + ","
                    + a.toString() + "," + fileName + "," + stamp + "\n");
            pw.close();
        } catch (Exception e) {
            sendErrorEmail(e, xml, null);
        }
    }

    public File getDiagramLog() throws MalformedURLException {
        Date d = new Date();
        String log = "/diagram-log-" + new SimpleDateFormat("yyyyMMdd").format(d) + ".csv";

        File f = new File(getCacheRoot() + log);
        return f;
    }

    protected DiagramSize getDiagramSize(Diagram d) {
        DiagramElementVisitor vis = new DiagramElementVisitor();
        DiagramSize mva = new DiagramSize();
        vis.visit(d, mva);
        return mva;
    }

    public static class DiagramSize implements VisitorAction {

        public int connections = 0;
        public int connecteds = 0;

        public void visit(DiagramElement de) {
            if (de instanceof Connection) {
                connections++;
            } else if (de instanceof Connected) {
                connecteds++;
            }
        }

        public String toString() {
            return connections + "," + connecteds;
        }
    }

    /**
     * Generates the SHA-1 hash of the XML content item.
     */
    public static String generateHash(String xml) {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-1");
            byte[] data = xml.getBytes();
            byte[] out = md.digest(data);

            // convert the byte to hex format method 1
            StringBuffer sb = new StringBuffer();
            for (int i = 0; i < out.length; i++) {
                sb.append(Integer.toString((out[i] & 0xff) + 0x100, 16).substring(1));
            }

            return sb.toString();
        } catch (NoSuchAlgorithmException e) {
            throw new LogicException("Algorithm doesn't exist!", e);
        }
    }

    private static final String CACHE_ROOT = "/var/www/server.kite9.org/kite9-repo";
    private static final String STATIC_URL_ROOT = "http://kite9.com/kite9-repo/";
    private static final String LOCAL_URL_ROOT = "http://localhost:9080/org.kite9.diagram.server/";
    private static final String URL_ROOT = "http://kite9.com";

    /**
     * Returns a handle to a file, whether or not it has been generated
     * 
     * @throws MalformedURLException
     */
    public File getCacheFile(String hash, String name) throws MalformedURLException {
        String first = hash.substring(0, 3);
        String rest = hash.substring(3);
        return getFile("cache", first, rest, name);
    }

    /**
     * Returns a handle to a file, whether or not it has been generated
     * 
     * @throws MalformedURLException
     */
    public File getFile(String sub0, String sub1, String sub2, String name) throws MalformedURLException {
        File out = new File(getCacheRoot());
        out = createAndCheck(out, sub0);
        out = createAndCheck(out, sub1);
        out = createAndCheck(out, sub2);

        if (name == null) {
            return out;
        }

        File actual = new File(out, name);

        return actual;
    }

    private File createAndCheck(File from, String name) {
        if (name != null) {
            from = new File(from, name);
        }
        if (!from.exists()) {
            boolean created = from.mkdir();
            if (!created) {
                throw new Kite9ProcessingException(
                        "Could not create directory for file cache (check inodes): " + from);
            }
        }

        return from;
    }

    protected String getCacheRoot() throws MalformedURLException {
        if (isLocal()) {
            if (ctx != null) {
                String out = new File(ctx.getRealPath("/")).getParent();
                String dir = out + "/ROOT/kite9-repo";
                new File(dir).mkdirs();
                return dir;
            } else {
                String dir = "target/kite9-repo";
                new File(dir).mkdirs();
                return dir;
            }
        } else {
            return CACHE_ROOT;
        }
    }

    protected String getStaticURLRoot() throws MalformedURLException {
        if (isLocal()) {
            return LOCAL_URL_ROOT;
        }
        return STATIC_URL_ROOT;
    }

    protected String getServletURLRoot(HttpServletRequest request) throws MalformedURLException {
        if (isLocal()) {
            return request.getContextPath();
        }
        return URL_ROOT + request.getContextPath();

    }

    protected String getXML(String hash, String file) throws IOException {
        File xmlFile = getCacheFile(hash, file);
        Reader xmlIn = new FileReader(xmlFile);
        StringWriter sw = new StringWriter(1000);
        RepositoryHelp.streamCopy(xmlIn, sw, true);
        return sw.toString();
    }

    protected String getXML(File xmlFile) throws IOException {
        Reader xmlIn = new FileReader(xmlFile);
        StringWriter sw = new StringWriter(1000);
        RepositoryHelp.streamCopy(xmlIn, sw, true);
        return sw.toString();
    }

    public static interface Action {

        public boolean handleExistingOutput(String hash, HttpServletRequest r);

        public void handleException(Throwable t, HttpServletRequest r);

        // builds a new diagram
        public Diagram getDiagram(String xml, File xmlFile, HttpServletRequest r) throws IOException;

        // pass in the diagram here.
        public void process(Project p, User u, Diagram d, String hash, boolean watermark) throws Exception;

        // return true if ok to generate
        public boolean checkComplexity(Project p, User u, DiagramSize ds) throws Exception;

    }

    protected void generateOrRetrieveDiagram(Project p, User u, String xml, String hash, Action a,
            HttpServletRequest r) {
        long time = System.currentTimeMillis();
        boolean ok = true;

        if ((hash == null) && (xml == null)) {
            throw new Kite9ProcessingException("No XML or Hash Provided!");
        }

        if (hash == null) {
            hash = generateHash(xml);
        }

        u = u == null ? NO_USER : u;
        p = p == null ? NO_PROJECT : p;

        File xmlFile;
        try {
            xmlFile = getCacheFile(hash, "diagram.xml");
        } catch (MalformedURLException e) {
            a.handleException(e, r);
            sendErrorEmail(e, null, r.getRequestURL().toString());
            return;
        }

        if (!a.handleExistingOutput(hash, r)) {
            // ok, diagram needs to be generated.
            DiagramSize ds = new DiagramSize();
            Diagram d = null;

            try {
                d = a.getDiagram(xml, xmlFile, r);

                if (d == null) {
                    throw new Kite9ProcessingException("No Diagram Provided (xml contains no diagram): \n" + xml);
                }

                ds = getDiagramSize(d);

                if (a.checkComplexity(p, u, ds)) {
                    goDiagramGeneration(p, u, hash, a, d);
                }

            } catch (Throwable t) {
                log("Could not generate diagram: \n" + xml, t);
                a.handleException(t, r);
                ok = false;
                sendErrorEmail(t, xml, r.getRequestURL().toString());
            } finally {
                storeDiagramDetails(p, u, xmlFile.toString(), System.currentTimeMillis() - time, !ok, ds, a, xml);
                storeDiagramXML(xmlFile, xml);
            }
        }
    }

    protected Object loadXML(String xml, File xmlFile, HttpServletRequest r) throws IOException {
        return helper.fromXML(xml);
    }

    /**
     * Ensures a time limit on diagram generation of 20 seconds.
     */
    protected void goDiagramGeneration(final Project p, final User u, final String hash, final Action a,
            final Diagram d) throws Exception {
        final Thread me = Thread.currentThread();
        final boolean watermark = !(u.isLicensed || p.isLicensed());
        Thread timer = new Thread(new Runnable() {

            public void run() {
                try {
                    Thread.sleep(20000);
                    me.interrupt();
                } catch (InterruptedException e) {
                    // exit normally, the diagram is complete.
                    log("Diagram completed ok", null);
                }
            }
        });

        try {
            timer.start();
            a.process(p, u, d, hash, watermark);
        } catch (InterruptedException e) {
            // run out of time.
            log("Run out of time generating diagram", null);
            throw new Kite9ProcessingException("Diagram couldn't be completed in time");
        } finally {
            timer.interrupt();
        }
    }

    protected void log(String string, Throwable e) {
        if (ctx != null) {
            ctx.log(string, e);
        } else {
            System.out.println(string);
            if (e != null) {
                e.printStackTrace();
            }
        }
    }

    protected void storeDiagramXML(File xmlFile, String xml) {
        try {
            if ((!xmlFile.exists()) && (xml != null)) {
                File f = xmlFile.getParentFile();
                if (!f.exists()) {
                    f.mkdir();
                }
                Writer xmlOut = new OutputStreamWriter(new FileOutputStream(xmlFile));
                xmlOut.write(xml);
                xmlOut.close();
            }
        } catch (Throwable t) {
            sendErrorEmail(t, xml, xmlFile.toString());
        }
    }

    protected void sendErrorFormBack(OutputStream os, String formName) throws IOException {
        OutputStreamWriter osw = new OutputStreamWriter(os);
        osw.write("form=" + formName);
        osw.close();
    }

    private String pattern = "[^0-9_a-zA-Z\\(\\)\\%\\-\\.]";
    private Pattern filePattern = Pattern.compile(pattern);

    protected String sanitizeFilePath(String badFileName) {
        StringBuffer cleanFileName = new StringBuffer();
        Matcher fileMatcher = filePattern.matcher(badFileName);
        boolean match = fileMatcher.find();
        while (match) {
            fileMatcher.appendReplacement(cleanFileName, "");
            match = fileMatcher.find();
        }
        fileMatcher.appendTail(cleanFileName);
        return cleanFileName.substring(0, cleanFileName.length() > 250 ? 250 : cleanFileName.length());
    }

    protected boolean basicCheckComplexity(Project p, User u, DiagramSize mva) {
        if (getAccessType(p, u) != AccessType.LICENSED) {
            if (mva.connecteds > MAX_UNLICENSED_ELEMENTS) {
                throw new Kite9ProcessingException("To have more than " + MAX_UNLICENSED_ELEMENTS
                        + " attr, you will need a kite9 license.  Your diagram has " + mva.connecteds
                        + " attr. User=" + u);
            }
        }

        return true;
    }

    public enum AccessType {
        NO_AUTH, LOGGED_IN, LICENSED
    };

    public AccessType getAccessType(Project p, User u) {
        // check license
        if ((p != null) && (p.isLicensed())) {
            return AccessType.LICENSED;
        }

        if (u != null) {
            if (u.isLicensed) {
                return AccessType.LICENSED;
            } else {
                return AccessType.LOGGED_IN;
            }
        }

        return AccessType.NO_AUTH;
    }

    protected ClientSideMapRenderer createMapRenderer() {
        return new ClientSideMapRenderer(10);
    }

    protected void validateXML(String xml) throws SAXException, IOException {
        // validate the xml against the schema
        InputSource is = new InputSource(new StringReader(xml));

        SchemaFactory factory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");

        // load a WXS schema, represented by a Schema instance
        Source schemaFile = new StreamSource(Diagram.class.getResourceAsStream("/adl_1.0.xsd"));
        Schema schema = factory.newSchema(schemaFile);

        Validator validator = schema.newValidator();

        SAXSource source = new SAXSource(is);
        validator.validate(source);
    }

    private ServletContext ctx;

    public void setServletContext(ServletContext servletContext) {
        this.ctx = servletContext;
    }

    protected void handleException(Exception ee, OutputStream os) {
        StringBuilder out = new StringBuilder();
        if (ee instanceof SAXParseException) {
            SAXParseException e = (SAXParseException) ee;
            out.append("<html><head><title>Validation Error</title></head><body>\n");
            out.append("<h1>Validation Error</h1>\n");
            out.append("<p>Line: " + e.getLineNumber() + "</p>\n");
            out.append("<p>Column: " + e.getColumnNumber() + "</p>\n");
            formatMessage(out, e.getMessage());
        } else {
            out.append("<html><head><title>XML Error</title></head><body>\n");
            out.append("<h1>Validation Error</h1>\n");
            formatMessage(out, ee.getMessage());
        }

        PrintWriter psw = new PrintWriter(os);
        psw.append(out.toString());
        psw.append("<h2>Detail: </h2>\n");
        psw.append("<pre>/n");
        ee.printStackTrace(psw);
        psw.append("</pre></body></html>");
        psw.close();
    }

    private void formatMessage(StringBuilder out, String m) {
        out.append("<h2>Message</h2>\n");
        out.append("<p>");
        out.append(m.replaceAll("\n", "</p><p>"));
        out.append("</p>");
    }

    public void sendErrorEmail(Throwable t, String xml, String url) {
        try {
            Properties props = new Properties();
            props.put("mail.smtp.host", "server.kite9.org");
            Session session = Session.getInstance(props);
            Message msg = new MimeMessage(session);
            msg.setFrom(new InternetAddress("servicetest@kite9.com"));
            msg.setRecipient(RecipientType.TO, new InternetAddress("rob@kite9.com"));
            String name = ctx.getServletContextName();
            boolean local = isLocal();
            msg.setSubject("Failure in " + (local ? "TEST" : name) + " Service: " + this.getClass().getName());
            StringWriter sw = new StringWriter(10000);
            PrintWriter pw = new PrintWriter(sw);
            pw.write("URL: " + url + "\n");

            t.printStackTrace(pw);

            if (xml != null) {
                pw.println();
                pw.print(xml);
            }

            pw.close();
            msg.setText(sw.toString());
            Transport.send(msg);
        } catch (Exception e) {
        } finally {
            t.printStackTrace();
        }
    }

    protected Diagram getDiagramFromXML(String xml, File xmlFile, HttpServletRequest r) throws IOException {
        if ((xml == null) && (xmlFile != null) && (xmlFile.exists())) {
            xml = getXML(xmlFile);
        }

        if (xml == null) {
            // empty diagram
            xml = "<?xml version=\"1.0\" ?><diagram xmlns=\"http://www.kite9.org/schema/adl\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" id=\"empty\"><allLinks></allLinks></diagram>";
        }

        Object o = loadXML(xml, xmlFile, r);

        if (o instanceof Diagram) {
            return (Diagram) o;
        }

        throw new Kite9ProcessingException("Can't parse xml into diagram: " + xml);
    }
}