/* vim: set ts=2 et sw=2 cindent fo=qroca: */ package com.globant.katari.shindig.domain; import java.util.List; import java.util.LinkedList; import java.io.IOException; import java.io.InputStream; import java.net.URL; import org.apache.commons.lang.Validate; import javax.persistence.Entity; import javax.persistence.Table; import javax.persistence.Column; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; import javax.persistence.Id; import javax.persistence.ElementCollection; import javax.persistence.CollectionTable; import javax.persistence.JoinColumn; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.xpath.XPath; import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathFactory; import javax.xml.xpath.XPathExpressionException; import org.w3c.dom.Document; import org.w3c.dom.NodeList; /** A social application, mainly represented by the gadget xml url. * * This id of this class is used as the open social appId. */ @Entity @Table(name = "applications") public class Application { /** The id of the application, 0 for a newly created one. */ @Id @GeneratedValue(strategy = GenerationType.AUTO) private long id; /** The application (gadget) title. * * This is obtained from the gadget xml. Never null. */ @Column(nullable = false) private String title; /** The optional gadget icon. * * This is obtained from the gadget xml. * * It is null if not known. */ @Column(nullable = true) private String icon; /** The optional gadget description. * * This is obtained from the gadget xml. * * It is null if not known. */ @Column(length = 4096, nullable = true) private String description; /** The optional gadget author. * * This is obtained from the gadget xml. * * It is null if not known. */ @Column(nullable = true) private String author; /** The optional gadget thumbnail. * * This is obtained from the gadget xml. * * It is null if not known. */ @Column(nullable = true) private String thumbnail; /** The url of the gadget xml spec. * * It is never null. */ @Column(nullable = false) private String url; /** The list of views that the gadget supports. * * If the gadget supports the view named 'default', the gadget can be shown * on every gadget group. Otherwise, the gadget must support the same view as * the group. In other words, if the gadget does not support the view of the * group, it cannot be added to that group. A gadget with an empty * supportedViews list cannot be used anywhere. * * It is never null. */ @ElementCollection @CollectionTable(name = "supported_views", joinColumns = @JoinColumn(name = "application_id") ) @Column(name = "view_name", nullable = false) private List<String> supportedViews = new LinkedList<String>(); /** Hibernate constructor. */ Application() { } /** Creates a new application. * * This constructor obtains all the gadget information from the xml obtained * from gadgetUrl. This constructor can throw an exception if the url is not * accessible. * * If the title is not found in the gadget xml spec, it sets it to the gadget * xml url. * * @param gadgetUrl with the url of the gadget xml. It cannot be empty. */ public Application(final String gadgetUrl) { Validate.notEmpty(gadgetUrl, "gadget url cannot be empty"); url = gadgetUrl; InputStream gadgetSpecStream = null; String message = "Error obtaining gadget information."; try { // This should probably go to some utility. gadgetSpecStream = new URL(url).openStream(); DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance(); domFactory.setNamespaceAware(true); DocumentBuilder builder = domFactory.newDocumentBuilder(); Document document = builder.parse(gadgetSpecStream); message = "Error obtaining gadget title"; title = getXpathValue(document, "/Module/ModulePrefs/@title"); if (title.length() == 0) { title = gadgetUrl; } message = "Error obtaining gadget icon"; icon = getXpathValue(document, "/Module/ModulePrefs/icon/text()"); message = "Error obtaining gadget description"; description = getXpathValue(document, "/Module/ModulePrefs/@description"); message = "Error obtaining gadget author"; author = getXpathValue(document, "/Module/ModulePrefs/@author"); message = "Error obtaining gadget thumbnail"; thumbnail = getXpathValue(document, "/Module/ModulePrefs/@thumbnail"); message = "Error obtaining gadget views"; List<String> viewAttributes; viewAttributes = getXpathValues(document, "/Module/Content/@view"); // The view attribute is a comma separated list of views. for (String viewAttribute : viewAttributes) { for (String view : viewAttribute.split(" *, *")) { supportedViews.add(view); } } // Checks for a content with no view. if (xpathMatches(document, "/Module/Content[not(@view)]")) { supportedViews.add("default"); } } catch (Exception e) { throw new RuntimeException(message, e); } finally { if (gadgetSpecStream != null) { try { gadgetSpecStream.close(); } catch (IOException e) { // Ignored, doesn't matter. } } } } /** Retuns the result of evaluating an xpath expression. * * @param document the document to evaluate the expression against. * * @param expression the xpath expression to evaluate. * * @return returns a string with the result of evaluationg the xpath * expression. It never returns null, even if the element is not found. */ private String getXpathValue(final Document document, final String expression) throws XPathExpressionException { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); XPathExpression expr = xpath.compile(expression); Object result = expr.evaluate(document, XPathConstants.STRING); // Tracing through java's code, it appears that result should never be // null: if the evaluated xpath expression did not match a node, evaluate // returns an empty string instead of null. But this is not documented in // the api. Validate.notNull(result, "Should never be null."); return result.toString(); } /** Retuns the result of evaluating an xpath expression that could result in * many elements. * * @param document the document to evaluate the expression against. It cannot * be null. * * @param expression the xpath expression to evaluate. It cannot be null. * * @return returns a list of strings with the result of evaluationg the xpath * expression. It never returns null. */ private List<String> getXpathValues(final Document document, final String expression) throws XPathExpressionException { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); XPathExpression expr = xpath.compile(expression); Object values = expr.evaluate(document, XPathConstants.NODESET); NodeList list = (NodeList) values; List<String> result = new LinkedList<String>(); for (int i = 0; i < list.getLength(); i++) { String nodeValue = list.item(i).getNodeValue(); if (nodeValue != null) { result.add(nodeValue); } } return result; } /** Checks if an xpath expresion finds a node. * * @param document the document to evaluate the expression against. It cannot * be null. * * @param expression the xpath expression to evaluate. It cannot be null. * * @return true if the expression finds a node, false otherwise. */ private boolean xpathMatches(final Document document, final String expression) throws XPathExpressionException { XPathFactory factory = XPathFactory.newInstance(); XPath xpath = factory.newXPath(); XPathExpression expr = xpath.compile(expression); Object values = expr.evaluate(document, XPathConstants.NODESET); NodeList list = (NodeList) values; return list.getLength() != 0; } /** @return long the id of the gadget instance. */ public long getId() { return id; } /** The title of the gadget. * * @return The title of gadget, usually obtained from the gadget xml * specification. It never returns null. */ public String getTitle() { return title; } /** The url of the icon of the gadget. * * @return The icon of the gadget, usually obtained from the gadget xml * specification. It returns null if the icon cannot be determined. */ public String getIcon() { return icon; } /** The description of the gadget. * * @return The description of the gadget, usually obtained from the gadget * xml specification. It returns null if the description cannot be * determined. It can also be an empty string. */ public String getDescription() { return description; } /** The author of the gadget. * * @return The author of the gadget, usually obtained from the gadget xml * specification. It returns null if the author cannot be determined. */ public String getAuthor() { return author; } /** The url of the thumbnail of the gadget. * * @return The thumbnail of the gadget, usually obtained from the gadget xml * specification. It returns null if the thumbnail cannot be determined. */ public String getThumbnail() { return thumbnail; } /** The url of the gadget xml spec. * * @return a string with the location of the gadget xml spec, never null. */ public String getUrl() { return url; } /** Verifies if the gadget supports the provided view. * * If the gadget has a default view, it means that it supports all views. * * @param view the view to check. It cannot be null. * * @return true if the gadget support the view. */ public boolean isViewSupported(final String view) { if (supportedViews.contains(view)) { return true; } return supportedViews.contains("default"); } }