OPENJPA-1851: Add JEST to OpenJPA trunk

git-svn-id: https://svn.apache.org/repos/asf/openjpa/trunk@1044138 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Pinaki Poddar 2010-12-09 21:50:47 +00:00
parent 315a47945c
commit bd4a82a07d
43 changed files with 5683 additions and 0 deletions

60
openjpa-jest/pom.xml Normal file
View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<!--
Please keep the project tag on one line to avoid confusing
the release plugin.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-parent</artifactId>
<version>2.2.0-SNAPSHOT</version>
</parent>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-jest</artifactId>
<packaging>jar</packaging>
<name>OpenJPA JEST</name>
<dependencies>
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-kernel</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.geronimo.specs</groupId>
<artifactId>geronimo-jpa_2.0_spec</artifactId>
</dependency>
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-persistence</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.4</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,342 @@
/*
* 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_BAD_REQUEST;
import static org.apache.openjpa.persistence.jest.Constants.QUALIFIER_FORMAT;
import static org.apache.openjpa.persistence.jest.Constants.QUALIFIER_PLAN;
import static org.apache.openjpa.persistence.jest.Constants._loc;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.kernel.BrokerImpl;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.persistence.FetchPlan;
import org.apache.openjpa.persistence.JPAFacadeHelper;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.persistence.OpenJPAQuery;
/**
* The abstract base class for all commands available to JEST.
*
* @author Pinaki Poddar
*
*/
abstract class AbstractCommand implements JESTCommand {
public static final char EQUAL = '=';
public static final String PATH_SEPARATOR = "/";
public static final Collection<String> EMPTY_LIST = Collections.emptySet();
protected ObjectFormatter<?> _formatter;
protected static PrototypeFactory<Format,ObjectFormatter<?>> _ff =
new PrototypeFactory<Format,ObjectFormatter<?>>();
private Map<String, String> _qualifiers = new HashMap<String, String>();
private Map<String, String> _args = new HashMap<String, String>();
private Map<String, String> _margs = new HashMap<String, String>();
protected final JPAServletContext ctx;
static {
_ff.register(Format.xml, XMLFormatter.class);
_ff.register(Format.json, JSONObjectFormatter.class);
}
protected AbstractCommand(JPAServletContext ctx) {
this.ctx = ctx;
}
public String getMandatoryArgument(String key) {
return get(key, _margs);
}
public String getArgument(String key) {
return get(key, _args);
}
public boolean hasArgument(String key) {
return has(key, _args);
}
public Map<String, String> getArguments() {
return _args;
}
public String getQualifier(String key) {
return get(key, _qualifiers);
}
public boolean hasQualifier(String key) {
return has(key, _qualifiers);
}
protected boolean isBooleanQualifier(String key) {
if (hasQualifier(key)) {
Object value = getQualifier(key);
return value == null || "true".equalsIgnoreCase(value.toString());
}
return false;
}
public Map<String, String> getQualifiers() {
return _qualifiers;
}
/**
* Parses HTTP Request for the qualifier and argument of a command.
* <br>
* Each servlet path segment, except the first (which is the command name itself), is a qualifier.
* Each qualifier can be either a key or a key-value pair separated by a = sign.
* <br>
* Each request parameter key-value pair is an argument. A concrete command may specify mandatory
* arguments (e.g. <code>type</code> must be mandatory argument for <code>find</code> command,
* or <code>q</code> for <code>query</code>. The mandatory arguments, if any, are <em>not</em> captured
* in the argument list.
* <br>
* The qualifiers and arguments are immutable after parse.
*/
public void parse() throws ProcessingException {
HttpServletRequest request = ctx.getRequest();
String path = request.getPathInfo();
if (path != null) {
path = path.substring(1);
String[] segments = path.split(PATH_SEPARATOR);
for (int i = 1; i < segments.length; i++) {
String segment = segments[i];
int idx = segment.indexOf(EQUAL);
if (idx == -1) {
_qualifiers.put(segment, null);
} else {
_qualifiers.put(segment.substring(0, idx), segment.substring(idx+1));
}
}
}
_qualifiers = Collections.unmodifiableMap(_qualifiers);
Enumeration<?> names = request.getParameterNames();
Collection<String> mandatoryArgs = getMandatoryArguments();
while (names.hasMoreElements()) {
String key = names.nextElement().toString();
if (key.startsWith("dojo.")) continue;
put(key, request.getParameter(key), mandatoryArgs.contains(key) ? _margs : _args);
}
_args = Collections.unmodifiableMap(_args);
_margs = Collections.unmodifiableMap(_margs);
validate();
}
/**
* Gets the mandatory arguments.
*
* @return empty list by default.
*/
protected Collection<String> getMandatoryArguments() {
return EMPTY_LIST;
}
/**
* Gets the minimum number of arguments excluding the mandatory arguments.
*
* @return zero by default.
*/
protected int getMinimumArguments() {
return 0;
}
/**
* Gets the maximum number of arguments excluding the mandatory arguments.
*
* @return Integer.MAX_VALUE by default.
*/
protected int getMaximumArguments() {
return Integer.MAX_VALUE;
}
protected Format getDefaultFormat() {
return Format.xml;
}
/**
* Gets the valid qualifiers.
*
* @return empty list by default.
*/
protected Collection<String> getValidQualifiers() {
return EMPTY_LIST;
}
/**
* Called post-parse to validate this command has requisite qualifiers and arguments.
*/
protected void validate() {
HttpServletRequest request = ctx.getRequest();
Collection<String> validQualifiers = getValidQualifiers();
for (String key : _qualifiers.keySet()) {
if (!validQualifiers.contains(key)) {
throw new ProcessingException(ctx,_loc.get("parse-invalid-qualifier", this, key, validQualifiers),
HTTP_BAD_REQUEST);
}
}
Collection<String> mandatoryArgs = getMandatoryArguments();
for (String key : mandatoryArgs) {
if (request.getParameter(key) == null) {
throw new ProcessingException(ctx, _loc.get("parse-missing-mandatory-argument", this, key,
request.getParameterMap().keySet()), HTTP_BAD_REQUEST);
}
}
if (_args.size() < getMinimumArguments()) {
throw new ProcessingException(ctx, _loc.get("parse-less-argument", this, _args.keySet(),
getMinimumArguments()), HTTP_BAD_REQUEST);
}
if (_args.size() > getMaximumArguments()) {
throw new ProcessingException(ctx, _loc.get("parse-less-argument", this, _args.keySet(),
getMinimumArguments()), HTTP_BAD_REQUEST);
}
}
private String get(String key, Map<String,String> map) {
return map.get(key);
}
private String put(String key, String value, Map<String,String> map) {
return map.put(key, value);
}
private boolean has(String key, Map<String,String> map) {
return map.containsKey(key);
}
public ObjectFormatter<?> getObjectFormatter() {
if (_formatter == null) {
String rformat = getQualifier(QUALIFIER_FORMAT);
Format format = null;
if (rformat == null) {
format = getDefaultFormat();
} else {
try {
format = Format.valueOf(rformat);
} catch (Exception e) {
throw new ProcessingException(ctx, _loc.get("format-not-supported", new Object[]{format,
ctx.getRequest().getPathInfo(), _ff.getRegisteredKeys()}), HTTP_BAD_REQUEST);
}
}
_formatter = _ff.newInstance(format);
if (_formatter == null) {
throw new ProcessingException(ctx, _loc.get("format-not-supported", new Object[]{format,
ctx.getRequest().getPathInfo(), _ff.getRegisteredKeys()}), HTTP_BAD_REQUEST);
}
}
return _formatter;
}
protected OpenJPAStateManager toStateManager(Object obj) {
if (obj instanceof OpenJPAStateManager)
return (OpenJPAStateManager)obj;
if (obj instanceof PersistenceCapable) {
return (OpenJPAStateManager)((PersistenceCapable)obj).pcGetStateManager();
}
return null;
}
protected List<OpenJPAStateManager> toStateManager(Collection<?> objects) {
List<OpenJPAStateManager> sms = new ArrayList<OpenJPAStateManager>();
for (Object o : objects) {
OpenJPAStateManager sm = toStateManager(o);
if (sm != null) sms.add(sm);
}
return sms;
}
protected void pushFetchPlan(Object target) {
if (!hasQualifier(QUALIFIER_PLAN))
return;
OpenJPAEntityManager em = ctx.getPersistenceContext();
FetchPlan plan = em.pushFetchPlan();
BrokerImpl broker = (BrokerImpl)JPAFacadeHelper.toBroker(em);
if (target instanceof OpenJPAEntityManager) {
broker.setCacheFinderQuery(false);
} else if (target instanceof OpenJPAQuery) {
broker.setCachePreparedQuery(false);
}
String[] plans = getQualifier(QUALIFIER_PLAN).split(",");
for (String p : plans) {
p = p.trim();
if (p.charAt(0) == '-') {
plan.removeFetchGroup(p.substring(1));
} else {
plan.addFetchGroup(p);
}
}
}
protected void popFetchPlan(boolean finder) {
if (!hasQualifier(QUALIFIER_PLAN))
return;
OpenJPAEntityManager em = ctx.getPersistenceContext();
BrokerImpl broker = (BrokerImpl)JPAFacadeHelper.toBroker(em);
if (finder) {
broker.setCacheFinderQuery(false);
} else {
broker.setCachePreparedQuery(false);
}
}
protected void debug(HttpServletRequest request, HttpServletResponse response, JPAServletContext ctx)
throws IOException {
response.setContentType(Constants.MIME_TYPE_PLAIN);
PrintWriter writer = response.getWriter();
writer.println("URI = [" + request.getRequestURI() + "]");
writer.println("URL = [" + request.getRequestURL() + "]");
writer.println("Servlet Path = [" + request.getServletPath() + "]"); // this is one we need
writer.println("Context Path = [" + request.getContextPath() + "]");
writer.println("Translated Path = [" + request.getPathTranslated() + "]");// not decoded
writer.println("Path Info = [" + request.getPathInfo() + "]");// decoded
String query = request.getQueryString();
if (query != null) {
query = URLDecoder.decode(request.getQueryString(),"UTF-8");
}
writer.println("Query = [" + query + "]"); // and this one
int i = 0;
for (Map.Entry<String, String> e : _qualifiers.entrySet()) {
writer.println("Qualifier [" + i + "] = [" + e.getKey() + ": " + e.getValue() + "]");
i++;
}
i = 0;
for (Map.Entry<String, String> e : _args.entrySet()) {
writer.println("Parameter [" + i + "] = [" + e.getKey() + ": " + e.getValue() + "]");
i++;
}
}
}

View File

@ -0,0 +1,107 @@
/*
* 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 java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.apache.openjpa.enhance.PersistenceCapable;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
/**
* Computes closure of a collection of managed objects.
*
* @author Pinaki Poddar
*
*/
public class Closure implements Iterable<OpenJPAStateManager> {
private Set<OpenJPAStateManager> _visited = new LinkedHashSet<OpenJPAStateManager>();
public Closure(OpenJPAStateManager root) {
this(Collections.singleton(root));
}
public Closure(Collection<OpenJPAStateManager> roots) {
for (OpenJPAStateManager sm : roots) {
visit(sm);
}
}
private void visit(OpenJPAStateManager sm) {
if (sm == null)
return;
boolean isVisited = !_visited.add(sm);
if (isVisited) return;
BitSet loaded = sm.getLoaded();
FieldMetaData[] fmds = sm.getMetaData().getFields();
for (FieldMetaData fmd : fmds) {
int idx = fmd.getIndex();
if (!loaded.get(idx))
continue;
if (fmd.getElement().getTypeMetaData() == null && fmd.getValue().getTypeMetaData() == null)
continue;
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.PC:
visit(toStateManager(sm.fetch(idx)));
break;
case JavaTypes.ARRAY:
Object[] values = (Object[])sm.fetch(idx);
for (Object o : values)
visit(toStateManager(o));
break;
case JavaTypes.COLLECTION:
Collection<?> members = (Collection<?>)sm.fetch(idx);
for (Object o : members)
visit(toStateManager(o));
break;
case JavaTypes.MAP:
Map<?,?> map = (Map<?,?>)sm.fetch(idx);
for (Map.Entry<?,?> entry : map.entrySet()) {
visit(toStateManager(entry.getKey()));
visit(toStateManager(entry.getValue()));
}
break;
default:
}
}
}
OpenJPAStateManager toStateManager(Object o) {
if (o instanceof PersistenceCapable) {
return (OpenJPAStateManager)((PersistenceCapable)o).pcGetStateManager();
}
return null;
}
public Iterator<OpenJPAStateManager> iterator() {
return _visited.iterator();
}
String ior(OpenJPAStateManager sm) {
return sm.getMetaData().getDescribedType().getSimpleName()+'-'+sm.getObjectId();
}
}

View File

@ -0,0 +1,141 @@
/*
* 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 org.apache.openjpa.lib.util.Localizer;
/**
* Static String constants
*
* @author Pinaki Poddar
*
*/
public interface Constants {
/**
* Command Qualifiers
*/
public static final String QUALIFIER_FORMAT = "format";
public static final String QUALIFIER_PLAN = "plan";
public static final String QUALIFIER_MAXRESULT = "max";
public static final String QUALIFIER_FIRSTRESULT = "first";
public static final String QUALIFIER_NAMED = "named";
public static final String QUALIFIER_SINGLE = "single";
/**
* Command Arguments
*/
public static final String ARG_QUERY = "q";
public static final String ARG_TYPE = "type";
/**
* Mime Types
*/
public static final String MIME_TYPE_PLAIN = "text/plain";
public static final String MIME_TYPE_JS = "text/javascript";
public static final String MIME_TYPE_CSS = "text/css";
public static final String MIME_TYPE_XML = "text/xml";
public static final String MIME_TYPE_JSON = "application/json";
public static final String CONTEXT_ROOT = "/";
public static final String JEST_TEMPLATE = "jest.html";
/**
* Servlet initialization parameters
*/
public static final String INIT_PARA_UNIT = "persistence.unit";
public static final String INIT_PARA_STANDALONE = "standalone";
/**
* Dojo Toolkit URL and Themes
*/
public static final String DOJO_BASE_URL = "http://ajax.googleapis.com/ajax/libs/dojo/1.5";
public static final String DOJO_THEME = "claro";
/**
* Root element of XML instances. Must match the name defined in <A href="jest-instance.xsd>jest-instance.xsd</A>.
*/
public static final String ROOT_ELEMENT_INSTANCE = "instances";
public static final String ATTR_ID = "id";
public static final String ATTR_REL = "rel";
public static final String ATTR_SRC = "src";
public static final String ATTR_TYPE = "type";
public static final String ATTR_NAME = "name";
public static final String ATTR_VERSION = "version";
public static final String ATTR_CLASS = "class";
public static final String ATTR_HREF = "href";
public static final String ATTR_STYLE = "style";
public static final String ATTR_NULL = "null";
public static final String ATTR_MEMBER_TYPE = "member-type";
public static final String ATTR_KEY_TYPE = "key-type";
public static final String ATTR_VALUE_TYPE = "value-type";
/**
* Elements and attributes in properties XML.
*/
public static final String ROOT_ELEMENT_PROPERTIES = "properties";
public static final String ELEMENT_PROPERTY = "property";
public static final String ATTR_PROPERTY_KEY = "name";
public static final String ATTR_PROPERTY_VALUE = "value";
public static final String ROOT_ELEMENT_ERROR = "error";
public static final String ELEMENT_ERROR_HEADER = "error-header";
public static final String ELEMENT_ERROR_MESSAGE = "error-message";
public static final String ELEMENT_ERROR_TRACE = "stacktrace";
/**
* Root element of XML meta-model. Must match the name defined in <A href="jest-model.xsd>jest-model.xsd</A>.
*/
public static final String ROOT_ELEMENT_MODEL = "metamodel";
public static final String ELEMENT_INSTANCE = "instance";
public static final String ELEMENT_URI = "uri";
public static final String ELEMENT_DESCRIPTION = "description";
public static final String ELEMENT_REF = "ref";
public static final String ELEMENT_NULL_REF = "null";
public static final String ELEMENT_MEMBER = "member";
public static final String ELEMENT_ENTRY = "entry";
public static final String ELEMENT_ENTRY_KEY = "key";
public static final String ELEMENT_ENTRY_VALUE = "value";
/**
* JEST resources
*/
// public static final String JEST_STYLESHEET = "jest.css";
// public static final String JEST_SCRIPT_INSTANCES = "instances.js";
// public static final String JEST_SCRIPT_DOMAIN = "model.js";
public static final String JEST_INSTANCE_XSD = "jest-instance.xsd";
static final String JAXP_SCHEMA_SOURCE = "http://java.sun.com/xml/jaxp/properties/schemaSource";
static final String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
/**
* Common instances
*/
public static final Localizer _loc = Localizer.forPackage(JESTContext.class);
public static final String NULL_VALUE = "null";
}

View File

@ -0,0 +1,60 @@
/*
* 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 java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import static org.apache.openjpa.persistence.jest.Constants.ARG_TYPE;
import static org.apache.openjpa.persistence.jest.Constants._loc;
/**
* Marshals a JPA meta-model in the configured format to the response output stream.
*
* @author Pinaki Poddar
*
*/
class DomainCommand extends AbstractCommand {
private static final List<String> _validQualifiers = Arrays.asList("format");
public DomainCommand(JPAServletContext ctx) {
super(ctx);
}
protected Collection<String> getValidQualifiers() {
return _validQualifiers;
}
protected int getMaximumArguments() {
return 0;
}
public String getAction() {
return "domain";
}
public void process() throws ProcessingException, IOException {
getObjectFormatter().writeOut(ctx.getPersistenceContext().getMetamodel(),
_loc.get("domain-title").toString(), _loc.get("domain-desc").toString(), ctx.getRequestURI(),
ctx.getResponse().getOutputStream());
}
}

View File

@ -0,0 +1,62 @@
/*
* 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 java.io.PrintWriter;
import java.io.StringWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Formats error stack trace.
*
* @author Pinaki Poddar
*
*/
class ExceptionFormatter extends XMLFormatter {
/**
* Creates a XML Document with given header and stack trace of the given error.
* @param header
* @param e
*/
public Document createXML(String header, Throwable e) {
Element root = newDocument(Constants.ROOT_ELEMENT_ERROR);
Document doc = root.getOwnerDocument();
Element errorHeader = doc.createElement(Constants.ELEMENT_ERROR_HEADER);
Element errorMessage = doc.createElement(Constants.ELEMENT_ERROR_MESSAGE);
Element stackTrace = doc.createElement(Constants.ELEMENT_ERROR_TRACE);
errorHeader.setTextContent(header);
errorMessage.appendChild(doc.createCDATASection(e.getMessage()));
StringWriter buf = new StringWriter();
e.printStackTrace(new PrintWriter(buf, true));
stackTrace.appendChild(doc.createCDATASection(buf.toString()));
root.appendChild(errorHeader);
root.appendChild(errorMessage);
root.appendChild(stackTrace);
return doc;
}
}

View File

@ -0,0 +1,100 @@
/*
* 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 org.apache.openjpa.persistence.jest.Constants.*;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.EntityManager;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import org.apache.openjpa.util.ApplicationIds;
/**
* @author Pinaki Poddar
*
*/
class FindCommand extends AbstractCommand {
private static final List<String> _mandatoryArgs = Arrays.asList(ARG_TYPE);
private static final List<String> _validQualifiers = Arrays.asList("format", "plan");
public FindCommand(JPAServletContext ctx) {
super(ctx);
}
@Override
protected Collection<String> getMandatoryArguments() {
return _mandatoryArgs;
}
@Override
protected int getMinimumArguments() {
return 1;
}
protected Collection<String> getValidQualifiers() {
return _validQualifiers;
}
@Override
public void process() throws ProcessingException {
OpenJPAEntityManager em = ctx.getPersistenceContext();
String type = getMandatoryArgument(ARG_TYPE);
ClassMetaData meta = ctx.resolve(type);
Map<String,String> parameters = getArguments();
Object[] pks = new Object[parameters.size()];
Iterator<Map.Entry<String,String>> params = parameters.entrySet().iterator();
for (int i = 0; i < parameters.size(); i++) {
pks[i] = params.next().getKey();
}
Object oid = ApplicationIds.fromPKValues(pks, meta);
pushFetchPlan(em);
try {
Object pc = em.find(meta.getDescribedType(), oid);
if (pc != null) {
OpenJPAStateManager sm = toStateManager(pc);
ObjectFormatter<?> formatter = getObjectFormatter();
ctx.getResponse().setContentType(formatter.getMimeType());
try {
formatter.writeOut(Collections.singleton(sm), em.getMetamodel(),
_loc.get("find-title").toString(), _loc.get("find-desc").toString(), ctx.getRequestURI(),
ctx.getResponse().getOutputStream());
} catch (IOException e) {
throw new ProcessingException(ctx, e);
}
} else {
throw new ProcessingException(ctx, _loc.get("entity-not-found", type, Arrays.toString(pks)),
HttpURLConnection.HTTP_NOT_FOUND);
}
} finally {
popFetchPlan(true);
}
}
}

View File

@ -0,0 +1,45 @@
/*
* 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 org.apache.openjpa.persistence.jest.Constants.NULL_VALUE;
import org.apache.openjpa.kernel.OpenJPAStateManager;
/**
* String reference of a managed object.
*
* @author Pinaki Poddar
*
*/
public class IOR {
public static final char DASH = '-';
/**
* Stringified representation of a managed instance identity.
* The simple Java type name and the persistent identity separated by a {@link Constants#DASH dash}.
*
* @param sm a managed instance.
* @return
*/
public static String toString(OpenJPAStateManager sm) {
if (sm == null) return NULL_VALUE;
return sm.getMetaData().getDescribedType().getSimpleName() + DASH + sm.getObjectId();
}
}

View File

@ -0,0 +1,92 @@
/*
* 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 java.io.IOException;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
/**
* Interface for JEST commands. A JEST command denotes a JPA operation such as <code>find</code>,
* <code>query</code> or <code>domain</code>. Besides signifying a JPA operation, a command may have
* zero or more qualifiers and arguments.
* <br>
* A qualifier qualifies the action to be performed. For example, a <code>query</code> command may be qualified
* to return a single instance as its result, or limit its result to first 20 instances etc.
* <br>
* An argument is an argument to the target JPA method. For example, <code>find</code> command has
* arguments for the type of the instance and the primary key. A <code>query</code> command has the
* query string as its argument.
* <p>
* A concrete command instance is an outcome of parsing a {@link HttpServletRequest request}.
* The {@link HttpServletRequest#getPathInfo() path} segments are parsed for qualifiers.
* The {@link HttpServletRequest#getQueryString() query string} is parsed for the arguments.
* <p>
* A JEST command often attaches special semantics to a standard URI syntax. For example, all JEST
* URI enforces that the first segment of a servlet path denotes the command moniker e.g. the URI<br>
* <code>http://www.jpa.com/jest/find/plan=myPlan?type=Person&1234</code><br>
* with context root <code>http://www.jpa.com/jest</code> has the servlet path <code>/find/plan=myPlan</code>
* and query string <code>type=Person&1234</code>.
* <br>
* The first path segment <code>find</code> will determine that the command is to <em>find</em> a
* persistent entity of type <code>Person</code> and primary key <code>1234</code> using a fetch plan
* named <code>myPlan</code>.
*
* @author Pinaki Poddar
*
*/
public interface JESTCommand {
/**
* Supported format monikers.
*/
public static enum Format {xml, json};
/**
* Parse the given request to populate qualifiers and parameters of this command.
* A command can interpret and consume certain path segments or parameters of the
* original request. During {@link #process(ServletRequest, ServletResponse, JPAServletContext) processing}
* phase, the parameters and qualifiers are accessed from the parsed command itself rather than
* from the
*/
public void parse() throws ProcessingException;
/**
* Accessors for this command's arguments and qualifiers.
* @return
* @exception IllegalStateException if accessed prior to parsing.
*/
public Map<String,String> getArguments();
public String getArgument(String key);
public boolean hasArgument(String key);
public Map<String,String> getQualifiers();
public String getQualifier(String key);
public boolean hasQualifier(String key);
/**
* Process the given request and write the output on to the given response in the given context.
* @throws ProcessingException
*
*/
public void process() throws ProcessingException, IOException;
}

View File

@ -0,0 +1,330 @@
/*
* 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_INTERNAL_ERROR;
import static java.net.HttpURLConnection.HTTP_NOT_FOUND;
import static org.apache.openjpa.persistence.jest.Constants.CONTEXT_ROOT;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.URI;
import java.net.URISyntaxException;
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}.
* <br>
* 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<String,JESTCommand> _cf = new PrototypeFactory<String,JESTCommand>();
public static final Localizer _loc = Localizer.forPackage(JESTContext.class);
private static final String ONE_YEAR_FROM_NOW;
public static final char QUERY_SEPARATOR = '?';
/**
* 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.
*/
public String getPersistenceUnitName() {
return _unit;
}
/**
* Gets the persistence context. The persistence context is lazily constructed because all commands
* may not need it.
*/
public OpenJPAEntityManager getPersistenceContext() {
if (_em == null) {
_em = _emf.createEntityManager();
}
return _em;
}
/**
* Gets the request.
*/
public HttpServletRequest getRequest() {
return _request;
}
/**
*
*/
public URI getRequestURI() {
StringBuffer buf = _request.getRequestURL();
String query = _request.getQueryString();
if (query != null) {
buf.append(QUERY_SEPARATOR).append(query);
}
try {
return new URI(buf.toString());
} catch (URISyntaxException e) {
throw new ProcessingException(this, _loc.get("bad-uri", _request.getRequestURL()), HTTP_INTERNAL_ERROR);
}
}
/**
* Gets the response.
*/
public HttpServletResponse getResponse() {
return _response;
}
/**
* Executes the request.
* <br>
* 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.
* <br>
* 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.
* <br>
* For example, let the original request URL be:<br>
* <code>http://host:port/demo/jest/find?type=Actor&Robert</code>
* <br>
* The response to this request is a HTML page that contained a hyperlink to <code>jest.css</code> stylesheet
* in its &lt;head&gt; section.<br>
* <code>&lt;link ref="jest.css" .....></code>
* <br>
* The browser will resolve the hyperlink by sending back another request as<br>
* <code>http://host:port/demo/jest/find/jest.css</code>
* <br>
*
* @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. <code>'/'</code> character.
* Otherwise, if the path starts with context root, then returns the substring before the
* next <code>'/'</code> character or end of the string, whichever is earlier.
* If the path does not start with context root, returns
* the substring before the first <code>'/'</code> 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);
}
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);
_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);
}
}
private void log(String s) {
log((short)-1, s);
}
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
* @return
*/
boolean isContextRoot(String path) {
return (path == null || CONTEXT_ROOT.equals(path));
}
/**
* 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(Constants.JEST_TEMPLATE);
CharArrayWriter out = new CharArrayWriter();
new TokenReplacedStream().replace(in, out, tokens);
_rootResource = out.toString();
}
_response.getOutputStream().write(_rootResource.getBytes());
}
}

View File

@ -0,0 +1,161 @@
/*
* 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 org.apache.openjpa.persistence.jest.Constants.INIT_PARA_UNIT;
import static org.apache.openjpa.persistence.jest.Constants.INIT_PARA_STANDALONE;
import static org.apache.openjpa.persistence.jest.Constants._loc;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import javax.persistence.Persistence;
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.apache.openjpa.kernel.AbstractBrokerFactory;
import org.apache.openjpa.kernel.BrokerFactory;
import org.apache.openjpa.persistence.JPAFacadeHelper;
import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
import org.apache.openjpa.persistence.OpenJPAPersistence;
/**
* A specialized HTTP servlet to interpret HTTP requests as Java Persistent API commands
* on a running persistence unit. The persistence unit is identified by the name of the
* unit and is supplied to this servlet during its initialization. The component using
* the persistent unit and this servlet must be within the same module scope.
* <p>
* The syntax of the request URL is described in
* <a href="https://cwiki.apache.org/openjpa/jest-syntax.html">OpenJPA web site</a>.
* <p>
* The response to a resource request is represented in various format, namely
* XML, JSON or a JavaScript that will dynamically render in the browser. The format
* can be controlled via the initialization parameter <code>response.format</code> in
* <code>&lt;init-param&gt;</code> clause or per request basis via <code>format=xml|dojo|json</code>
* encoded in the path expression of the Request URI.
* <p>
* Servlet initialization parameter
* <table cellspacing="20px">
* <tr><th>Parameter</th><th>Value</th></tr>
* <tr><td>persistence.unit</td><td>Name of the persistence unit. Mandatory</td></tr>
* <tr><td>response.format</td><td>Default format used for representation. Defaults to <code>xml</code>.</td></tr>
* </table>
* <br>
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class JESTServlet extends HttpServlet {
private String _unit;
private boolean _debug;
private OpenJPAEntityManagerFactory _emf;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
_debug = "true".equalsIgnoreCase(config.getInitParameter("debug"));
_unit = config.getInitParameter(INIT_PARA_UNIT);
if (_unit == null) {
throw new ServletException(_loc.get("no-persistence-unit-param").toString());
}
boolean standalone = "true".equalsIgnoreCase(config.getInitParameter(INIT_PARA_STANDALONE));
if (standalone) {
createPersistenceUnit();
}
if (findPersistenceUnit()) {
config.getServletContext().log(_loc.get("servlet-init", _unit).toString());
} else {
config.getServletContext().log(_loc.get("servlet-not-init", _unit).toString());
}
}
/**
* Peeks into the servlet path of the request to create appropriate {@link JESTCommand JEST command}.
* Passes the request on to the command which is responsible for generating a response.
*/
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
debug(request);
if (findPersistenceUnit()) {
JESTContext ctx = new JESTContext(_unit, _emf, request, response);
try {
ctx.execute();
} catch (Exception e) {
handleError(ctx, e);
}
} else {
throw new ServletException(_loc.get("no-persistence-unit", _unit).toString());
}
}
protected void createPersistenceUnit() throws ServletException {
try {
Map<String, Object> map = new HashMap<String, Object>();
map.put("openjpa.EntityManagerFactoryPool", true);
_emf = OpenJPAPersistence.cast(Persistence.createEntityManagerFactory(_unit, map));
} catch (Exception e) {
throw new ServletException(_loc.get("no-persistence-unit").toString(), e);
}
}
protected boolean findPersistenceUnit() {
if (_emf == null) {
BrokerFactory bf = AbstractBrokerFactory.getPooledFactoryForKey(_unit);
if (bf != null) {
_emf = (OpenJPAEntityManagerFactory)bf.getUserObject(JPAFacadeHelper.EMF_KEY);
}
}
return _emf != null;
}
protected void handleError(JPAServletContext ctx, Throwable t) throws IOException {
if (t instanceof ProcessingException) {
((ProcessingException)t).printStackTrace();
} else {
new ProcessingException(ctx, t).printStackTrace();
}
}
@Override
public void destroy() {
_emf = null;
_unit = null;;
}
private void debug(HttpServletRequest r) {
if (!_debug) return;
// log("-----------------------------------------------------------");
log(r.getRemoteUser() + "@" + r.getRemoteHost() + ":" + r.getRemotePort() + "[" + r.getPathInfo() + "]");
// log("Request URL = [" + request.getRequestURL() + "]");
// log("Request URI = [" + request.getRequestURI() + "]");
// log("Servlet Path = [" + request.getServletPath() + "]");
// log("Context Path = [" + request.getContextPath() + "]");
// log("Path Info = [" + request.getPathInfo() + "]");
// log("Path Translated = [" + request.getPathTranslated() + "]");
}
public void log(String s) {
System.err.println(s);
super.log(s);
}
}

View File

@ -0,0 +1,82 @@
/*
* 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 java.net.URI;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.openjpa.lib.log.Log;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
/**
* An operating context provides a {@link EntityManage persistence context} and utility functions within
* which all JEST commands execute.
*
* @author Pinaki Poddar
*
*/
public interface JPAServletContext {
/**
* Get the persistence context of the operational context.
*/
public OpenJPAEntityManager getPersistenceContext();
/**
* Get the persistence unit name.
*/
public String getPersistenceUnitName();
/**
* Get the HTTP Request.
*/
public HttpServletRequest getRequest();
/**
* Get the HTTP Response.
*/
public HttpServletResponse getResponse();
/**
* Get the requested URI.
* @return
*/
public URI getRequestURI();
/**
* Resolve the given alias to meta-data of the persistent type.
* @param alias a moniker for the Java type. It can be fully qualified type name or entity name
* or simple name of the actual persistent Java class.
*
* @return meta-data for the given name.
* @exception raises runtime exception if the given name can not be identified to a persistent
* Java type.
*/
public ClassMetaData resolve(String alias);
/**
* Logging message.
* @param level OpenJPA defined {@link Log#INFO log levels}. Invalid levels will print the message on console.
* @param message a printable message.
*/
public void log(short level, String message);
}

View File

@ -0,0 +1,54 @@
/*
* 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;
/**
* A generic interface for a JSON encoded instance.
*
* @author Pinaki Poddar
*
*/
public interface JSON {
/**
* Render into a string buffer.
*
* @param level level at which this instance is being rendered
* @return a mutable buffer
*/
public StringBuilder asString(int level);
public static final char FIELD_SEPARATOR = ',';
public static final char MEMBER_SEPARATOR = ',';
public static final char VALUE_SEPARATOR = ':';
public static final char IOR_SEPARTOR = '-';
public static final char QUOTE = '"';
public static final char SPACE = ' ';
public static final char OBJECT_START = '{';
public static final char OBJECT_END = '}';
public static final char ARRAY_START = '[';
public static final char ARRAY_END = ']';
public static final String NEWLINE = "\r\n";
public static final String NULL_LITERAL = "null";
public static final String REF_MARKER = "$ref";
public static final String ID_MARKER = "$id";
public static final String ARRAY_EMPTY = "[]";
}

View File

@ -0,0 +1,225 @@
/*
* 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 java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/**
* A JSON instance for persistence.
* <br>
* Persistent instances have a persistent identity that extends beyond the process lifetime unlike other common
* identity such as {@linkplain System#identityHashCode(Object) identity hash code} for a Java instance in a JVM.
* <br>
* A JSONObject instance must need such a persistent identity.
*
* @author Pinaki Poddar
*
*/
public class JSONObject implements JSON {
private final String _type;
private final String _id;
private final boolean _ref;
private final Map<String, Object> _values;
public JSONObject(String type, Object id, boolean ref) {
_type = type;
_id = id.toString();
_ref = ref;
_values = new LinkedHashMap<String, Object>();
}
public void set(String key, Object value) {
_values.put(key, value);
}
public void write(PrintWriter writer) {
writer.println(toString());
}
public String toString() {
return asString(0).toString();
}
public StringBuilder asString(int indent) {
StringBuilder buf = new StringBuilder().append(OBJECT_START);
buf.append(encodeField(_ref ? REF_MARKER : ID_MARKER, ior(), 0));
if (_ref) {
return buf.append(OBJECT_END);
}
StringBuilder tab = newIndent(indent+1);
for (Map.Entry<String, Object> e : _values.entrySet()) {
buf.append(FIELD_SEPARATOR).append(NEWLINE);
buf.append(tab).append(encodeField(e.getKey(), e.getValue(), indent+1));
}
buf.append(NEWLINE)
.append(newIndent(indent))
.append(OBJECT_END);
return buf;
}
/**
* Encoding a JSON field is a quoted field name, followed by a :, followed by a value (which itself can be JSON)
* @param field
* @param value
* @param indent
* @return
*/
private static StringBuilder encodeField(String field, Object value, int indent) {
return new StringBuilder()
.append(quoteFieldName(field))
.append(VALUE_SEPARATOR)
.append(quoteFieldValue(value, indent));
}
private static StringBuilder newIndent(int indent) {
char[] tabs = new char[indent*4];
Arrays.fill(tabs, SPACE);
return new StringBuilder().append(tabs);
}
StringBuilder ior() {
return new StringBuilder(_type).append('-').append(_id);
}
private static StringBuilder quoteFieldName(String s) {
return new StringBuilder().append(QUOTE).append(s).append(QUOTE);
}
/**
* Creates a StringBuilder for the given value.
* If the value is null, outputs <code>null</code> without quote
* If the value is Number, outputs the value without quote
* If the value is JSON, outputs the string rendition of value
* Otherwise quoted value
* @param o
* @return
*/
private static StringBuilder quoteFieldValue(Object o, int indent) {
if (o == null) return new StringBuilder(NULL_LITERAL);
if (o instanceof Number) return new StringBuilder(o.toString());
if (o instanceof JSON) return ((JSON)o).asString(indent);
return quoted(o.toString());
}
private static StringBuilder quoted(Object o) {
if (o == null) return new StringBuilder(NULL_LITERAL);
return new StringBuilder().append(QUOTE).append(o.toString()).append(QUOTE);
}
public static class Array implements JSON {
private List<Object> _members = new ArrayList<Object>();
public void add(Object o) {
_members.add(o);
}
public String toString() {
return asString(0).toString();
}
public StringBuilder asString(int indent) {
StringBuilder buf = new StringBuilder().append(ARRAY_START);
StringBuilder tab = JSONObject.newIndent(indent+1);
for (Object o : _members) {
if (buf.length() > 1) buf.append(MEMBER_SEPARATOR);
buf.append(NEWLINE);
if (o instanceof JSON)
buf.append(tab).append(((JSON)o).asString(indent+1));
else
buf.append(tab).append(o);
}
buf.append(NEWLINE)
.append(JSONObject.newIndent(indent))
.append(ARRAY_END);
return buf;
}
}
public static class KVMap implements JSON {
private Map<Object,Object> _entries = new LinkedHashMap<Object,Object>();
public void put(Object k, Object v) {
_entries.put(k,v);
}
public String toString() {
return asString(0).toString();
}
public StringBuilder asString(int indent) {
StringBuilder buf = new StringBuilder().append(ARRAY_START);
StringBuilder tab = JSONObject.newIndent(indent+1);
for (Map.Entry<Object, Object> e : _entries.entrySet()) {
if (buf.length()>1) buf.append(MEMBER_SEPARATOR);
buf.append(NEWLINE);
Object key = e.getKey();
if (key instanceof JSON)
buf.append(tab).append(((JSON)key).asString(indent+1));
else
buf.append(tab).append(key);
buf.append(VALUE_SEPARATOR);
Object value = e.getValue();
if (value instanceof JSON)
buf.append(((JSON)value).asString(indent+2));
else
buf.append(value);
}
buf.append(NEWLINE)
.append(JSONObject.newIndent(indent))
.append(ARRAY_END);
return buf;
}
}
public static void main(String[] args) throws Exception {
JSONObject o = new JSONObject("Person", 1234, false);
JSONObject r = new JSONObject("Person", 1234, true);
JSONObject f = new JSONObject("Person", 2345, false);
Array a = new Array();
a.add(f);
a.add(3456);
a.add(null);
a.add(r);
a.add(null);
KVMap map = new KVMap();
map.put("k1", r);
map.put("k2", f);
map.put("k3", null);
map.put("k4", 3456);
map.put(null, 6789);
f.set("name", "Mary");
f.set("age", 30);
f.set("friend", r);
o.set("name", "John");
o.set("age", 20);
o.set("friend", f);
o.set("friends", a);
o.set("map", map);
System.err.println(o);
}
}

View File

@ -0,0 +1,296 @@
/*
* 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 java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Reader;
import java.net.URI;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Metamodel;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.persistence.meta.Members;
import static org.apache.openjpa.persistence.jest.Constants.MIME_TYPE_JSON;
/**
* Marshals a root instance and its persistent closure as JSON object.
* The closure is resolved against the persistence context that contains the root instance.
* The JSON format introduces a $id and $ref to address reference that pure JSON does not.
*
* @author Pinaki Poddar
*
*/
public class JSONObjectFormatter implements ObjectFormatter<JSON> {
public String getMimeType() {
return MIME_TYPE_JSON;
}
public void encode(Object obj, JPAServletContext ctx) {
if (obj instanceof OpenJPAStateManager) {
try {
JSON result = encodeManagedInstance((OpenJPAStateManager)obj,
ctx.getPersistenceContext().getMetamodel());
PrintWriter writer = ctx.getResponse().getWriter();
writer.println(result.toString());
} catch (Exception e) {
throw new ProcessingException(ctx, e);
}
} else {
throw new RuntimeException(this + " does not know how to encode " + obj);
}
return;
}
public JSON writeOut(Collection<OpenJPAStateManager> sms, Metamodel model, String title, String desc,
URI uri, OutputStream out) throws IOException {
JSON json = encode(sms,model);
out.write(json.toString().getBytes());
return json;
}
public JSON encode(Collection<OpenJPAStateManager> sms, Metamodel model) {
return encodeManagedInstances(sms, model);
}
/**
* Encodes the given managed instance into a new XML element as a child of the given parent node.
*
* @param sm a managed instance, can be null.
* @param parent the parent node to which the new node be attached.
*/
private JSON encodeManagedInstance(final OpenJPAStateManager sm, Metamodel model) {
return encodeManagedInstance(sm, new HashSet<OpenJPAStateManager>(), 0, false, model);
}
private JSON encodeManagedInstances(final Collection<OpenJPAStateManager> sms, Metamodel model) {
JSONObject.Array result = new JSONObject.Array();
for (OpenJPAStateManager sm : sms) {
result.add(encodeManagedInstance(sm, new HashSet<OpenJPAStateManager>(), 0, false, model));
}
return result;
}
/**
* Encodes the closure of a persistent instance into a XML element.
*
* @param sm the managed instance to be encoded. Can be null.
* @param parent the parent XML element to which the new XML element be added. Must not be null. Must be
* owned by a document.
* @param visited the persistent instances that had been encoded already. Must not be null or immutable.
*
* @return the new element. The element has been appended as a child to the given parent in this method.
*/
private JSONObject encodeManagedInstance(final OpenJPAStateManager sm, final Set<OpenJPAStateManager> visited,
int indent, boolean indentPara, Metamodel model) {
if (visited == null) {
throw new IllegalArgumentException("null closure for encoder");
}
if (sm == null) {
return null;
}
boolean ref = !visited.add(sm);
JSONObject root = new JSONObject(typeOf(sm), sm.getObjectId(), ref);;
if (ref) {
return root;
}
BitSet loaded = sm.getLoaded();
StoreContext ctx = (StoreContext)sm.getGenericContext();
List<Attribute<?, ?>> attrs = MetamodelHelper.getAttributesInOrder(sm.getMetaData(), model);
for (int i = 0; i < attrs.size(); i++) {
FieldMetaData fmd = ((Members.Member<?, ?>) attrs.get(i)).fmd;
if (!loaded.get(fmd.getIndex()))
continue;
Object value = sm.fetch(fmd.getIndex());
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.BOOLEAN:
case JavaTypes.BYTE:
case JavaTypes.CHAR:
case JavaTypes.DOUBLE:
case JavaTypes.FLOAT:
case JavaTypes.INT:
case JavaTypes.LONG:
case JavaTypes.SHORT:
case JavaTypes.BOOLEAN_OBJ:
case JavaTypes.BYTE_OBJ:
case JavaTypes.CHAR_OBJ:
case JavaTypes.DOUBLE_OBJ:
case JavaTypes.FLOAT_OBJ:
case JavaTypes.INT_OBJ:
case JavaTypes.LONG_OBJ:
case JavaTypes.SHORT_OBJ:
case JavaTypes.BIGDECIMAL:
case JavaTypes.BIGINTEGER:
case JavaTypes.DATE:
case JavaTypes.NUMBER:
case JavaTypes.CALENDAR:
case JavaTypes.LOCALE:
case JavaTypes.STRING:
case JavaTypes.ENUM:
root.set(fmd.getName(),value);
break;
case JavaTypes.PC:
if (value == null) {
root.set(fmd.getName(), null);
} else {
root.set(fmd.getName(),encodeManagedInstance(ctx.getStateManager(value), visited,
indent+1, false, model));
}
break;
case JavaTypes.ARRAY:
Object[] values = (Object[])value;
value = Arrays.asList(values);
// no break;
case JavaTypes.COLLECTION:
if (value == null) {
root.set(fmd.getName(), null);
break;
}
Collection<?> members = (Collection<?>)value;
JSONObject.Array array = new JSONObject.Array();
root.set(fmd.getName(), array);
if (members.isEmpty()) {
break;
}
boolean basic = fmd.getElement().getTypeMetaData() == null;
for (Object o : members) {
if (o == null) {
array.add(null);
} else {
if (basic) {
array.add(o);
} else {
array.add(encodeManagedInstance(ctx.getStateManager(o), visited, indent+1, true,
model));
}
}
}
break;
case JavaTypes.MAP:
if (value == null) {
root.set(fmd.getName(), null);
break;
}
Set<Map.Entry> entries = ((Map)value).entrySet();
JSONObject.KVMap map = new JSONObject.KVMap();
root.set(fmd.getName(), map);
if (entries.isEmpty()) {
break;
}
boolean basicKey = fmd.getElement().getTypeMetaData() == null;
boolean basicValue = fmd.getValue().getTypeMetaData() == null;
for (Map.Entry<?,?> e : entries) {
Object k = e.getKey();
Object v = e.getValue();
if (!basicKey) {
k = encodeManagedInstance(ctx.getStateManager(k), visited, indent+1, true, model);
}
if (!basicValue) {
v = encodeManagedInstance(ctx.getStateManager(e.getValue()), visited,
indent+1, false, model);
}
map.put(k,v);
}
break;
case JavaTypes.INPUT_STREAM:
case JavaTypes.INPUT_READER:
root.set(fmd.getName(), streamToString(value));
break;
case JavaTypes.PC_UNTYPED:
case JavaTypes.OBJECT:
case JavaTypes.OID:
root.set(fmd.getName(), "***UNSUPPORTED***");
}
}
return root;
}
String typeOf(OpenJPAStateManager sm) {
return sm.getMetaData().getDescribedType().getSimpleName();
}
/**
* Convert the given stream (either an InutStream or a Reader) to a String
* to be included in CDATA section of a XML document.
*
* @param value the field value to be converted. Can not be null
* @return
*/
String streamToString(Object value) {
Reader reader = null;
if (value instanceof InputStream) {
reader = new BufferedReader(new InputStreamReader((InputStream)value));
} else if (value instanceof Reader) {
reader = (Reader)value;
} else {
throw new RuntimeException();
}
CharArrayWriter writer = new CharArrayWriter();
try {
for (int c; (c = reader.read()) != -1;) {
writer.write(c);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return writer.toString();
}
@Override
public JSON encode(Metamodel model) {
// TODO Auto-generated method stub
return null;
}
@Override
public JSON writeOut(Metamodel model, String title, String desc, URI uri, OutputStream out) throws IOException {
throw new UnsupportedOperationException();
}
}

View File

@ -0,0 +1,187 @@
/*
* 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 java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.persistence.meta.Members;
import static org.apache.openjpa.persistence.jest.Constants.*;
/**
* @author Pinaki Poddar
*
*/
public class MetamodelHelper {
public static final char DASH = '-';
public static final char UNDERSCORE = '_';
/**
* Attribute Category makes a finer distinction over PersistentAttributeType declared in
* {@link Attribute.PersistentAttributeType} such as id, version, lob or enum.
* <br>
* <b>Important</b>: The name of the enumerated elements is important because
* a) some of these names are same as in Attribute.PersistentAttributeType enumeration
* b) names are used by XML serialization with underscores replaced by dash and decapitalized
*
*/
public static enum AttributeCategory {
ID, VERSION, BASIC, ENUM, EMBEDDED, LOB,
ONE_TO_ONE, MANY_TO_ONE, ONE_TO_MANY, ELEMENT_COLLECTION, MANY_TO_MANY
}
public static List<Attribute<?,?>> getAttributesInOrder(Class<?> cls, Metamodel model) {
return getAttributesInOrder(model.managedType(cls));
}
public static List<Attribute<?,?>> getAttributesInOrder(ClassMetaData meta, Metamodel model) {
return getAttributesInOrder(meta.getDescribedType(), model);
}
/**
* Gets the attributes of the given type in defined order.
* @param type
* @return
*/
public static List<Attribute<?,?>> getAttributesInOrder(ManagedType<?> type) {
List<Attribute<?,?>> list = new ArrayList<Attribute<?,?>>(type.getAttributes());
Collections.sort(list, new AttributeComparator());
return list;
}
public static boolean isId(Attribute<?,?> a) {
if (a instanceof SingularAttribute)
return ((SingularAttribute<?,?>)a).isId();
return false;
}
public static boolean isVersion(Attribute<?,?> a) {
if (a instanceof SingularAttribute)
return ((SingularAttribute<?,?>)a).isVersion();
return false;
}
public static boolean isEnum(Attribute<?,?> a) {
if (a instanceof Members.Member) {
int type = ((Members.Member<?,?>)a).fmd.getDeclaredTypeCode();
return type == JavaTypes.ENUM;
}
return false;
}
public static boolean isLob(Attribute<?,?> a) {
if (a instanceof Members.Member) {
int type = ((Members.Member<?,?>)a).fmd.getDeclaredTypeCode();
return type == JavaTypes.INPUT_READER || type == JavaTypes.INPUT_STREAM;
}
return false;
}
/**
* Gets a ordinal value of enumerated persistent attribute category.
*
* @param attr
* @return
*/
public static AttributeCategory getAttributeCategory(Attribute<?,?> attr) {
if (isId(attr))
return AttributeCategory.ID;
if (isVersion(attr))
return AttributeCategory.VERSION;
if (isLob(attr))
return AttributeCategory.LOB;
if (isEnum(attr))
return AttributeCategory.ENUM;
switch (attr.getPersistentAttributeType()) {
case BASIC :
return AttributeCategory.BASIC;
case EMBEDDED:
return AttributeCategory.EMBEDDED;
case ONE_TO_ONE:
return AttributeCategory.ONE_TO_ONE;
case MANY_TO_ONE:
return AttributeCategory.MANY_TO_ONE;
case ONE_TO_MANY:
case ELEMENT_COLLECTION:
return AttributeCategory.ONE_TO_MANY;
case MANY_TO_MANY:
return AttributeCategory.MANY_TO_MANY;
}
throw new RuntimeException(attr.toString());
}
public static String getTagByAttributeType(Attribute<?, ?> attr) {
return getAttributeCategory(attr).name().replace(UNDERSCORE, DASH).toLowerCase();
}
/**
* Gets name of the attribute type. For collection and map type attribute, the name is
* appended with generic type argument names.
* @param attr
* @return
*/
public static String getAttributeTypeName(Attribute<?, ?> attr) {
StringBuilder name = new StringBuilder(attr.getJavaType().getSimpleName());
switch (attr.getPersistentAttributeType()) {
case ONE_TO_MANY:
case ELEMENT_COLLECTION:
name.append("&lt;")
.append(((PluralAttribute<?,?,?>)attr).getBindableJavaType().getSimpleName())
.append("&gt;");
break;
case MANY_TO_MANY:
name.append("&lt;")
.append(((MapAttribute<?,?,?>)attr).getKeyJavaType().getSimpleName())
.append(',')
.append(((MapAttribute<?,?,?>)attr).getBindableJavaType().getSimpleName())
.append("&gt;");
break;
default:
}
return name.toString();
}
/**
* Compares attribute by their category and within the same category by name.
*
*/
public static class AttributeComparator implements Comparator<Attribute<?,?>> {
public int compare(Attribute<?, ?> a1, Attribute<?, ?> a2) {
AttributeCategory t1 = getAttributeCategory(a1);
AttributeCategory t2 = getAttributeCategory(a2);
if (t1.equals(t2)) {
return a1.getName().compareTo(a2.getName());
} else {
return t1.compareTo(t2);
}
}
}
}

View File

@ -0,0 +1,107 @@
/*
* 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 java.io.IOException;
import java.io.OutputStream;
import java.net.URI;
import java.text.SimpleDateFormat;
import java.util.Collection;
import javax.persistence.metamodel.Metamodel;
import javax.servlet.http.HttpServletResponse;
import org.apache.openjpa.kernel.OpenJPAStateManager;
/**
* A parameterized interface defines the protocol for converting {@link OpenJPAStateManager managed} persistence
* instances or a persistent {@link Metamodel domain model} into a form suitable for transport to a language-neutral
* client such as an web browser.
* <p>
* The interface prefers that the resultant resource as a <em>complete</em> representation i.e. all the references
* contained in the resource can be resolved within the same resource itself. As the intended recipient of this
* resource is a remote client, an <em>incomplete</em> resource will require the client to request further for
* any (unresolved) reference resulting in a <em>chatty</em> protocol.
* <p>
* This interface also defines methods for writing the representation into an output stream e.g.
* {@link HttpServletResponse#getOutputStream() response output stream} of a HTTP Servlet.
* <p>
* Implementation Note: Each concrete formatter type is registered with {@linkplain PrototypeFactory factory}
* that requires the implementation to have a no-argument constructor.
*
* @param <T> the type of encoded output
*
* @author Pinaki Poddar
*
*/
public interface ObjectFormatter<T> {
public static final SimpleDateFormat dateFormat = new SimpleDateFormat("MMM dd, yyyy");
/**
* Gets the mime type produced by this formatter.
*/
public String getMimeType();
/**
* Encode the {@link Closure persistent closure} of the given collection of managed instances as a
* resource e.g a XML or HTML document or an interactive document with JavaScript or a JSON array.
* Exact nature of the output type is the generic parameter of this interface.
*
* @param objs a collection of managed instances
* @param model domain model
*
* @return an encoded object e.g. a XML or HTML Document or a JSON object.
*/
public T encode(Collection<OpenJPAStateManager> objs, Metamodel model);
/**
* Encode the given domain model in to a object.
*
* @param model a meta-model of managed types
*
* @return an encoded object e.g. a XML or HTML Document or a JSON object.
*/
public T encode(Metamodel model);
/**
* Encodes the {@link Closure persistent closure} of the given collection of objects, then write it into
* the given output stream.
*
* @param objs the collection of objects to be formatted.
* @param model a meta-model of managed types, provided for easier introspection if necessary
* @param title TODO
* @param desc TODO
* @param uri TODO
* @param writer a text-oriented output stream
* @throws IOException
*/
public T writeOut(Collection<OpenJPAStateManager> objs, Metamodel model,
String title, String desc, URI uri, OutputStream out) throws IOException;
/**
* Encodes the given domain model, then write it into the given output stream.
*
* @param model a meta-model of managed types
* @param writer a text-oriented output stream
*
* @throws IOException
*/
public T writeOut(Metamodel model, String title, String desc, URI uri, OutputStream out) throws IOException;
}

View File

@ -0,0 +1,97 @@
/*
* 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_INTERNAL_ERROR;
import static org.apache.openjpa.persistence.jest.Constants.MIME_TYPE_XML;
import java.io.IOException;
import java.net.URLDecoder;
import javax.servlet.http.HttpServletResponse;
import org.apache.openjpa.lib.util.Localizer.Message;
import org.w3c.dom.Document;
/**
* Specialized RuntimException thrown by JEST commands.
* The exception can be serialized to the output stream of a HTTP Servlet response as a HTML page.
*
* @author Pinaki Poddar
*
*/
@SuppressWarnings("serial")
public class ProcessingException extends RuntimeException {
private final JPAServletContext ctx;
private final int _errorCode;
public ProcessingException(JPAServletContext ctx, Throwable error) {
this(ctx, error, HTTP_INTERNAL_ERROR);
}
public ProcessingException(JPAServletContext ctx, Throwable error, int errorCode) {
super(error);
this.ctx = ctx;
this._errorCode = errorCode;
}
public ProcessingException(JPAServletContext ctx, Message message, int errorCode) {
super(message.toString());
this.ctx = ctx;
this._errorCode = errorCode;
}
public ProcessingException(JPAServletContext ctx, Throwable error, Message message) {
this(ctx, error, message, HTTP_INTERNAL_ERROR);
}
public ProcessingException(JPAServletContext ctx, Throwable error, Message message, int errorCode) {
super(message.toString(), error);
this.ctx = ctx;
this._errorCode = errorCode;
}
/**
* Prints the stack trace in a HTML format on the given response output stream.
*
* @param response
* @throws IOException
*/
public void printStackTrace() {
HttpServletResponse response = ctx.getResponse();
response.setContentType(MIME_TYPE_XML);
response.setStatus(_errorCode);
String uri = ctx.getRequestURI().toString();
try {
uri = URLDecoder.decode(uri, "UTF-8");
} catch (Exception e) {
}
Throwable t = this.getCause() == null ? this : getCause();
ExceptionFormatter formatter = new ExceptionFormatter();
Document xml = formatter.createXML("Request URI: " + uri, t);
try {
formatter.write(xml, response.getOutputStream());
} catch (IOException e) {
e.printStackTrace();
throw new RuntimeException("Request URI: " + uri, e);
}
}
}

View File

@ -0,0 +1,77 @@
/*
* 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 org.apache.openjpa.persistence.jest.Constants.*;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.HttpURLConnection;
import java.util.Iterator;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.w3c.dom.Document;
/**
* Represents configuration properties in HTML.
*
* @author Pinaki Poddar
*
*/
public class PropertiesCommand extends AbstractCommand {
private static final char DOT = '.';
public PropertiesCommand(JPAServletContext ctx) {
super(ctx);
}
protected int getMaximumArguments() {
return 0;
}
@Override
public void process() throws ProcessingException, IOException {
HttpServletResponse response = ctx.getResponse();
response.setContentType(MIME_TYPE_XML);
Map<String,Object> properties = ctx.getPersistenceContext().getProperties();
removeBadEntries(properties);
PropertiesFormatter formatter = new PropertiesFormatter();
String caption = _loc.get("properties-caption", ctx.getPersistenceUnitName()).toString();
Document xml = formatter.createXML(caption, "", "", properties);
formatter.write(xml, response.getOutputStream());
response.setStatus(HttpURLConnection.HTTP_OK);
}
private void removeBadEntries(Map<String,Object> map) {
Iterator<String> keys = map.keySet().iterator();
for (; keys.hasNext();) {
if (keys.next().indexOf(DOT) == -1) keys.remove();
}
}
@Override
protected Format getDefaultFormat() {
return Format.xml;
}
}

View File

@ -0,0 +1,49 @@
/*
* 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 java.util.Arrays;
import java.util.Map;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Formats a key-value pair in a HTML Document.
*
* @author Pinaki Poddar
*
*/
class PropertiesFormatter extends XMLFormatter {
public Document createXML(String title, String tkey, String tvalue, Map<String,Object> properties) {
Element root = newDocument(Constants.ROOT_ELEMENT_PROPERTIES);
for (Map.Entry<String,Object> entry : properties.entrySet()) {
Element property = root.getOwnerDocument().createElement("property");
Object value = entry.getValue();
String v = value == null
? Constants.NULL_VALUE
: value.getClass().isArray() ? Arrays.toString((Object[])value) : value.toString();
property.setAttribute(Constants.ATTR_PROPERTY_KEY, entry.getKey());
property.setAttribute(Constants.ATTR_PROPERTY_VALUE, v);
root.appendChild(property);
}
return root.getOwnerDocument();
}
}

View File

@ -0,0 +1,126 @@
/*
* 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 java.lang.reflect.Constructor;
import java.util.Collections;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import org.apache.openjpa.kernel.Filters;
/**
* A factory for a specific type of objects registered by a key.
* The client registers a type indexed by name.
* The client can get a new instance of the registered type.
* The requested registered type <em>not</em> necessarily have to have a no-arg
* constructor. The constructor arguments can be passed during
* {@link #newInstance(Class, Object...) new instance} request. Based on the
* arguments, a matching constructor, if any, is located and invoked.
*
* <K> type of key for this registry
* <T> base type of the objects to construct
*
* @author Pinaki Poddar
*
*/
public class PrototypeFactory<K,T> {
private Map<K, Class<? extends T>> _registry = new TreeMap<K, Class<? extends T>>();
/**
* Register the given class with the given key.
*
* @param key a non-null key.
* @param prototype a type.
*/
public void register(K key, Class<? extends T> prototype) {
_registry.put(key, prototype);
}
/**
* Create a new instance of the type {@linkplain #register(Object, Class) registered} before
* with the given key, if any.
* The given arguments are used to identify a constructor of the registered type and
* passed to the constructor of the registered type.
*
* @param key a key to identify a registered type.
* @param args arguments to pass to the constructor of the type.
*
* @return null if no type has been registered against the given key.
*/
public T newInstance(K key, Object... args) {
return _registry.containsKey(key) ? newInstance(_registry.get(key), args) : null;
}
/**
* Gets the keys registered in this factory.
*
* @return immutable set of registered keys.
*/
public Set<K> getRegisteredKeys() {
return Collections.unmodifiableSet(_registry.keySet());
}
private T newInstance(Class<? extends T> type, Object... args) {
try {
return findConstructor(type, getConstructorParameterTypes(args)).newInstance(args);
} catch (Exception e) {
throw new RuntimeException();
}
}
Class<?>[] getConstructorParameterTypes(Object... args) {
if (args == null || args.length == 0) {
return new Class<?>[0];
}
Class<?>[] types = new Class<?>[args.length];
for (int i = 0; i < args.length; i++) {
types[i] = args[i] == null ? Object.class : args[i].getClass();
}
return types;
}
/**
* Finds a constructor of the given class with given argument types.
*/
Constructor<? extends T> findConstructor(Class<? extends T> cls, Class<?>[] types) {
try {
return cls.getConstructor(types);
} catch (Exception e) {
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> cons : constructors) {
Class<?>[] paramTypes = cons.getParameterTypes();
boolean match = false;
if (paramTypes.length == types.length) {
for (int i = 0; i < paramTypes.length; i++) {
match = paramTypes[i].isAssignableFrom(Filters.wrap(types[i]));
if (!match)
break;
}
}
if (match) {
return (Constructor<? extends T>)cons;
}
}
}
throw new RuntimeException();//_loc.get("fill-ctor-none", cls, Arrays.toString(types)).getMessage());
}
}

View File

@ -0,0 +1,98 @@
/*
* 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 java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static org.apache.openjpa.persistence.jest.Constants.*;
import javax.persistence.EntityManager;
import javax.persistence.Query;
import org.apache.openjpa.persistence.ArgumentException;
import org.apache.openjpa.persistence.OpenJPAEntityManager;
import static java.net.HttpURLConnection.HTTP_BAD_REQUEST;;
/**
* Executes query.
*
* @author Pinaki Poddar
*
*/
class QueryCommand extends AbstractCommand {
private static final List<String> _mandatoryArgs = Arrays.asList(ARG_QUERY);
private static final List<String> _validQualifiers = Arrays.asList(
QUALIFIER_FORMAT, QUALIFIER_PLAN, QUALIFIER_NAMED, QUALIFIER_SINGLE,
QUALIFIER_FIRSTRESULT, QUALIFIER_MAXRESULT);
public QueryCommand(JPAServletContext ctx) {
super(ctx);
}
@Override
protected Collection<String> getMandatoryArguments() {
return _mandatoryArgs;
}
@Override
protected int getMinimumArguments() {
return 0;
}
protected Collection<String> getValidQualifiers() {
return _validQualifiers;
}
@Override
public void process() throws ProcessingException {
String spec = getMandatoryArgument(ARG_QUERY);
OpenJPAEntityManager em = ctx.getPersistenceContext();
try {
Query query = isBooleanQualifier(QUALIFIER_NAMED) ? em.createNamedQuery(spec) : em.createQuery(spec);
if (hasQualifier(QUALIFIER_FIRSTRESULT))
query.setFirstResult(Integer.parseInt(getQualifier(QUALIFIER_FIRSTRESULT)));
if (hasQualifier(QUALIFIER_MAXRESULT))
query.setMaxResults(Integer.parseInt(getQualifier(QUALIFIER_MAXRESULT)));
pushFetchPlan(query);
Map<String, String> args = getArguments();
for (Map.Entry<String, String> entry : args.entrySet()) {
query.setParameter(entry.getKey(), entry.getValue());
}
getObjectFormatter()
.writeOut(toStateManager(isBooleanQualifier(QUALIFIER_SINGLE)
? Collections.singleton(query.getSingleResult()) : query.getResultList()),
em.getMetamodel(),
_loc.get("query-title").toString(), _loc.get("query-desc").toString(), ctx.getRequestURI(),
ctx.getResponse().getOutputStream());
} catch (ArgumentException e1) {
throw new ProcessingException(ctx, e1, _loc.get("query-execution-error", spec), HTTP_BAD_REQUEST);
} catch (Exception e) {
throw new ProcessingException(ctx, e, _loc.get("query-execution-error", spec));
} finally {
popFetchPlan(false);
}
}
}

View File

@ -0,0 +1,180 @@
/*
* 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 java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.util.Arrays;
/**
* Reads from an input stream and writes to an output stream after replacing matched tokens
* by their counterpart.
*
*
* @author Pinaki Poddar
*
*/
public class TokenReplacedStream {
/**
* Read the given input stream and replaces the tokens as it reads. The replaced stream is written to the
* given output stream.
*
* @param in a non-null input stream
* @param out a character oriented writer
* @param replacements an even number of Strings. Any occurrence of the even-indexed i-th String in the
* input stream will be replaced by the (i+1)-th String in the output writer.
*/
public void replace(InputStream in, Writer out, String... prs) throws IOException {
if (prs.length%2 != 0)
throw new IllegalArgumentException("Even number of pattern/string pairs: " + Arrays.toString(prs)
+ ". Must be even number of arguments.");
Pattern[] patterns = new Pattern[prs.length/2];
for (int i = 0; i < prs.length; i += 2) {
patterns[i/2] = new Pattern(prs[i], prs[i+1]);
}
StringBuilder tmp = new StringBuilder();
for (int c = 0; (c = in.read()) != -1;) {
int cursor = match((char)c, patterns);
if (cursor < 0) { // no pattern recognized at all
if (tmp.length() > 0) { // append partial match then discard partial memory
for (int j = 0; j < tmp.length(); j++) {
out.write(tmp.charAt(j));
}
tmp.delete(0, tmp.length());
}
out.write((char)c); // directly output
} else {
Pattern p = matched(patterns); // has any pattern matched completely
if (p != null) { // a pattern matched completely
char[] replace = p.replace().toCharArray();
for (int j = 0; j < replace.length; j++) {
out.write(replace[j]);
}
reset(patterns);
tmp.delete(0, tmp.length());
} else {
tmp.append((char)c); // remember partial match
}
}
}
}
/**
* Match the given character to all patterns and return the index of highest match.
* @param c a character to match
* @param patterns an array of patterns
* @return -1 if character matched no pattern
*/
int match(char c, Pattern...patterns) {
if (patterns == null)
return -1;
int result = -1;
for (Pattern p : patterns) {
result = Math.max(result, p.match(c));
}
return result;
}
/**
* Gets the pattern if any in matched state
* @param patterns
* @return
*/
Pattern matched(Pattern...patterns) {
if (patterns == null)
return null;
for (Pattern p : patterns) {
if (p.isMatched()) return p;
}
return null;
}
/**
* Resets all the patterns.
* @param patterns
*/
void reset(Pattern...patterns) {
if (patterns == null)
return;
for (Pattern p : patterns) {
p.reset();
}
}
public static class Pattern {
private final char[] chars;
private final String _replace;
private int _cursor;
/**
* Construct a pattern and its replacement.
*/
public Pattern(String s, String replace) {
if (s == null || s.length() == 0)
throw new IllegalArgumentException("Pattern [" + s + "] can not be empty or null ");
if (replace == null)
throw new IllegalArgumentException("Replacement [" + replace + "] is null for pattern [" + s + "]");
chars = s.toCharArray();
_cursor = -1;
_replace = replace;
}
/**
* Match the given character with the current cursor and advance the matching length.
* @param c
* @return the matching length. -1 denotes the pattern did not match the character.
*/
public int match(char c) {
if (c != chars[++_cursor]) {
reset();
}
return _cursor;
}
/**
* Reset the cursor. Subsequent matching will begin at start.
*/
public void reset() {
_cursor = -1;
}
/**
* Is this pattern matched fully?
* A pattern is fully matched when the matching length is equal to the length of the pattern string.
*/
public boolean isMatched() {
return _cursor == chars.length-1;
}
/**
* Gets the string to be replaced.
*/
public String replace() {
return _replace;
}
public String toString() {
return new String(chars) + ":" + _cursor;
}
}
}

View File

@ -0,0 +1,516 @@
/*
* 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 org.apache.openjpa.persistence.jest.Constants.*;
import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.net.URI;
import java.net.URLEncoder;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.ManagedType;
import javax.persistence.metamodel.MapAttribute;
import javax.persistence.metamodel.Metamodel;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.apache.openjpa.kernel.OpenJPAStateManager;
import org.apache.openjpa.kernel.StoreContext;
import org.apache.openjpa.meta.ClassMetaData;
import org.apache.openjpa.meta.FieldMetaData;
import org.apache.openjpa.meta.JavaTypes;
import org.apache.openjpa.meta.ValueMetaData;
import org.apache.openjpa.persistence.meta.Members;
import org.apache.openjpa.util.InternalException;
import org.w3c.dom.CDATASection;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
/**
* Marshals a root instance and its persistent closure as an XML element.
* The closure is resolved against the persistence context that contains the root instance.
* The XML document adheres to the <code>jest-instance.xsd</code> schema.
*
* @author Pinaki Poddar
*
*/
public class XMLFormatter implements ObjectFormatter<Document> {
public static final Schema _xsd;
private static final DocumentBuilder _builder;
private static final Transformer _transformer;
private static final String EMPTY_TEXT = " ";
static {
try {
_builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
_transformer = TransformerFactory.newInstance().newTransformer();
SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
InputStream xsd = XMLFormatter.class.getResourceAsStream(JEST_INSTANCE_XSD);
_xsd = factory.newSchema(new StreamSource(xsd));
_transformer.setOutputProperty(OutputKeys.METHOD, "xml");
_transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
_transformer.setOutputProperty(OutputKeys.INDENT, "yes");
_transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
_transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
_transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getMimeType() {
return MIME_TYPE_XML;
}
/**
* Encodes the closure of given collection of managed instance into a new XML document
* according to JEST Instance XML Schema.
*
* @param sm a collection of managed instances.
* @param parent the parent node to which the new node be attached.
*/
public Document encode(final Collection<OpenJPAStateManager> sms, Metamodel model) {
Element root = newDocument(ROOT_ELEMENT_INSTANCE);
Closure closure = new Closure(sms);
for (OpenJPAStateManager sm : closure) {
encodeManagedInstance(sm, root, false, model);
}
return root.getOwnerDocument();
}
/**
* Encodes the given meta-model into a new XML document according to JEST Domain XML Schema.
*
* @param model a persistent domain model. Must not be null.
*/
public Document encode(Metamodel model) {
Element root = newDocument(ROOT_ELEMENT_MODEL);
for (ManagedType<?> t : model.getManagedTypes()) {
encodeManagedType(t, root);
}
return root.getOwnerDocument();
}
/**
* Create a new document with the given tag as the root element.
*
* @param rootTag the tag of the root element
*
* @return the document element of a new document
*/
public Element newDocument(String rootTag) {
Document doc = _builder.newDocument();
Element root = doc.createElement(rootTag);
doc.appendChild(root);
String[] nvpairs = new String[] {
"xmlns:xsi", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI,
// "xsi:noNamespaceSchemaLocation", JEST_INSTANCE_XSD,
ATTR_VERSION, "1.0",
};
for (int i = 0; i < nvpairs.length; i += 2) {
root.setAttribute(nvpairs[i], nvpairs[i+1]);
}
return root;
}
@Override
public Document writeOut(Collection<OpenJPAStateManager> objs, Metamodel model, String title, String desc,
URI uri, OutputStream out) throws IOException {
Document doc = encode(objs, model);
decorate(doc, title, desc, uri);
write(doc, out);
return doc;
}
@Override
public Document writeOut(Metamodel model, String title, String desc, URI uri, OutputStream out)
throws IOException {
Document doc = encode(model);
decorate(doc, title, desc, uri);
write(doc, out);
return doc;
}
Document decorate(Document doc, String title, String desc, URI uri) {
Element root = doc.getDocumentElement();
Element instance = (Element)root.getElementsByTagName(ELEMENT_INSTANCE).item(0);
Element uriElement = doc.createElement(ELEMENT_URI);
uriElement.setTextContent(uri == null ? NULL_VALUE : uri.toString());
Element descElement = doc.createElement(ELEMENT_DESCRIPTION);
descElement.setTextContent(desc == null ? NULL_VALUE : desc);
root.insertBefore(uriElement, instance);
root.insertBefore(descElement, instance);
return doc;
}
public void write(Document doc, OutputStream out) throws IOException {
try {
_transformer.transform(new DOMSource(doc), new StreamResult(out));
} catch (Exception e) {
throw new IOException(e);
}
}
public void write(Document doc, Writer writer) throws IOException {
try {
_transformer.transform(new DOMSource(doc), new StreamResult(writer));
} catch (Exception e) {
throw new IOException(e);
}
}
/**
* Encodes the closure of a persistent instance into a XML element.
*
* @param sm the managed instance to be encoded. Can be null.
* @param parent the parent XML element to which the new XML element be added. Must not be null. Must be
* owned by a document.
* @param visited the persistent instances that had been encoded already. Must not be null or immutable.
*
* @return the new element. The element has been appended as a child to the given parent in this method.
*/
private Element encodeManagedInstance(final OpenJPAStateManager sm, final Element parent,
boolean isRef, Metamodel model) {
if (parent == null)
throw new InternalException(_loc.get("format-xml-null-parent"));
Document doc = parent.getOwnerDocument();
if (doc == null)
throw new InternalException(_loc.get("format-xml-null-doc"));
if (sm == null || isRef) {
return encodeRef(parent, sm);
}
Element root = doc.createElement(ELEMENT_INSTANCE);
parent.appendChild(root);
root.setAttribute(ATTR_ID, ior(sm));
Element child = null;
BitSet loaded = sm.getLoaded();
StoreContext ctx = (StoreContext)sm.getGenericContext();
List<Attribute<?, ?>> attrs = MetamodelHelper.getAttributesInOrder(sm.getMetaData(), model);
for (int i = 0; i < attrs.size(); child = null, i++) {
Members.Member<?, ?> attr = (Members.Member<?, ?>) attrs.get(i);
FieldMetaData fmd = attr.fmd;
if (!loaded.get(fmd.getIndex()))
continue;
String tag = MetamodelHelper.getTagByAttributeType(attr);
Object value = sm.fetch(fmd.getIndex());
switch (fmd.getDeclaredTypeCode()) {
case JavaTypes.BOOLEAN:
case JavaTypes.BYTE:
case JavaTypes.CHAR:
case JavaTypes.DOUBLE:
case JavaTypes.FLOAT:
case JavaTypes.INT:
case JavaTypes.LONG:
case JavaTypes.SHORT:
case JavaTypes.BOOLEAN_OBJ:
case JavaTypes.BYTE_OBJ:
case JavaTypes.CHAR_OBJ:
case JavaTypes.DOUBLE_OBJ:
case JavaTypes.FLOAT_OBJ:
case JavaTypes.INT_OBJ:
case JavaTypes.LONG_OBJ:
case JavaTypes.SHORT_OBJ:
case JavaTypes.BIGDECIMAL:
case JavaTypes.BIGINTEGER:
case JavaTypes.DATE:
case JavaTypes.NUMBER:
case JavaTypes.CALENDAR:
case JavaTypes.LOCALE:
case JavaTypes.STRING:
case JavaTypes.ENUM:
child = doc.createElement(tag);
child.setAttribute(ATTR_NAME, fmd.getName());
if (value == null) {
encodeNull(child);
} else {
encodeBasic(child, value, fmd.getDeclaredType());
}
break;
case JavaTypes.OID:
child = doc.createElement(ELEMENT_REF);
child.setAttribute(ATTR_NAME, fmd.getName());
if (value == null) {
encodeNull(child);
} else {
encodeBasic(child, value, fmd.getDeclaredType());
}
break;
case JavaTypes.PC:
child = doc.createElement(tag);
child.setAttribute(ATTR_NAME, fmd.getName());
child.setAttribute(ATTR_TYPE, typeOf(fmd));
OpenJPAStateManager other = ctx.getStateManager(value);
encodeManagedInstance(other, child, true, model);
break;
case JavaTypes.ARRAY:
Object[] values = (Object[])value;
value = Arrays.asList(values);
// no break;
case JavaTypes.COLLECTION:
child = doc.createElement(tag);
child.setAttribute(ATTR_NAME, fmd.getName());
child.setAttribute(ATTR_TYPE, typeOf(fmd));
child.setAttribute(ATTR_MEMBER_TYPE, typeOf(fmd.getElement().getDeclaredType()));
if (value == null) {
encodeNull(child);
break;
}
Collection<?> members = (Collection<?>)value;
boolean basic = fmd.getElement().getTypeMetaData() == null;
for (Object o : members) {
Element member = doc.createElement(ELEMENT_MEMBER);
child.appendChild(member);
if (o == null) {
encodeNull(member);
} else {
if (basic) {
encodeBasic(member, o, o.getClass());
} else {
encodeManagedInstance(ctx.getStateManager(o), member, true, model);
}
}
}
break;
case JavaTypes.MAP:
child = doc.createElement(tag);
child.setAttribute(ATTR_NAME, fmd.getName());
child.setAttribute(ATTR_TYPE, typeOf(fmd));
child.setAttribute(ATTR_KEY_TYPE, typeOf(fmd.getElement().getDeclaredType()));
child.setAttribute(ATTR_VALUE_TYPE, typeOf(fmd.getValue().getDeclaredType()));
if (value == null) {
encodeNull(child);
break;
}
Set<Map.Entry> entries = ((Map)value).entrySet();
boolean basicKey = fmd.getElement().getTypeMetaData() == null;
boolean basicValue = fmd.getValue().getTypeMetaData() == null;
for (Map.Entry<?,?> e : entries) {
Element entry = doc.createElement(ELEMENT_ENTRY);
Element entryKey = doc.createElement(ELEMENT_ENTRY_KEY);
Element entryValue = doc.createElement(ELEMENT_ENTRY_VALUE);
entry.appendChild(entryKey);
entry.appendChild(entryValue);
child.appendChild(entry);
if (e.getKey() == null) {
encodeNull(entryKey);
} else {
if (basicKey) {
encodeBasic(entryKey, e.getKey(), e.getKey().getClass());
} else {
encodeManagedInstance(ctx.getStateManager(e.getKey()), entryKey, true, model);
}
}
if (e.getValue() == null) {
encodeNull(entryValue);
} else {
if (basicValue) {
encodeBasic(entryValue, e.getValue(), e.getValue().getClass());
} else {
encodeManagedInstance(ctx.getStateManager(e.getValue()), entryValue, true, model);
}
}
}
break;
case JavaTypes.INPUT_STREAM:
case JavaTypes.INPUT_READER:
child = doc.createElement(tag);
child.setAttribute(ATTR_NAME, fmd.getName());
child.setAttribute(ATTR_TYPE, typeOf(fmd));
if (value == null) {
encodeNull(child);
} else {
CDATASection data = doc.createCDATASection(streamToString(value));
child.appendChild(data);
}
break;
case JavaTypes.PC_UNTYPED:
case JavaTypes.OBJECT:
System.err.println("Not handled " + fmd.getName() + " of type " + fmd.getDeclaredType());
}
if (child != null) {
root.appendChild(child);
}
}
return root;
}
/**
* Sets the given value element as null. The <code>null</code> attribute is set to true.
*
* @param element the XML element to be set
*/
private void encodeNull(Element element) {
element.setAttribute(ATTR_NULL, "true");
}
private Element encodeRef(Element parent, OpenJPAStateManager sm) {
Element ref = parent.getOwnerDocument().createElement(sm == null ? ELEMENT_NULL_REF : ELEMENT_REF);
if (sm != null)
ref.setAttribute(ATTR_ID, ior(sm));
// IMPORTANT: for xml transformer not to omit the closing tag, otherwise dojo is confused
ref.setTextContent(EMPTY_TEXT);
parent.appendChild(ref);
return ref;
}
/**
* Sets the given value element. The <code>type</code> is set to the given runtime type.
* String form of the given object is set as the text content.
*
* @param element the XML element to be set
* @param obj value of the element. Never null.
*/
private void encodeBasic(Element element, Object obj, Class<?> runtimeType) {
element.setAttribute(ATTR_TYPE, typeOf(runtimeType));
if (obj instanceof Date)
element.setTextContent(dateFormat.format(obj));
else
element.setTextContent(obj == null ? NULL_VALUE : obj.toString());
}
/**
* Convert the given stream (either an InutStream or a Reader) to a String
* to be included in CDATA section of a XML document.
*
* @param value the field value to be converted. Can not be null
* @return
*/
private String streamToString(Object value) {
Reader reader = null;
if (value instanceof InputStream) {
reader = new BufferedReader(new InputStreamReader((InputStream)value));
} else if (value instanceof Reader) {
reader = (Reader)value;
} else {
throw new RuntimeException();
}
CharArrayWriter writer = new CharArrayWriter();
try {
for (int c; (c = reader.read()) != -1;) {
writer.write(c);
}
} catch (IOException ex) {
throw new RuntimeException(ex);
}
return writer.toString();
}
private void encodeManagedType(ManagedType<?> type, Element parent) {
Document doc = parent.getOwnerDocument();
Element root = doc.createElement(type.getPersistenceType().toString().toLowerCase());
parent.appendChild(root);
root.setAttribute(ATTR_NAME, type.getJavaType().getSimpleName());
List<Attribute<?,?>> attributes = MetamodelHelper.getAttributesInOrder(type);
for (Attribute<?,?> a : attributes) {
String tag = MetamodelHelper.getTagByAttributeType(a);
Element child = doc.createElement(tag);
root.appendChild(child);
child.setAttribute(ATTR_TYPE, typeOf(a.getJavaType()));
if (a instanceof PluralAttribute) {
if (a instanceof MapAttribute) {
child.setAttribute(ATTR_KEY_TYPE, typeOf(((MapAttribute)a).getKeyJavaType()));
child.setAttribute(ATTR_VALUE_TYPE, typeOf(((MapAttribute)a).getBindableJavaType()));
} else {
child.setAttribute(ATTR_MEMBER_TYPE, typeOf(((PluralAttribute)a).getBindableJavaType()));
}
}
child.setTextContent(a.getName());
}
}
void validate(Document doc) throws Exception {
Validator validator = _xsd.newValidator();
validator.validate(new DOMSource(doc));
}
String ior(OpenJPAStateManager sm) {
return typeOf(sm) + "-" + sm.getObjectId();
}
String typeOf(OpenJPAStateManager sm) {
return sm.getMetaData().getDescribedType().getSimpleName();
}
String typeOf(Class<?> cls) {
return cls.getSimpleName();
}
String typeOf(ClassMetaData meta) {
return meta.getDescribedType().getSimpleName();
}
String typeOf(ValueMetaData vm) {
if (vm.getTypeMetaData() == null)
return typeOf(vm.getType());
return typeOf(vm.getTypeMetaData());
}
String typeOf(FieldMetaData fmd) {
return fmd.getType().getSimpleName();
}
}

View File

@ -0,0 +1,29 @@
<!--
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.
-->
<html>
<link href="help.css" type="text/css" rel="stylesheet">
<body>
<h3>Entity Name</h3>
Specify simple name of a persistent entity.
JEST will resolve the name to the fully-qualified name of the entity.
</body>
</html>

View File

@ -0,0 +1,51 @@
<!--
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.
-->
<html>
<link href="help.css" type="text/css" rel="stylesheet">
<body>
<h3>Dynamic Fetch Plan</h3>
Specify one or more named fetch plan. Separate multiple names with comma, e.g.
<p align="center">
myPlanA, mYPlanB
</p>
There are two pre-defined plans named: <b>default</b> and <b>all</b>. The <b>default</b> plan that fetches
all properties of basic type (i.e. <tt>String, int, Date</tt> etc.) and uni-cardinality relations, are
active by default. As the plans are additive, to exclude the default plan, you can specify
<p align="center">
myPlanA, -default
</p>
Fetch Plan determines which properties will be fetched when an entity instance is accessed from the data store.
A plan can traverse relationship path into other entities at arbitrary depth. By default, when an entity
is fetched, all properties of basic type (i.e. <tt>String, int, Date</tt> etc.) and uni-cardinality relations are
fetched.
<br>
JPA specification allows <tt>@Fetch.LAZY</tt> and <tt>@Fetch.EAGER</tt> annotation on persistent properties to control
fetch behavior. But only <em>statically</em> i.e. at class definition.
<p>
OpenJPA, on the other hand, provides a far richer syntax and semantics to its user to define the
properties to be fetched through its dynamic Fetch Plan facility. And, more importantly, these
fetch plans can be modified <em>dynamically</em> per use case basis.
<p>
To learn more aboout Fetch Plan, Refer <a href="http://openjpa.apache.org/documentation.html">OpenJPA documentation</a>.
</body>
</html>

View File

@ -0,0 +1,32 @@
<!--
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.
-->
<html>
<link href="help.css" type="text/css" rel="stylesheet">
<body>
<h3>JPQL Query</h3>
Specify a JPQL query or name of a pre-defined NamedQuery.
<br>
Both queries can accept query bind parameters. The type of the parameters are guessed by JEST
from the string you specify in this web form.
<p>
If using named query, check the Named Query Box.
</body>
</html>

View File

@ -0,0 +1,28 @@
<!--
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.
-->
<html>
<link href="help.css" type="text/css" rel="stylesheet">
<body>
<h3>Response Format</h3>
Select the response format as XML or JSON. By default, JEST responses are in XML format.
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 999 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -0,0 +1,166 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- 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. -->
<!-- ========================================================================= -->
<!-- Schema for serialized persistence instance. -->
<!-- ========================================================================= -->
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified" elementFormDefault="qualified"
version="1.0">
<xsd:annotation>
<xsd:documentation><![CDATA[
Describes closure of managed persistence instance.
Each instance is described by all its loaded persistent attribute.
The related instances are resolved within the document root.
Document root represents zero or more instances.
The file must be named "jest-instance.xsd".
]]>
</xsd:documentation>
</xsd:annotation>
<!-- The root element of the document contains zero or more instances -->
<xsd:element name="instances">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="uri" minOccurs="1" maxOccurs="1" type="xsd:anyURI"/>
<xsd:element name="description" minOccurs="0" maxOccurs="1" type="xsd:string"/>
<xsd:element name="instance" minOccurs="0" maxOccurs="unbounded" type="instance-type" />
</xsd:sequence>
<xsd:attribute name="version" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<!-- The root element for a single instance. Children of this element are persistent attribute -->
<!-- Persistent Attributes occur in order. The order is determined by the attribute category. -->
<!-- Attribute category is determined by the enumerated PersistentAttributeType defined in -->
<!-- javax.persistence.metamodel and then further refined by id, version, lob and enum. -->
<!-- See org.apache.openjpa.persistence.jest.MetamodelHelper for further details. -->
<xsd:complexType name="instance-type">
<xsd:sequence>
<xsd:element name="id" type="basic-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="version" type="basic-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="basic" type="basic-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="enum" type="basic-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="embedded" type="instance-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="lob" type="lob-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="one-to-one" type="singular-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="many-to-one" type="singular-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="element-collection" type="collection-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="one-to-many" type="collection-attr-type" minOccurs="0" maxOccurs="unbounded"/>
<xsd:element name="many-to-many" type="map-attr-type" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
<xsd:attribute name="id" type="xsd:ID" use="required" />
</xsd:complexType>
<!-- A reference to another instance within the same(?) document -->
<xsd:complexType name="ref-type">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="id" type="xsd:IDREF" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<!-- A null reference -->
<xsd:complexType name="ref-null">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<!-- Basic Attribute has a name and its runtime type -->
<!-- non-null value appears as text content. -->
<!-- null value appears as attribute with empty text . -->
<xsd:complexType name="basic-attr-type">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" use="required" />
<xsd:attribute name="null" type="xsd:boolean" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<!-- Large Binary Objects (LOB) represented as hex array -->
<xsd:complexType name="lob-attr-type">
<xsd:simpleContent>
<xsd:extension base="xsd:hexBinary">
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" use="required" />
<xsd:attribute name="null" type="xsd:boolean" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<!-- Singular attribute is a reference to another instance or a null reference. -->
<xsd:complexType name="singular-attr-type">
<xsd:choice>
<xsd:element name="null" type="ref-null" />
<xsd:element name="ref" type="ref-type" />
</xsd:choice>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" use="required" />
</xsd:complexType>
<!-- Collection attributes list their members with their runtime type -->
<!-- Members can be basic or other managed instance -->
<xsd:complexType name="collection-attr-type">
<xsd:sequence>
<xsd:element name="member" type="member-type" minOccurs="0"
maxOccurs="unbounded" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" use="required" />
<xsd:attribute name="member-type" type="xsd:string" use="required" />
</xsd:complexType>
<!-- Map attributes list their entries with runtime type of key and value -->
<!-- Both key and value can be independently basic or other managed instance -->
<xsd:complexType name="map-attr-type">
<xsd:sequence>
<xsd:element name="entry" type="entry-type" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
<xsd:attribute name="type" type="xsd:string" use="required" />
<xsd:attribute name="key-type" type="xsd:string" use="required" />
<xsd:attribute name="value-type" type="xsd:string" use="required" />
</xsd:complexType>
<!-- Value of a member of basic type. -->
<xsd:complexType name="basic-value-type">
<xsd:simpleContent>
<xsd:extension base="xsd:string">
<xsd:attribute name="null" type="xsd:boolean" />
</xsd:extension>
</xsd:simpleContent>
</xsd:complexType>
<!-- Value of a member of a collection/map -->
<xsd:complexType name="member-type">
<xsd:choice>
<xsd:element name="basic" type="basic-value-type" />
<xsd:element name="null" type="ref-null" />
<xsd:element name="ref" type="ref-type" />
</xsd:choice>
</xsd:complexType>
<!-- Denotes entry of a map element -->
<xsd:complexType name="entry-type">
<xsd:sequence>
<xsd:element name="key" type="member-type" minOccurs="1" maxOccurs="1" />
<xsd:element name="value" type="member-type" minOccurs="1" maxOccurs="1" />
</xsd:sequence>
</xsd:complexType>
</xsd:schema>

View File

@ -0,0 +1,248 @@
/*
* 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.
*/
/** -----------------------------------------------------------------------
* Basic layout consists of a left menu section (class='menu')and two
* vertical sections for command specification (class='highlight') and
* JEST response (class='canvas') respectively.
* --------------------------------------------------------------------- */
/** -----------------------------------------------------------------------
* div section containing the command menu.
* Lodged on the top-left corner
* -------------------------------------------------------------------- */
.menu {
position:absolute;
left:0em;
top:80px;
width:10em;
}
/** -----------------------------------------------------------------------
* Highlighted blocks appear relative to the menu. The same top position
* of the menu and shifted to left of the menu by the menu's width+padding.
* --------------------------------------------------------------------- */
.highlight {
position:relative;
left:10em;
top:0em;
// background-color:#F9F9F9;
width:60em;
// border:1px solid black;
// padding:0em 1em 1em 1em; // top, right, bottom, left
}
.logo {
font-size:2em;
font-weight:bold;
font-family: "Palatino Linotype, Times Roman"
}
/** -----------------------------------------------------------------------
* Canvas section appears relative to the menu and the highlighted section.
* The top position is relative to the highlighted section and left position
* is relative to the menu section.
* --------------------------------------------------------------------- */
.canvas {
position:relative;
left:10em;
top:1em;
width:60em;
padding:0em 1em 0em 1em; // top, right, bottom, left
}
.help {
float:right;
text-align:right;
margin-right:0.1em;
margin-top: -0.1em;
}
/** -----------------------------------------------------------------------
* Visible/Invisible divisions
* --------------------------------------------------------------------- */
.open {
display:block;
}
.close {
display:none;
}
/** -----------------------------------------------------------------------
* Hyperlinks
* --------------------------------------------------------------------- */
.url {
color:blue;
font-size:1.1em;
font-family:"Courier New", Arial;
border:0px;
cursor:pointer;
}
.url-invalid {
color:red;
font-size:0.9em;
font-family:"Courier New", Arial;
}
/** -----------------------------------------------------------------------
* XML Tag
* --------------------------------------------------------------------- */
pre, code {
background-color: white;
}
.tag {
color:green;
font-family:"Courier New";
font-weight:bold;
border:0px;
}
/** -----------------------------------------------------------------------
* Data Table used for Tabular HTML
* --------------------------------------------------------------------- */
table.data td th {
width : 70%;
border-collapse:collapse;
border:2px solid black;
margin: 10px 10px 10px 10px;
}
tr {
vertical-align:top;
}
th {
background-color:#444444;
color:white;
text-align:left;
}
caption {
background-color:#000000;
color:white;
font-size:1.2em;
font-weight:bold;
padding:5px 5px 5px 5px;
}
/** -----------------------------------------------------------------------
* Alternate Table Row
* --------------------------------------------------------------------- */
tr.even td {
background-color: #FFFFFF; color: black;
padding:2px 20px;
border:2px solid black;
vertical-align:top;
}
tr.odd td {
background-color: #EEEEEE; color: black;
padding:2px 20px;
border:2px solid black;
vertical-align:top;
}
td.mandatory {
font-weight:bold;
color:red;
}
td input {
width : 99%;
}
td select {
width : 99%;
}
/** -----------------------------------------------------------------------
* Paragraph with smaller line breaks
* --------------------------------------------------------------------- */
p.small {
line-height:60%;
}
/** -----------------------------------------------------------------------
* Error Page
* --------------------------------------------------------------------- */
.error-header {
color:red;
font-size:2em;
font-weight:bold;
}
.error-message {
color:red;
font-size:1.2em;
}
/*
* JPA styles
*/
.id {
color:red;
font-weight:bold;
}
.enum {
color:magenta;
font-weight:bold;
}
.basic {
color:green;
font-weight:bold;
}
.one-to-one {
color:lightblue;
font-weight:bold;
}
.one-to-many {
color:darkblue;
font-weight:bold;
}
.ref {
color : blue;
}
.null {
color : red;
}
.delimiter {
color:lightgray;
font-weight:bold;
}
.attr-name {
color:gray;
}
.attr-value {
color:green;
}
.node-value {
font-weight:bold;
}
.entity {
font-weight:bold;
}
.metamodel {
font-weight:bold;
}
.instances {
font-weight:bold;
}
.instance {
font-weight:bold;
}

View File

@ -0,0 +1,329 @@
<!--
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.
-->
<!--
The single web page for JEST is a templated HTML. This page consists of three major divisions
a) a section holds the menu items for available commands
b) a section for the command window where user enters command specifications
c) a section displays the server response of the command execution
This templated file contains replaceable tokens in ${token} form. JEST servlet replaces the tokens with
actual deployment values on initialization.
The menu navigation and command window are managed by jest.js script.
The rendition of server response is managed by dojo script.
All hyperlinks in this page are relative to the base URI of this page to prevent
any cross-domain scripting.
-->
<html>
<head>
<!-- ==================================================================================== -->
<!-- Base URI is replaced with actual deployment data on initialization -->
<!-- ==================================================================================== -->
<base href="${jest.uri}">
<!-- base href="http://localhost:8080/demo/jest/" -->
<title>JEST: REST on OpenJPA</title>
<link type="text/css" rel="stylesheet" href="${dojo.base}/dijit/themes/${dojo.theme}/${dojo.theme}.css">
<!-- link type="text/css" rel="stylesheet" href="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dijit/themes/claro/claro.css" -->
<link type="text/css" rel="stylesheet" href="jest.css">
<script type="text/javascript" language="javascript" djConfig="parseOnLoad: true, isDebug: true"
src="${dojo.base}/dojo/dojo.xd.js">
</script>
<!-- script type="text/javascript" djConfig="parseOnLoad:true;isDebug=true" src="http://ajax.googleapis.com/ajax/libs/dojo/1.5/dojo/dojo.xd.js"></script -->
<script type="text/javascript" language="javascript" src="jest.js">
</script>
<meta content="REST,OpenJPA,JPA,JEST">
</head>
<body class=" ${dojo.theme} ">
<!-- body class=" claro " -->
<a name="top"></a>
<img src="images/jest.jpg" width="63px" height="69px" alt="JEST logo" align="middle">
<span class="logo">JEST: REST on OpenJPA</span>
<hr>
<!-- ==================================================================================== -->
<!-- Section for menu based commands. -->
<!-- -->
<!-- Each menu opens a command window by making a menu section visible -->
<!-- ==================================================================================== -->
<div id="menu" class="menu">
<table cellspacing="1em">
<tr><td><img alt="Home" src="images/home.jpg" width="100" height="100"></td></tr>
<tr><td style="text-align:center"><a href="javascript:openMenu('home');">Info</a></td></tr>
<tr><td><img alt="Domain" src="images/domain.jpg" width="100" height="100"></td></tr>
<tr><td style="text-align:center"><a href="javascript:openMenu('domain');">browse domain</a></td></tr>
<tr><td><img alt="Find" src="images/find.jpg" width="100" height="100"></td></tr>
<tr><td style="text-align:center"><a href="javascript:openMenu('find');">find instances</a></td></tr>
<tr><td><img alt="Query" src="images/query2.png" width="100" height="100"></td></tr>
<tr><td style="text-align:center"><a href="javascript:openMenu('query');">query objects</a></td></tr>
<tr><td><img alt="View" src="images/properties.jpg" width="100" height="100"></td></tr>
<tr><td style="text-align: center"><a href="javascript:openMenu('properties');">view properties</a></td></tr>
<tr><td><img alt="Deploy" src="images/monitor.jpg" width="100" height="100"></td></tr>
<tr><td style="text-align:center"><a href="javascript:openMenu('deploy');">deploy</a></td></tr>
</table>
</div>
<!-- ==================================================================================== -->
<!-- Section for each command specification -->
<!-- -->
<!-- Contains a set of div sub-sections each for a single command. A command window -->
<!-- consists of a brief description of the command and set of input elements where user -->
<!-- can enter command qualifiers and arguments. -->
<!-- The URI for the command is updated as user entry changes. If the user clicks on the -->
<!-- URI hyperlink, then a request with the URI is sent to the server. -->
<!-- ==================================================================================== -->
<!-- Introduces basic information about JEST -->
<!-- ==================================================================================== -->
<div id="home" class="highlight" dojoType="dijit.TitlePane"
title="<a href='http://openjpa.apache.org/jest-usage.html' target='_blank'>JEST</a>
<b>facilitates RESTful interaction with OpenJPA</b>">
<UL>
<LI>Deployed as a HTTP servlet in <b>any</b> web or enterprise application module using
OpenJPA as its JPA persistence provider.
</LI>
<LI>Completely <b>metadata-driven</b> and hence <b>generic</b> as opposed to specific to any
application domain.
</LI>
<LI>Introduces a <b>URI syntax</b>. Interprets the HTTP request as a JPA resource based on
that syntax. The response is in <tt>XML</tt> or <tt>JSON</tt> format. </LI>
<LI>Representational state for persistent instances is <b>coherent</b>.
The response always contains the <em>closure</em> of the persistent instances.
</LI>
<LI>Connects to the <b>persistent unit</b> of an application in the same module. Or
instantiates its own persistence unit.
</LI>
</UL>
</div>
<!-- ==================================================================================== -->
<!-- Describes deployment details -->
<!-- ==================================================================================== -->
<div id="deploy" class="highlight" style="display:none;" dojoType="dijit.TitlePane"
title='<a href="http://openjpa.apache.org/jest-usage.html" target="_blank">Deploy JEST</a>
<b>as a servlet in a web application.</b>'>
Following <code>WEB-INF/web.xml</code> descriptor will enable JEST to
operate on a persistence unit named <code>${persistence.unit}</code>. Of course, JEST
servlet must be in the same module scope of the application that is using using
<code>${persistence.unit}</code> as its persistence unit.
<br>
<pre>
<code class="tag">&lt;servlet&gt;</code>
<code class="tag">&lt;servlet-name&gt;</code>${servlet.name}<code class="tag">&lt;/servlet-name&gt;</code>
<code class="tag">&lt;servlet-class&gt;</code><span style="color:blue">org.apache.openjpa.persistence.jest.JESTServlet</span><code class="tag">&lt;/servlet-class&gt;</code>
<code class="tag">&lt;init-param&gt;</code>
<code class="tag">&lt;param-name&gt;</code><span style="color:red;">persistence.unit</span><code class="tag">&lt;/param-name&gt;</code>
<code class="tag">&lt;param-value&gt;</code><span style="color:red">${persistence.unit}</span><code class="tag">&lt;/param-value&gt;</code>
<code class="tag">&lt;/init-param&gt;</code>
<code class="tag">&lt;/servlet&gt;</code>
<code class="tag">&lt;servlet-mapping&gt;</code>
<code class="tag">&lt;servlet-name&gt;</code>${servlet.name}<code class="tag">&lt;/servlet-name&gt;</code>
<code class="tag">&lt;url-pattern&gt;</code><span style="color:red">/${servlet.name}/*</span><code class="tag">&lt;/url-pattern&gt;</code>
<code class="tag">&lt;/servlet-mapping&gt;</code>
</pre>
When an web application module named <code>${webapp.name}</code> containing the above JEST servlet
in a servlet container runs on <code>${server.name}</code> at port ${server.port}, then the JEST servlet
can be accessed at <br>
<code class="url">http://${server.name}:${server.port}/${webapp.name}/${servlet.name}/</code>
<br>
(<span style="color:red">do not miss the the trailing forward-slash / character</span>)
<p>
</div>
<!-- ==================================================================================== -->
<!-- Command window for find -->
<!-- -->
<!-- The div element contains a table and some of the table columns hold the input -->
<!-- elements. The div, table and input elements are all identified with following -->
<!-- naming convention: -->
<!-- the main div section identifier is the moniker of the command itself i.e. 'find' -->
<!-- the table identifier is 'find.command' -->
<!-- the input element identifiers are 'find.{qualifier or argument name}' e.g. -->
<!-- 'find.type' or 'find.format'. The qualifier or argument name must be the same as -->
<!-- specified in jest.js JavaScript. -->
<!-- -->
<!-- The naming convention is crucial because the JavaScript jest.js identifies these -->
<!-- elements by this implicit naming conventions. -->
<!-- -->
<!-- The user entries result in a relative URI displayed in this section itself. -->
<!-- The event handlers on the input elements update the URI on change. -->
<!-- The user can click the hyperlink to send the request to the server. -->
<!-- ==================================================================================== -->
<div id="find" class="highlight" style="display:none;" dojoType="dijit.TitlePane"
title='<a href="http://openjpa.apache.org/jest-syntax.html" target="_blank">Find</a>
<b>persistent objects by primary identifier.</b>'>
<!-- a form for user to fill in the find specifications. Script uses these entries to -->
<!-- build up a request URI for JEST server -->
<table id="find.command">
<tr>
<th width="30%" class="mandatory" title="Name of a persistent entity to find.">Entity Name
<span class="help">
<img src="images/help.jpg" width="8px" height="8px" onclick="showHelp('Entity', 'help/entity-name.html');">
</span>
</th>
<th width="30%" class="mandatory" title="Primary Key of the entity.">Identifier</th>
<th width="30%" title="Fetch Plan(s) to use. Separate each plan with comma.">Fetch Plan
<span class="help">
<img src="images/help.jpg" width="8px" height="8px"
onclick="showHelp('Fetch Plan', 'help/fetch-plan.html');">
</span>
</th>
<th width="10%" title="Format of the response.">Format
<span class="help">
<img src="images/help.jpg" width="8px" height="8px"
onclick="showHelp('Response Format', 'help/response-format.html');"></img>
</span>
</th>
</tr>
<tr>
<td><input id="find.type" onblur="javascript:toURI('find');"></td>
<td><input id="find.pk" onblur="javascript:toURI('find');"></td>
<td><input id="find.plan" onblur="javascript:toURI('find');" ></td>
<td>
<select id="find.format" onchange="javascript:toURI('find');" >
<option value=""></option>
<option value="xml">XML</option>
<option value="json">JSON</option>
</select>
</td>
</tr>
</table>
<p>
<!-- Script will fill in the URI in this element based on user entries from the above -->
<!-- The element is made to look like a hyperlink but actually a span to omit the -->
<!-- associated complexity of preventing the browser's default handling of hyperlink -->
<!-- click events. -->
<b>URI: </b><span id="find.uri" class="url-invalid">filling the form will update
this hyperlink.</span>
<button id="find.execute" style="display:none;">
<img src="images/arrow_right.jpg" width="12px" height="12px"></button>
</div>
<!-- ==================================================================================== -->
<!-- Command window for Query -->
<!-- -->
<!-- Similar to window for 'find' command. The added complexity is variable number of -->
<!-- query binding parameters that the user may enter for a query. -->
<!-- Each query binding parameter is accepted via two inputs on a dynamically created row -->
<!-- The dynamic row is identified as 'query.vararg.{n}' and the pair of input elements -->
<!-- 'query.vararg.{n}.key' and 'query.vararg.{n}.value' respectively. {n} is a monotonic -->
<!-- integer. If a dynamically created row is removed by the user, the index {n} does not -->
<!-- decrement. -->
<!-- ==================================================================================== -->
<div id="query" class="highlight" style="display:none;" dojoType="dijit.TitlePane"
title='<a href="http://openjpa.apache.org/jest-syntax.html" target="_blank">Query</a>
<b>with JPQL or named query and binding parameters.</b>'>
<!-- a form for user to fill in the find specifications. Script uses these entries to -->
<!-- build up a request URI for JEST server. This form is more complicated than that -->
<!-- of find, because a query can accept variable number of binding parameters -->
<table id="query.command">
<tr>
<th width="20%" class="mandatory" title="JPQL query or a named query">Query
<span class="help"><img src="images/help.jpg" width="8px" height="8px"
onclick="showHelp('Query', 'help/query.html');"></img></span></th>
<th width="10%"style="display: hidden"></th>
<th width="10%"style="display: hidden"></th>
<th width="10%"style="display: hidden"></th>
<th width="5%" title="Is it a named query?">Single</th>
<th width="5%" title="Returns single result?">Named</th>
<th width="30%" title="Fetch Plan(s) to use. Separate each plan with comma.">Fetch Plan
<span class="help"><img src="images/help.jpg" width="8px" height="8px"
onclick="showHelp('Fetch Plan', 'help/fetch-plan.html');"></img></span></th>
<th width="10%" title="Format of the response.">Format
</tr>
<tr>
<td colspan="4"><input id="query.q" onblur="javascript:toURI('query');"></td>
<td><input id="query.single" type="checkbox" value="true" onchange="javascript:toURI('query');"></td>
<td><input id="query.named" type="checkbox" value="true" onchange="javascript:toURI('query');"></td>
<td><input id="query.plan" onblur="javascript:toURI('query');" ></td>
<td>
<select id="query.format" onchange="javascript:toURI('query');" >
<option value=""></option>
<option value="xml">XML</option>
<option value="json">JSON</option>
</select>
</td>
</tr>
<tr id="query.vararg.0">
<td>
<button onclick="javascript:addVarArgRow('query.vararg',0,'Bind Query Parameter');">Bind Parameter</button>
</td>
</tr>
</table>
<p>
<!-- Script will fill in the URI in this element based on user entries from the above -->
<!-- The element is made to look like a hyperlink but actually a span to omit the -->
<!-- associated complexity of preventing the browser's default handling of hyperlink -->
<!-- click events. -->
<b>URI: </b><span id="query.uri" class="url-invalid">filling the form will update
this hyperlink.</span> <button id="query.execute" style="display:none;">
<img src="images/arrow_right.jpg" width="12px" height="12px"></button>
</div>
<!-- ==================================================================================== -->
<!-- Command window for browsing persistent domain model -->
<!-- ==================================================================================== -->
<div id="domain" class="highlight" style="display:none;" dojoType="dijit.TitlePane"
title='<a href="http://openjpa.apache.org/jest-syntax.html" target="_blank">Browse</a>
<b>persistent domain model.</b>'>
This command accepts no qualifier or arguments. <br>
<b>JEST URI: </b><span id="domain.uri" class="url"
onclick="javascript:render('domain','canvas', 'domain', 'xml');">domain</span>
</div>
<!-- ==================================================================================== -->
<!-- Command window for viewing configuration of the persistence unit. -->
<!-- ==================================================================================== -->
<div id="properties" class="highlight" style="display:none;" dojoType="dijit.TitlePane"
title='<a href="http://openjpa.apache.org/jest-syntax.html" target="_blank">View</a>
<b>configuration properties of the persistence unit.</b>'>
This command accepts no qualifier or arguments <br>
<b>URI: </b><span id="properties.uri" class="url"
onclick="javascript:render('properties','canvas', 'properties', 'xml');">properties</span>
</div>
<!-- ==================================================================================== -->
<!-- This empty section will be filled in by server response -->
<!-- ==================================================================================== -->
<div id="canvas" class="canvas">
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,60 @@
# 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.
no-persistence-unit-param: Missing <b>persistence.unit</b> parameter. JEST Servlet must be \
configured with a parameter named <b>persistence.unit<b> in &lt;init-param&gt; clause \
of &lt;servlet&gt; declaration in <code>WEB-INF/web.xml</code> descriptor.
servlet-init: JEST Servlet is initialized for "{0}" persistence unit.
servlet-not-init: JEST Servlet can not find "{0}" persistence unit during servlet initialization. \
JEST Servlet will try to locate the unit when a request is to be served.
no-persistence-unit: JEST can not locate the component using persistence unit <b>{0}</b>. This can happen \
for several reasons: \
<OL>the component is not initialized. </OL>\
<OL>the component and JEST servlet do not belong to the same deployment module</OL>\
<OL>the component did not configure the persistence unit for pooling. To enable pooling, \
create the persistence unit with configuration property <code>openjpa.EntityManagerFactoryPool=true</code>.<br> \
The property must be passed to <code>Persistence.createEntityManagerFactory(String unit, Map props)</code> \
with the second <code>Map</code> argument and <em>not</em> via <code>META-INF/persistence.xml</code></OL>.
resource-not-found: Can not locate resource {0}. <br>This can happen for wrong URI syntax. See \
<A href="http://openjpa.apache.org/jest-syntax.html" target="_blank">JEST URI Help page</A> for correct syntax.
query-execution-error: Error executing query "{0}". See stacktrace for details.
parse-invalid-qualifier: {0} command does not recognize "{1}" as a qualifier. Valid qualifiers are {2}.
parse-missing-mandatory-argument: {0} command must have "{1}" argument. Available arguments are {2}.
parse-less-argument: {0} command must have at least {2} argument. Available arguments are {1}.
# ----------------------------------------------------------------------
# Format related error
# ----------------------------------------------------------------------
format-xml-null-parent: A null XML parent element encountered during serialization
format-xml-null-doc: Given parent element is not part of XML document
format-xml-null-closure: Set of visited instances can not be null for serialization
format-not-supported: format {0} in command {1} is not registered. Available formats are {2}.
properties-caption: Configuration of {0} Persistence Unit
entity-not-found: Resource of type {0} with identifier {1} is not found.
bad-uri: Can not reconstruct URI from original URL {0}
find-title: JEST find
find-desc: JEST find command may return more than one result. Why?
query-title: JEST query
query-desc: JEST query command may return more than the directly selected result. Why?
domain-command: JEST domain
domain-desc: JEST domain command prints the persistent domain model