| package org.eclipse.stem.ui.ge.servlet; |
| /******************************************************************************* |
| * Copyright (c) 2006 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License v1.0 |
| * which accompanies this distribution, and is available at |
| * http://www.eclipse.org/legal/epl-v10.html |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| import java.io.BufferedReader; |
| import java.io.File; |
| import java.io.FileFilter; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.PrintWriter; |
| import java.net.URLEncoder; |
| import java.util.Arrays; |
| import java.util.Comparator; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.HashMap; |
| |
| import javax.servlet.ServletConfig; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServlet; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.eclipse.stem.ui.Activator; |
| |
| |
| /** |
| * SlideShowServlet sends back a valid KML file for a GET or POST |
| * action |
| * <pre> |
| * The way that it works is this: |
| * |
| * A KML file is launched to GoogleEarth that |
| * has a network link with the following URL |
| * http://localhost:8080/SlideShow/slideshow?id=<id>&folder=<path> |
| * where <id> is a unique id |
| * <path> is the directory containing the |
| * kml files generated by STEM. |
| * |
| * GoogleEarth will then issue http GET requests with |
| * the specified URL which will cause this servlet's |
| * doGet() method to be invoked. |
| * |
| * It retrieves the correct file in one of 2 ways: |
| * |
| * The servlet keeps a map of info keyed by the <id> that |
| * has a list of files that was found in the folder |
| * and a counter that is used to send back the N'th file. |
| * and increment N. |
| * |
| * If a filename was specified in the URL |
| * http://localhost:8080/STEM/slideshow?...file=<filename> |
| * then that file will be read and returned to GE. |
| * This file will always be the most recently generated |
| * KML file. This will help ensure that GE does not fall |
| * behind the STEM simulation. |
| * </pre> |
| * |
| */ |
| public class SlideShowServlet extends HttpServlet { |
| /** |
| * |
| */ |
| private static final long serialVersionUID = 1L; |
| |
| /** |
| * Used by this and other servlets to control debug output |
| */ |
| public static boolean DEBUG = false; |
| |
| //public static boolean DEBUG = true; |
| /** |
| * change to true to cause output to be sent to console instead |
| * of GE |
| */ |
| private static final boolean TEST = false; |
| |
| private static final long start = new Date().getTime(); |
| /** |
| * Servlet context. |
| * |
| */ |
| public static final String CONTEXT = "STEM"; |
| /** |
| * default folder for KML files. |
| */ |
| static final String STEM_FOLDER = "c:\\tmp\\ge"; |
| |
| /** |
| * collection of information about a session |
| * Key = id passed to servlet |
| * |
| */ |
| |
| private static HashMap<String,SlideShowInfo> _infoMap = null; |
| |
| /** |
| * set true when we get a request that only sets the BBox info |
| */ |
| private boolean bboxSet = false; |
| /** |
| * Handle the initial setup |
| */ |
| public void init(ServletConfig config) throws ServletException { |
| super.init(config); |
| //debug("init: Default folder="+STEM_FOLDER); |
| |
| _infoMap = new HashMap<String,SlideShowInfo>(); |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| protected void doGet(HttpServletRequest request, HttpServletResponse response) |
| throws ServletException, IOException { |
| |
| doPost(request, response); |
| } |
| |
| /* (non-Javadoc) |
| * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse) |
| */ |
| protected void doPost(HttpServletRequest request,HttpServletResponse response) |
| throws ServletException, IOException { |
| |
| int counter = 0; |
| |
| String fileName = null; |
| SlideShowInfo info = null; |
| |
| File[] files = null; |
| |
| try { |
| // get info from url |
| info = getParameters(request); |
| String key = info.getKey(); |
| if (bboxSet) { |
| // This was a request to return BBOX only |
| processNoOp(request,response); |
| } |
| // make sure there is some delay between requests |
| info.delay(); |
| fileName = info.getFileName(); |
| if (fileName != null) { |
| // get a specific file |
| |
| processFile(request,response,info,fileName); |
| return; |
| } else { |
| // get saved list of files |
| files = info.getFiles(); |
| // if never filled in |
| if (files == null) { |
| // get updated list of files |
| files = getFiles(info); |
| } |
| |
| } |
| counter = getCounter(request,response,info); |
| // getCounter will have updated info.getFiles |
| files = info.getFiles(); // added files |
| if (files.length == 0) { |
| // if no .kml files in folder |
| if (info.isWaitForKML()) { |
| sendScreenOverlay(request, response, "Waiting to Start"); |
| |
| } else { |
| response.sendError(HttpServletResponse.SC_BAD_REQUEST, |
| "No KML files in specified folder."); |
| } |
| return; |
| } |
| File kmlFile = files[counter]; |
| // String print = "key: "+key+" Counter: " + counter + |
| // " addr: " + request.getServerName() + ":"+request.getServerPort()+ |
| // " #files: "+files.length + |
| // " kmlFile: "+kmlFile.getAbsolutePath(); |
| //debug(print); |
| debug("Send to GE: "+kmlFile); |
| |
| FileInputStream fis = new FileInputStream(kmlFile); |
| // insert is additioal KML to be sent with output |
| String[] insert = info.getInsert(); |
| |
| sendOutput(request,response, fis,insert ); |
| info.setCounter(counter); |
| _infoMap.put(key, info); |
| } catch (Throwable e) { |
| e.printStackTrace(); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, |
| e.getMessage()); |
| } |
| } |
| |
| |
| /** |
| * Get the counter that keeps track of which file |
| * is to be processed next. |
| * |
| * It does more thatn just get the next value for the counter, |
| * it will check if there are more files to process and if not |
| * do the handling of this case. |
| * |
| * @param request ServletRequest |
| * @param response ServletResponse |
| * @param info instance of information saved from step to step |
| * @return N where we want to display the Nth file |
| * from the specified folder. |
| * 0 if starting over |
| * |
| * @throws Exception |
| */ |
| synchronized |
| private int getCounter(HttpServletRequest request, |
| HttpServletResponse response, |
| SlideShowInfo info) |
| throws Exception{ |
| |
| int counter = info.getCounter(); |
| counter++; |
| |
| // if done all of files currently in list |
| int waits = 10; // wait for 10 seconds |
| int max = info.getFiles().length; |
| while (true) { |
| |
| if (counter >= max) { |
| // reached end of filelist |
| |
| if (info.isWaitForKML()) { |
| Thread.sleep(1*1000); |
| info.setFiles(null); |
| getFiles(info); |
| max = info.getFiles().length; |
| |
| waits--; |
| if (waits > 0) |
| continue; |
| // waited and still no more files |
| // indicate to user we are paused |
| counter = max-1; |
| if (! info.isPause()) { |
| // if we have not yet claimed to be pausing |
| //debug("pause: "); |
| info.setPause(true); |
| // send pause as icon to GE |
| String host = getHost(request); |
| String url = "http://"+host+"/"+ |
| CONTEXT+"/image?text="+ |
| URLEncoder.encode("Paused","UTF-8"); |
| String[] insert = buildScreenOverlay(url); |
| info.setInsert(insert); |
| } |
| break; |
| } else { |
| // pause before restarting |
| // TODO remove debug |
| debug("Pause 10 seconds before restarting"); |
| Thread.sleep(10*1000); |
| counter = 0; |
| break; |
| } |
| } else { |
| if (info.isPause()) { |
| //debug("Change pause to active"); |
| // we were paused so change image to active |
| info.setPause(false); |
| Thread.sleep(2*1000); |
| String host = getHost(request); |
| String url = "http://"+host+"/"+ |
| CONTEXT+"/image?text="+ |
| URLEncoder.encode("Active","UTF-8"); |
| |
| String[] insert = buildScreenOverlay(url); |
| info.setInsert(insert); |
| |
| } |
| break; |
| } |
| } // while(true) |
| |
| return counter; |
| |
| } |
| /** |
| * send back an empty response |
| * |
| * @param request |
| * @param response |
| * |
| * @throws ServletException |
| * @throws IOException |
| */ |
| |
| private void processNoOp(HttpServletRequest request, |
| HttpServletResponse response) |
| throws ServletException, IOException{ |
| try { |
| |
| PrintWriter os = new PrintWriter(response.getOutputStream()); |
| //PrintStream os = System.out; |
| response.setContentType("application/keyhole"); |
| |
| os.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); |
| os.println("<kml xmlns=\"http://earth.google.com/kml/2.0\">"); |
| os.println("<Document>"); |
| os.println("</Document>"); |
| os.println("</kml>"); |
| |
| os.flush(); |
| } catch (Throwable e) { |
| e.printStackTrace(); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage()); |
| } |
| } |
| /** |
| * Process a request to serve up a specific file |
| * |
| * @param request |
| * @param response |
| * @param fileName |
| * @throws ServletException |
| * @throws IOException |
| */ |
| |
| private void processFile(HttpServletRequest request, |
| HttpServletResponse response, |
| SlideShowInfo info, String fileName) |
| throws ServletException, IOException{ |
| try { |
| |
| File kmlFile = new File(fileName); |
| String oldFileName = fileName.replace("_Current","_Old"); |
| File oldFile = new File(oldFileName); |
| String[] insert = info.getInsert(); |
| FileInputStream fis = null; |
| int counter = 0; |
| while (true) { |
| try { |
| if (! kmlFile.exists() ) { |
| Thread.sleep(2*1000); |
| counter++; |
| // after 20 seconds use old file |
| if (counter > 10) { |
| rename(oldFile,kmlFile); |
| } |
| continue; |
| } |
| fis = new FileInputStream(kmlFile); |
| //debug(" kmlFile: "+kmlFile.getAbsolutePath()); |
| break; |
| } catch (Exception e) { |
| error("processFile failed",e); |
| return; |
| } |
| } |
| debug("Send to GE: "+kmlFile); |
| sendOutput(request,response, fis,insert ); |
| |
| rename(kmlFile,oldFile); |
| |
| } catch (Throwable e) { |
| e.printStackTrace(); |
| response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR,e.getMessage()); |
| } |
| } |
| |
| |
| /** |
| * switch files by renaming |
| */ |
| private boolean rename(File oldFile, File newFile) { |
| try { |
| if (newFile.exists()) |
| newFile.delete(); |
| |
| oldFile.renameTo(newFile); |
| } catch (RuntimeException e) { |
| return false; |
| } |
| return true; |
| } |
| /** |
| * @param request |
| */ |
| private String getHost(HttpServletRequest request) { |
| int port = request.getServerPort(); |
| String host = request.getServerName()+":"+port; |
| return host; |
| } |
| |
| /** |
| * Get the list of files in the folder |
| * |
| * |
| * @param info instance that holds misc info about session |
| * @return array of Files in the specified folder |
| */ |
| |
| public File[] getFiles(SlideShowInfo info) { |
| File[] files = null; |
| File folder = null; |
| try { |
| files = info.getFiles(); |
| |
| if (files == null || files.length == 0) { |
| folder = new File(info.getFolderName()); |
| files = fileList(folder); |
| if (files == null) |
| files = new File[0]; |
| info.setFiles(files); |
| } |
| } catch (RuntimeException e) { |
| |
| error("getFiles() failed. ",e); |
| files = new File[0]; |
| } |
| |
| return files; |
| } |
| |
| /** |
| * get the list of files in the specified folder. |
| * The files will only be files with the ".kml" |
| * extension and will be sorted by filename. |
| * Also netlink files will be filtered out |
| * |
| * @param folder Directory that contains KML files to display |
| * @return array of sorted filenames |
| */ |
| |
| private File[] fileList(File folder) { |
| |
| FileFilter ff = new FileFilter() { |
| public boolean accept(File f){ |
| if (f.getName().toLowerCase().startsWith("net")) { |
| return false; |
| } |
| if (f.getName().toLowerCase().endsWith(".kml")) { |
| return true; |
| } |
| return false; |
| } |
| }; |
| |
| Comparator<? super File> compare = new Comparator<File>() { |
| public int compare(File o1, File o2) { |
| File f1 = (File)o1; |
| File f2 = (File)o2; |
| int answer = f1.getName().compareTo(f2.getName()); |
| return answer; |
| } |
| |
| }; |
| |
| try { |
| File[] files = folder.listFiles(ff); |
| if (files == null) { |
| return new File[0]; |
| } |
| Arrays.sort(files,0,files.length,compare); |
| return files; |
| } catch (Exception e) { |
| e.printStackTrace(); |
| return null; |
| } |
| } |
| /** |
| * Obtain the following parameters from the URL. |
| * <pre> |
| * id: A key that makes this session unique |
| * Because GoogleEarth sets up a new session for each |
| * request, we cannot use the normal Servlet session |
| * protocol. |
| * We keep a hashmap of session info keyed by an ID that |
| * is passed in the URL |
| * |
| * folder: Where to find the KML files |
| * |
| * delay: Time to delay between returning data |
| * |
| * wait: If "y" them if now new files send a "pause" icon |
| * |
| * file: If spcified then return the specified file |
| * |
| * BBOX: If found then GE has return the current view coordinates |
| * |
| * BBoxSet: If found then GE has returned BBOX and no answer is |
| * required. |
| * |
| * id is then used to access infoMap and |
| * and return an instance of SlideShowInfo which contains current |
| * cycle counter, array of files and boolean wait indicator. |
| * If there is no existing entry then it builds a new |
| * SlideShowInfo instance from the url parameters and stores |
| * it in the infoMap. |
| * |
| * @param request Servlet request instance. |
| * @return instance of SlideShowInfo |
| */ |
| private SlideShowInfo getParameters(HttpServletRequest request) { |
| // get the id from the URL parameters. |
| // http://localhost:8080/slideshow?id=useridkey |
| String mykey="DEFAULT"; |
| String folderName = STEM_FOLDER; |
| boolean waitForKML = false; |
| String fileName = null; |
| String delay = "2"; |
| String bbox = null; |
| bboxSet = false; |
| Enumeration e = request.getParameterNames(); |
| StringBuffer sb = new StringBuffer(); |
| while (e.hasMoreElements()) { |
| String name = (String)e.nextElement(); |
| String value = (String)request.getParameter(name); |
| sb.append(name+": "+value+" "); |
| if (name.equals("id")) { |
| mykey = value; |
| } else if (name.equals("folder")) { |
| folderName = value; |
| } else if (name.equals("delay")) { |
| delay = value; |
| |
| } else if (name.equals("wait")) { |
| if (value.equals("y")) { |
| waitForKML = true; |
| } |
| } else if (name.equals("file")) { |
| fileName = value; |
| } else if (name.equals("BBOX")) { |
| bbox = value; |
| } else if (name.equals("bboxSet")) { |
| bboxSet = true; |
| } |
| } |
| //debug("getParameters: "+sb.toString()); |
| SlideShowInfo info = _infoMap.get(mykey); |
| if (info == null) { |
| info = new SlideShowInfo(mykey,folderName,waitForKML, |
| fileName,delay); |
| } |
| if (bbox != null) |
| info.setBBOX(bbox); |
| _infoMap.put(mykey,info); |
| return info; |
| |
| } |
| /** |
| * Send the KML file to the caller (GoogleEarth) |
| * |
| * If TEST=true then it will be written to the browser |
| * for testing. |
| * |
| * @param request |
| * @param response |
| * @param kml InputStream holding file contents |
| * @param insert KML to be inserted in output |
| * @throws IOException |
| */ |
| synchronized |
| private void sendOutput(HttpServletRequest request, |
| HttpServletResponse response, |
| InputStream kml, String[] insert) |
| throws IOException { |
| // set TEST=true to get readable output instead of |
| // launching a KML file |
| |
| response.setContentType("application/keyhole"); |
| |
| |
| try { |
| OutputStream os = response.getOutputStream(); |
| if (TEST) { |
| os = System.out; |
| } |
| copy(kml,os,insert); |
| } catch (Exception e) { |
| String msg = "SlideShowServlet.sendOutput() failed while sending kmlFile"; |
| error(msg+"\n"+e.getMessage(),null); |
| |
| } |
| |
| } |
| /** |
| * build a segment of KML to be inserted into the |
| * output written to GoogleEarth. |
| * |
| * @param url |
| * @return KML to be inserted |
| */ |
| private String[] buildScreenOverlay( String url) { |
| String[] insert = new String[] |
| {"<ScreenOverlay id=\"Status\">", |
| " <description>STEM status</description>", |
| " <name>Status</name>\n", |
| "<icon>", |
| "<href>", |
| url, |
| "</href>", |
| "</icon>\n", |
| // following positions image to loop left |
| "<overlayXY x=\"0\" y=\"1\" xunits=\"fraction\" yunits=\"fraction\"/>\n", |
| "<screenXY x=\"0\" y=\"1\" xunits=\"fraction\" yunits=\"fraction\"/>\n", |
| // use actual size of image |
| "<size x=\"0\" y=\"0\" xunits=\"fraction\" yunits=\"fraction\"/>\n", |
| "</ScreenOverlay>\n" |
| }; |
| |
| return insert; |
| } |
| /** |
| * Send the KML file to the caller (GoogleEarth) |
| * |
| * |
| * @param response |
| * @param info |
| * @param msg A string that will be converted |
| * to an image and displayed by GoogleEarth |
| * @throws IOException |
| */ |
| |
| private void sendScreenOverlay(HttpServletRequest request, |
| HttpServletResponse response, |
| String msg) |
| throws IOException { |
| |
| response.setContentType("application/keyhole"); |
| |
| |
| try { |
| String host = getHost(request); |
| String url = "http://"+host+"/"+ |
| CONTEXT+"/image?text="+ |
| URLEncoder.encode(msg,"UTF-8"); |
| |
| String[] insert = buildScreenOverlay(url); |
| |
| PrintWriter os = new PrintWriter(response.getOutputStream()); |
| //PrintStream os = System.out; |
| response.setContentType("application/keyhole"); |
| |
| os.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>"); |
| os.println("<kml xmlns=\"http://earth.google.com/kml/2.0\">"); |
| os.println("<Document>"); |
| for (int i=0; i<insert.length; i++) { |
| os.println(insert[i]); |
| } |
| os.println("</Document>"); |
| os.println("</kml>"); |
| |
| os.flush(); |
| } catch (Exception e) { |
| String emsg = "SlideShowServlet.sendScreenOverlay() failed while writing kmlFile"; |
| error(emsg+"\n"+e.getMessage(),null); |
| |
| } |
| |
| } |
| |
| |
| private static String getClock() { |
| long now = new Date().getTime()-start; |
| return Long.toString(now); |
| } |
| |
| /** |
| * return the info instance associated with the specified key |
| * @param key |
| * @return instance of SlideShowInfo |
| */ |
| public static SlideShowInfo getInfo(String key) { |
| SlideShowInfo info = _infoMap.get(key); |
| return info; |
| } |
| /** |
| * copy a KML file to the servlet response output stream |
| * and perhaps insert additional KML statements |
| * |
| * @param is KML file input |
| * @param os Servlet response output stream |
| * @param insert optional statements to be inserted |
| * after the <document> statement. |
| */ |
| private void copy(InputStream is, OutputStream os, String[] insert) |
| throws IOException { |
| |
| InputStreamReader isr = new InputStreamReader(is); |
| BufferedReader br = new BufferedReader(isr); |
| String line = null; |
| |
| |
| try { |
| |
| while (true) { |
| line = br.readLine(); |
| if (line == null) |
| break; |
| |
| byte[] buffer = line.getBytes(); |
| os.write(buffer, 0, buffer.length); |
| if (insert!=null && |
| line.toLowerCase().startsWith("<document>")) { |
| for (int i=0; i<insert.length; i++) { |
| buffer = insert[i].getBytes(); |
| os.write(buffer, 0, buffer.length); |
| } |
| } |
| } |
| os.flush(); |
| os.close(); |
| is.close(); |
| } catch (Throwable e) { |
| |
| error("SlideShowServlet.copy(): Error while copying KML file. " |
| +e.toString(),null); |
| try { |
| is.close(); |
| os.close(); |
| } catch (Exception e2) { |
| // already reported it |
| } |
| } |
| |
| } |
| |
| /** |
| * print debug messages to the server log. |
| * @param msg |
| */ |
| public static void debug(String msg) { |
| if (DEBUG) { |
| Activator.logInformation(getClock()+" GEServlet: "+msg); |
| } |
| } |
| |
| /** |
| * debug output for exceptions |
| * @param msg |
| * @param e |
| */ |
| public static void error(String msg,Throwable e) { |
| //log(msg,e); |
| Activator.logInformation(msg); |
| if (e != null) |
| if (DEBUG) |
| e.printStackTrace(); |
| else |
| Activator.logInformation(e.toString()); |
| |
| } |
| } // class |