Package org.exist.http.urlrewrite

Source Code of org.exist.http.urlrewrite.XQueryURLRewrite$CachingResponseWrapper

/*
*  eXist Open Source Native XML Database
*  Copyright (C) 2001-08 Wolfgang M. Meier
*  wolfgang@exist-db.org
*  http://exist-db.org
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU Lesser General Public License
*  as published by the Free Software Foundation; either version 2
*  of the License, or (at your option) any later version.
*
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU Lesser General Public License for more details.
*
*  You should have received a copy of the GNU Lesser General Public License
*  along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*  $Id$
*/
package org.exist.http.urlrewrite;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;

import org.apache.log4j.Logger;

import org.exist.http.servlets.Authenticator;
import org.exist.http.servlets.BasicAuthenticator;
import org.exist.security.internal.web.HttpAccount;
import org.exist.source.Source;
import org.exist.source.DBSource;
import org.exist.source.SourceFactory;
import org.exist.source.FileSource;
import org.exist.xquery.functions.request.RequestModule;
import org.exist.xquery.functions.response.ResponseModule;
import org.exist.xquery.functions.session.SessionModule;
import org.exist.xquery.*;
import org.exist.xquery.value.Sequence;
import org.exist.xquery.value.Item;
import org.exist.xquery.value.Type;
import org.exist.xquery.value.NodeValue;
import org.exist.Namespaces;
import org.exist.EXistException;
import org.exist.collections.Collection;
import org.exist.dom.DocumentImpl;
import org.exist.dom.BinaryDocument;
import org.exist.xmldb.XmldbURI;
import org.exist.security.*;
import org.exist.security.xacml.AccessContext;
import org.exist.storage.BrokerPool;
import org.exist.storage.DBBroker;
import org.exist.storage.XQueryPool;
import org.exist.storage.lock.Lock;
import org.exist.storage.serializers.Serializer;
import org.exist.util.MimeType;
import org.exist.util.serializer.SAXSerializer;
import org.exist.util.serializer.SerializerPool;
import org.exist.http.servlets.HttpRequestWrapper;
import org.exist.http.servlets.HttpResponseWrapper;
import org.exist.http.Descriptor;

import org.apache.commons.io.output.ByteArrayOutputStream;

import org.w3c.dom.Node;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import org.xml.sax.SAXException;
import org.xmldb.api.base.Database;
import org.xmldb.api.DatabaseManager;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponseWrapper;
import javax.xml.transform.OutputKeys;

import java.net.URISyntaxException;
import java.util.*;
import java.util.Map.Entry;
import java.util.regex.Pattern;
import java.util.regex.Matcher;

import static java.nio.charset.StandardCharsets.UTF_8;

/**
* A servlet to redirect HTTP requests. Similar to the popular UrlRewriteFilter, but
* based on XQuery.
*
* The request is passed to an XQuery whose return value determines where the request will be
* redirected to. An empty return value means the request will be passed through the filter
* untouched. Otherwise, the query should return a single XML element, which will instruct the filter
* how to further process the request. Details about the format can be found in the main documentation.
*
* The request is forwarded via {@link javax.servlet.RequestDispatcher#forward(javax.servlet.ServletRequest, javax.servlet.ServletResponse)}.
* Contrary to HTTP forwarding, there is no additional roundtrip to the client. It all happens on
* the server. The client will not notice the redirect.
*
* Please read the <a href="http://exist-db.org/urlrewrite.html">documentation</a> for further information.
*/
public class XQueryURLRewrite extends HttpServlet {

    private static final Logger LOG = Logger.getLogger(XQueryURLRewrite.class);

    public final static String RQ_ATTR = "org.exist.forward";
    public final static String RQ_ATTR_REQUEST_URI = "org.exist.forward.request-uri";
    public final static String RQ_ATTR_SERVLET_PATH = "org.exist.forward.servlet-path";
    public final static String RQ_ATTR_RESULT = "org.exist.forward.result";
    public final static String RQ_ATTR_ERROR = "org.exist.forward.error";
   
   
    public final static String DRIVER = "org.exist.xmldb.DatabaseImpl";

    private final static Pattern NAME_REGEX = Pattern.compile("^.*/([^/]+)$", 0);

    private ServletConfig config;

    private final Map<String, ModelAndView> urlCache =
            Collections.synchronizedMap(new TreeMap<String, ModelAndView>());

    protected Subject defaultUser = null;
    protected BrokerPool pool;

    // path to the query
    private String query = null;
   
    //private boolean checkModified = true;

    private boolean compiledCache = true;

    private RewriteConfig rewriteConfig;

    private Authenticator authenticator;

    @Override
    public void init(ServletConfig filterConfig) throws ServletException {
        // save FilterConfig for later use
        this.config = filterConfig;

        query = filterConfig.getInitParameter("xquery");
       
//        String opt = filterConfig.getInitParameter("check-modified");
//        if (opt != null)
//            checkModified = opt != null && opt.equalsIgnoreCase("true");
       
        final String opt = filterConfig.getInitParameter("compiled-cache");
        if (opt != null)
          {compiledCache = opt != null && opt.equalsIgnoreCase("true");}
       
    }

    @Override
    protected void service(HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
        if (rewriteConfig == null) {
            configure();
            rewriteConfig = new RewriteConfig(this);
        }
       
        final long start = System.currentTimeMillis();
        final HttpServletRequest request = servletRequest;
        final HttpServletResponse response = servletResponse;

        if (LOG.isTraceEnabled()) {
            LOG.trace(request.getRequestURI());
        }
        final Descriptor descriptor = Descriptor.getDescriptorSingleton();
        if(descriptor != null && descriptor.requestsFiltered())
        {
            final String attr = (String) request.getAttribute("XQueryURLRewrite.forwarded");
            if (attr == null) {
//                request = new HttpServletRequestWrapper(request, /*formEncoding*/ "utf-8" );
                //logs the request if specified in the descriptor
                descriptor.doLogRequestInReplayLog(request);

                request.setAttribute("XQueryURLRewrite.forwarded", "true");
            }
        }

        Subject user = defaultUser;
     
        Subject requestUser = HttpAccount.getUserFromServletRequest(request);
        if (requestUser != null) {
            user = requestUser;
        } else {
            // Secondly try basic authentication
            final String auth = request.getHeader("Authorization");
            if (auth != null) {
                requestUser = authenticator.authenticate(request, response);
                if (requestUser != null) {
                    user = requestUser;
                }
            }
        }

        try {
            configure();
            //checkCache(user);

            final RequestWrapper modifiedRequest = new RequestWrapper(request);
            final URLRewrite staticRewrite = rewriteConfig.lookup(modifiedRequest);
            if (staticRewrite != null && !staticRewrite.isControllerForward()) {
                modifiedRequest.setPaths(staticRewrite.resolve(modifiedRequest), staticRewrite.getPrefix());
               
                if (LOG.isTraceEnabled()) {
                    LOG.trace("Forwarding to target: " + staticRewrite.getTarget());
                }
                staticRewrite.doRewrite(modifiedRequest, response);
            } else {
               
              if (LOG.isTraceEnabled()) {
                    LOG.trace("Processing request URI: " + request.getRequestURI());
              }
                if (staticRewrite != null) {
                    // fix the request URI
                    staticRewrite.updateRequest(modifiedRequest);
                }

                // check if the request URI is already in the url cache
                ModelAndView modelView = getFromCache(
                    request.getHeader("Host") + request.getRequestURI(),
                    user);
               
                if (LOG.isDebugEnabled()) {
                  LOG.debug(
                  "Checked cache for URI: " + modifiedRequest.getRequestURI() +
                  " original: " + request.getRequestURI()
              );
                }
                // no: create a new model and view configuration
                if (modelView == null) {
                    modelView = new ModelAndView();
                    // Execute the query
                    Sequence result = Sequence.EMPTY_SEQUENCE;
                    DBBroker broker = null;
                    try {
                        broker = pool.get(user);
                        modifiedRequest.setAttribute(RQ_ATTR_REQUEST_URI, request.getRequestURI());
                       
                        final Properties outputProperties = new Properties();
                       
                    outputProperties.setProperty(OutputKeys.INDENT, "yes");
                    outputProperties.setProperty(OutputKeys.ENCODING, "UTF-8");
                    outputProperties.setProperty(OutputKeys.MEDIA_TYPE, MimeType.XML_TYPE.getName());
                       
                        result = runQuery(broker, modifiedRequest, response, modelView, staticRewrite, outputProperties);

                        logResult(broker, result);
                       
                        if (response.isCommitted()) {
                          return;
                        }

                      // process the query result
                      if (result.getItemCount() == 1) {
                          final Item resource = result.itemAt(0);
                          if (!Type.subTypeOf(resource.getType(), Type.NODE))
                              {throw new ServletException("XQueryURLRewrite: urlrewrite query should return an element!");}
                          Node node = ((NodeValue) resource).getNode();
                          if (node.getNodeType() == Node.DOCUMENT_NODE)
                              {node = ((Document) node).getDocumentElement();}
                          if (node.getNodeType() != Node.ELEMENT_NODE) {
                              //throw new ServletException("Redirect XQuery should return an XML element!");
                            response(broker, response, outputProperties, result);
                            return;
                          }
                          Element elem = (Element) node;
                          if (!(Namespaces.EXIST_NS.equals(elem.getNamespaceURI()))) {
                            response(broker, response, outputProperties, result);
                            return;
  //                            throw new ServletException("Redirect XQuery should return an element in namespace " + Namespaces.EXIST_NS);
                          }
 
                          if (Namespaces.EXIST_NS.equals(elem.getNamespaceURI()) && "dispatch".equals(elem.getLocalName())) {
                              node = elem.getFirstChild();
                              while (node != null) {
                                  if (node.getNodeType() == Node.ELEMENT_NODE && Namespaces.EXIST_NS.equals(node.getNamespaceURI())) {
                                      final Element action = (Element) node;
                                      if ("view".equals(action.getLocalName())) {
                                          parseViews(modifiedRequest, action, modelView);
                                      } else if ("error-handler".equals(action.getLocalName())) {
                                        parseErrorHandlers(modifiedRequest, action, modelView);
                                      } else if ("cache-control".equals(action.getLocalName())) {
                                          final String option = action.getAttribute("cache");
                                          modelView.setUseCache("yes".equals(option));
                                      } else {
                                          final URLRewrite urw = parseAction(modifiedRequest, action);
                                          if (urw != null)
                                              {modelView.setModel(urw);}
                                      }
                                  }
                                  node = node.getNextSibling();
                              }
                              if (modelView.getModel() == null)
                                  {modelView.setModel(new PassThrough(config, elem, modifiedRequest));}
                          } else if (Namespaces.EXIST_NS.equals(elem.getNamespaceURI()) && "ignore".equals(elem.getLocalName())) {
                              modelView.setModel(new PassThrough(config, elem, modifiedRequest));
                              final NodeList nl = elem.getElementsByTagNameNS(Namespaces.EXIST_NS, "cache-control");
                              if (nl.getLength() > 0) {
                                  elem = (Element) nl.item(0);
                                  final String option = elem.getAttribute("cache");
                                  modelView.setUseCache("yes".equals(option));
                              }
                          } else {
                            response(broker, response, outputProperties, result);
                            return;
                          }
                      } else if (result.getItemCount() > 1) {
                            response(broker, response, outputProperties, result);
                          return;
                      }
                     
                      if (modelView.useCache()) {
                            LOG.debug("Caching request to " + request.getRequestURI());
                            urlCache.put(modifiedRequest.getHeader("Host") + request.getRequestURI(), modelView);
                        }

                    } finally {
                        pool.release(broker);
                    }

                    // store the original request URI to org.exist.forward.request-uri
                    modifiedRequest.setAttribute(RQ_ATTR_REQUEST_URI, request.getRequestURI());
                    modifiedRequest.setAttribute(RQ_ATTR_SERVLET_PATH, request.getServletPath());

                }
                if (LOG.isTraceEnabled()) {
                    LOG.trace("URLRewrite took " + (System.currentTimeMillis() - start) + "ms.");
                }
              final HttpServletResponse wrappedResponse =
                new CachingResponseWrapper(response, modelView.hasViews() || modelView.hasErrorHandlers());
                if (modelView.getModel() == null)
                    {modelView.setModel(new PassThrough(config, modifiedRequest));}

                if (staticRewrite != null) {
                  if (modelView.getModel().doResolve())
                    {staticRewrite.rewriteRequest(modifiedRequest);}
                  else
                    {modelView.getModel().setAbsolutePath(modifiedRequest);}
                }
                modifiedRequest.allowCaching(!modelView.hasViews());
                doRewrite(modelView.getModel(), modifiedRequest, wrappedResponse);

                int status = ((CachingResponseWrapper) wrappedResponse).getStatus();
                if (status == HttpServletResponse.SC_NOT_MODIFIED) {
                  response.flushBuffer();
                } else if (status < 400) {
                  if (modelView.hasViews())
                    {applyViews(modelView, modelView.views, response, modifiedRequest, wrappedResponse);}
                  else
                    {((CachingResponseWrapper) wrappedResponse).flush();}
                } else {
                  // HTTP response code indicates an error
                  if (modelView.hasErrorHandlers()) {
                        final byte[] data = ((CachingResponseWrapper) wrappedResponse).getData();
                        if (data != null)
                            {modifiedRequest.setAttribute(RQ_ATTR_ERROR, new String(data, UTF_8));}
                    applyViews(modelView, modelView.errorHandlers, response, modifiedRequest, wrappedResponse);
                  } else {
                    flushError(response, wrappedResponse);
                  }
                }
            }
//            Sequence result;
//            if ((result = (Sequence) request.getAttribute(RQ_ATTR_RESULT)) != null) {
//                writeResults(response, broker, result);
//            }
        } catch (final Throwable e) {
            LOG.error("Error while processing " + servletRequest.getRequestURI() + ": " + e.getMessage(), e);
            throw new ServletException("An error occurred while processing request to " + servletRequest.getRequestURI() + ": "
                    + e.getMessage(), e);

        }
    }
   
  private void applyViews(ModelAndView modelView, List<URLRewrite> views, HttpServletResponse response, RequestWrapper modifiedRequest,
      HttpServletResponse currentResponse)
      throws IOException, ServletException {
    int status;
        HttpServletResponse wrappedResponse = currentResponse;
    for (int i = 0; i < views.size(); i++) {
      final URLRewrite view = (URLRewrite) views.get(i);
     
      // get data returned from last action
      byte[] data = ((CachingResponseWrapper) wrappedResponse).getData();
      // determine request method to use for calling view
      String method = view.getMethod();
      if (method == null) {
        method = "POST"// default is POST
      }
     
      final RequestWrapper wrappedReq = new RequestWrapper(modifiedRequest);
      wrappedReq.allowCaching(false);
      wrappedReq.setMethod(method);
      wrappedReq.setBasePath(modifiedRequest.getBasePath());
      wrappedReq.setCharacterEncoding(wrappedResponse.getCharacterEncoding());
      wrappedReq.setContentType(wrappedResponse.getContentType());

      if (data != null)
        {wrappedReq.setData(data);}
     
      wrappedResponse = new CachingResponseWrapper(response, true);
      doRewrite(view, wrappedReq, wrappedResponse);

      // catch errors in the view
      status = ((CachingResponseWrapper) wrappedResponse).getStatus();
            if (status >= 400) {
                if (modelView != null && modelView.hasErrorHandlers()) {
                    data = ((CachingResponseWrapper) wrappedResponse).getData();
                    final String msg = data == null ? "" : new String(data, UTF_8);
                    modifiedRequest.setAttribute(RQ_ATTR_ERROR, msg);
                    applyViews(null, modelView.errorHandlers, response, modifiedRequest, wrappedResponse);
                    break;
                } else {
                    flushError(response, wrappedResponse);
                }
        break;
      } else if (i == views.size() - 1)
                {((CachingResponseWrapper)wrappedResponse).flush();}
    }
  }

    private void response(DBBroker broker, HttpServletResponse response, Properties outputProperties, Sequence resultSequence) throws IOException {

      final String encoding = outputProperties.getProperty(OutputKeys.ENCODING);
        final ServletOutputStream sout = response.getOutputStream();
        final PrintWriter output = new PrintWriter(new OutputStreamWriter(sout, encoding));
    if (!response.containsHeader("Content-Type")){
      String mimeType = outputProperties.getProperty(OutputKeys.MEDIA_TYPE);
      if (mimeType != null) {
        final int semicolon = mimeType.indexOf(';');
        if (semicolon != Constants.STRING_NOT_FOUND) {
          mimeType = mimeType.substring(0, semicolon);
        }
        response.setContentType(mimeType + "; charset=" + encoding);
      }
    }
       
//        response.addHeader( "pragma", "no-cache" );
//        response.addHeader( "Cache-Control", "no-cache" );

        final Serializer serializer = broker.getSerializer();
      serializer.reset();
   
      final SerializerPool serializerPool = SerializerPool.getInstance();

      final SAXSerializer sax = (SAXSerializer) serializerPool.borrowObject(SAXSerializer.class);
      try {
        sax.setOutput(output, outputProperties);

        serializer.setProperties(outputProperties);
        serializer.setSAXHandlers(sax, sax);
          serializer.toSAX(resultSequence, 1, resultSequence.getItemCount(), false, false);
         
      } catch (final SAXException e) {
        throw new IOException(e);
      } finally {
        serializerPool.returnObject(sax);
      }
      output.flush();
      output.close();
    }

  private void flushError(HttpServletResponse response, HttpServletResponse wrappedResponse) throws IOException {
        if (!response.isCommitted()) {
            final byte[] data = ((CachingResponseWrapper) wrappedResponse).getData();
            if (data != null) {
                response.setContentType(wrappedResponse.getContentType());
                response.setCharacterEncoding(wrappedResponse.getCharacterEncoding());
                response.getOutputStream().write(data);
                response.flushBuffer();
            }
        }
    }

  private ModelAndView getFromCache(String url, Subject user) throws EXistException, ServletException, PermissionDeniedException {
    /* Make sure we have a broker *before* we synchronize on urlCache or we may run
     * into a deadlock situation (with method checkCache)
     */
    final ModelAndView model = urlCache.get(url);
    if (model == null)
      {return null;}

    DBBroker broker = null;
    try {
      broker = pool.get(user);

      model.getSourceInfo().source.validate(broker.getSubject(), Permission.EXECUTE);
     
      if (model.getSourceInfo().source.isValid(broker) != Source.VALID) {
                ModelAndView removed = urlCache.remove(url);
        return null;
      }
     
      if (LOG.isDebugEnabled()) {
        LOG.debug("Using cached entry for " + url);
      }
      return model;
    } finally {
      pool.release(broker);
    }
  }
 
//    private void checkCache(Subject user) throws EXistException {
//        if (checkModified) {
//            // check if any of the currently used sources has been updated
//          DBBroker broker = null;
//            try {
//                broker = pool.get(user);
//
//                for (Entry<ModelAndView, Source> entry : sources.entrySet() )
//              if (entry.getValue().isValid(broker) != Source.VALID)
//                urlCache.remove(entry.getKey());
//
//            } finally {
//                pool.release(broker);
//            }
//        }
//    }

    protected void clearCaches() throws EXistException {
    urlCache.clear();
    }
   
    /**
     * Process a rewrite action. Method checks if the target path is mapped
     * to another action in controller-config.xml. If yes, replaces the current action
     * with the new action.
     *
     * @param action
     * @param request
     * @param response
     * @throws IOException
     * @throws ServletException
     */
    protected void doRewrite(URLRewrite action, RequestWrapper request, HttpServletResponse response) throws IOException, ServletException {
        if (action.getTarget() != null && !(action instanceof Redirect)) {
            final String uri = action.resolve(request);
            URLRewrite staticRewrite = rewriteConfig.lookup(uri, request.getServerName(), true, action);

            if (staticRewrite != null) {
                staticRewrite.copyFrom(action);
                action = staticRewrite;
                RequestWrapper modifiedRequest = new RequestWrapper(request);
                modifiedRequest.setPaths(uri, action.getPrefix());
               
                if (LOG.isTraceEnabled()) {
                    LOG.trace("Forwarding to : " + action.toString() + " url: " + action.getURI());
                }
                request = modifiedRequest;
            }
        }
        action.prepareRequest(request);
        action.doRewrite(request, response);
    }

    protected ServletConfig getConfig() {
        return config;
    }
   
    private URLRewrite parseAction(HttpServletRequest request, Element action) throws ServletException {
        URLRewrite rewrite = null;
        if ("forward".equals(action.getLocalName())) {
            rewrite = new PathForward(config, action, request.getRequestURI());
        } else if ("redirect".equals(action.getLocalName())) {
            rewrite = new Redirect(action, request.getRequestURI());
//        } else if ("call".equals(action.getLocalName())) {
//            rewrite = new ModuleCall(action, queryContext, request.getRequestURI());
        }
        return rewrite;
    }

    private void parseViews(HttpServletRequest request, Element view, ModelAndView modelView) throws ServletException {
        Node node = view.getFirstChild();
        while (node != null) {
            if (node.getNodeType() == Node.ELEMENT_NODE && Namespaces.EXIST_NS.equals(node.getNamespaceURI())) {
                final URLRewrite urw = parseAction(request, (Element) node);
                if (urw != null)
                    {modelView.addView(urw);}
            }
            node = node.getNextSibling();
        }
    }

    private void parseErrorHandlers(HttpServletRequest request, Element view, ModelAndView modelView) throws ServletException {
        Node node = view.getFirstChild();
        while (node != null) {
            if (node.getNodeType() == Node.ELEMENT_NODE && Namespaces.EXIST_NS.equals(node.getNamespaceURI())) {
                final URLRewrite urw = parseAction(request, (Element) node);
                if (urw != null)
                    {modelView.addErrorHandler(urw);}
            }
            node = node.getNextSibling();
        }
    }
   
    private void configure() throws ServletException {
      if (pool != null)
        {return;}
        try {
            final Class<?> driver = Class.forName(DRIVER);
            final Database database = (Database) driver.newInstance();
            database.setProperty("create-database", "true");
            DatabaseManager.registerDatabase(database);
            LOG.debug("Initialized database");
        } catch(final Exception e) {
            final String errorMessage="Failed to initialize database driver";
            LOG.error(errorMessage,e);
            throw new ServletException(errorMessage+": " + e.getMessage(), e);
        }

    try {
      pool = BrokerPool.getInstance();
    } catch (final EXistException e) {
            throw new ServletException("Could not intialize db: " + e.getMessage(), e);
    }
       
    defaultUser = pool.getSecurityManager().getGuestSubject();
   
    final String username = config.getInitParameter("user");
    if(username != null) {
      final String password = config.getInitParameter("password");
      try {
        Subject user = pool.getSecurityManager().authenticate(username, password);
            if (user != null && user.isAuthenticated())
              {defaultUser = user;}
      } catch (final AuthenticationException e) {
        LOG.error("User can not be authenticated ("+username+"), using default user.");
      }
    }
        authenticator = new BasicAuthenticator(pool);
    }

    private void logResult(DBBroker broker, Sequence result) throws IOException, SAXException {
        if (LOG.isTraceEnabled() && result.getItemCount() > 0) {
            final Serializer serializer = broker.getSerializer();
            serializer.reset();

            final Item item = result.itemAt(0);
            if (Type.subTypeOf(item.getType(), Type.NODE)) {
                LOG.trace(serializer.serialize((NodeValue) item));
            }
        }
    }

    @Override
    public void destroy() {
        config = null;
    }
   
    private SourceInfo getSourceInfo(DBBroker broker, RequestWrapper request, URLRewrite staticRewrite) throws ServletException {
        final String moduleLoadPath = config.getServletContext().getRealPath("/");
        final String basePath = staticRewrite == null ? "." : staticRewrite.getTarget();
        if (basePath == null) {
            return getSource(broker, moduleLoadPath);
        } else {
            return findSource(request, broker, basePath);
        }
    }

    private Sequence runQuery(DBBroker broker, RequestWrapper request, HttpServletResponse response,
                ModelAndView model,
                              URLRewrite staticRewrite, Properties outputProperties)
        throws ServletException, XPathException, PermissionDeniedException {
        // Try to find the XQuery
        final SourceInfo sourceInfo = getSourceInfo(broker, request, staticRewrite);

        if (sourceInfo == null) {
            return Sequence.EMPTY_SEQUENCE; // no controller found
        }
        final String basePath = staticRewrite == null ? "." : staticRewrite.getTarget();
       
        final XQuery xquery = broker.getXQueryService();
        final XQueryPool xqyPool = xquery.getXQueryPool();
   
        CompiledXQuery compiled = null;
        if (compiledCache) {
      compiled = xqyPool.borrowCompiledXQuery(broker, sourceInfo.source);
        }
        XQueryContext queryContext;
        if (compiled == null) {
      queryContext = xquery.newContext(AccessContext.REST);
    } else {
      queryContext = compiled.getContext();
    }
        // Find correct module load path
    queryContext.setModuleLoadPath(sourceInfo.moduleLoadPath);
        declareVariables(queryContext, sourceInfo, staticRewrite, basePath, request, response);
        if (compiled == null) {
      try {
        compiled = xquery.compile(queryContext, sourceInfo.source);
      } catch (final IOException e) {
        throw new ServletException("Failed to read query from " + query, e);
      }
    }
        model.setSourceInfo(sourceInfo);
       
//    This used by controller.xql only ?
//    String xdebug = request.getParameter("XDEBUG_SESSION_START");
//    if (xdebug != null)
//      compiled.getContext().setDebugMode(true);

//      outputProperties.put("base-uri", collectionURI.toString());

        try {
      return xquery.execute(compiled, null, outputProperties);
    } finally {
            queryContext.runCleanupTasks();
      xqyPool.returnCompiledXQuery(sourceInfo.source, compiled);
    }
    }

    protected String adjustPathForSourceLookup(String basePath, String path) {
      if (LOG.isTraceEnabled()) {
        LOG.trace("request path=" + path);
      }
      if(basePath.startsWith(XmldbURI.EMBEDDED_SERVER_URI_PREFIX) && path.startsWith(basePath.replace(XmldbURI.EMBEDDED_SERVER_URI_PREFIX, ""))) {
            path = path.replace(basePath.replace(XmldbURI.EMBEDDED_SERVER_URI_PREFIX, ""), "");
       
      } else if(path.startsWith("/db/")) {
            path = path.substring(4);
        }

        if(path.startsWith("/")) {
             path = path.substring(1);
        }

        if (LOG.isTraceEnabled()) {
          LOG.trace("adjusted request path=" + path);
        }
        return path;
    }

    private SourceInfo findSource(HttpServletRequest request, DBBroker broker, String basePath) throws ServletException {
        final String requestURI = request.getRequestURI();
        String path = requestURI.substring(request.getContextPath().length());
       
        if (LOG.isTraceEnabled()) {
          LOG.trace("basePath=" + basePath);
        }
        path = adjustPathForSourceLookup(basePath, path);

        final String[] components = path.split("/");
        SourceInfo sourceInfo = null;
        if (basePath.startsWith(XmldbURI.XMLDB_URI_PREFIX)) {
          if (LOG.isTraceEnabled()) {
            LOG.trace("Looking for controller.xql in the database, starting from: " + basePath);
          }
            try {
                final XmldbURI locationUri = XmldbURI.xmldbUriFor(basePath);
                final Collection collection = broker.openCollection(locationUri, Lock.READ_LOCK);
                if (collection == null) {
                    LOG.warn("Controller base collection not found: " + basePath);
                    return null;
                }

                Collection subColl = collection;
                DocumentImpl controllerDoc = null;
                for (int i = 0; i < components.length; i++) {
                    DocumentImpl doc = null;
                    try {
                        if (components[i].length() > 0 && subColl.hasChildCollection(broker, XmldbURI.createInternal(components[i]))) {
                            final XmldbURI newSubCollURI = subColl.getURI().append(components[i]);
                            if (LOG.isTraceEnabled()) {
                              LOG.trace("Inspecting sub-collection: " + newSubCollURI);
                            }
                            subColl = broker.openCollection(newSubCollURI, Lock.READ_LOCK);
                            if (subColl != null) {
                                if (LOG.isTraceEnabled()) {
                                  LOG.trace("Looking for controller.xql in " + subColl.getURI());
                                }
                                final XmldbURI docUri = subColl.getURI().append("controller.xql");
                                doc = broker.getXMLResource(docUri, Lock.READ_LOCK);
                                if (doc != null) {
                                  if (controllerDoc != null) {
                                    controllerDoc.getUpdateLock().release(Lock.READ_LOCK);
                                  }
                                    controllerDoc = doc;
                                }
                            } else {
                                break;
                            }
                        } else {
                            break;
                        }
                    } catch (final PermissionDeniedException e) {
                        LOG.debug("Permission denied while scanning for XQueryURLRewrite controllers: " +
                            e.getMessage(), e);
                        break;
                    } catch (final Exception e) {
                        LOG.debug("Bad collection URI: " + path);
                        break;
                   
                    } finally {
                        if (doc != null && controllerDoc == null) {
                            doc.getUpdateLock().release(Lock.READ_LOCK);
                        }
                       
                        if (subColl != null && subColl != collection) {
                            subColl.getLock().release(Lock.READ_LOCK);
                        }
                    }
                }
                collection.getLock().release(Lock.READ_LOCK);
                if (controllerDoc == null) {
                    try {
                        final XmldbURI docUri = collection.getURI().append("controller.xql");
                        controllerDoc = broker.getXMLResource(docUri, Lock.READ_LOCK);
                    } catch (final PermissionDeniedException e) {
                        LOG.debug("Permission denied while scanning for XQueryURLRewrite controllers: " +
                            e.getMessage(), e);
                    }
                }
                if (controllerDoc == null) {
                    LOG.warn("XQueryURLRewrite controller could not be found for path: " + path);
                    return null;
                }

                if(LOG.isTraceEnabled()) {
                    LOG.trace("Found controller file: " + controllerDoc.getURI());
                }
                try {
                    if (controllerDoc.getResourceType() != DocumentImpl.BINARY_FILE ||
                                !"application/xquery".equals(controllerDoc.getMetadata().getMimeType())) {
                        LOG.warn("XQuery resource: " + query + " is not an XQuery or " +
                                "declares a wrong mime-type");
                        return null;
                    }
                    final String controllerPath = controllerDoc.getCollection().getURI().getRawCollectionPath();
                   
                    sourceInfo = new SourceInfo(new DBSource(broker, (BinaryDocument) controllerDoc, true), "xmldb:exist://" + controllerPath);
                    sourceInfo.controllerPath = controllerPath.substring(locationUri.getCollectionPath().length());

                    return sourceInfo;
                } finally {
                    if (controllerDoc != null) {
                        controllerDoc.getUpdateLock().release(Lock.READ_LOCK);
                    }
                }
            } catch (final URISyntaxException e) {
                LOG.warn("Bad URI for base path: " + e.getMessage(), e);
                return null;
            } catch (final PermissionDeniedException e) {
                LOG.debug("Permission denied while scanning for XQueryURLRewrite controllers: " + e.getMessage(), e);
              return null;
            }
        } else {
            if (LOG.isTraceEnabled()) {
              LOG.trace("Looking for controller.xql in the filesystem, starting from: " + basePath);
            }
            final String realPath = config.getServletContext().getRealPath(basePath);
            final File baseDir = new File(realPath);
            if (!baseDir.isDirectory()) {
                LOG.warn("Base path for XQueryURLRewrite does not point to a directory");
                return null;
            }

            File controllerFile = null;
            File subDir = baseDir;
            for (int i = 0; i < components.length; i++) {
                if (components[i].length() > 0) {
                    subDir = new File(subDir, components[i]);
                    if (subDir.isDirectory()) {
                        File cf = new File(subDir, "controller.xql");
                        if (cf.canRead())
                            {controllerFile = cf;}
                    } else {
                        break;
                    }
                }
            }
            if (controllerFile == null) {
                File cf = new File(baseDir, "controller.xql");
                if (cf.canRead())
                    {controllerFile = cf;}
            }
            if (controllerFile == null) {
                LOG.warn("XQueryURLRewrite controller could not be found");
                return null;
            }
            if (LOG.isTraceEnabled()) {
                LOG.trace("Found controller file: " + controllerFile.getAbsolutePath());
            }
            final String parentPath = controllerFile.getParentFile().getAbsolutePath();
           
            sourceInfo = new SourceInfo(new FileSource(controllerFile, "UTF-8", true), parentPath);
            sourceInfo.controllerPath = parentPath.substring(baseDir.getAbsolutePath().length());
            // replace windows path separators
            sourceInfo.controllerPath = sourceInfo.controllerPath.replace('\\', '/');
           
            return sourceInfo;
        }
    }
   
    private SourceInfo getSource(DBBroker broker, String moduleLoadPath) throws ServletException {
        SourceInfo sourceInfo;
        if (query.startsWith(XmldbURI.XMLDB_URI_PREFIX)) {
            // Is the module source stored in the database?
            try {
                final XmldbURI locationUri = XmldbURI.xmldbUriFor(query);
                DocumentImpl sourceDoc = null;
                try {
                    sourceDoc = broker.getXMLResource(locationUri.toCollectionPathURI(), Lock.READ_LOCK);
                    if (sourceDoc == null)
                        {throw new ServletException("XQuery resource: " + query + " not found in database");}
                    if (sourceDoc.getResourceType() != DocumentImpl.BINARY_FILE ||
                            !"application/xquery".equals(sourceDoc.getMetadata().getMimeType()))
                        {throw new ServletException("XQuery resource: " + query + " is not an XQuery or " +
                                "declares a wrong mime-type");}
                    sourceInfo = new SourceInfo(new DBSource(broker, (BinaryDocument) sourceDoc, true),
                        locationUri.toString());
                } catch (final PermissionDeniedException e) {
                    throw new ServletException("permission denied to read module source from " + query);
                } finally {
                    if(sourceDoc != null)
                        {sourceDoc.getUpdateLock().release(Lock.READ_LOCK);}
                }
            } catch(final URISyntaxException e) {
                throw new ServletException(e.getMessage(), e);
            }
        } else {
            try {
                sourceInfo = new SourceInfo(SourceFactory.getSource(broker, moduleLoadPath, query, true), moduleLoadPath);
            } catch (final IOException e) {
                throw new ServletException("IO error while reading XQuery source: " + query);
            } catch (final PermissionDeniedException e) {
                throw new ServletException("Permission denied while reading XQuery source: " + query);
            }
        }
        return sourceInfo;
    }

    private void declareVariables(XQueryContext context, SourceInfo sourceInfo, URLRewrite staticRewrite, String basePath,
      RequestWrapper request, HttpServletResponse response)
      throws XPathException {
    final HttpRequestWrapper reqw = new HttpRequestWrapper(request, "UTF-8", "UTF-8", false);
    final HttpResponseWrapper respw = new HttpResponseWrapper(response);
    // context.declareNamespace(RequestModule.PREFIX,
    // RequestModule.NAMESPACE_URI);
    context.declareVariable(RequestModule.PREFIX + ":request", reqw);
    context.declareVariable(ResponseModule.PREFIX + ":response", respw);
    context.declareVariable(SessionModule.PREFIX + ":session", reqw.getSession( false ));

        context.declareVariable("exist:controller", sourceInfo.controllerPath);
        request.setAttribute("$exist:controller", sourceInfo.controllerPath);
        context.declareVariable("exist:root", basePath);
        request.setAttribute("$exist:root", basePath);
        context.declareVariable("exist:context", request.getContextPath());
        request.setAttribute("$exist:context", request.getContextPath());
        final String prefix = staticRewrite == null ? null : staticRewrite.getPrefix();
        context.declareVariable("exist:prefix", prefix == null ? "" : prefix);
        request.setAttribute("$exist:prefix", prefix == null ? "" : prefix);
        String path;
        if (sourceInfo.controllerPath.length() > 0 && !"/".equals(sourceInfo.controllerPath))
            {path = request.getInContextPath().substring(sourceInfo.controllerPath.length());}
        else
            {path = request.getInContextPath();}
        final int p = path.lastIndexOf(';');
        if(p != Constants.STRING_NOT_FOUND)
            {path = path.substring(0, p);}
        context.declareVariable("exist:path", path);
        request.setAttribute("$exist:path", path);

        String resource = "";
        final Matcher nameMatcher = NAME_REGEX.matcher(path);
        if (nameMatcher.matches()) {
            resource = nameMatcher.group(1);
        }
        context.declareVariable("exist:resource", resource);
        request.setAttribute("$exist:resource", resource);

        if (LOG.isDebugEnabled()) {
            LOG.debug("\nexist:path = " + path + "\nexist:resource = " + resource + "\nexist:controller = " + sourceInfo.controllerPath);
        }
  }

    private class ModelAndView {

        URLRewrite rewrite = null;
        List<URLRewrite> views = new LinkedList<URLRewrite>();
        List<URLRewrite> errorHandlers = null;
        boolean useCache = false;
        SourceInfo sourceInfo = null;

        private ModelAndView() {
        }

        public void setSourceInfo(SourceInfo sourceInfo) {
            this.sourceInfo = sourceInfo;
        }

        public SourceInfo getSourceInfo() {
            return sourceInfo;
        }

        public void setModel(URLRewrite model) {
            this.rewrite = model;
        }

        public URLRewrite getModel() {
            return rewrite;
        }

        public void addErrorHandler(URLRewrite handler) {
          if (errorHandlers == null)
            {errorHandlers = new LinkedList<URLRewrite>();}
          errorHandlers.add(handler);
        }
       
        public void addView(URLRewrite view) {
            views.add(view);
        }

        public boolean hasViews() {
            return views.size() > 0;
        }

        public boolean hasErrorHandlers() {
          return errorHandlers != null && errorHandlers.size() > 0;
        }
       
        public boolean useCache() {
            return useCache;
        }

        public void setUseCache(boolean useCache) {
            this.useCache = useCache;
        }
    }

    private static class SourceInfo {

        Source source;
        String controllerPath = "";
        String moduleLoadPath;

        private SourceInfo(Source source, String moduleLoadPath) {
            this.source = source;
            this.moduleLoadPath = moduleLoadPath;
        }
    }

    public static class RequestWrapper extends javax.servlet.http.HttpServletRequestWrapper {

        Map<String, List<String>> addedParams = new HashMap<String, List<String>>();

        Map attributes = new HashMap();
       
        ServletInputStream sis = null;
        BufferedReader reader = null;

        String contentType = null;
        int contentLength = 0;
        String characterEncoding = null;
        String method = null;
        String inContextPath = null;
        String servletPath;
        String basePath = null;
        boolean allowCaching = true;

        private void addNameValue(String name, String value, Map<String, List<String>> map) {
            List<String> values = map.get(name);
            if(values == null) {
                values = new ArrayList<String>();
            }
            values.add(value);
            map.put(name, values);
        }

        protected RequestWrapper(HttpServletRequest request) {
            super(request);

            // copy parameters
            for(final Map.Entry<String, String[]> param : (Set<Map.Entry<String, String[]>>)request.getParameterMap().entrySet()) {
                for(final String paramValue : param.getValue()) {
                    addNameValue(param.getKey(), paramValue, addedParams);
                }
            }
            /*for(Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {

                String key = e.nextElement();
                String[] value = request.getParameterValues(key);
                addedParams.put(key, value);
            }*/

            contentType = request.getContentType();
        }

        protected void allowCaching(boolean cache) {
          this.allowCaching = cache;
        }
       
        @Override
        public String getRequestURI() {
            String uri = inContextPath == null ? super.getRequestURI() : getContextPath() + inContextPath;
           
            // Strip jsessionid from uris. New behavior of jetty
            // see jira.codehaus.org/browse/JETTY-1146
            final int pos = uri.indexOf(";jsessionid=");
            if(pos>0)
                {uri=uri.substring(0, pos);}
           
            return uri;
        }

        public String getInContextPath() {
            if (inContextPath == null)
                {return getRequestURI().substring(getContextPath().length());}
            return inContextPath;
        }

        public void setInContextPath(String path) {
            inContextPath = path;
        }

        @Override
        public String getMethod() {
            if (method == null)
                {return super.getMethod();}
            return method;
        }

        public void setMethod(String method) {
            this.method = method;
        }

        /**
         * Change the requestURI and the servletPath
         *
         * @param requestURI the URI of the request without the context path
         * @param servletPath the servlet path
         */
        public void setPaths(String requestURI, String servletPath) {
            this.inContextPath = requestURI;
            if (servletPath == null)
                {this.servletPath = requestURI;}
            else
                {this.servletPath = servletPath;}
        }

        public void setBasePath(String base) {
            this.basePath = base;
        }

        public String getBasePath() {
            return basePath;
        }

        /**
         * Change the base path of the request, e.g. if the original request pointed
         * to /fs/foo/baz, but the request should be forwarded to /foo/baz.
         *
         * @param base the base path to remove
         */
        public void removePathPrefix(String base) {
            setPaths(getInContextPath().substring(base.length()),
                servletPath != null ? servletPath.substring(base.length()) : null);
        }

        @Override
        public String getServletPath() {
            return servletPath == null ? super.getServletPath() : servletPath;
        }

        @Override
        public String getPathInfo() {
            final String path = getInContextPath();
            final String sp = getServletPath();
            if (sp == null)
                {return null;}
            if (path.length() < sp.length()) {
                LOG.error("Internal error: servletPath = " + sp + " is longer than path = " + path);
                return null;
            }
            return path.length() == sp.length() ? null : path.substring(sp.length());
       }

        @Override
        public String getPathTranslated() {
      final String pathInfo = getPathInfo();
      if (pathInfo == null) {
        super.getPathTranslated();
      }
      if( pathInfo == null ) {
        return( null );
      }
       return super.getSession().getServletContext().getRealPath(pathInfo);
        }
       
        protected void setData(byte[] data) {
            if (data == null)
                {data = new byte[0];}
            contentLength = data.length;
            sis = new CachingServletInputStream(data);
        }

        public void addParameter(String name, String value) {
            addNameValue(name, value, addedParams);
        }

        @Override
        public String getParameter(String name) {
            final List<String> paramValues = addedParams.get(name);
            if (paramValues != null && paramValues.size() > 0)
                {return paramValues.get(0);}
            return null;
        }

        @Override
        public Map<String, String[]> getParameterMap() {
            final Map<String, String[]> parameterMap = new HashMap<String, String[]>();
            for(final Entry<String, List<String>> param : addedParams.entrySet()) {
                final List<String> values = param.getValue();
                if(values != null) {
                    parameterMap.put(param.getKey(), values.toArray(new String[values.size()]));
                } else {
                    parameterMap.put(param.getKey(), new String[]{});
                }
            }
            return parameterMap;
        }

        @Override
        public Enumeration<String> getParameterNames() {
            return Collections.enumeration(addedParams.keySet());
        }

        @Override
        public String[] getParameterValues(String name) {
            final List<String> values = addedParams.get(name);

            if(values != null) {
                return values.toArray(new String[values.size()]);
            } else {
                return null;
            }
        }

        @Override
        public ServletInputStream getInputStream() throws IOException {
            if (sis == null)
                {return super.getInputStream();}
            return sis;
        }

        @Override
        public BufferedReader getReader() throws IOException {
            if (sis == null)
                {return super.getReader();}
            if (reader == null)
                {reader = new BufferedReader(new InputStreamReader(sis, getCharacterEncoding()));}
            return reader;
        }

        @Override
        public String getContentType() {
            if (contentType == null)
                {return super.getContentType();}
            return contentType;
        }

        protected void setContentType(String contentType) {
            this.contentType = contentType;
        }

        @Override
        public int getContentLength() {
            if (sis == null)
                {return super.getContentLength();}
            return contentLength;
        }

        @Override
        public void setCharacterEncoding(String encoding) throws UnsupportedEncodingException {
            this.characterEncoding = encoding;
        }

        @Override
        public String getCharacterEncoding() {
            if (characterEncoding == null)
                {return super.getCharacterEncoding();}
            return characterEncoding;
        }

        @Override
        public String getHeader(String s) {
            if ("If-Modified-Since".equals(s) && !allowCaching)
                {return null;}
            return super.getHeader(s);
        }

        @Override
        public long getDateHeader(String s) {
            if ("If-Modified-Since".equals(s) && !allowCaching)
                {return -1;}
            return super.getDateHeader(s);
        }

        //        public void setAttribute(String key, Object value) {
//            attributes.put(key, value);
//        }
//
//        public Object getAttribute(String key) {
//            Object value = attributes.get(key);
//            if (value == null)
//                value = super.getAttribute(key);
//            return value;
//        }
//
//        public Enumeration getAttributeNames() {
//            Vector v = new Vector();
//            for (Enumeration e = super.getAttributeNames(); e.hasMoreElements();) {
//                v.add(e.nextElement());
//            }
//            for (Iterator i = attributes.keySet().iterator(); i.hasNext();) {
//                v.add(i.next());
//            }
//            return v.elements();
//        }
    }

    private class CachingResponseWrapper extends HttpServletResponseWrapper {

        @SuppressWarnings("unused")
    protected HttpServletResponse origResponse;
        protected CachingServletOutputStream sos = null;
        protected PrintWriter writer = null;
        protected int status = HttpServletResponse.SC_OK;
        protected String contentType = null;
        protected boolean cache;

        public CachingResponseWrapper(HttpServletResponse servletResponse, boolean cache) {
            super(servletResponse);
            this.cache = cache;
            this.origResponse = servletResponse;
        }

        @Override
        public PrintWriter getWriter() throws IOException {
            if (!cache)
                {return super.getWriter();}
            if (sos != null)
                {throw new IOException("getWriter cannnot be called after getOutputStream");}
            sos = new CachingServletOutputStream();
            if (writer == null)
                {writer = new PrintWriter(new OutputStreamWriter(sos, getCharacterEncoding()));}
            return writer;
        }

        @Override
        public ServletOutputStream getOutputStream() throws IOException {
            if (!cache)
                {return super.getOutputStream();}
            if (writer != null)
                {throw new IOException("getOutputStream cannnot be called after getWriter");}
            if (sos == null)
                {sos = new CachingServletOutputStream();}
            return sos;
        }

        public byte[] getData() {
            return sos != null ? sos.getData() : null;
        }

        @Override
    public void setContentType(String type) {
          if (contentType != null)
            {return;}
        this.contentType = type;
        if (!cache)
            {super.setContentType(type);}
    }
       
    @Override
    public String getContentType() {
      return contentType != null ? contentType : super.getContentType();
    }

    @Override
    public void setHeader(String name, String value) {
      if ("Content-Type".equals(name))
        {setContentType(value);}
      else
        {super.setHeader(name, value);}
    }
   
    public int getStatus() {
            return status;
        }
       
        @Override
        public void setStatus(int i) {
            this.status = i;
            super.setStatus(i);
        }

        @Override
        public void setStatus(int i, String msg) {
            this.status = i;
            super.setStatus(i, msg);
        }

        @Override
        public void sendError(int i, String msg) throws IOException {
            this.status = i;
            super.sendError(i, msg);
        }

        @Override
        public void sendError(int i) throws IOException {
            this.status = i;
            super.sendError(i);
        }

        @Override
        public void setContentLength(int i) {
            if (!cache)
                {super.setContentLength(i);}
        }

    @Override
        public void flushBuffer() throws IOException {
            if (!cache)
                {super.flushBuffer();}
        }
       
        public void flush() throws IOException {
          if (cache) {
            if (contentType != null)
              {super.setContentType(contentType);}
          }
          if (sos != null) {
              final ServletOutputStream out = super.getOutputStream();
              out.write(sos.getData());
              out.flush();
            }
        }
    }

    private class CachingServletOutputStream extends ServletOutputStream {

        protected ByteArrayOutputStream ostream = new ByteArrayOutputStream(512);

        protected byte[] getData() {
            return ostream.toByteArray();
        }

        @Override
        public void write(int b) throws IOException {
            ostream.write(b);
        }

        @Override
        public void write(byte b[]) throws IOException {
            ostream.write(b);
        }

        @Override
        public void write(byte b[], int off, int len) throws IOException {
            ostream.write(b, off, len);
        }
    }

    private static class CachingServletInputStream extends ServletInputStream {

        protected ByteArrayInputStream istream;

        public CachingServletInputStream(byte[] data) {
            if (data == null)
                {istream = new ByteArrayInputStream(new byte[0]);}
            else
                {istream = new ByteArrayInputStream(data);}
        }
       
        @Override
        public int read() throws IOException {
           return istream.read();
        }

        @Override
        public int read(byte b[]) throws IOException {
            return istream.read(b);
        }

        @Override
        public int read(byte b[], int off, int len) throws IOException {
            return istream.read(b, off, len);
        }

        @Override
        public int available() throws IOException {
            return istream.available();
        }
    }
}
TOP

Related Classes of org.exist.http.urlrewrite.XQueryURLRewrite$CachingResponseWrapper

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.