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

org.ow2.opensuit.xml.chart.BaseChart Maven / Gradle / Ivy

There is a newer version: 1.0.3
Show newest version
/**
 * Open SUIT - Simple User Interface Toolkit
 * 
 * Copyright (c) 2009 EBM Websourcing, http://www.ebmwebsourcing.com/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package org.ow2.opensuit.xml.chart;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.lang.reflect.Type;
import java.util.HashMap;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jfree.chart.ChartRenderingInfo;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.encoders.EncoderUtil;
import org.jfree.chart.encoders.ImageFormat;
import org.jfree.chart.imagemap.ImageMapUtilities;
import org.jfree.chart.plot.Plot;
import org.jfree.ui.RectangleInsets;
import org.ow2.opensuit.core.session.OpenSuitSession;
import org.ow2.opensuit.core.session.PageContext;
import org.ow2.opensuit.core.util.HtmlUtils;
import org.ow2.opensuit.core.util.UrlBuilder;
import org.ow2.opensuit.xml.base.Application;
import org.ow2.opensuit.xml.base.binding.Do;
import org.ow2.opensuit.xml.base.binding.Expression;
import org.ow2.opensuit.xml.base.html.IFrame;
import org.ow2.opensuit.xml.base.html.IView;
import org.ow2.opensuit.xml.base.page.IPage;
import org.ow2.opensuit.xml.interfaces.IBeanProvider;
import org.ow2.opensuit.xmlmap.annotations.XmlAncestor;
import org.ow2.opensuit.xmlmap.annotations.XmlAttribute;
import org.ow2.opensuit.xmlmap.annotations.XmlChild;
import org.ow2.opensuit.xmlmap.annotations.XmlChildren;
import org.ow2.opensuit.xmlmap.annotations.XmlDoc;
import org.ow2.opensuit.xmlmap.annotations.XmlElement;
import org.ow2.opensuit.xmlmap.interfaces.IInitializable;
import org.ow2.opensuit.xmlmap.interfaces.IInitializationSupport;
import org.ow2.opensuit.xmlmap.interfaces.IInstantiationContext;

@XmlDoc("This is the base abstract component that enables JFreeChart integration in open SUIT.")
@XmlElement
public abstract class BaseChart implements IView, IInitializable, IBeanProvider
{
	protected Log logger = LogFactory.getLog(this.getClass());
	
	@XmlAncestor
	protected Application root;
	
	@XmlAncestor
	private IBeanProvider parentProvider;
	
	@XmlDoc("Image width (in pixels).")
	@XmlAttribute(name="Width", required=true)
	private int width;
	
	@XmlDoc("Image height (in pixels).")
	@XmlAttribute(name="Height", required=true)
	private int height;
	
	@XmlDoc("Determines whether the char displays the legend." +
			"Default: true.")
	@XmlAttribute(name="ShowLegend", required=false)
	private boolean showLegend= true;
	
	
	@XmlDoc("Enables chart auto-reload.
" + "This expression returns the auto-reload time interval (in milliseconds).
" + "A null value (0) disables the auto-reload behavior.
" + "Note: with auto-reload behavior, the 'ChartTime' expression should always return System.currentTimeMillis()") @XmlChild(name="AutoReloadInterval", required=false) protected Expression autoReloadInterval; @XmlDoc("The chart time.
" + "This expression returns a timestamp (long) representing the latest time when data " + "required to produce the chart was computed.") @XmlChild(name="ChartTime") protected Expression chartTime; @XmlDoc("The chart title.
" + "This will be displayed inside the chart image, and will also be used as the image filename.") @XmlChild(name="Title") protected Expression title; @XmlDoc("Some actions to perform at prerender time.
" + "This is where you may use the $control object to invalidate() the cached chart.") @XmlChildren(name="OnPreRender", minOccurs=0) private Do[] onPreRender; // --- [+] IIdentifiable implementation @XmlAncestor private IPage page; @XmlAncestor private IFrame frame; public String getPathID() { if(page != null) return page.getPath()+"/Chart"; else return "/"+frame.getName()+"/Chart"; } // --- [-] IIdentifiable implementation public void initialize(IInitializationSupport initSupport, IInstantiationContext instContext) { root.registerRequestHandler(getPathID(), this); if(chartTime != null && initSupport.initialize(chartTime)) { if(!chartTime.isConvertible(Long.class)) initSupport.addValidationMessage(this, "ChartDate", IInitializationSupport.ERROR, "Expression 'ChartDate' must return a long (timestamp)."); } if(autoReloadInterval != null && initSupport.initialize(autoReloadInterval)) { if(!autoReloadInterval.isConvertible(Integer.class)) initSupport.addValidationMessage(this, "AutoReloadInterval", IInitializationSupport.ERROR, "Expression 'AutoReloadInterval' must return a number."); } } protected String getTitle(HttpServletRequest request) { try { return title.invoke(request, String.class); } catch (Exception e) { return "chart"; } } public void preRender(HttpServletRequest request) throws Exception { if(onPreRender != null) { for(Do d : onPreRender) d.invoke(request); } } public void render(HttpServletRequest request, HttpServletResponse response) throws Exception { int autoReloadInterval = 0; if(this.autoReloadInterval != null) autoReloadInterval = this.autoReloadInterval.invoke(request, Integer.class); String titleStr = title.invoke(request, String.class); String fileNameNoExt = titleStr;// TODO: encode ? int w = width; int h = height; UrlBuilder url = root.createServiceUrl(request, this, "serveImage", fileNameNoExt+".png"); url.setParameter("w", String.valueOf(w)); url.setParameter("h", String.valueOf(h)); url.setParameter("title", titleStr); /* No: not supported UrlBuilder urlLow = root.createServiceUrl(request, this, "serveWaiting", fileNameNoExt+".png"); urlLow.setParameter("w", String.valueOf(w)); urlLow.setParameter("h", String.valueOf(h)); */ // --- make chart data and compute image CachedChartData data = getData(request, titleStr); // --- in order not to stuck the page rendering (but preferably the chart image download), // --- we just compute the image map here, and not the image (much faster!) // ChartRenderingInfo info = new ChartRenderingInfo(); // data.newImage = makeImage(data.chart, w, h, info); ChartRenderingInfo info = computeInfo(data.chart, w, h); String imgName = HtmlUtils.formatId(fileNameNoExt); String mapName = imgName+"_map"; // --- Render html PrintWriter writer = response.getWriter(); if(autoReloadInterval > 0) { // --- include Common.js (for newXHR()) HtmlUtils.includeBaseJavaScript(request, response, "Common.js"); // --- include Chart.js HtmlUtils.includeJavaScript(request, response, "org/ow2/opensuit/xml/chart/Chart.js"); UrlBuilder urlMap = root.createServiceUrl(request, this, "reloadMap", mapName); urlMap.setParameter("w", String.valueOf(w)); urlMap.setParameter("h", String.valueOf(h)); urlMap.setParameter("name", mapName); urlMap.setParameter("title", titleStr); // --- write script that sets the interval writer.println(""); } // --- render image and map // ImageMapUtilities.writeImageMap(writer, mapName, info, new StandardToolTipTagFragmentGenerator(), new URLFragmentGenerator()); ImageMapUtilities.writeImageMap(writer, mapName, info); // --- render image writer.print("");
		writer.print(HtmlUtils.encode2HTML(titleStr));
		writer.print(""); } /** * This method computes the Chart Rendering Info, required to produce the * image map. * This method is much faster than making the image, as it relies on an * inoperant Graphics object. * @param chart the chart model * @param w image width * @param h image height * @return */ private ChartRenderingInfo computeInfo(JFreeChart chart, int w, int h) { long start = System.currentTimeMillis(); try { ChartRenderingInfo info = new ChartRenderingInfo(); BufferedImage img = new BufferedImage(1,1,BufferedImage.TYPE_INT_ARGB); Graphics2D g2 = new InoperantGraphics(img.createGraphics()); chart.draw(g2, new Rectangle2D.Double(0, 0, w, h), info); return info; } finally { long end = System.currentTimeMillis(); logger.debug("Chart info ("+w+"x"+h+") computed in "+(end-start)+" ms"); } } /** * This method processes the chart image with given with and height * @param chart the chart model * @param w image width * @param h image height * @param info the chart rendering info to populate (may be null) * @return */ private BufferedImage makeImage(JFreeChart chart, int w, int h, ChartRenderingInfo info) { long start = System.currentTimeMillis(); try { return chart.createBufferedImage(w, h, info); } finally { long end = System.currentTimeMillis(); logger.debug("Chart image ("+w+"x"+h+") computed in "+(end-start)+" ms"); } } private JFreeChart makeChart(HttpServletRequest request, String title) throws Exception { logger.debug("makeChart('"+title+"')"); Plot plot = makePlot(request); plot.setInsets(new RectangleInsets(0.0, 5.0, 5.0, 5.0)); // --- now we know for sure we have to recompute the chart JFreeChart chart = new JFreeChart(title, JFreeChart.DEFAULT_TITLE_FONT, plot, showLegend); // TODO? ChartFactory.getChartTheme().apply(chart); return chart; } private CachedChartData getData(HttpServletRequest request, String title) throws Exception { // --- retrieve from cache CachedChartData data = getCache(request); if(data != null && data.newImage != null) { // --- this is the image request right after having rendered the HTML page // --- do not recompute in any case logger.debug("getData(): a new image is waiting to be served: use cached image."); return data; } long time = chartTime.invoke(request, Long.class); if(data != null && data.time >= time) { logger.debug("getData(): cached chart still valid. Reuse cache."); return data; } // --- need to compute logger.debug("getData(): compute chart."); Plot plot = makePlot(request); plot.setInsets(new RectangleInsets(0.0, 5.0, 5.0, 5.0)); // --- now we know for sure we have to recompute the chart data = new CachedChartData(); data.chart = makeChart(request, title); data.time = time; // --- store in cache and return setCache(request, data); return data; } public void reloadMap(HttpServletRequest request, HttpServletResponse response) throws Exception { int w = Integer.parseInt(request.getParameter("w")); int h = Integer.parseInt(request.getParameter("h")); String mapName = request.getParameter("name"); String title = request.getParameter("title"); // --- clear cached chart and reload removeCache(request); CachedChartData data = getData(request, title); // --- here we know this is an Ajax request, so we can afford computing the real image // --- as it's asynchronous, and the browser will request for it right away // ChartRenderingInfo info = computeInfo(data.chart, w, h); ChartRenderingInfo info = new ChartRenderingInfo(); data.newImage = makeImage(data.chart, w, h, info); OpenSuitSession session = OpenSuitSession.getSession(request); response.setContentType("text/html;charset="+session.getLocaleConfig().getCharSet()); // iResponse.setHeader("Cache-Control", "no-store"); //HTTP 1.1 response.setHeader("Cache-Control", "no-cache"); //HTTP 1.1 response.setHeader("Pragma", "no-cache"); //HTTP 1.0 response.setDateHeader("Expires", 0); //prevents caching at the proxy server PrintWriter writer = response.getWriter(); // --- write map ImageMapUtilities.writeImageMap(writer, mapName, info); writer.flush(); writer.close(); } public void serveWaiting(HttpServletRequest request, HttpServletResponse response) throws Exception { long ifModifiedSince = request.getDateHeader("If-Modified-Since"); if(ifModifiedSince > 0) { // --- 304: not modified response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } // --- compute image int w = Integer.parseInt(request.getParameter("w")); int h = Integer.parseInt(request.getParameter("h")); // --- create and draw waiting image BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_3BYTE_BGR); Graphics g = img.getGraphics(); // TODO (houglass?) g.setColor(Color.red); g.fillRect(0, 0, w, h); long now = System.currentTimeMillis(); // --- force Pragma and Cache-Control headers // if servlet is in a security-constraint (web.xml), the servlet container // may set Pragma: no-cache and CacheControl: no-cache response.setHeader("Pragma", ""); response.setHeader("Cache-Control", "public"); response.addHeader("Cache-Control", "max-age=86400");//one day // --- set Last-Modified, Date and Expires response.setDateHeader("Last-Modified", now); response.setDateHeader("Date", now); // not needed with max-age response.setDateHeader("Expires", now + (EXPIRATION_DELAY_SEC * 1000)); // 1 day /* // --- disable cache response.setHeader("Cache-Control", "no-cache"); //HTTP 1.1 response.setHeader("Pragma", "no-cache"); //HTTP 1.0 response.setDateHeader("Expires", 0); //prevents caching at the proxy server */ // --- set mime type response.setContentType("image/png"); OutputStream output = response.getOutputStream(); // --- write image bytes EncoderUtil.writeBufferedImage(img, ImageFormat.PNG, output); output.flush(); output.close(); } public void serveImage(HttpServletRequest request, HttpServletResponse response) throws Exception { String titleStr = request.getParameter("title"); int w = Integer.parseInt(request.getParameter("w")); int h = Integer.parseInt(request.getParameter("h")); CachedChartData data = getData(request, titleStr); // --- retrieve possible cached image (computed when rendering the image map) BufferedImage cachedImage = data.newImage; // --- clear cached image data.newImage = null; // --- manage cache long lastModified = data.time; long ifModifiedSince = request.getDateHeader("If-Modified-Since"); // --- date headers don't contain milliseconds: test the seconds value if((lastModified / 1000L) <= (ifModifiedSince / 1000L)) { // --- 304: not modified logger.debug("serveImage(): client cache still valid: not modified (304)"); response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); return; } if(ifModifiedSince == 0) logger.debug("serveImage(): no client cache: serve."); else logger.debug("serveImage(): client cache obsolete: serve."); // --- set cache info long now = System.currentTimeMillis(); // --- force Pragma and Cache-Control headers // if servlet is in a security-constraint (web.xml), the servlet container // may set Pragma: no-cache and CacheControl: no-cache response.setHeader("Pragma", ""); response.setHeader("Cache-Control", "public"); response.addHeader("Cache-Control", "must-revalidate"); response.addHeader("Cache-Control", "max-age=0"); // --- set Last-Modified, Date and Expires response.setDateHeader("Last-Modified", lastModified); response.setDateHeader("Date", now); // not needed with max-age response.setDateHeader("Expires", now + (EXPIRATION_DELAY_SEC * 1000)); // 1 day /* // --- disable cache response.setHeader("Cache-Control", "no-cache"); //HTTP 1.1 response.setHeader("Pragma", "no-cache"); //HTTP 1.0 response.setDateHeader("Expires", 0); //prevents caching at the proxy server */ // --- set mime type response.setContentType("image/png"); OutputStream output = response.getOutputStream(); if(cachedImage == null) cachedImage = makeImage(data.chart, w, h, null); // --- write image bytes EncoderUtil.writeBufferedImage(cachedImage, ImageFormat.PNG, output); output.flush(); output.close(); } public abstract Plot makePlot(HttpServletRequest request) throws Exception; // ======================================================================= // === Cache Management // ======================================================================= private static class CachedChartData implements Serializable { private static final long serialVersionUID = 1L; public BufferedImage newImage; public JFreeChart chart; public long time; } private CachedChartData getCache(HttpServletRequest request) { OpenSuitSession session = OpenSuitSession.getSession(request); PageContext ctx = session.getCurrentPageContext(); HashMap obj2Cache = (HashMap)ctx.getAttribute("_chartCache_"); if(obj2Cache == null) return null; return obj2Cache.get(this); } private void setCache(HttpServletRequest request, CachedChartData cache) { OpenSuitSession session = OpenSuitSession.getSession(request); PageContext ctx = session.getCurrentPageContext(); HashMap obj2Cache = (HashMap)ctx.getAttribute("_chartCache_"); if(obj2Cache == null) { obj2Cache = new HashMap(1); ctx.setAttribute("_chartCache_", obj2Cache); } obj2Cache.put(this, cache); } private void removeCache(HttpServletRequest request) { OpenSuitSession session = OpenSuitSession.getSession(request); PageContext ctx = session.getCurrentPageContext(); HashMap obj2Cache = (HashMap)ctx.getAttribute("_chartCache_"); if(obj2Cache == null) return; obj2Cache.remove(this); } // ================================================================================= // === IBeanProvider // ================================================================================= public Class getBeanType(String iName) throws UnresolvedBeanError { if(parentProvider != null) return parentProvider.getBeanType(iName); return null; } public Type getBeanGenericType(String iName) throws UnresolvedBeanError { if(parentProvider != null) return parentProvider.getBeanGenericType(iName); return null; } public Object getBeanValue(HttpServletRequest iRequest, String iName) throws Exception { if(parentProvider != null) return parentProvider.getBeanValue(iRequest, iName); return null; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy