blob: 8ca9ba8ed510cc907be849f94a7c555cd1b6582c [file] [log] [blame]
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>&amp;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