EmbedServlet2.java 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466
  1. /**
  2. * $Id: EmbedServlet.java,v 1.18 2014/01/31 22:27:07 gaudenz Exp $
  3. * Copyright (c) 2011-2012, JGraph Ltd
  4. *
  5. * TODO
  6. *
  7. * We could split the static part and the stencils into two separate requests
  8. * in order for multiple graphs in the pages to not load the static part
  9. * multiple times. This is only relevant if the embed arguments are different,
  10. * in which case there is a problem with parsin the graph model too soon, ie.
  11. * before certain stencils become available.
  12. *
  13. * Easier solution is for the user to move the embed script to after the last
  14. * graph in the page and merge the stencil arguments.
  15. *
  16. * Note: The static part is roundly 105K, the stencils are much smaller in size.
  17. * This means if the embed function is widely used, it will make sense to factor
  18. * out the static part because only stencils will change between pages.
  19. */
  20. package com.mxgraph.online;
  21. import java.io.ByteArrayOutputStream;
  22. import java.io.IOException;
  23. import java.io.InputStream;
  24. import java.io.OutputStream;
  25. import java.io.PrintWriter;
  26. import java.net.URL;
  27. import java.net.URLConnection;
  28. import java.text.DateFormat;
  29. import java.text.SimpleDateFormat;
  30. import java.util.Date;
  31. import java.util.HashMap;
  32. import java.util.HashSet;
  33. import java.util.Locale;
  34. import javax.servlet.ServletException;
  35. import javax.servlet.http.HttpServlet;
  36. import javax.servlet.http.HttpServletRequest;
  37. import javax.servlet.http.HttpServletResponse;
  38. import org.apache.commons.text.StringEscapeUtils;
  39. import com.google.appengine.api.utils.SystemProperty;
  40. /**
  41. * Servlet implementation class OpenServlet
  42. */
  43. public class EmbedServlet2 extends HttpServlet
  44. {
  45. /**
  46. *
  47. */
  48. private static final long serialVersionUID = 1L;
  49. /**
  50. *
  51. */
  52. protected static String SHAPES_PATH = "/shapes";
  53. /**
  54. *
  55. */
  56. protected static String STENCIL_PATH = "/stencils";
  57. /**
  58. *
  59. */
  60. protected static String lastModified = null;
  61. /**
  62. *
  63. */
  64. protected HashMap<String, String> stencils = new HashMap<String, String>();
  65. /**
  66. *
  67. */
  68. protected HashMap<String, String[]> libraries = new HashMap<String, String[]>();
  69. /**
  70. * @see HttpServlet#HttpServlet()
  71. */
  72. public EmbedServlet2()
  73. {
  74. if (lastModified == null)
  75. {
  76. // Uses deployment date as lastModified header
  77. String applicationVersion = SystemProperty.applicationVersion.get();
  78. Date uploadDate = new Date(Long
  79. .parseLong(applicationVersion
  80. .substring(applicationVersion.lastIndexOf(".") + 1))
  81. / (2 << 27) * 1000);
  82. DateFormat httpDateFormat = new SimpleDateFormat(
  83. "EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
  84. lastModified = httpDateFormat.format(uploadDate);
  85. }
  86. initLibraries(libraries);
  87. }
  88. /**
  89. * Sets up collection of stencils
  90. */
  91. public static void initLibraries(HashMap<String, String[]> libraries)
  92. {
  93. libraries.put("mockup",
  94. new String[] { SHAPES_PATH + "/mockup/mxMockupButtons.js" });
  95. libraries.put("arrows2", new String[] { SHAPES_PATH + "/mxArrows.js" });
  96. libraries.put("bpmn",
  97. new String[] { SHAPES_PATH + "/bpmn/mxBpmnShape2.js",
  98. STENCIL_PATH + "/bpmn.xml" });
  99. libraries.put("er", new String[] { SHAPES_PATH + "/er/mxER.js" });
  100. libraries.put("ios",
  101. new String[] { SHAPES_PATH + "/mockup/mxMockupiOS.js" });
  102. libraries.put("rackGeneral",
  103. new String[] { SHAPES_PATH + "/rack/mxRack.js",
  104. STENCIL_PATH + "/rack/general.xml" });
  105. libraries.put("rackF5", new String[] { STENCIL_PATH + "/rack/f5.xml" });
  106. libraries.put("lean_mapping",
  107. new String[] { SHAPES_PATH + "/mxLeanMap.js",
  108. STENCIL_PATH + "/lean_mapping.xml" });
  109. libraries.put("basic", new String[] { SHAPES_PATH + "/mxBasic.js",
  110. STENCIL_PATH + "/basic.xml" });
  111. libraries.put("ios7icons",
  112. new String[] { STENCIL_PATH + "/ios7/icons.xml" });
  113. libraries.put("ios7ui",
  114. new String[] { SHAPES_PATH + "/ios7/mxIOS7Ui.js",
  115. STENCIL_PATH + "/ios7/misc.xml" });
  116. libraries.put("android", new String[] { SHAPES_PATH + "/mxAndroid.js",
  117. STENCIL_PATH + "electrical/transmission" });
  118. libraries.put("electrical/transmission",
  119. new String[] { SHAPES_PATH + "/mxElectrical.js",
  120. STENCIL_PATH + "/electrical/transmission.xml" });
  121. libraries.put("mockup/buttons",
  122. new String[] { SHAPES_PATH + "/mockup/mxMockupButtons.js" });
  123. libraries.put("mockup/containers",
  124. new String[] { SHAPES_PATH + "/mockup/mxMockupContainers.js" });
  125. libraries.put("mockup/forms",
  126. new String[] { SHAPES_PATH + "/mockup/mxMockupForms.js" });
  127. libraries.put("mockup/graphics",
  128. new String[] { SHAPES_PATH + "/mockup/mxMockupGraphics.js",
  129. STENCIL_PATH + "/mockup/misc.xml" });
  130. libraries.put("mockup/markup",
  131. new String[] { SHAPES_PATH + "/mockup/mxMockupMarkup.js" });
  132. libraries.put("mockup/misc",
  133. new String[] { SHAPES_PATH + "/mockup/mxMockupMisc.js",
  134. STENCIL_PATH + "/mockup/misc.xml" });
  135. libraries.put("mockup/navigation",
  136. new String[] { SHAPES_PATH + "/mockup/mxMockupNavigation.js",
  137. STENCIL_PATH + "/mockup/misc.xml" });
  138. libraries.put("mockup/text",
  139. new String[] { SHAPES_PATH + "/mockup/mxMockupText.js" });
  140. libraries.put("floorplan",
  141. new String[] { SHAPES_PATH + "/mxFloorplan.js",
  142. STENCIL_PATH + "/floorplan.xml" });
  143. libraries.put("bootstrap",
  144. new String[] { SHAPES_PATH + "/mxBootstrap.js",
  145. STENCIL_PATH + "/bootstrap.xml" });
  146. libraries.put("gmdl", new String[] { SHAPES_PATH + "/mxGmdl.js",
  147. STENCIL_PATH + "/gmdl.xml" });
  148. libraries.put("cabinets", new String[] { SHAPES_PATH + "/mxCabinets.js",
  149. STENCIL_PATH + "/cabinets.xml" });
  150. libraries.put("archimate",
  151. new String[] { SHAPES_PATH + "/mxArchiMate.js" });
  152. libraries.put("archimate3",
  153. new String[] { SHAPES_PATH + "/mxArchiMate3.js" });
  154. libraries.put("sysml", new String[] { SHAPES_PATH + "/mxSysML.js" });
  155. libraries.put("eip", new String[] { SHAPES_PATH + "/mxEip.js",
  156. STENCIL_PATH + "/eip.xml" });
  157. libraries.put("networks", new String[] { SHAPES_PATH + "/mxNetworks.js",
  158. STENCIL_PATH + "/networks.xml" });
  159. libraries.put("aws3d", new String[] { SHAPES_PATH + "/mxAWS3D.js",
  160. STENCIL_PATH + "/aws3d.xml" });
  161. libraries.put("pid2inst",
  162. new String[] { SHAPES_PATH + "/pid2/mxPidInstruments.js" });
  163. libraries.put("pid2misc",
  164. new String[] { SHAPES_PATH + "/pid2/mxPidMisc.js",
  165. STENCIL_PATH + "/pid/misc.xml" });
  166. libraries.put("pid2valves",
  167. new String[] { SHAPES_PATH + "/pid2/mxPidValves.js" });
  168. libraries.put("pidFlowSensors",
  169. new String[] { STENCIL_PATH + "/pid/flow_sensors.xml" });
  170. }
  171. /**
  172. * @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
  173. */
  174. protected void doGet(HttpServletRequest request,
  175. HttpServletResponse response) throws ServletException, IOException
  176. {
  177. try
  178. {
  179. String qs = request.getQueryString();
  180. if (qs != null && qs.equals("stats"))
  181. {
  182. writeStats(response);
  183. }
  184. else
  185. {
  186. // Checks or sets last modified date of delivered content.
  187. // Date comparison not needed. Only return 304 if
  188. // delivered by this servlet instance.
  189. String modSince = request.getHeader("If-Modified-Since");
  190. if (modSince != null && modSince.equals(lastModified)
  191. && request.getParameter("fetch") == null)
  192. {
  193. response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  194. }
  195. else
  196. {
  197. writeEmbedResponse(request, response);
  198. }
  199. }
  200. }
  201. catch (Exception e)
  202. {
  203. response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
  204. }
  205. }
  206. public void writeEmbedResponse(HttpServletRequest request,
  207. HttpServletResponse response) throws IOException
  208. {
  209. response.setCharacterEncoding("UTF-8");
  210. response.setContentType("application/javascript; charset=UTF-8");
  211. response.setHeader("Last-Modified", lastModified);
  212. if (request.getParameter("fetch") != null)
  213. {
  214. response.setHeader("Cache-Control", "no-store");
  215. }
  216. OutputStream out = response.getOutputStream();
  217. // Creates XML for stencils
  218. PrintWriter writer = new PrintWriter(out);
  219. // Writes JavaScript and adds function call with
  220. // stylesheet and stencils as arguments
  221. writer.println(createEmbedJavaScript(request));
  222. response.setStatus(HttpServletResponse.SC_OK);
  223. writer.flush();
  224. writer.close();
  225. }
  226. public String createEmbedJavaScript(HttpServletRequest request)
  227. throws IOException
  228. {
  229. String sparam = request.getParameter("s");
  230. String dev = request.getParameter("dev");
  231. StringBuffer result = new StringBuffer("[");
  232. StringBuffer js = new StringBuffer("");
  233. // Processes each stencil only once
  234. HashSet<String> done = new HashSet<String>();
  235. // Processes each lib only once
  236. HashSet<String> libsLoaded = new HashSet<String>();
  237. if (sparam != null)
  238. {
  239. String[] names = sparam.split(";");
  240. for (int i = 0; i < names.length; i++)
  241. {
  242. if (names[i].indexOf("..") < 0 && !done.contains(names[i]))
  243. {
  244. if (names[i].equals("*"))
  245. {
  246. js.append(readXmlFile("/js/shapes-14-6-5.min.js", false));
  247. result.append(
  248. "'" + readXmlFile("/stencils.xml", true) + "'");
  249. }
  250. else
  251. {
  252. // Checks if any JS files are associated with the library
  253. // name and injects the JS into the page
  254. String[] libs = libraries.get(names[i]);
  255. if (libs != null)
  256. {
  257. for (int j = 0; j < libs.length; j++)
  258. {
  259. if (!libsLoaded.contains(libs[j]))
  260. {
  261. String tmp = stencils.get(libs[j]);
  262. libsLoaded.add(libs[j]);
  263. if (tmp == null)
  264. {
  265. try
  266. {
  267. tmp = readXmlFile(libs[j],
  268. !libs[j].toLowerCase()
  269. .endsWith(".js"));
  270. // Cache for later use
  271. if (tmp != null)
  272. {
  273. stencils.put(libs[j], tmp);
  274. }
  275. }
  276. catch (NullPointerException e)
  277. {
  278. // This seems possible according to access log so ignore stencil
  279. }
  280. }
  281. if (tmp != null)
  282. {
  283. // TODO: Add JS to Javascript code inline. This had to be done to quickly
  284. // add JS-based dynamic loading to the existing embed setup where everything
  285. // dynamic is passed via function call, so an indirection via eval must be
  286. // used even though the JS could be parsed directly by adding it to JS.
  287. if (libs[j].toLowerCase()
  288. .endsWith(".js"))
  289. {
  290. js.append(tmp);
  291. }
  292. else
  293. {
  294. if (result.length() > 1)
  295. {
  296. result.append(",");
  297. }
  298. result.append("'" + tmp + "'");
  299. }
  300. }
  301. }
  302. }
  303. }
  304. else
  305. {
  306. String tmp = stencils.get(names[i]);
  307. if (tmp == null)
  308. {
  309. try
  310. {
  311. tmp = readXmlFile(
  312. "/stencils/" + names[i] + ".xml",
  313. true);
  314. // Cache for later use
  315. if (tmp != null)
  316. {
  317. stencils.put(names[i], tmp);
  318. }
  319. }
  320. catch (NullPointerException e)
  321. {
  322. // This seems possible according to access log so ignore stencil
  323. }
  324. }
  325. if (tmp != null)
  326. {
  327. if (result.length() > 1)
  328. {
  329. result.append(",");
  330. }
  331. result.append("'" + tmp + "'");
  332. }
  333. }
  334. }
  335. done.add(names[i]);
  336. }
  337. }
  338. }
  339. result.append("]");
  340. // LATER: Detect protocol of request in dev
  341. // mode to avoid security errors
  342. String proto = "https://";
  343. String setCachedUrls = "";
  344. String[] urls = request.getParameterValues("fetch");
  345. if (urls != null)
  346. {
  347. HashSet<String> completed = new HashSet<String>();
  348. for (int i = 0; i < urls.length; i++)
  349. {
  350. try
  351. {
  352. // Checks if URL already fetched to avoid duplicates
  353. if (!completed.contains(urls[i]) && Utils.sanitizeUrl(urls[i]))
  354. {
  355. completed.add(urls[i]);
  356. URL url = new URL(urls[i]);
  357. URLConnection connection = url.openConnection();
  358. ByteArrayOutputStream stream = new ByteArrayOutputStream();
  359. Utils.copy(connection.getInputStream(), stream);
  360. setCachedUrls += "GraphViewer.cachedUrls['"
  361. + StringEscapeUtils.escapeEcmaScript(urls[i])
  362. + "'] = decodeURIComponent('"
  363. + StringEscapeUtils.escapeEcmaScript(
  364. Utils.encodeURIComponent(
  365. stream.toString("UTF-8"),
  366. Utils.CHARSET_FOR_URL_ENCODING))
  367. + "');";
  368. }
  369. }
  370. catch (Exception e)
  371. {
  372. // ignore
  373. }
  374. }
  375. }
  376. // Installs a callback to load the stencils after the viewer was injected
  377. return "window.onDrawioViewerLoad = function() {" + setCachedUrls
  378. + "mxStencilRegistry.parseStencilSets(" + result.toString()
  379. + ");" + js + "GraphViewer.processElements(); };"
  380. + "var t = document.getElementsByTagName('script');"
  381. + "if (t != null && t.length > 0) {"
  382. + "var script = document.createElement('script');"
  383. + "script.type = 'text/javascript';" + "script.src = '" + proto
  384. + ((dev != null && dev.equals("1")) ? "test" : "www")
  385. + ".draw.io/js/viewer-static.min.js';"
  386. + "t[0].parentNode.appendChild(script);}";
  387. }
  388. public void writeStats(HttpServletResponse response) throws IOException
  389. {
  390. PrintWriter writer = new PrintWriter(response.getOutputStream());
  391. writer.println("<html>");
  392. writer.println("<body>");
  393. writer.println("Deployed: " + lastModified);
  394. writer.println("</body>");
  395. writer.println("</html>");
  396. writer.flush();
  397. }
  398. public String readXmlFile(String filename, boolean xmlContent)
  399. throws IOException
  400. {
  401. String result = readFile(filename);
  402. if (xmlContent)
  403. {
  404. result = result.replaceAll("'", "\\\\'").replaceAll("\t", "")
  405. .replaceAll("\n", "");
  406. }
  407. return result;
  408. }
  409. public String readFile(String filename) throws IOException
  410. {
  411. InputStream is = getServletContext().getResourceAsStream(filename);
  412. return Utils.readInputStream(is);
  413. }
  414. }