| /** |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You under the Apache License, Version 2.0 |
| * (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.oodt.grid; |
| |
| import java.io.BufferedInputStream; |
| import java.io.IOException; |
| import java.util.Iterator; |
| import java.util.List; |
| import javax.servlet.ServletException; |
| import javax.servlet.http.HttpServletRequest; |
| import javax.servlet.http.HttpServletResponse; |
| |
| import org.apache.oodt.product.HttpRedirectException; |
| import org.apache.oodt.product.LargeProductQueryHandler; |
| import org.apache.oodt.product.ProductException; |
| import org.apache.oodt.product.QueryHandler; |
| import org.apache.oodt.product.Retriever; |
| import org.apache.oodt.xmlquery.LargeResult; |
| import org.apache.oodt.xmlquery.Result; |
| import org.apache.oodt.xmlquery.XMLQuery; |
| |
| |
| /** |
| * Product query servlet handles product queries. It always returns the first matching |
| * product, if any. If no handler can provide a product, it returns 404 Not Found. If |
| * there are no query handlers, it returns 404 Not Found. |
| * |
| */ |
| public class ProductQueryServlet extends QueryServlet { |
| /** {@inheritDoc} */ |
| protected List getServers(Configuration config) { |
| return config.getProductServers(); |
| } |
| |
| /** {@inheritDoc} */ |
| protected void handleQuery(XMLQuery query, List handlers, HttpServletRequest req, HttpServletResponse res) |
| throws IOException, ServletException { |
| if (handlers.isEmpty()) { |
| res.sendError(HttpServletResponse.SC_NOT_FOUND, "no query handlers available to handle query"); |
| return; |
| } |
| |
| try { // OK, let's try |
| for (Iterator i = handlers.iterator(); i.hasNext();) { // Try each query handler |
| QueryHandler handler = (QueryHandler) i.next(); // Get the query handler |
| query = handler.query(query); // Give it the query |
| if (!query.getResults().isEmpty()) { // Did it give any result? |
| Result result = (Result) query.getResults().get(0); // Yes, get the result |
| deliverResult(handler, result, res); // And deliver it |
| return; // Done! |
| } |
| } |
| } catch (ProductException ex) { |
| if (ex instanceof HttpRedirectException) { |
| HttpRedirectException hre = (HttpRedirectException) ex; |
| res.sendRedirect(hre.getLocation()); |
| } else { |
| res.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, ex.getMessage()); |
| } |
| return; |
| } |
| |
| res.sendError(HttpServletResponse.SC_NOT_FOUND, "no matching products from any query handler"); |
| } |
| |
| /** |
| * Deliver a result. This streams the product data. |
| * |
| * @param handler Which handler produced the result. |
| * @param result The result. |
| * @param res The HTTP response to which to send the result data. |
| * @throws IOException if an error occurs. |
| */ |
| private void deliverResult(QueryHandler handler, Result result, HttpServletResponse res) throws IOException { |
| characterize(handler, result, res); // First, describe it using HTTP headers |
| if (result instanceof LargeResult) { // Is it a large result? |
| LargeResult lr = (LargeResult) result; // Yes, this is gonna take some special work |
| LargeProductQueryHandler lpqh = (LargeProductQueryHandler) handler; // First treat 'em as large |
| ProductRetriever retriever = new ProductRetriever(lpqh); // Large ones need a retriever |
| lr.setRetriever(retriever); // Set the retriever |
| } |
| BufferedInputStream in = null; // Start with no input stream |
| try { // Then try ... |
| in = new BufferedInputStream(result.getInputStream()); // To open the input stream |
| byte[] buf = new byte[512]; // And a byte buffer for data |
| int num; // And a place to count data |
| while ((num = in.read(buf)) != -1) // While we read |
| res.getOutputStream().write(buf, 0, num); // We write |
| res.getOutputStream().flush(); // Flush to commit response |
| } finally { // And finally |
| if (in != null) try { // If we opened it |
| in.close(); // Close it |
| } catch (IOException ignore) {} // Ignoring any error during closing |
| } // Because come on, it's just closing! |
| } // For fsck's sake! |
| |
| /** |
| * Characterize a result by using HTTP headers. |
| * |
| * @param result Result to characterize. |
| * @param res HTTP response to set headers in. |
| */ |
| private void characterize(QueryHandler handler, Result result, HttpServletResponse res) { |
| String contentType = result.getMimeType(); // Grab the content type |
| res.setContentType(contentType); // Set it |
| long size = result.getSize(); // Grab the size |
| if (size >= 0) |
| res.addHeader("Content-Length", String.valueOf(size)); // Don't use setContentLength(int) |
| if (!displayable(contentType)) // Finally, if a browser can't show it |
| this.suggestFilename(handler, result, res); // Then suggest a save-as filename |
| } |
| |
| /** |
| * Tell if a result is displayable. This compares its MIME type to a list of MIME |
| * types commonly displayble by browsers. |
| * |
| * @param contentType MIME type. |
| * @return a <code>boolean</code> value. |
| */ |
| protected static boolean displayable(String contentType) { |
| for (int i = 0; i < DISPLAYABLE_TYPES.length; ++i) // For each displayable type |
| if (DISPLAYABLE_TYPES[i].equals(contentType)) return true; // Does it match? |
| return false; // None of 'em do, it's not displayable |
| } |
| |
| /** |
| * We can suggest a filename (if the client happens to be a browser) using a |
| * content-disposition header. |
| * |
| * @param res a <code>HttpServletResponse</code> value. |
| */ |
| protected void suggestFilename(QueryHandler handler, Result result, HttpServletResponse res) { |
| |
| String resource = result.getResourceID(); |
| if (resource == null || resource.length() == 0) resource = "product.dat"; |
| |
| // suggest some names based on resource mime type |
| String contentType = res.getContentType(); |
| |
| // "zip" mime types |
| if (contentType!=null) { |
| if (contentType.equals("application/x-compressed") || |
| contentType.equals("application/x-zip-compressed") || |
| contentType.equals("application/zip") || |
| contentType.equals("multipart/x-zip") ) { |
| |
| // resource = resource.replaceAll("\\..+",".zip"); // replace extension with .zip |
| resource = "products_" + resource + ".zip"; |
| } |
| } |
| |
| // set "Content-disposition" header |
| res.addHeader("Content-disposition", "attachment; filename=\"" + resource + "\""); |
| |
| } |
| |
| /** |
| * MIME types commonly displayable by browsers. |
| */ |
| private static final String[] DISPLAYABLE_TYPES = { |
| "text/plain", "text/richtext", "text/enriched", "text/tab-separated-values", "text/html", "text/xml", "text/rtf", |
| "message/rfc822", "message/partial", "message/external-body", "message/news", "message/http", |
| "message/delivery-status", "message/disposition-notification", "message/s-http", "application/rtf", |
| "application/pdf", "image/jpeg", "image/gif", "image/tiff", "image/png", "audio/basic", "audio/32kadpcm", |
| "audio/mpeg", "video/mpeg", "video/quicktime" |
| }; |
| |
| /** |
| * Retriever that retrieves product data over a method call boundary to a large |
| * product query handler. |
| */ |
| private static class ProductRetriever implements Retriever { |
| /** |
| * Creates a new <code>ProductRetriever</code> instance. |
| * |
| * @param handler a <code>LargeProductQueryHandler</code> value. |
| */ |
| public ProductRetriever(LargeProductQueryHandler handler) { |
| this.handler = handler; |
| } |
| |
| /** {@inheritDoc} */ |
| public byte[] retrieveChunk(String id, long offset, int len) throws ProductException { |
| return handler.retrieveChunk(id, offset, len); |
| } |
| |
| /** {@inheritDoc} */ |
| public void close(String id) throws ProductException { |
| handler.close(id); |
| } |
| |
| /** Handler to use. */ |
| private LargeProductQueryHandler handler; |
| } |
| } |
| |