highcharts 1.5 ExportController.java using servlet-api-2.5

Recently we have integrated Moxie Group’s GWT Highchart in our project and ran into the conflict with ExportController’s servlet-api-3.0 multi-part issues.

Like the old GWT does, it uses and includes servlet-api-2.5 or rather jetty 7.  Fortunately our project has already got apache-fileupload in its dependency and we could use the fileitemiterator to read the multi-part items. Changes are made in processrequest(HttpServletRequest request, HttpServletResponse response) and getParameter(HttpServletRequest request, String name) Here comes the modified code:

package com.highcharts.export.controller;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.util.Enumeration;

import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.batik.transcoder.TranscoderException;
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.log4j.Logger;
import org.mortbay.log.Log;

import com.highcharts.export.util.MimeType;
import com.highcharts.export.util.SVGRasterizer;
import com.highcharts.export.util.SVGRasterizerException;

public class ExportController extends HttpServlet
{
private static final long serialVersionUID = 1L;

private static final String REQUEST_METHOD_POST = "POST";

private static final String CONTENT_TYPE_MULTIPART = "multipart/";

private static final String FORBIDDEN_WORD = "<!ENTITY";

protected static Logger logger = Logger.getLogger("exportservlet");

public ExportController()
{
super();
}

public void init()
{
}

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
processrequest(request, response);
}

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{
processrequest(request, response);
}

public void processrequest(HttpServletRequest request, HttpServletResponse response) throws IOException,
ServletException
{
String svg = getParameter(request, "svg");
String filename = getFilename(getParameter(request, "filename"));
Float width = getWidth(getParameter(request, "width"));
MimeType mime = getMime(getParameter(request, "type"));

try
{
boolean multi = isMultipartRequest(request);

if (multi)
{
try
{
final FileItemFactory factory = new DiskFileItemFactory();
final ServletFileUpload upload = new ServletFileUpload(factory);
final FileItemIterator filesIterator = upload.getItemIterator(request);
while (filesIterator.hasNext())
{
final FileItemStream item = filesIterator.next();
final String name = item.getFieldName();
if ("svg".equals(name))
{
svg = new String(toByteArray(item), "UTF-8");
} else if ("filename".equals(name))
{
filename = getFilename(new String(toByteArray(item), "UTF-8"));
} else if ("width".equals(name))
{
width = getWidth(new String(toByteArray(item), "UTF-8"));
} else if ("type".equals(name))
{
mime = getMime(new String(toByteArray(item), "UTF-8"));
}
}
} catch (Exception e)
{
Log.warn("Failed to parse multi-part parameter: " + e);
}
} else
{

}
if (svg == null || svg.isEmpty())
{
throw new ServletException("The required - svg - post parameter is missing");
}
if (svg.indexOf(FORBIDDEN_WORD) > -1 || svg.indexOf(FORBIDDEN_WORD.toLowerCase()) > -1)
{
throw new ServletException("The - svg - post parameter could contain a malicious attack");
}

ExportController.writeFileContentToHttpResponse(svg, filename, width, mime, response);

} catch (IOException ioe)
{
logger.error("Oops something happened here redirect to error-page, " + ioe.getMessage());
sendError(request, response, ioe);
} catch (ServletException sce)
{
logger.error("Oops something happened here redirect to error-page, " + sce.getMessage());
sendError(request, response, sce);
}
}

public static byte[] toByteArray(final FileItemStream item) throws IOException
{
final InputStream in = item.openStream();
final ByteArrayOutputStream out = new ByteArrayOutputStream();
copy(in, out);
final byte[] byteArray = out.toByteArray();
in.close();
return byteArray;
}

public static void copy(final InputStream in, final OutputStream out) throws IOException
{
final byte[] buf = new byte[1024 * 8];
while (true)
{
final int length = in.read(buf);
if (length == -1)
{
break;
}
out.write(buf, 0, length);
}
out.flush();
}

/*
* Util methods
*/

public static void writeFileContentToHttpResponse(String svg, String filename, Float width, MimeType mime,
HttpServletResponse response) throws IOException, ServletException
{

ByteArrayOutputStream stream = new ByteArrayOutputStream();

if (!MimeType.SVG.equals(mime))
{
try
{
stream = SVGRasterizer.getInstance().transcode(stream, svg, mime, width);
} catch (SVGRasterizerException sre)
{
logger.error("Error while transcoding svg file to an image", sre);
stream.close();
throw new ServletException("Error while transcoding svg file to an image");
} catch (TranscoderException te)
{
logger.error("Error while transcoding svg file to an image", te);
stream.close();
throw new ServletException("Error while transcoding svg file to an image");
}
} else
{
stream.write(svg.getBytes());
}

// prepare response
response.reset();
response.setContentLength(stream.size());
response.setCharacterEncoding("utf-8");
response.setHeader("Content-disposition", "attachment; filename=" + filename + "." + mime.name().toLowerCase());
response.setHeader("Content-type", mime.getType());
// set encoding before writing to out, check this
ServletOutputStream out = response.getOutputStream();
// Send content to Browser
out.write(stream.toByteArray());
out.flush();
}

public static final boolean isMultipartRequest(HttpServletRequest request)
{
// inspired by org.apache.commons.fileupload
logger.debug("content-type " + request.getContentType());
return REQUEST_METHOD_POST.equalsIgnoreCase(request.getMethod()) && request.getContentType() != null
&& request.getContentType().toLowerCase().startsWith(CONTENT_TYPE_MULTIPART);
}

private String getParameter(HttpServletRequest request, String name) throws IOException, ServletException
{

return request.getParameter(name);

}

private String getFilename(String name)
{
return (name != null) ? name : "chart";
}

private static Float getWidth(String width)
{
if (width != null && !width.isEmpty())
{
Float parsedWidth = Float.valueOf(width);
if (parsedWidth.compareTo(0.0F) > 0)
{
return parsedWidth;
}
}
return null;
}

private static MimeType getMime(String mime)
{
MimeType type = MimeType.get(mime);
if (type != null)
{
return type;
}
return MimeType.PNG;
}

protected void sendError(HttpServletRequest request, HttpServletResponse response, Throwable ex) throws IOException,
ServletException
{
String headers = null;
String htmlHeader =
"<HTML><HEAD><TITLE>Highcharts Export error</TITLE><style type=\"text/css\">"
+ "body {font-family: \"Trebuchet MS\", Arial, Helvetica, sans-serif;} table {border-collapse: collapse;}th {background-color:green;color:white;} td, th {border: 1px solid #98BF21;} </style></HEAD><BODY>";
String htmlFooter = "</BODY></HTML>";

response.setContentType("text/html");

PrintWriter out = response.getWriter();
Enumeration<String> e = request.getHeaderNames();
String svg = this.getParameter(request, "svg");

out.println(htmlHeader);
out.println("<h3>Error while converting SVG</h3>");
out.println("<h4>Error message</h4>");
out.println("<p>" + ex.getMessage() + "</p>");
out.println("<h4>Debug steps</h4><ol>"
+ "<li>Copy the SVG:<br/><textarea cols=100 rows=5>"
+ svg
+ "</textarea></li>"
+ "<li>Go to <a href='http://validator.w3.org/#validate_by_input' target='_blank'>validator.w3.org/#validate_by_input</a></li>"
+ "<li>Paste the SVG</li>" + "<li>Click More Options and select SVG 1.1 for Use Doctype</li>"
+ "<li>Click the Check button</li></ol>");

out.println("<h4>Request Headers</h4>");
out.println("<TABLE>");
out.println("<tr><th> Header </th><th> Value </th>");

while (e.hasMoreElements())
{
headers = (String) e.nextElement();
if (headers != null)
{
out.println("<tr><td align=center><b>" + headers + "</td>");
out.println("<td align=center>" + request.getHeader(headers) + "</td></tr>");
}
}
out.println("</TABLE><BR>");
out.println(htmlFooter);

}
}
<pre>
Advertisements

travelmix – Ajax/JSON-RPC-based realtime on map public transport routing using EFA

应用EFA实现在线即时地图导航

(EFA是大部分德国公共交通公司使用的导航时刻表系统)

地图使用cloudemade.(采用openstreetmap(OSM)地图)

Ajax/JSON-RPC-based realtime on map public transport routing using EFA

例图:

1. 德国曼海姆,海德堡,卡鲁(Germany – Mannheim, Heidelberg, Karlsruhe)

2. 德国弗莱堡(Germany – Freiburg)

3. 德国曼海姆市中心 (Germany – Mannheim city)

4. 奥地利Linz (Austria – Linz)

5. 英国伦敦 (UK – London)

6. 瑞士苏黎世 (Switzerland – Zurich)

Demo安装&使用方法

打开travelmix-service.exe (或java -jar travalmix-service.jar)

用浏览器打开travelmix-app里的index.html文件

注:travelmix-service初始端口为9080,电脑必须连接网络

下载(download)

travelmix后台服务Windows: http://travelmix.googlecode.com/files/travelmix-service-1.0.exe

travelmix后台服务Java Jar(与windows版相同): http://travelmix.googlecode.com/files/travelmix-service-1.0.jar

travelmix前端程序:http://travelmix.googlecode.com/files/travelmix-app-1.0.zip

结构演示

e下 1.7 (exia) – e-hentai.org批量下载器

e下1.7下载:e-hentai.org批量下载工具 (e-hentai.org galleries downloader)

exia 1.7版本更新(2012-10-11):

1. 更正漫画名称乱码 (更好的支持HTML字符)
2. 增加下载稳定度 (续下漫画册,错误自动等待,重新下载等,更正下载地址含特殊符号的错误)
3. 探测并重新下载错误图片

 

e-hentai.org是现今最大的”假”p2p漫画分享网站,一个很好的同人网站。。上面的漫画无奇不有,除了最多的英文日文外还有非常多的中文漫画册。值得推荐。

  • 支持代理设置
  • 支持Cookie, User Agent等设置
  • 支持批量下载搜索结果
  • 智能执行错误分析
  • 注:由于服务器下载速度受限制。如需要下载多个文档,请同时开启多个”e下”程序然后设置不同代理再下载。(未来的版本将自动更换代理。)

下载:

* Windows: [http://exia.googlecode.com/files/exia-1.7.exe]

* Linux, Mac etc.: [http://exia.googlecode.com/files/exia-1.7.jar]

使用方法:

例:

1. 关键词搜索批量下载:如”chinese”

–> 下载所有包含此关键词的漫画册(>400部)

2. 通过输入漫画册网址下载整部漫画册:http://g.e-hentai.org/g/494953/7c3ec35c08/

–> 下载整本漫画

3. 输入漫画图网址下载:http://g.e-hentai.org/s/14b9c859ed/493328-1

–> 下载从本页开始所有的漫画

源文件: https://code.google.com/p/exia/source/browse/#git%2Fsrc%2Fcn%2Fkk%2Fexia

从jar里提取资源

使用Java时经常会碰到需要从jar文件里提取资源的情况。Java里提供了getClass().getResource()和getResourceAsStream()这两个函数来读取jar里的文件。但有时候要是不是直接使用该资源,而是需要把资源文件名称位置传递给第三方的时候就不怎么方便了。下面这个帮助函数把资源文件先提取出来储存在一个临时文件里用来继续加工。getResourceFile返回的File为该资源的临时文件:

如从jar里提取根目录下的cfg.properties文件:File cfgFile = getResourceFile(“/cfg.properties”);

public final File getResourceFile(String resourceFile) {
try {
File tmpFile = File.createTempFile("resource", null);
writeStream(getClass().getResourceAsStream(resourceFile), tmpFile);
return tmpFile;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}

private static final void writeStream(InputStream in, File file) throws IOException {
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
int len;
byte[] buffer = new byte[1024 * 8];
while ((len = in.read(buffer)) &gt; 0) {
out.write(buffer, 0, len);
}
out.close();
}

boolean array / BitSet与String之间的转换

在安卓里,shared preferences只能储存简单类型(如int, boolean, string等)。其它的类型要么需要储存到数据库或其它缓存里,要么就要先转换成shared preferences所支持的类型。

这里我们将boolean array或BitSet储存在为String。然后在转换回来。

    public final static byte[] toByteArray(final BitSet bs) {
        final int bitsLength = bs.length();
        final int bytesLength = (bitsLength + 7) / 8;
        final byte[] bytes = new byte[bytesLength];
        int byteIdx;
        int bitIdx;
        for (int idx = bs.nextSetBit(0); idx >= 0; idx = bs.nextSetBit(idx + 1)) {
            byteIdx = bytesLength - idx / 8 - 1;
            bitIdx = idx % 8;
            bytes[byteIdx] |= 1 << bitIdx;
        }
        return bytes;
    }

    public final static BitSet fromByteArray(final byte[] bytes) {
        final int bitsLength = bytes.length * 8;
        final BitSet bs = new BitSet(bitsLength);
        for (int i = 0; i < bitsLength; i++) {
            if (((bytes[bytes.length - i / 8 - 1] >> (i % 8)) & 1) == 1) {
                bs.set(i);
            }
        }
        return bs;
    }

    public final static String toString(final BitSet bs) {
        return new String(toByteArray(bs), Charset.forName("ISO-8859-1"));
    }

    public final static BitSet fromString(final String data) {
        return fromByteArray(data.getBytes(Charset.forName("ISO-8859-1")));
    }

在Activity里面就很方便了:

    @Override
    protected void onPause() {
        super.onPause();
        getPreferences(Activity.MODE_PRIVATE).edit().putString("BITS", toString(this.bs)).commit();
    }

    @Override
    protected void onResume() {
        super.onResume();
        this.bs = fromString(getPreferences(Activity.MODE_PRIVATE).getString("BITS", ""));
    }

测试:

    static public void main(String[] args) throws IOException, AWTException, SecurityException, NoSuchFieldException,
            IllegalArgumentException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
        BitSet bs = new BitSet();
        for (int i = 0; i < 1000; i++) {
            if (Math.random() < 0.5) {
                bs.set(i);
            }
        }

        String storedData = toString(bs);
        BitSet resumedData = fromString(storedData);
        System.out.println(resumedData.equals(bs));
    }

把boolean[]转换成BitSet:(boolean[] length必须另外存储进shared preferences里)
(注:推荐使用BitSet。在java里每个boolean都占用一个byte的位置。这样当boolean[]很大时,就很浪费内存了。在手机,平板电脑上内存可是很珍贵的。):

    public static final boolean[] fromBitSet(final BitSet bs, final int length) {
        final boolean[] b= new boolean[length];
        for (int idx = bs.nextSetBit(0); idx >= 0; idx = bs.nextSetBit(idx + 1)) {
            b[idx] = true;
        }
        return b;
    }

    public static final BitSet toBitSet(final boolean[] b) {
        final int l = b.length;
        final BitSet bs = new BitSet(l);
        for (int i = 0; i < l; i++) {
            if (b[i]) {
                bs.set(i);
            }
        }
        return bs;
    }

QQ火拼俄罗斯方块外挂(QQTetris bot with java source)

下载

Windows:http://xytetrisbot.googlecode.com/files/xytetris-1.3-fast.exe

快捷键dll(32bit):http://xytetrisbot.googlecode.com/files/JIntellitype32.dll

需改名为JIntellitype.dll并放在与xytetris.exe同个文件夹里。

快捷键dll(64bit):http://xytetrisbot.googlecode.com/files/JIntellitype.dll

玩了几圈QQ火拼俄罗斯方块。一时来兴编了个机器人程序。算法比较死板,算得分,然后采用得分最高的步骤。

运算得分的计数值经过初步预算(simulator)。每种方案(normal,long life,等)平均算了1天左右。所以还不是最理想的。如有兴趣可以试着修改一下。

游戏块,道具等是通过截屏分析得出的。我没有花更多的时间分析QQTetris的内存结构或网络流。相信通过读出内存可以更快的预知数据的。(QQTetris像是会预读20左右的游戏块和道具在内存里。)

xytetrisbot xytetris

 

源代码:https://code.google.com/p/xytetrisbot/

Java:在Windows里实现快速截图

在Java里使用java.awt.Robot来屏幕截图非常的慢。直接使用RobotPeer或者native JNI的函数能数倍的提高速度,实现即时截图。

  • RobotPeer可以通过Toolkit直接生成。
  • WRobotPeer里“private native getRGBPixels”的应用是通过reflection实现的。

下面是测试结果:

// 使用Robot
Robot.getPixelColor(1024 * 768): 3850 ms
Robot.createScreenCapture(1024 * 768): 19 ms

// 使用RobotPeer
RobotPeer.getRGBPixel(1024 * 768): 3686 ms
RobotPeer.getRGBPixels(1024 * 768): 10 ms

// 使用RobotPeer.getRGBPixels(int x, int y, int w, int h, int[] buffer) (native)
RobotPeer.getRGBPixels(1024 * 768, buffer): 7 ms

测试代码:

//
// 使用Robot
//
final Robot robot = new Robot();
long start = System.currentTimeMillis();
int x = 0;
int y = 0;
for (int i = 0; i &lt; 1024 * 768; i++) {
robot.getPixelColor(x++, y);
if (x == 1024) {
y++;
}
}
System.out.println("Robot.getPixelColor(1024 * 768): " + (System.currentTimeMillis() - start) + " ms");
start = System.currentTimeMillis();
robot.createScreenCapture(new Rectangle(0, 0, 1024, 768));
System.out.println("Robot.createScreenCapture(1024 * 768): " + (System.currentTimeMillis() - start) + " ms");</code>

//
// 使用RobotPeer
//
final RobotPeer peer = ((ComponentFactory) Toolkit.getDefaultToolkit()).createRobot(null, null);
start = System.currentTimeMillis();
for (int i = 0; i &lt; 1024 * 768; i++) {
peer.getRGBPixel(x++, y);
if (x == 1024) {
y++;
}
}
System.out.println("RobotPeer.getRGBPixel(1024 * 768): " + (System.currentTimeMillis() - start) + " ms");
start = System.currentTimeMillis();
peer.getRGBPixels(new Rectangle(0, 0, 1024, 768));
System.out.println("RobotPeer.getRGBPixels(1024 * 768): " + (System.currentTimeMillis() - start) + " ms");

//
// 使用RobotPeer.getRGBPixels(int x, int y, int w, int h, int[] buffer) (native)
//
final Class[] params = new Class[] { int.class, int.class, int.class, int.class, int[].class };
final Method getRGBPixelsMethod = peer.getClass().getDeclaredMethod("getRGBPixels", params);
getRGBPixelsMethod.setAccessible(true);
final int[] buffer = new int[1024 * 768];
start = System.currentTimeMillis();
getRGBPixelsMethod.invoke(peer, 0, 0, 1024, 768, buffer);
System.out.println("RobotPeer.getRGBPixels(1024 * 768, buffer): " + (System.currentTimeMillis() - start) + " ms");

如果是纯粹想在自己的电脑上提升速度。也不妨试一下binary weaving。就是覆盖rt.jar里的WRobotPeer.java文件。

测试结果:

Robot.getPixelColor(1024 * 768): 3446 ms
Robot.createScreenCapture(1024 * 768): 23 ms
RobotPeer.getRGBPixel(1024 * 768): 3387 ms
RobotPeer.getRGBPixels(1024 * 768): 10 ms
RobotPeer.getRGBPixels(1024 * 768, buffer): 8 ms
RobotPeer.getRGBPixels(1024 * 768, buffer) direct: 7 ms

WRobotPeer.java文件:

package sun.awt.windows;

import java.awt.Rectangle;
import java.awt.peer.RobotPeer;

public class WRobotPeer extends WObjectPeer
        implements RobotPeer {
    public WRobotPeer() {
        create();
    }
    
    private synchronized native void _dispose();

    protected void disposeImpl() {
        _dispose();
    }

    public native void create();

    public native void mouseMoveImpl(int paramInt1, int paramInt2);

    public void mouseMove(int paramInt1, int paramInt2) {
        mouseMoveImpl(paramInt1, paramInt2);
    }

    public native void mousePress(int paramInt);

    public native void mouseRelease(int paramInt);

    public native void mouseWheel(int paramInt);

    public native void keyPress(int paramInt);

    public native void keyRelease(int paramInt);

    public int getRGBPixel(int paramInt1, int paramInt2) {
        return getRGBPixelImpl(paramInt1, paramInt2);
    }

    public native int getRGBPixelImpl(int paramInt1, int paramInt2);

    public int[] getRGBPixels(Rectangle paramRectangle) {
        int[] arrayOfInt = new int[paramRectangle.width * paramRectangle.height];
        getRGBPixels(paramRectangle.x, paramRectangle.y, paramRectangle.width, paramRectangle.height, arrayOfInt);
        return arrayOfInt;
    }

    public native void getRGBPixels(int x, int y, int w, int h, int[] buffer);
}