/**********************************************************************
Copyright (c) 2009 Erik Bengtson and others. All rights reserved.
Licensed 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.
Contributors:
...
**********************************************************************/
package org.datanucleus.api.rest;
import java.io.IOException;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.datanucleus.ClassLoaderResolver;
import org.datanucleus.NucleusContext;
import org.datanucleus.api.json.JsonAPI;
import org.datanucleus.api.json.JsonAPIImpl;
import org.datanucleus.exceptions.ClassNotResolvedException;
import org.datanucleus.exceptions.NucleusException;
import org.datanucleus.exceptions.NucleusObjectNotFoundException;
import org.datanucleus.exceptions.NucleusUserException;
import org.datanucleus.metadata.AbstractClassMetaData;
import org.datanucleus.metadata.IdentityType;
import org.datanucleus.metadata.PersistenceUnitMetaData;
import org.datanucleus.store.query.Query;
import org.datanucleus.util.NucleusLogger;
import org.datanucleus.util.TypeConversionHelper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
/**
* This servlet exposes persistent class via RESTful HTTP requests. HTTP GET (retrieve/query), POST (update/insert),
* PUT (update/insert), DELETE
*/
public class RestServlet extends HttpServlet
{
public static final NucleusLogger LOGGER_REST = NucleusLogger.getLoggerInstance("DataNucleus.REST");
JsonAPI api = null;
public void init(ServletConfig config) throws ServletException
{
String factory = config.getInitParameter("persistence-context");
if (factory == null)
{
throw new ServletException("You haven't specified \"persistence-context\" property defining the persistence unit");
}
Map props = new HashMap();
try
{
// Try extracting properties from persistence-unit
NucleusContext nucCtx = new NucleusContext("JDO", null); // Do we need JDO here?
PersistenceUnitMetaData pumd = nucCtx.getMetaDataManager().getMetaDataForPersistenceUnit(factory);
props.putAll(pumd.getProperties());
}
catch (Exception e)
{
LOGGER_REST.error("Exception with persistence-unit ", e);
// TODO Try extracting properties from jdoconfig
}
if (props.isEmpty())
{
// Fallback properties
LOGGER_REST.warn("Falling back to use embedded HSQLDB datastore");
props.put("appengine.orm.disable.duplicate.pmf.exception", "true");
props.put("datanucleus.storeManagerType", "rdbms");
props.put("datanucleus.ConnectionURL", "jdbc:hsqldb:mem:nucleus");
props.put("datanucleus.ConnectionDriverName", "org.hsqldb.jdbcDriver");
props.put("datanucleus.ConnectionUserName", "sa");
props.put("datanucleus.autoCreateTables", "true");
}
api = new JsonAPIImpl(props);
super.init(config);
}
/**
* Get the class name
* @param req
* @return
*/
private String getClassName(HttpServletRequest req)
{
String path = req.getRequestURI().substring(req.getContextPath().length() + req.getServletPath().length());
StringTokenizer tokenizer = new StringTokenizer(path, "/");
return tokenizer.nextToken();
}
/**
* Get the class name
* @param req
* @return
*/
private String getClassNameForPath(HttpServletRequest req)
{
String path = req.getRequestURI().substring(req.getContextPath().length() + req.getServletPath().length());
StringTokenizer token = new StringTokenizer(path, "/");
return token.nextToken();
}
/**
* get the id (second path segment)
* @param req
* @return
*/
private JSONObject getId(JsonAPI api, HttpServletRequest req)
{
String path = req.getRequestURI().substring(req.getContextPath().length() + req.getServletPath().length());
StringTokenizer tokenizer = new StringTokenizer(path, "/");
String className = tokenizer.nextToken(); // ignore class name
JSONObject id = new JSONObject();
try
{
if (tokenizer.hasMoreTokens())
{
String token = tokenizer.nextToken();
if (token == null)
{
return null;
}
AbstractClassMetaData cmd = api.getMetaDataManager()
.getMetaDataForClass(className, api.getClassLoaderResolver());
if (cmd != null && cmd.usesSingleFieldIdentityClass())
{
Object value = TypeConversionHelper.convertTo(token,
cmd.getMetaDataForMemberAtRelativePosition(cmd.getPKMemberPositions()[0]).getType());
id.put("id", value);
return id;
}
id.put("id", token);
return id;
}
try
{
if (req.getContentLength() > 0)
{
char[] buffer = new char[req.getContentLength()];
req.getReader().read(buffer);
String idtext = new String(buffer);
idtext = URLDecoder.decode(idtext, "UTF-8");
// if it's a JSONObject
JSONObject jsonobj = new JSONObject(idtext);
return jsonobj;
}
}
catch (IOException e)
{
throw new RuntimeException(e);
}
}
catch (JSONException e)
{
e.printStackTrace();
}
return id;
}
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
try
{
String className = getClassName(req);
ClassLoaderResolver clr = api.getClassLoaderResolver();
AbstractClassMetaData cmd = api.getMetaDataManager().getMetaDataForEntityName(className);
try
{
if (cmd == null)
{
cmd = api.getMetaDataManager().getMetaDataForClass(className, clr);
}
}
catch (ClassNotResolvedException ex)
{
JSONObject error = new JSONObject();
error.put("exception", ex.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(404);
resp.setHeader("Content-Type", "application/json");
return;
}
JSONObject id = getId(api, req);
if (id == null)
{
// no id provided
try
{
// get the whole extent
String queryString = "SELECT FROM " + cmd.getFullClassName();
if (req.getQueryString() != null)
{
// query by filter
queryString += " WHERE " + URLDecoder.decode(req.getQueryString(), "UTF-8");
}
try
{
api.getTransaction().begin();
Query query = api.newQuery("JDOQL", queryString);
List result = (List)query.execute();
JSONArray array = new JSONArray(result);
resp.getWriter().write(array.toString());
resp.setHeader("Content-Type", "application/json");
resp.setStatus(200);
api.getTransaction().commit();
}
finally
{
if (api.getTransaction().isActive())
{
api.getTransaction().rollback();
}
api.close();
}
return;
}
catch (NucleusUserException e)
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(400);
resp.setHeader("Content-Type", "application/json");
return;
}
catch (NucleusException ex)
{
JSONObject error = new JSONObject();
error.put("exception", ex.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(404);
resp.setHeader("Content-Type", "application/json");
return;
}
catch (RuntimeException ex)
{
ex.printStackTrace();
// errors from the google appengine may be raised when running queries
JSONObject error = new JSONObject();
error.put("exception", ex.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(404);
resp.setHeader("Content-Type", "application/json");
return;
}
}
if (cmd.getIdentityType() != IdentityType.APPLICATION)
{
JSONObject error = new JSONObject();
error.put("exception", "Only application identity types are support.");
resp.getWriter().write(error.toString());
resp.setStatus(404);
resp.setHeader("Content-Type", "application/json");
return;
}
try
{
api.getTransaction().begin();
id.put("class", getClassNameForPath(req));
Object result = api.findObject(id);
resp.getWriter().write(result.toString());
resp.setHeader("Content-Type", "application/json");
api.getTransaction().commit();
return;
}
catch (NucleusObjectNotFoundException ex)
{
resp.setContentLength(0);
resp.setStatus(404);
return;
}
catch (NucleusException ex)
{
JSONObject error = new JSONObject();
error.put("exception", ex.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(404);
resp.setHeader("Content-Type", "application/json");
return;
}
finally
{
if (api.getTransaction().isActive())
{
api.getTransaction().rollback();
}
api.close();
}
}
catch (JSONException e)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(404);
resp.setHeader("Content-Type", "application/json");
}
catch (JSONException e1)
{
// ignore
}
}
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
doPost(req, resp);
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
resp.addHeader("Allow", " GET, HEAD, POST, PUT, TRACE, OPTIONS");
resp.setContentLength(0);
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
String className = getClassName(req);
ClassLoaderResolver clr = api.getClassLoaderResolver();
AbstractClassMetaData cmd = api.getMetaDataManager().getMetaDataForEntityName(className);
try
{
if (cmd == null)
{
cmd = api.getMetaDataManager().getMetaDataForClass(className, clr);
}
}
catch (ClassNotResolvedException ex)
{
resp.setStatus(404);
return;
}
JSONObject id = getId(api, req);
if (id == null)
{
// no id provided
try
{
// get the whole extent
String queryString = "SELECT FROM " + cmd.getFullClassName();
if (req.getQueryString() != null)
{
// query by filter
queryString += " WHERE " + URLDecoder.decode(req.getQueryString(), "UTF-8");
}
try
{
api.getTransaction().begin();
Query query = api.newQuery("JDOQL", queryString);
/* Object result = */query.execute();
resp.setStatus(200);
api.getTransaction().commit();
}
finally
{
if (api.getTransaction().isActive())
{
api.getTransaction().rollback();
}
api.close();
}
return;
}
catch (NucleusUserException e)
{
resp.setStatus(400);
return;
}
catch (NucleusException ex)
{
resp.setStatus(404);
return;
}
catch (RuntimeException ex)
{
resp.setStatus(404);
return;
}
}
if (cmd.getIdentityType() != IdentityType.APPLICATION)
{
resp.setStatus(404);
return;
}
try
{
api.getTransaction().begin();
id.put("class", getClassNameForPath(req));
api.findObject(id);
resp.setStatus(200);
api.getTransaction().commit();
return;
}
catch (NucleusException ex)
{
resp.setStatus(404);
return;
}
catch (JSONException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
finally
{
if (api.getTransaction().isActive())
{
api.getTransaction().rollback();
}
api.close();
}
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
if (req.getContentLength() < 1)
{
resp.setContentLength(0);
resp.setStatus(400);// bad request
return;
}
char[] buffer = new char[req.getContentLength()];
req.getReader().read(buffer);
String str = new String(buffer);
JSONObject jsonobj;
try
{
api.getTransaction().begin();
jsonobj = new JSONObject(str);
jsonobj.put("class", getClassNameForPath(req));
api.persist(jsonobj);
resp.getWriter().write(jsonobj.toString());
resp.setHeader("Content-Type", "application/json");
api.getTransaction().commit();
}
catch (ClassNotResolvedException e)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(500);
resp.setHeader("Content-Type", "application/json");
LOGGER_REST.error(e.getMessage(), e);
}
catch (JSONException e1)
{
throw new RuntimeException(e1);
}
}
catch (NucleusUserException e)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(400);
resp.setHeader("Content-Type", "application/json");
LOGGER_REST.error(e.getMessage(), e);
}
catch (JSONException e1)
{
throw new RuntimeException(e1);
}
}
catch (NucleusException e)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(500);
resp.setHeader("Content-Type", "application/json");
LOGGER_REST.error(e.getMessage(), e);
}
catch (JSONException e1)
{
throw new RuntimeException(e1);
}
}
catch (JSONException e)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(500);
resp.setHeader("Content-Type", "application/json");
LOGGER_REST.error(e.getMessage(), e);
}
catch (JSONException e1)
{
throw new RuntimeException(e1);
}
}
finally
{
if (api.getTransaction().isActive())
{
api.getTransaction().rollback();
}
api.close();
}
resp.setStatus(201);// created
}
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
{
try
{
String className = getClassName(req);
ClassLoaderResolver clr = api.getClassLoaderResolver();
AbstractClassMetaData cmd = api.getMetaDataManager().getMetaDataForEntityName(className);
try
{
if (cmd == null)
{
cmd = api.getMetaDataManager().getMetaDataForClass(className, clr);
}
}
catch (ClassNotResolvedException ex)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", ex.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(404);
resp.setHeader("Content-Type", "application/json");
}
catch (JSONException e)
{
// will not happen
}
return;
}
api.getTransaction().begin();
JSONObject id = getId(api, req);
id.put("class", getClassNameForPath(req));
api.deleteObject(id);
api.getTransaction().commit();
}
catch (NucleusObjectNotFoundException ex)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", ex.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(400);
resp.setHeader("Content-Type", "application/json");
return;
}
catch (JSONException e)
{
// will not happen
}
}
catch (NucleusUserException e)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(400);
resp.setHeader("Content-Type", "application/json");
return;
}
catch (JSONException e1)
{
// ignore
}
}
catch (NucleusException e)
{
try
{
JSONObject error = new JSONObject();
error.put("exception", e.getMessage());
resp.getWriter().write(error.toString());
resp.setStatus(500);
resp.setHeader("Content-Type", "application/json");
LOGGER_REST.error(e.getMessage(), e);
}
catch (JSONException e1)
{
// ignore
}
}
catch (JSONException e)
{
if (api.getTransaction().isActive())
{
api.getTransaction().rollback();
}
api.close();
}
finally
{
if (api.getTransaction().isActive())
{
api.getTransaction().rollback();
}
api.close();
}
resp.setContentLength(0);
resp.setStatus(204);// created
}
}