org.apache.openjpa.persistence.jest.JESTContext Maven / Gradle / Ivy
The newest version!
/*
* 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.openjpa.persistence.jest;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Calendar;
import java.util.Date;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.openjpa.conf.OpenJPAConfiguration;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.lib.util.Localizer;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.MetaDataRepository;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
/**
* An operational context combines a {@link OpenJPAEntityManager persistence context} and a HTTP execution
* context expressed as a {@link HttpServletRequest request} and {@link HttpServletResponse response}.
*
* This context {@link #getAction(String) parses} the HTTP request URL to identity the command and then
* {@link #execute() executes} it.
*
* @author Pinaki Poddar
*
*/
public class JESTContext implements JPAServletContext {
private final String _unit;
private final OpenJPAEntityManagerFactory _emf;
private OpenJPAEntityManager _em;
private final HttpServletRequest _request;
private final HttpServletResponse _response;
protected MetaDataRepository _repos;
private String _rootResource;
protected Log _log;
protected static PrototypeFactory _cf = new PrototypeFactory<>();
public static final Localizer _loc = Localizer.forPackage(JESTContext.class);
private static final String ONE_YEAR_FROM_NOW;
public static final char QUERY_SEPARATOR = '?';
public static final String CONTEXT_ROOT = "/";
public static final String JEST_TEMPLATE = "jest.html";
/**
* Registers known commands in a {@link PrototypeFactory registry}.
*
*/
static {
_cf.register("find", FindCommand.class);
_cf.register("query", QueryCommand.class);
_cf.register("domain", DomainCommand.class);
_cf.register("properties", PropertiesCommand.class);
Calendar now = Calendar.getInstance();
now.add(Calendar.YEAR, 1);
ONE_YEAR_FROM_NOW = new Date(now.getTimeInMillis()).toString();
}
public JESTContext(String unit, OpenJPAEntityManagerFactory emf, HttpServletRequest request,
HttpServletResponse response) {
_unit = unit;
_emf = emf;
_request = request;
_response = response;
OpenJPAConfiguration conf = _emf.getConfiguration();
_log = conf.getLog("JEST");
_repos = conf.getMetaDataRepositoryInstance();
}
/**
* Gets the name of the persistence unit.
*/
@Override
public String getPersistenceUnitName() {
return _unit;
}
/**
* Gets the persistence context. The persistence context is lazily constructed because all commands
* may not need it.
*/
@Override
public OpenJPAEntityManager getPersistenceContext() {
if (_em == null) {
_em = _emf.createEntityManager();
}
return _em;
}
/**
* Gets the request.
*/
@Override
public HttpServletRequest getRequest() {
return _request;
}
@Override
public String getRequestURI() {
StringBuffer buf = _request.getRequestURL();
String query = _request.getQueryString();
if (!isEmpty(query)) {
buf.append(QUERY_SEPARATOR).append(query);
}
return buf.toString();
}
/**
* Gets the response.
*/
@Override
public HttpServletResponse getResponse() {
return _response;
}
/**
* Executes the request.
*
* Execution starts with parsing the {@link HttpServletRequest#getPathInfo() request path}.
* The {@linkplain #getAction(String) first path segment} is interpreted as action key, and
* if a action with the given key is registered then the control is delegated to the command.
* The command parses the entire {@link HttpServletRequest request} for requisite qualifiers and
* arguments and if the parse is successful then the command is
* {@linkplain JESTCommand#process() executed} in this context.
*
* If path is null, or no command is registered for the action or the command can not parse
* the request, then a last ditch attempt is made to {@linkplain #findResource(String) find} a resource.
* This fallback lookup is important because the response can contain hyperlinks to stylesheets or
* scripts. The browser will resolve such hyperlinks relative to the original response.
*
* For example, let the original request URL be:
* http://host:port/demo/jest/find?type=Actor&Robert
*
* The response to this request is a HTML page that contained a hyperlink to jest.css
stylesheet
* in its <head> section.
* <link ref="jest.css" .....>
*
* The browser will resolve the hyperlink by sending back another request as
* http://host:port/demo/jest/find/jest.css
*
*
* @throws Exception
*/
public void execute() throws Exception {
String path = _request.getPathInfo();
if (isContextRoot(path)) {
getRootResource();
return;
}
String action = getAction(path);
JESTCommand command = _cf.newInstance(action, this);
if (command == null) {
findResource(path.substring(1));
return;
}
try {
command.parse();
command.process();
} catch (ProcessingException e1) {
throw e1;
} catch (Exception e2) {
try {
findResource(path.substring(action.length()+1));
} catch (ProcessingException e3) {
throw e2;
}
}
}
/**
* Gets the action from the given path.
*
* @param path a string
* @return if null, returns context root i.e. '/'
character.
* Otherwise, if the path starts with context root, then returns the substring before the
* next '/'
character or end of the string, whichever is earlier.
* If the path does not start with context root, returns
* the substring before the first '/'
character or end of the string, whichever is earlier.
*/
public static String getAction(String path) {
if (path == null)
return CONTEXT_ROOT;
if (path.startsWith(CONTEXT_ROOT))
path = path.substring(1);
int idx = path.indexOf(CONTEXT_ROOT);
return idx == -1 ? path : path.substring(0, idx);
}
@Override
public ClassMetaData resolve(String alias) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
return _repos.getMetaData(alias, loader, true);
}
/**
* A resource is always looked up with respect to this class.
*
* @param rsrc
* @throws ProcessingException
*/
void findResource(String rsrc) throws ProcessingException {
_response.setHeader("Cache-Control", "public");
_response.setHeader("Expires", ONE_YEAR_FROM_NOW);
InputStream in = getClass().getResourceAsStream(rsrc);
if (in == null) { // try again as a relative path
if (rsrc.startsWith(CONTEXT_ROOT)) {
in = getClass().getResourceAsStream(rsrc.substring(1));
}
if (in == null) {
throw new ProcessingException(this, _loc.get("resource-not-found", rsrc), HTTP_NOT_FOUND);
}
}
try {
String mimeType = _request.getSession().getServletContext().getMimeType(rsrc);
if (mimeType == null) {
mimeType = "application/text";
}
_response.setContentType(mimeType);
OutputStream out = _response.getOutputStream();
if (mimeType.startsWith("image/")) {
byte[] b = new byte[1024];
int i = 0;
for (int l = 0; (l = in.read(b)) != -1;) {
out.write(b, 0, l);
i += l;
}
_response.setContentLength(i);
} else {
for (int c = 0; (c = in.read()) != -1;) {
out.write((char)c);
}
}
} catch (IOException e) {
throw new ProcessingException(this, e, _loc.get("resource-not-found", rsrc), HTTP_NOT_FOUND);
}
}
@Override
public void log(short level, String message) {
switch (level) {
case Log.INFO: _log.info(message); break;
case Log.ERROR: _log.fatal(message); break;
case Log.FATAL: _log.fatal(message); break;
case Log.TRACE: _log.trace(message); break;
case Log.WARN: _log.warn(message); break;
default: _request.getSession().getServletContext().log(message);
break;
}
}
/**
* Is this path a context root?
* @param path
*/
boolean isContextRoot(String path) {
return (path == null || CONTEXT_ROOT.equals(path));
}
boolean isEmpty(String s) {
return s == null || s.trim().length() == 0;
}
/**
* Root resource is a HTML template with deployment specific tokens such as name of the persistence unit
* or base url. On first request for this resource, the tokens in the templated HTML file gets replaced
* by the actual deployment specific value into a string. This string (which is an entire HTML file)
* is then written to the response.
*
* @see TokenReplacedStream
* @throws IOException
*/
private void getRootResource() throws IOException {
_response.setHeader("Cache-Control", "public");
_response.setHeader("Expires", ONE_YEAR_FROM_NOW);
if (_rootResource == null) {
String[] tokens = {
"${persistence.unit}", getPersistenceUnitName(),
"${jest.uri}", _request.getRequestURL().toString(),
"${webapp.name}", _request.getContextPath().startsWith(CONTEXT_ROOT)
? _request.getContextPath().substring(1)
: _request.getContextPath(),
"${servlet.name}", _request.getServletPath().startsWith(CONTEXT_ROOT)
? _request.getServletPath().substring(1)
: _request.getServletPath(),
"${server.name}", _request.getServerName(),
"${server.port}", ""+_request.getServerPort(),
"${dojo.base}", Constants.DOJO_BASE_URL,
"${dojo.theme}", Constants.DOJO_THEME,
};
InputStream in = getClass().getResourceAsStream(JEST_TEMPLATE);
CharArrayWriter out = new CharArrayWriter();
new TokenReplacedStream().replace(in, out, tokens);
_rootResource = out.toString();
}
_response.getOutputStream().write(_rootResource.getBytes());
}
}