Java tutorial
/** * Copyright (c) 2014-2015 openHAB UG (haftungsbeschraenkt) 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 */ package org.openhab.ui.cometvisu.servlet; import java.io.Closeable; import java.io.File; import java.io.FileNotFoundException; import java.io.FilenameFilter; import java.io.IOException; import java.io.OutputStream; import java.io.RandomAccessFile; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; import java.util.Calendar; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.zip.GZIPOutputStream; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.core.MediaType; import org.apache.commons.io.FileUtils; import org.eclipse.smarthome.core.items.Item; import org.eclipse.smarthome.core.items.ItemNotFoundException; import org.eclipse.smarthome.core.items.events.ItemEventFactory; import org.eclipse.smarthome.core.library.types.StringType; import org.eclipse.smarthome.core.persistence.FilterCriteria; import org.eclipse.smarthome.core.persistence.FilterCriteria.Ordering; import org.eclipse.smarthome.core.persistence.HistoricItem; import org.eclipse.smarthome.core.persistence.QueryablePersistenceService; import org.eclipse.smarthome.core.types.Command; import org.eclipse.smarthome.model.sitemap.Sitemap; import org.eclipse.smarthome.model.sitemap.SitemapProvider; import org.openhab.ui.cometvisu.internal.Config; import org.openhab.ui.cometvisu.internal.config.ConfigHelper.Transform; import org.openhab.ui.cometvisu.internal.config.VisuConfig; import org.openhab.ui.cometvisu.internal.editor.dataprovider.beans.DataBean; import org.openhab.ui.cometvisu.internal.editor.dataprovider.beans.ItemBean; import org.openhab.ui.cometvisu.internal.rrs.beans.Feed; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.caucho.java.WorkDir; import com.caucho.quercus.QuercusContext; import com.caucho.quercus.QuercusDieException; import com.caucho.quercus.QuercusEngine; import com.caucho.quercus.QuercusErrorException; import com.caucho.quercus.QuercusExitException; import com.caucho.quercus.QuercusLineRuntimeException; import com.caucho.quercus.QuercusRequestAdapter; import com.caucho.quercus.QuercusRuntimeException; import com.caucho.quercus.env.Env; import com.caucho.quercus.env.QuercusValueException; import com.caucho.quercus.env.StringValue; import com.caucho.quercus.page.QuercusPage; import com.caucho.quercus.servlet.api.QuercusHttpServletRequest; import com.caucho.quercus.servlet.api.QuercusHttpServletRequestImpl; import com.caucho.quercus.servlet.api.QuercusHttpServletResponse; import com.caucho.quercus.servlet.api.QuercusHttpServletResponseImpl; import com.caucho.quercus.servlet.api.QuercusServletContextImpl; import com.caucho.util.CurrentTime; import com.caucho.util.L10N; import com.caucho.vfs.FilePath; import com.caucho.vfs.Path; import com.caucho.vfs.Vfs; import com.caucho.vfs.WriteStream; import com.google.gson.Gson; /** * Servlet for CometVisu static + php files * * @author Tobias Brutigam - initial contribution and API * @author BalusC - code for static files (taken from FileServlet) * @author Scott Ferguson - code for php files (taken from QuercusServletImpl) * @see com.caucho.quercus.servlet.QuercusServletImpl * * @link * http://balusc.blogspot.com/2009/02/fileservlet-supporting-resume-and.html * @link http://quercus.caucho.com/ */ public class CometVisuServlet extends HttpServlet { /** * */ private static final long serialVersionUID = 4448918908615003303L; private static final L10N L = new L10N(CometVisuServlet.class); private static final Logger logger = LoggerFactory.getLogger(CometVisuServlet.class); private static final int DEFAULT_BUFFER_SIZE = 10240; // ..bytes = 10KB. private static final long DEFAULT_EXPIRE_TIME = 604800000L; // ..ms = 1 // week. private static final String MULTIPART_BOUNDARY = "MULTIPART_BYTERANGES"; private Pattern sitemapPattern = Pattern.compile(".*/visu_config_(oh_)?([^\\.]+)\\.xml"); private Pattern configStorePattern = Pattern.compile("config/visu_config_oh_([a-z0-9_]+)\\.xml"); private String rrsLogPath = "/plugins/rsslog/rsslog_oh.php"; private final String rssLogMessageSeparator = "\\|"; private DateFormat rssPubDateFormat = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss Z", Locale.ENGLISH); protected String root; protected File rootFolder; protected File userFileFolder; // protected String serverAlias; protected String defaultUserDir; protected QuercusEngine engine; protected ServletContext _servletContext; protected QuercusContext _quercus; protected ServletConfig _config; private CometVisuApp cometVisuApp; public CometVisuServlet(String filesystemDir, CometVisuApp cometVisuApp) { root = filesystemDir; rootFolder = new File(root); userFileFolder = new File(org.eclipse.smarthome.config.core.ConfigConstants.getConfigFolder() + Config.COMETVISU_WEBAPP_USERFILE_FOLDER); defaultUserDir = System.getProperty("user.dir"); engine = new QuercusEngine(); engine.getQuercus().setIni("include_path", ".:" + rootFolder.getAbsolutePath()); this.cometVisuApp = cometVisuApp; } /** * initialize the script manager. */ @Override public final void init(ServletConfig config) throws ServletException { super.init(config); _config = config; _servletContext = config.getServletContext(); checkServletAPIVersion(); // Path pwd = new FilePath(_servletContext.getRealPath("/")); Path pwd = new FilePath(rootFolder.getAbsolutePath()); Path webInfDir = pwd; logger.info("initial pwd " + pwd); logger.info("initial webinf " + webInfDir); engine.getQuercus().setPwd(pwd); engine.getQuercus().setWebInfDir(webInfDir); // need to set these for non-Resin containers if (!CurrentTime.isTest() && !engine.getQuercus().isResin()) { Vfs.setPwd(pwd); WorkDir.setLocalWorkDir(webInfDir.lookup("work")); } engine.getQuercus().init(); engine.getQuercus().start(); } /** * {@inheritDoc} * * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { File requestedFile = getRequestedFile(req); Matcher match = configStorePattern.matcher(req.getParameter("config")); if (requestedFile.getName().endsWith("save_config.php") && match.find() && req.getParameter("type") != null && req.getParameter("type").equals("xml")) { saveConfig(req, resp); } else { phpService(requestedFile, req, resp); } } private Sitemap getSitemap(String sitemapname) { for (SitemapProvider provider : cometVisuApp.getSitemapProviders()) { Sitemap sitemap = provider.getSitemap(sitemapname); if (sitemap != null) { return sitemap; } } return null; } /** * {@inheritDoc} * * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest, * javax.servlet.http.HttpServletResponse) */ @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { File requestedFile = getRequestedFile(req); String path = req.getPathInfo() != null ? req.getPathInfo() : "/index.html"; Matcher matcher = sitemapPattern.matcher(path); if (matcher.find()) { // add headers for cometvisu clients autoconfiguration resp.setHeader("X-CometVisu-Backend-LoginUrl", "/rest/" + Config.COMETVISU_BACKEND_ALIAS + "/" + Config.COMETVISU_BACKEND_LOGIN_ALIAS); resp.setHeader("X-CometVisu-Backend-Name", "openhab2"); // serve autogenerated config from openhab sitemap if no real config file exists if (!requestedFile.exists()) { Sitemap sitemap = getSitemap(matcher.group(2)); if (sitemap != null) { logger.debug("reading sitemap '{}'", sitemap); VisuConfig config = new VisuConfig(sitemap, cometVisuApp, rootFolder); // logger.info("response: "+config.getConfigXml()); resp.setContentType(MediaType.APPLICATION_XML); resp.getWriter().write(config.getConfigXml(req)); resp.flushBuffer(); return; } else { throw new ServletException("Sitemap '" + matcher.group(1) + "' could not be found"); } } } // logger.info("Path: " + req.getPathInfo()); if (path.matches(".*editor/dataproviders/.+\\.(php|json)$") || path.matches(".*designs/get_designs\\.php$")) { dataProviderService(requestedFile, req, resp); } else if (path.equalsIgnoreCase(rrsLogPath)) { processRssLogRequest(requestedFile, req, resp); } else if (requestedFile.getName().endsWith(".php")) { phpService(requestedFile, req, resp); } else { processStaticRequest(requestedFile, req, resp, true); } } protected File getRequestedFile(HttpServletRequest req) throws UnsupportedEncodingException { String requestedFile = req.getPathInfo(); File file = null; // check services folder if a file exists there if (requestedFile != null) { file = new File(userFileFolder, URLDecoder.decode(requestedFile, "UTF-8")); } // serve the file from the cometvisu src directory if (file == null || !file.exists() || file.isDirectory()) { file = requestedFile != null ? new File(rootFolder, URLDecoder.decode(requestedFile, "UTF-8")) : rootFolder; } if (file.isDirectory()) { // search for an index file FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith("index.") && (name.endsWith(".php") || name.endsWith(".html")); } }; for (String dirFile : file.list(filter)) { // take the first one found file = new File(file, dirFile); break; } } return file; } /** * serves an RSS-Feed from a persisted string item backend for the CometVisu * rrslog-plugin * * @param file * @param request * @param response */ private void processRssLogRequest(File file, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // retrieve the item if (request.getParameter("f") == null) return; String[] itemNames = request.getParameter("f").split(","); List<Item> items = new ArrayList<Item>(); for (String name : itemNames) { try { Item item = cometVisuApp.getItemRegistry().getItem(name); items.add(item); } catch (ItemNotFoundException e) { logger.error("item '{}' not found", name); } } if (items.size() > 0) { // Fallback to first persistenceService from list if (!CometVisuApp.getPersistenceServices().entrySet().iterator().hasNext()) { throw new IllegalArgumentException("No Persistence service found."); } if (request.getParameter("c") != null) { if (items.size() == 1) { // new log message should be store String title = request.getParameter("h"); String message = request.getParameter("c"); String state = request.getParameter("state"); // Build message Command command = new StringType(title + rssLogMessageSeparator + message + rssLogMessageSeparator + state + rssLogMessageSeparator + items.get(0).getName()); // Use the event publisher to store the item in the defined // persistance services cometVisuApp.getEventPublisher() .post(ItemEventFactory.createCommandEvent(items.get(0).getName(), command)); } // send empty response?? response.setContentType("text/plain"); response.getWriter().write(""); response.flushBuffer(); } else if (request.getParameter("dump") != null) { } else if (request.getParameter("r") != null) { // delete all log lines older than the timestamp and optional a // filter // => not possible to remove data from persistence service response.setContentType("text/plain"); response.getWriter().write( "Cannot execute query: It is not possible to delete data from openHAB PersistenceService"); response.flushBuffer(); } else if (request.getParameter("u") != null) { // update state response.setContentType("text/plain"); response.getWriter().write( "Cannot execute query: It is not possible to update data from openHAB PersistenceService"); response.flushBuffer(); } else if (request.getParameter("d") != null) { // update state response.setContentType("text/plain"); response.getWriter().write( "Cannot execute query: It is not possible to delete data from openHAB PersistenceService"); response.flushBuffer(); } else { Feed feed = new Feed(); feed.feedUrl = request.getRequestURL().toString(); feed.title = "RSS supplied logs"; feed.link = request.getRequestURL().toString(); feed.author = ""; feed.description = "RSS supplied logs"; feed.type = "rss20"; // Define the data filter FilterCriteria filter = new FilterCriteria(); Calendar start = Calendar.getInstance(); // retrieve only the historic states from the last 7 days + BeginDate is required for RRD4j service start.add(Calendar.DAY_OF_YEAR, -7); // Date end = new Date(); filter.setBeginDate(start.getTime()); // filter.setEndDate(end); filter.setPageSize(25); filter.setOrdering(Ordering.DESCENDING); for (Item item : items) { filter.setItemName(item.getName()); Iterator<Entry<String, QueryablePersistenceService>> pit = CometVisuApp.getPersistenceServices() .entrySet().iterator(); QueryablePersistenceService persistenceService = pit.next().getValue(); // Get the data from the persistence store Iterable<HistoricItem> result = persistenceService.query(filter); Iterator<HistoricItem> it = result.iterator(); boolean forceStop = false; while (!forceStop && !it.hasNext()) { if (pit.hasNext()) { persistenceService = pit.next().getValue(); result = persistenceService.query(filter); } else { // no persisted data found for this item in any of // the available persistence services forceStop = true; } } if (it.hasNext()) { logger.debug("persisted data for item {} found in service {}", item.getName(), persistenceService.getName()); } // Iterate through the data int i = 0; while (it.hasNext()) { i++; HistoricItem historicItem = it.next(); if (historicItem.getState() == null || historicItem.getState().toString().isEmpty()) continue; org.openhab.ui.cometvisu.internal.rrs.beans.Entry entry = new org.openhab.ui.cometvisu.internal.rrs.beans.Entry(); entry.publishedDate = historicItem.getTimestamp().getTime(); logger.info(rssPubDateFormat.format(entry.publishedDate) + ": " + historicItem.getState()); entry.tags = historicItem.getName(); String[] content = historicItem.getState().toString().split(rssLogMessageSeparator); if (content.length == 0) { entry.content = historicItem.getState().toString(); } else if (content.length == 1) { entry.content = content[0]; } else if (content.length == 2) { entry.title = content[0]; entry.content = content[1]; } else if (content.length == 3) { entry.title = content[0]; entry.content = content[1]; entry.state = content[2]; } else if (content.length == 4) { entry.title = content[0]; entry.content = content[1]; entry.state = content[2]; // ignore tags in content[3] as is is already known // by item name } feed.entries.add(entry); } if ("rrd4j".equals(persistenceService.getName()) && FilterCriteria.Ordering.DESCENDING.equals(filter.getOrdering())) { // the RRD4j PersistenceService does not support descending ordering so we do it manually Collections.sort(feed.entries, new Comparator<org.openhab.ui.cometvisu.internal.rrs.beans.Entry>() { @Override public int compare(org.openhab.ui.cometvisu.internal.rrs.beans.Entry o1, org.openhab.ui.cometvisu.internal.rrs.beans.Entry o2) { return Long.compare(o2.publishedDate, o1.publishedDate); } }); } logger.debug("querying {} item from {} to {} => {} results on service {}", filter.getItemName(), filter.getBeginDate(), filter.getEndDate(), i, persistenceService.getName()); } if (request.getParameter("j") != null) { // request data in JSON format response.setContentType("application/json"); response.getWriter().write("{\"responseData\": { \"feed\": " + marshalJson(feed) + "},\"responseDetails\":null,\"responseStatus\":200}"); } else { // request data in RSS format response.setContentType(MediaType.APPLICATION_ATOM_XML); // as the json bean structure does not map the rss structure // we cannot just marshal an XML String rss = "<?xml version=\"1.0\"?>\n<rss version=\"2.0\">\n<channel>\n"; rss += "<title>" + feed.title + "</title>\n"; rss += "<link>" + feed.link + "</link>\n"; rss += "<desrciption>" + feed.description + "</desription>\n"; for (org.openhab.ui.cometvisu.internal.rrs.beans.Entry entry : feed.entries) { rss += "<item>"; rss += "<title>" + entry.title + "</title>"; rss += "<description>" + entry.content + "</description>"; Date pubDate = new Date(entry.publishedDate); rss += "<pubDate>" + rssPubDateFormat.format(pubDate) + "</pubDate>"; rss += "</item>\n"; } rss += "</channel></rss>"; response.getWriter().write(rss); } response.flushBuffer(); } } } /** * Process the actual request. * * @param request * The request to be processed. * @param response * The response to be created. * @param content * Whether the request body should be written (GET) or not * (HEAD). * @throws IOException * If something fails at I/O level. * * @author BalusC * @link * http://balusc.blogspot.com/2009/02/fileservlet-supporting-resume-and * .html */ private void processStaticRequest(File file, HttpServletRequest request, HttpServletResponse response, boolean content) throws IOException { // Validate the requested file // ------------------------------------------------------------ if (file == null) { // Get requested file by path info. String requestedFile = request.getPathInfo(); // Check if file is actually supplied to the request URL. if (requestedFile == null) { // Do your thing if the file is not supplied to the request URL. // Throw an exception, or send 404, or show default/warning // page, or // just ignore it. response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // URL-decode the file name (might contain spaces and on) and // prepare // file object. file = new File(rootFolder, URLDecoder.decode(requestedFile, "UTF-8")); } // Check if file actually exists in filesystem. if (!file.exists()) { // Do your thing if the file appears to be non-existing. // Throw an exception, or send 404, or show default/warning page, or // just ignore it. response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } // Prepare some variables. The ETag is an unique identifier of the file. String fileName = file.getName(); long length = file.length(); long lastModified = file.lastModified(); String eTag = fileName + "_" + length + "_" + lastModified; long expires = System.currentTimeMillis() + DEFAULT_EXPIRE_TIME; // Validate request headers for caching // --------------------------------------------------- // If-None-Match header should contain "*" or ETag. If so, then return // 304. String ifNoneMatch = request.getHeader("If-None-Match"); if (ifNoneMatch != null && matches(ifNoneMatch, eTag)) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setHeader("ETag", eTag); // Required in 304. response.setDateHeader("Expires", expires); // Postpone cache with 1 // week. return; } // If-Modified-Since header should be greater than LastModified. If so, // then return 304. // This header is ignored if any If-None-Match header is specified. long ifModifiedSince = request.getDateHeader("If-Modified-Since"); if (ifNoneMatch == null && ifModifiedSince != -1 && ifModifiedSince + 1000 > lastModified) { response.setStatus(HttpServletResponse.SC_NOT_MODIFIED); response.setHeader("ETag", eTag); // Required in 304. response.setDateHeader("Expires", expires); // Postpone cache with 1 // week. return; } // Validate request headers for resume // ---------------------------------------------------- // If-Match header should contain "*" or ETag. If not, then return 412. String ifMatch = request.getHeader("If-Match"); if (ifMatch != null && !matches(ifMatch, eTag)) { response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return; } // If-Unmodified-Since header should be greater than LastModified. If // not, then return 412. long ifUnmodifiedSince = request.getDateHeader("If-Unmodified-Since"); if (ifUnmodifiedSince != -1 && ifUnmodifiedSince + 1000 <= lastModified) { response.sendError(HttpServletResponse.SC_PRECONDITION_FAILED); return; } // Validate and process range // ------------------------------------------------------------- // Prepare some variables. The full Range represents the complete file. Range full = new Range(0, length - 1, length); List<Range> ranges = new ArrayList<Range>(); // Validate and process Range and If-Range headers. String range = request.getHeader("Range"); if (range != null) { // Range header should match format "bytes=n-n,n-n,n-n...". If not, // then return 416. if (!range.matches("^bytes=\\d*-\\d*(,\\d*-\\d*)*$")) { response.setHeader("Content-Range", "bytes */" + length); // Required // in // 416. response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); return; } // If-Range header should either match ETag or be greater then // LastModified. If not, // then return full file. String ifRange = request.getHeader("If-Range"); if (ifRange != null && !ifRange.equals(eTag)) { try { long ifRangeTime = request.getDateHeader("If-Range"); // Throws // IAE // if // invalid. if (ifRangeTime != -1 && ifRangeTime + 1000 < lastModified) { ranges.add(full); } } catch (IllegalArgumentException ignore) { ranges.add(full); } } // If any valid If-Range header, then process each part of byte // range. if (ranges.isEmpty()) { for (String part : range.substring(6).split(",")) { // Assuming a file with length of 100, the following // examples returns bytes at: // 50-80 (50 to 80), 40- (40 to length=100), -20 // (length-20=80 to length=100). long start = sublong(part, 0, part.indexOf("-")); long end = sublong(part, part.indexOf("-") + 1, part.length()); if (start == -1) { start = length - end; end = length - 1; } else if (end == -1 || end > length - 1) { end = length - 1; } // Check if Range is syntactically valid. If not, then // return 416. if (start > end) { response.setHeader("Content-Range", "bytes */" + length); // Required // in // 416. response.sendError(HttpServletResponse.SC_REQUESTED_RANGE_NOT_SATISFIABLE); return; } // Add range. ranges.add(new Range(start, end, length)); } } } // Prepare and initialize response // -------------------------------------------------------- // Get content type by file name and set default GZIP support and // content disposition. String contentType = getServletContext().getMimeType(fileName); boolean acceptsGzip = false; String disposition = "inline"; // If content type is unknown, then set the default value. // For all content types, see: // http://www.w3schools.com/media/media_mimeref.asp // To add new content types, add new mime-mapping entry in web.xml. if (contentType == null) { contentType = "application/octet-stream"; } // If content type is text, then determine whether GZIP content encoding // is supported by // the browser and expand content type with the one and right character // encoding. if (contentType.startsWith("text")) { String acceptEncoding = request.getHeader("Accept-Encoding"); acceptsGzip = acceptEncoding != null && accepts(acceptEncoding, "gzip"); contentType += ";charset=UTF-8"; } // Else, expect for images, determine content disposition. If content // type is supported by // the browser, then set to inline, else attachment which will pop a // 'save as' dialogue. else if (!contentType.startsWith("image")) { String accept = request.getHeader("Accept"); disposition = accept != null && accepts(accept, contentType) ? "inline" : "attachment"; } // Initialize response. response.reset(); response.setBufferSize(DEFAULT_BUFFER_SIZE); response.setHeader("Content-Disposition", disposition + ";filename=\"" + fileName + "\""); response.setHeader("Accept-Ranges", "bytes"); response.setHeader("ETag", eTag); response.setDateHeader("Last-Modified", lastModified); response.setDateHeader("Expires", expires); // Send requested file (part(s)) to client // ------------------------------------------------ // Prepare streams. RandomAccessFile input = null; OutputStream output = null; try { // Open streams. input = new RandomAccessFile(file, "r"); output = response.getOutputStream(); if (ranges.isEmpty() || ranges.get(0) == full) { // Return full file. Range r = full; response.setContentType(contentType); response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total); if (content) { if (acceptsGzip) { // The browser accepts GZIP, so GZIP the content. response.setHeader("Content-Encoding", "gzip"); output = new GZIPOutputStream(output, DEFAULT_BUFFER_SIZE); } else { // Content length is not directly predictable in case of // GZIP. // So only add it if there is no means of GZIP, else // browser will hang. response.setHeader("Content-Length", String.valueOf(r.length)); } // Copy full range. copy(input, output, r.start, r.length); } } else if (ranges.size() == 1) { // Return single part of file. Range r = ranges.get(0); response.setContentType(contentType); response.setHeader("Content-Range", "bytes " + r.start + "-" + r.end + "/" + r.total); response.setHeader("Content-Length", String.valueOf(r.length)); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206. if (content) { // Copy single part range. copy(input, output, r.start, r.length); } } else { // Return multiple parts of file. response.setContentType("multipart/byteranges; boundary=" + MULTIPART_BOUNDARY); response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); // 206. if (content) { // Cast back to ServletOutputStream to get the easy println // methods. ServletOutputStream sos = (ServletOutputStream) output; // Copy multi part range. for (Range r : ranges) { // Add multipart boundary and header fields for every // range. sos.println(); sos.println("--" + MULTIPART_BOUNDARY); sos.println("Content-Type: " + contentType); sos.println("Content-Range: bytes " + r.start + "-" + r.end + "/" + r.total); // Copy single part range of multi part range. copy(input, output, r.start, r.length); } // End with multipart boundary. sos.println(); sos.println("--" + MULTIPART_BOUNDARY + "--"); } } } finally { // Gently close streams. close(output); close(input); } } /** * Save config file send by editor * * @param request * @param response * @throws ServletException * @throws IOException */ private final void saveConfig(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String fileName = request.getParameter("config"); File file = new File(userFileFolder, URLDecoder.decode(fileName, "UTF-8")); response.setContentType(MediaType.APPLICATION_JSON); class Response { public Boolean success = false; public String message = ""; Response() { } } Response resp = new Response(); if (file.exists()) { // file exists and we only write if the file exists (creating new files this way is prohibited for security // reasons logger.debug("save config file'{}' requested", file); // check is backup folder exists File backupFolder = new File(userFileFolder, "config/backup/"); boolean backup = true; if (!backupFolder.exists()) { try { backupFolder.mkdir(); } catch (SecurityException e) { logger.error("Error creating backup directory for CometVisu config files"); backup = false; } } if (backup) { // Backup existing file File backupFile = new File(backupFolder, file.getName() + "-" + System.currentTimeMillis()); FileUtils.copyFile(file, backupFile); } // write data to file String data = request.getParameter("data"); FileUtils.writeStringToFile(file, data); resp.success = true; resp.message = "File saved"; } response.getWriter().write(marshalJson(resp)); response.flushBuffer(); } /** * replaces the dataproviders in * <cometvisu-src>/editor/dataproviders/*.(php|json) + * * @param file * @param request * @param response * @throws ServletException * @throws IOException */ private final void dataProviderService(File file, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { logger.debug("dataprovider '{}' requested", file.getName()); List<Object> beans = new ArrayList<Object>(); if (file.getName().equals("dpt_list.json")) { // return all transforms available for openhab for (Transform transform : Transform.values()) { DataBean bean = new DataBean(); bean.label = transform.toString().toLowerCase(); bean.value = "OH:" + bean.label; beans.add(bean); } } else if (file.getName().equals("list_all_addresses.php")) { // all item names for (Item item : this.cometVisuApp.getItemRegistry().getItems()) { ItemBean bean = new ItemBean(); bean.value = item.getName(); bean.label = item.getName(); // TODO handle other types bean.hints.put("transform", "OH:string"); beans.add(bean); } } else if (file.getName().equals("list_all_icons.php")) { // all item names File iconDir = new File(rootFolder, "icon/knx-uf-iconset/128x128_white/"); FilenameFilter filter = new FilenameFilter() { @Override public boolean accept(File dir, String name) { // TODO Auto-generated method stub return name.endsWith(".png"); } }; for (File iconFile : iconDir.listFiles(filter)) { if (iconFile.isFile()) { String iconName = iconFile.getName().replace(".png", ""); DataBean bean = new DataBean(); bean.label = iconName; bean.value = iconName; beans.add(bean); } } } else if (file.getName().equals("list_all_plugins.php")) { // all plugins // all item names File iconDir = new File(rootFolder, "plugins/"); for (File icon : iconDir.listFiles()) { if (icon.isDirectory()) { DataBean bean = new DataBean(); bean.label = icon.getName(); bean.value = icon.getName(); beans.add(bean); } } } else if (file.getName().equals("get_designs.php")) { // all designs File designDir = new File(rootFolder, "designs/"); for (File design : designDir.listFiles()) { if (design.isDirectory()) { beans.add(design.getName()); } } } else if (file.getName().equals("list_all_rrds.php")) { // all item names } response.setContentType(MediaType.APPLICATION_JSON); response.getWriter().write(marshalJson(beans)); response.flushBuffer(); } private String marshalJson(Object bean) { Gson gson = new Gson(); return gson.toJson(bean); } /** * Service. */ private final void phpService(File file, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { Env env = null; WriteStream ws = null; QuercusHttpServletRequest req = new QuercusHttpServletRequestImpl(request); QuercusHttpServletResponse res = new QuercusHttpServletResponseImpl(response); try { Path path = getPath(file, req); logger.info("phpService path: " + path); QuercusPage page; try { page = engine.getQuercus().parse(path); } catch (FileNotFoundException e) { // php/2001 logger.debug(e.toString(), e); response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } ws = openWrite(response); // php/2002 // for non-Resin containers // for servlet filters that do post-request work after Quercus ws.setDisableCloseSource(true); // php/6006 ws.setNewlineString("\n"); QuercusContext quercus = engine.getQuercus(); env = quercus.createEnv(page, ws, req, res); // php/815d env.setPwd(path.getParent()); logger.info("setting user dir to " + path.getParent().getNativePath()); System.setProperty("user.dir", path.getParent().getNativePath()); quercus.setServletContext(new QuercusServletContextImpl(_servletContext)); try { env.start(); // php/2030, php/2032, php/2033 // Jetty hides server classes from web-app // http://docs.codehaus.org/display/JETTY/Classloading // // env.setGlobalValue("request", env.wrapJava(request)); // env.setGlobalValue("response", env.wrapJava(response)); // env.setGlobalValue("servletContext", // env.wrapJava(_servletContext)); StringValue prepend = quercus.getIniValue("auto_prepend_file").toStringValue(env); if (prepend.length() > 0) { Path prependPath = env.lookup(prepend); if (prependPath == null) env.error(L.l("auto_prepend_file '{0}' not found.", prepend)); else { QuercusPage prependPage = engine.getQuercus().parse(prependPath); prependPage.executeTop(env); } } env.executeTop(); StringValue append = quercus.getIniValue("auto_append_file").toStringValue(env); if (append.length() > 0) { Path appendPath = env.lookup(append); if (appendPath == null) env.error(L.l("auto_append_file '{0}' not found.", append)); else { QuercusPage appendPage = engine.getQuercus().parse(appendPath); appendPage.executeTop(env); } } // return; } catch (QuercusExitException e) { throw e; } catch (QuercusErrorException e) { throw e; } catch (QuercusLineRuntimeException e) { logger.debug(e.toString(), e); ws.println(e.getMessage()); // return; } catch (QuercusValueException e) { logger.debug(e.toString(), e); ws.println(e.toString()); // return; } catch (StackOverflowError e) { RuntimeException myException = new RuntimeException( L.l("StackOverflowError at {0}", env.getLocation()), e); throw myException; } catch (Throwable e) { if (response.isCommitted()) e.printStackTrace(ws.getPrintWriter()); ws = null; throw e; } finally { if (env != null) env.close(); // don't want a flush for an exception if (ws != null && env != null && env.getDuplex() == null) ws.close(); System.setProperty("user.dir", defaultUserDir); } } catch (QuercusDieException e) { // normal exit logger.trace(e.getMessage(), e); } catch (QuercusExitException e) { // normal exit logger.trace(e.getMessage(), e); } catch (QuercusErrorException e) { // error exit logger.error(e.getMessage(), e); } catch (RuntimeException e) { throw e; } catch (Throwable e) { handleThrowable(response, e); } } protected Path getPath(File file, QuercusHttpServletRequest req) { // php/8173 Path pwd = engine.getQuercus().getPwd().copy(); String servletPath = QuercusRequestAdapter.getPageServletPath(req); if (servletPath.startsWith("/")) { servletPath = servletPath.substring(1); } Path path = pwd.lookupChild(servletPath); // php/2010, php/2011, php/2012 if (path.isFile()) { return path; } StringBuilder sb = new StringBuilder(); if (path.exists()) sb.append(servletPath); String pathInfo = QuercusRequestAdapter.getPagePathInfo(req); if (pathInfo != null) { if (pathInfo.startsWith("/")) { pathInfo = pathInfo.substring(1); } if (sb.length() > 1) sb.append("/"); sb.append(pathInfo); } String scriptPath = sb.toString(); path = pwd.lookupChild(scriptPath); if (file != null && !path.isFile()) path = path.lookupChild(file.getName()); logger.info("ServletPath '{}', PathInfo: '{}', ScriptPath: '{}' => Path '{}'", servletPath, pathInfo, scriptPath, path); return path; /* * jetty getRealPath() de-references symlinks, which causes problems * with MergePath // php/8173 Path pwd = getQuercus().getPwd().copy(); * * String scriptPath = QuercusRequestAdapter.getPageServletPath(req); * String pathInfo = QuercusRequestAdapter.getPagePathInfo(req); * * Path path = pwd.lookup(req.getRealPath(scriptPath)); * * if (path.isFile()) return path; * * // XXX: include * * String fullPath; if (pathInfo != null) fullPath = scriptPath + * pathInfo; else fullPath = scriptPath; * * return pwd.lookup(req.getRealPath(fullPath)); */ } protected void handleThrowable(HttpServletResponse response, Throwable e) throws IOException, ServletException { throw new ServletException(e); } protected WriteStream openWrite(HttpServletResponse response) throws IOException { WriteStream ws; OutputStream out = response.getOutputStream(); ws = Vfs.openWrite(out); return ws; } /** * Makes sure the servlet container supports Servlet API 2.4+. */ protected void checkServletAPIVersion() { int major = _servletContext.getMajorVersion(); int minor = _servletContext.getMinorVersion(); if (major < 2 || major == 2 && minor < 4) throw new QuercusRuntimeException(L.l("Quercus requires Servlet API 2.4+.")); } /** * Returns true if the given accept header accepts the given value. * * @param acceptHeader * The accept header. * @param toAccept * The value to be accepted. * @return True if the given accept header accepts the given value. */ private static boolean accepts(String acceptHeader, String toAccept) { String[] acceptValues = acceptHeader.split("\\s*(,|;)\\s*"); Arrays.sort(acceptValues); return Arrays.binarySearch(acceptValues, toAccept) > -1 || Arrays.binarySearch(acceptValues, toAccept.replaceAll("/.*$", "/*")) > -1 || Arrays.binarySearch(acceptValues, "*/*") > -1; } /** * Returns true if the given match header matches the given value. * * @param matchHeader * The match header. * @param toMatch * The value to be matched. * @return True if the given match header matches the given value. */ private static boolean matches(String matchHeader, String toMatch) { String[] matchValues = matchHeader.split("\\s*,\\s*"); Arrays.sort(matchValues); return Arrays.binarySearch(matchValues, toMatch) > -1 || Arrays.binarySearch(matchValues, "*") > -1; } /** * Returns a substring of the given string value from the given begin index * to the given end index as a long. If the substring is empty, then -1 will * be returned * * @param value * The string value to return a substring as long for. * @param beginIndex * The begin index of the substring to be returned as long. * @param endIndex * The end index of the substring to be returned as long. * @return A substring of the given string value as long or -1 if substring * is empty. */ private static long sublong(String value, int beginIndex, int endIndex) { String substring = value.substring(beginIndex, endIndex); return (substring.length() > 0) ? Long.parseLong(substring) : -1; } /** * Copy the given byte range of the given input to the given output. * * @param input * The input to copy the given range to the given output for. * @param output * The output to copy the given range from the given input for. * @param start * Start of the byte range. * @param length * Length of the byte range. * @throws IOException * If something fails at I/O level. */ private static void copy(RandomAccessFile input, OutputStream output, long start, long length) throws IOException { byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; int read; if (input.length() == length) { // Write full range. while ((read = input.read(buffer)) > 0) { output.write(buffer, 0, read); } } else { // Write partial range. input.seek(start); long toRead = length; while ((read = input.read(buffer)) > 0) { if ((toRead -= read) > 0) { output.write(buffer, 0, read); } else { output.write(buffer, 0, (int) toRead + read); break; } } } } /** * Close the given resource. * * @param resource * The resource to be closed. */ private static void close(Closeable resource) { if (resource != null) { try { resource.close(); } catch (IOException ignore) { // Ignore IOException. If you want to handle this anyway, it // might be useful to know // that this will generally only be thrown when the client // aborted the request. } } } /** * This class represents a byte range. */ protected class Range { long start; long end; long length; long total; /** * Construct a byte range. * * @param start * Start of the byte range. * @param end * End of the byte range. * @param total * Total length of the byte source. */ public Range(long start, long end, long total) { this.start = start; this.end = end; this.length = end - start + 1; this.total = total; } } }