Refs #1774 Move REST API to portal

This commit is contained in:
Yuriy Artamonov 2013-01-15 15:06:49 +00:00
parent 49c8521df5
commit 8461de1740
17 changed files with 2973 additions and 0 deletions

View File

@ -0,0 +1,46 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.cuba.core.Locator;
import com.haulmont.cuba.core.sys.AppContext;
import com.haulmont.cuba.core.sys.SecurityContext;
import com.haulmont.cuba.security.global.UserSession;
import com.haulmont.cuba.security.sys.UserSessionManager;
import java.util.UUID;
/**
* Author: Alexander Chevelev
* Date: 25.04.2011
* Time: 15:36:34
*/
public class Authentication {
public static Authentication me(String sessionId) {
UserSession userSession = getSession(sessionId);
if (userSession == null) {
return null;
}
AppContext.setSecurityContext(new SecurityContext(userSession));
return new Authentication();
}
public void forget() {
AppContext.setSecurityContext(null);
}
private static UserSession getSession(String sessionIdStr) {
UUID sessionId;
try {
sessionId = UUID.fromString(sessionIdStr);
} catch (Exception e) {
return null;
}
return ((UserSessionManager) Locator.lookup(UserSessionManager.NAME)).getSession(sessionId);
}
}

View File

@ -0,0 +1,96 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.cuba.core.global.EntityLoadInfo;
import com.haulmont.cuba.core.global.UuidProvider;
import java.util.*;
/**
* Author: Alexander Chevelev
* Date: 27.04.2011
* Time: 0:55:10
* $Id$
*/
public class CommitRequest {
private Collection commitInstances;
private Collection removeInstances;
private boolean softDeletion = true;
private HashSet<String> newInstanceIds = new HashSet<String>();
private Map<String, InstanceRef> instanceRefs = new HashMap<String, InstanceRef>();
private Set<String> commitIds = new HashSet<>();
public Collection getCommitInstances() {
return commitInstances == null ? Collections.emptyList() : commitInstances;
}
public Collection getRemoveInstances() {
return removeInstances == null ? Collections.emptyList() : removeInstances;
}
public boolean isSoftDeletion() {
return softDeletion;
}
public void setCommitInstances(Collection commitInstances) {
this.commitInstances = commitInstances;
}
public void setRemoveInstances(Collection removeInstances) {
this.removeInstances = removeInstances;
}
public void setSoftDeletion(boolean softDeletion) {
this.softDeletion = softDeletion;
}
public Collection getNewInstanceIds() {
return Collections.unmodifiableSet(newInstanceIds);
}
public Set<String> getCommitIds() {
return commitIds;
}
public void setCommitIds(Set<String> commitIds) {
this.commitIds = commitIds;
}
public InstanceRef parseInstanceRefAndRegister(String id) throws InstantiationException, IllegalAccessException {
boolean isNew = false;
if (id.startsWith("NEW-")) {
id = id.substring("NEW-".length());
isNew = true;
}
InstanceRef existingRef = instanceRefs.get(id);
if (existingRef != null) {
return existingRef;
}
EntityLoadInfo loadInfo = EntityLoadInfo.parse(id);
if (loadInfo == null) {
if (isNew) {
UUID uuid = UuidProvider.createUuid();
id = id + "-" + uuid;
loadInfo = EntityLoadInfo.parse(id);
if (loadInfo == null) {
throw new RuntimeException("Cannot parse id: " + id);
}
} else
throw new RuntimeException("Cannot parse id: " + id);
}
if (isNew)
newInstanceIds.add(id);
InstanceRef result = new InstanceRef(loadInfo);
instanceRefs.put(id, result);
return result;
}
}

View File

@ -0,0 +1,169 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
~ Haulmont Technology proprietary and confidential.
~ Use is subject to license terms.
-->
<!-- ========================================================================= -->
<!-- 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 CommitRequest structure.
]]>
</xsd:documentation>
</xsd:annotation>
<!-- The root element of the document -->
<xsd:element name="CommitRequest">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="commitInstances" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="instance" minOccurs="0" maxOccurs="unbounded" type="instance-type"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="newInstanceIds" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="newId" minOccurs="0" maxOccurs="unbounded"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
<xsd:element name="deleteInstances" minOccurs="0" maxOccurs="1">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="instance" minOccurs="0" maxOccurs="unbounded" type="instance-type"/>
</xsd:sequence>
</xsd:complexType>
</xsd:element>
</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. -->
<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="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="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: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="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="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,45 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import javax.activation.MimeType;
import java.util.HashMap;
import java.util.Map;
/**
* Author: Alexander Chevelev
* Date: 26.04.2011
* Time: 2:00:09
*/
public class ConversionFactory {
private Map<String, Convertor> convertors = new HashMap<String, Convertor>();
public ConversionFactory() {
convertors.put("xml", new XMLConvertor());
convertors.put("json", new JSONConvertor());
}
public Convertor getConvertor(MimeType requestedForm) {
if (requestedForm == null) {
return convertors.values().iterator().next();
}
for (Convertor convertor : convertors.values()) {
if (requestedForm.match(convertor.getMimeType())) {
return convertor;
}
}
return convertors.values().iterator().next();
}
public Convertor getConvertor(String type) {
Convertor convertor = convertors.get(type);
if (convertor == null)
convertors.values().iterator().next();
return convertor;
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.entity.Entity;
import javax.activation.MimeType;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import java.util.Map;
public interface Convertor {
public MimeType getMimeType();
Object process(Entity entity, MetaClass metaclass, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException;
Object process(List<Entity> entities, MetaClass metaClass, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException;
Object process(Map<Entity, Entity> entityMap, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException;
CommitRequest parseCommitRequest(String content);
void write(HttpServletResponse response, Object o) throws IOException;
}

View File

@ -0,0 +1,34 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
/**
* Author: Alexander Chevelev
* Date: 20.04.2011
* Time: 18:13:08
*/
public class ConvertorHelper {
public static final Comparator<MetaProperty> PROPERTY_COMPARATOR = new Comparator<MetaProperty>() {
public int compare(MetaProperty p1, MetaProperty p2) {
return p1.getName().compareTo(p2.getName());
}
};
public static List<MetaProperty> getOrderedProperties(MetaClass metaClass) {
List<MetaProperty> result = new ArrayList<MetaProperty>(metaClass.getProperties());
Collections.sort(result, PROPERTY_COMPARATOR);
return result;
}
}

View File

@ -0,0 +1,498 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.chile.core.datatypes.Datatype;
import com.haulmont.chile.core.datatypes.Datatypes;
import com.haulmont.chile.core.datatypes.impl.*;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.app.DataService;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.core.sys.restapi.template.MetaClassRepresentation;
import com.haulmont.cuba.security.entity.EntityOp;
import com.haulmont.cuba.security.global.UserSession;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.activation.MimeType;
import javax.inject.Inject;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.*;
/**
* Author: Alexander Chevelev
* Date: 19.04.2011
* Time: 22:03:25
*
* @version $Id$
*/
@Controller
public class DataServiceController {
private static Log log = LogFactory.getLog(DataServiceController.class);
private DataService svc;
//todo wire
private ConversionFactory conversionFactory = new ConversionFactory();
private Collection availableBasicTypes;
@Inject
private MetadataTools metadataTools;
@Inject
public DataServiceController(DataService svc) {
this.svc = svc;
}
@RequestMapping(value = "/find.{type}", method = RequestMethod.GET)
public void find(@PathVariable String type,
@RequestParam(value = "e") String entityRef,
@RequestParam(value = "s") String sessionId,
HttpServletRequest request,
HttpServletResponse response) throws IOException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Authentication authentication = Authentication.me(sessionId);
if (authentication == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
EntityLoadInfo loadInfo = EntityLoadInfo.parse(entityRef);
if (loadInfo == null) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
MetaClass metaClass = loadInfo.getMetaClass();
if (!readPermitted(metaClass)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
response.addHeader("Access-Control-Allow-Origin", "*");
UUID idObject = loadInfo.getId();
try {
LoadContext loadCtx = new LoadContext(metaClass);
loadCtx.setId(idObject);
loadCtx.setUseSecurityConstraints(true);
if (loadInfo.getViewName() != null)
loadCtx.setView(loadInfo.getViewName());
Entity entity = svc.load(loadCtx);
if (entity == null) {
response.setStatus(HttpServletResponse.SC_NOT_FOUND);
} else {
Convertor convertor = conversionFactory.getConvertor(type);
Object result = convertor.process(entity, metaClass, request.getRequestURI());
convertor.write(response, result);
}
} finally {
authentication.forget();
}
}
@RequestMapping(value = "/query.{type}", method = RequestMethod.GET)
public void query(@PathVariable String type,
@RequestParam(value = "e") String entityName,
@RequestParam(value = "q") String queryStr,
@RequestParam(value = "view", required = false) String view,
@RequestParam(value = "first", required = false) Integer firstResult,
@RequestParam(value = "max", required = false) Integer maxResults,
@RequestParam(value = "s") String sessionId,
HttpServletRequest request,
HttpServletResponse response) throws IOException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Authentication authentication = Authentication.me(sessionId);
if (authentication == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
response.addHeader("Access-Control-Allow-Origin", "*");
MetaClass metaClass = getMetaClass(entityName);
if (metaClass == null) {
response.sendError(HttpServletResponse.SC_NOT_FOUND, "Unknown entity name " + entityName);
}
EntityOp queryEntityOp = getQueryEntityOp(queryStr);
if (!entityOpPermitted(metaClass, queryEntityOp)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
Map<String, String[]> queryParams = new HashMap<String, String[]>(request.getParameterMap());
queryParams.remove("e");
queryParams.remove("q");
queryParams.remove("view");
queryParams.remove("first");
queryParams.remove("s");
queryParams.remove("max");
try {
LoadContext loadCtx = new LoadContext(metaClass);
loadCtx.setUseSecurityConstraints(true);
LoadContext.Query query = new LoadContext.Query(queryStr);
loadCtx.setQuery(query);
if (firstResult != null)
query.setFirstResult(firstResult);
if (maxResults != null)
query.setMaxResults(maxResults);
for (Map.Entry<String, String[]> entry : queryParams.entrySet()) {
String paramKey = entry.getKey();
if (paramKey.endsWith("_type"))
continue;
if (entry.getValue().length != 1) {
response.sendError(HttpServletResponse.SC_BAD_REQUEST);
return;
}
String paramValue = entry.getValue()[0];
Object parsedParam = parseQueryParameter(paramKey, paramValue, queryParams);
query.addParameter(paramKey, parsedParam);
}
loadCtx.setView(view == null ? View.LOCAL : view);
List<Entity> entities = svc.loadList(loadCtx);
Convertor convertor = conversionFactory.getConvertor(type);
Object result = convertor.process(entities, metaClass, request.getRequestURI());
convertor.write(response, result);
} finally {
authentication.forget();
}
}
@RequestMapping(value = "/commit", method = RequestMethod.POST)
public void commit(@RequestParam(value = "s") String sessionId,
@RequestHeader(value = "Content-Type") MimeType contentType,
@RequestBody String requestContent,
HttpServletRequest request,
HttpServletResponse response) throws
IOException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Authentication authentication = Authentication.me(sessionId);
if (authentication == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
response.addHeader("Access-Control-Allow-Origin", "*");
Convertor convertor = conversionFactory.getConvertor(contentType);
try {
CommitRequest commitRequest = convertor.parseCommitRequest(requestContent);
Collection commitInstances = commitRequest.getCommitInstances();
Collection newInstanceIds = commitRequest.getNewInstanceIds();
//send error if the user don't have permissions to commit at least one of the entities
if (!commitPermitted(commitInstances, newInstanceIds)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
Collection removeInstances = commitRequest.getRemoveInstances();
//send error if the user don't have permissions to remove at least one of the entities
if (!removePermitted(removeInstances)) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
NotDetachedCommitContext commitContext = new NotDetachedCommitContext();
commitContext.setCommitInstances(commitInstances);
commitContext.setRemoveInstances(removeInstances);
commitContext.setSoftDeletion(commitRequest.isSoftDeletion());
commitContext.setNewInstanceIds(newInstanceIds);
Map<Entity, Entity> result = svc.commitNotDetached(commitContext);
Object converted = convertor.process(result, request.getRequestURI());
convertor.write(response, converted);
} finally {
authentication.forget();
}
}
@RequestMapping(value = "/deployViews", method = RequestMethod.POST)
public void deployViews(@RequestParam(value = "s") String sessionId,
@RequestBody String requestContent,
HttpServletRequest request,
HttpServletResponse response) throws
IOException, InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Authentication authentication = Authentication.me(sessionId);
if (authentication == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
response.addHeader("Access-Control-Allow-Origin", "*");
ViewRepository viewRepository = MetadataProvider.getViewRepository();
try {
viewRepository.deployViews(new StringReader(requestContent));
} finally {
authentication.forget();
}
}
@RequestMapping(value = "/printDomain", method = RequestMethod.GET)
public void printDomain(@RequestParam(value = "s") String sessionId,
HttpServletResponse response) throws
IOException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, TemplateException {
Authentication authentication = Authentication.me(sessionId);
if (authentication == null) {
response.sendError(HttpServletResponse.SC_FORBIDDEN);
return;
}
response.addHeader("Access-Control-Allow-Origin", "*");
response.setContentType("text/html");
response.setCharacterEncoding("UTF-8");
response.setLocale(UserSessionProvider.getLocale());
PrintWriter writer = response.getWriter();
try {
ViewRepository viewRepository = MetadataProvider.getViewRepository();
List<View> views = viewRepository.getAll();
Map<MetaClass, List<View>> meta2views = new HashMap<MetaClass, List<View>>();
for (View view : views) {
MetaClass metaClass = MetadataProvider.getSession().getClass(view.getEntityClass());
if (!readPermitted(metaClass))
continue;
List<View> viewList = meta2views.get(metaClass);
if (viewList == null) {
viewList = new ArrayList<View>();
meta2views.put(metaClass, viewList);
}
viewList.add(view);
}
List<MetaClassRepresentation> classes = new ArrayList<MetaClassRepresentation>();
Set<MetaClass> metas = new HashSet<MetaClass>(metadataTools.getAllPersistentMetaClasses());
metas.addAll(metadataTools.getAllEmbeddableMetaClasses());
for (MetaClass meta : metas) {
if (!readPermitted(meta))
continue;
MetaClassRepresentation rep = new MetaClassRepresentation(meta, meta2views.get(meta));
classes.add(rep);
}
Collections.sort(classes, new Comparator<MetaClassRepresentation>() {
public int compare(MetaClassRepresentation o1, MetaClassRepresentation o2) {
return o1.getName().compareTo(o2.getName());
}
});
Map<String, Object> values = new HashMap<String, Object>();
values.put("knownEntities", classes);
String[] availableTypes = getAvailableBasicTypes();
values.put("availableTypes", availableTypes);
Configuration cfg = new Configuration();
cfg.setDefaultEncoding("UTF-8");
cfg.setOutputEncoding("UTF-8");
cfg.setClassForTemplateLoading(DataServiceController.class, "template");
cfg.setObjectWrapper(new DefaultObjectWrapper());
Template template = cfg.getTemplate("domain.ftl");
template.process(values, writer);
} finally {
authentication.forget();
}
}
private Object parseQueryParameter(String paramKey, String paramValue, Map<String, String[]> queryParams) {
String[] typeName = queryParams.get(paramKey + "_type");
//if the type is specified
if (typeName != null && typeName.length == 1) {
return parseByTypename(paramValue, typeName[0]);
}
//if the type is not specified
else if (typeName == null) {
return tryParse(paramValue);
}
//if several types have been declared
else {
throw new IllegalStateException("Too many parameters in request");
}
}
/**
* Tries to parse a string value into some of the available Datatypes
* when no Datatype was specified.
*
* @param value value to parse
* @return parsed value
*/
private Object tryParse(String value) {
try {
return parseByDatatypeName(value, UUIDDatatype.NAME);
} catch (Exception ignored) {
}
try {
return parseByDatatypeName(value, DateTimeDatatype.NAME);
} catch (ParseException ignored) {
}
try {
return parseByDatatypeName(value, TimeDatatype.NAME);
} catch (ParseException ignored) {
}
try {
return parseByDatatypeName(value, DateDatatype.NAME);
} catch (ParseException ignored) {
}
try {
return parseByDatatypeName(value, BigDecimalDatatype.NAME);
} catch (ParseException ignored) {
}
try {
return parseByDatatypeName(value, DoubleDatatype.NAME);
} catch (ParseException ignored) {
}
try {
if (value.equalsIgnoreCase("true") || value.equalsIgnoreCase("false")) {
return parseByDatatypeName(value, BooleanDatatype.NAME);
}
} catch (ParseException ignored) {
}
//return string value if couldn't parse into specific type
return value;
}
private Object parseByDatatypeName(String value, String name) throws ParseException {
Datatype datatype = Datatypes.get(name);
return datatype.parse(value);
}
/**
* Parses string value into specific type
* @param value value to parse
* @param typeName Datatype name
* @return parsed object
*/
private Object parseByTypename(String value, String typeName) {
Datatype datatype = Datatypes.get(typeName);
try {
return datatype.parse(value);
} catch (ParseException e) {
throw new IllegalArgumentException("Cannot parse specified parameter");
}
}
private MetaClass getMetaClass(String entityName) {
Collection<MetaClass> classes = metadataTools.getAllPersistentMetaClasses();
for (MetaClass metaClass : classes) {
if (entityName.equals(metaClass.getName()))
return metaClass;
}
return null;
}
/**
* Checks if the user have permissions to commit (create or update)
* all of the entities.
*
* @param commitInstances entities to commit
* @param newInstanceIds ids of the new entities
* @return true - if the user can commit all of the requested entities, false -
* if he don't have permissions to commit at least one of the entities.
*/
private boolean commitPermitted(Collection commitInstances, Collection newInstanceIds) {
for (Object commitInstance : commitInstances) {
Entity entity = (Entity) commitInstance;
String fullId = entity.getMetaClass().getName() + "-" + entity.getId();
if (newInstanceIds.contains(fullId)) {
if (!createPermitted(entity.getMetaClass()))
return false;
} else if (!updatePermitted(entity.getMetaClass())) {
return false;
}
}
return true;
}
/**
* Checks if the user have permissions to remove all of the requested entities.
*
* @param removeInstances entities to remove
* @return true - if the user can remove all of the requested entities, false -
* if he don't have permissions to remove at least one of the entities.
*/
private boolean removePermitted(Collection removeInstances) {
for (Object removeInstance : removeInstances) {
Entity next = (Entity) removeInstance;
if (!removePermitted(next.getMetaClass()))
return false;
}
return true;
}
private boolean readPermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.READ);
}
private boolean createPermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.CREATE);
}
private boolean updatePermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.UPDATE);
}
private boolean removePermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.DELETE);
}
private boolean entityOpPermitted(MetaClass metaClass, EntityOp entityOp) {
UserSession session = UserSessionProvider.getUserSession();
return session.isEntityOpPermitted(metaClass, entityOp);
}
/**
* Returns EntityOp query requests to
*
* @param query JPQL query
* @return EntityOp.READ or EntityOp.UPDATE or EntityOp.DELETE or null
*/
private static EntityOp getQueryEntityOp(String query) {
if (query == null)
return null;
query = query.trim().toLowerCase();
if (query.isEmpty())
return null;
int firstSpaceIndex = query.indexOf(' ');
int endIndex = firstSpaceIndex != -1 ? firstSpaceIndex : query.length();
String op = query.substring(0, endIndex);
if ("select".equals(op))
return EntityOp.READ;
else if ("update".equals(op))
return EntityOp.UPDATE;
else if ("delete".equals(op))
return EntityOp.DELETE;
return null;
}
public String[] getAvailableBasicTypes() {
Set<String> allAvailableTypes = Datatypes.getNames();
TreeSet<String> availableTypes = new TreeSet<String>();
//byteArray is not supported as a GET parameter
for (String type : allAvailableTypes)
if (!"byteArray".equals(type))
availableTypes.add(type);
return availableTypes.toArray(new String[availableTypes.size()]);
}
}

View File

@ -0,0 +1,65 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.cuba.core.entity.BaseUuidEntity;
import com.haulmont.cuba.core.global.EntityLoadInfo;
import java.util.UUID;
/**
* Author: Alexander Chevelev
* Date: 14.05.2011
* Time: 1:18:58
*/
public class InstanceRef {
private EntityLoadInfo loadInfo;
private BaseUuidEntity instance;
public InstanceRef(EntityLoadInfo loadInfo) throws InstantiationException, IllegalAccessException {
if (loadInfo == null)
throw new NullPointerException("No load info passed");
this.loadInfo = loadInfo;
MetaClass childMetaClass = this.loadInfo.getMetaClass();
instance = childMetaClass.createInstance();
instance.setId(this.loadInfo.getId());
}
public UUID getId() {
return loadInfo.getId();
}
public MetaClass getMetaClass() {
return loadInfo.getMetaClass();
}
public BaseUuidEntity getInstance() {
return instance;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
InstanceRef that = (InstanceRef) o;
if (!loadInfo.getId().equals(that.loadInfo.getId())) return false;
if (!loadInfo.getMetaClass().equals(that.loadInfo.getMetaClass())) return false;
return true;
}
@Override
public int hashCode() {
int result1 = loadInfo.getId().hashCode();
result1 = 31 * result1 + loadInfo.getMetaClass().hashCode();
return result1;
}
}

View File

@ -0,0 +1,392 @@
/*
* 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 com.haulmont.cuba.core.sys.restapi;
import com.haulmont.chile.core.datatypes.impl.StringDatatype;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.cuba.core.entity.BaseUuidEntity;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.security.entity.EntityAttrAccess;
import com.haulmont.cuba.security.entity.EntityOp;
import com.haulmont.cuba.security.global.UserSession;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.persistence.Id;
import javax.servlet.http.HttpServletResponse;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.*;
/**
*
* @version $Id$
*/
public class JSONConvertor implements Convertor {
public static final String MIME_STR = "application/json;charset=UTF-8";
public static final MimeType MIME_TYPE_JSON;
static {
try {
MIME_TYPE_JSON = new MimeType(MIME_STR);
} catch (MimeTypeParseException e) {
throw new RuntimeException(e);
}
}
public MimeType getMimeType() {
return MIME_TYPE_JSON;
}
public MyJSONObject process(Entity entity, MetaClass metaclass, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
return encodeInstance(entity, new HashSet<Entity>(), metaclass);
}
public MyJSONObject.Array process(List<Entity> entities, MetaClass metaClass, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
MyJSONObject.Array result = new MyJSONObject.Array();
for (Entity entity : entities) {
MyJSON item = encodeInstance(entity, new HashSet<Entity>(), metaClass);
result.add(item);
}
return result;
}
public MyJSONObject.Array process(Map<Entity, Entity> entityMap, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
MyJSONObject.Array result = new MyJSONObject.Array();
for (Map.Entry<Entity, Entity> entry : entityMap.entrySet()) {
Entity key = entry.getKey();
Entity value = entry.getValue();
MyJSONObject keyJson = encodeInstance(key, new HashSet<Entity>(), getMetaClass(key));
MyJSONObject valueJson = encodeInstance(value, new HashSet<Entity>(), getMetaClass(value));
MyJSONObject.Array mapping = new MyJSONObject.Array();
mapping.add(keyJson);
mapping.add(valueJson);
result.add(mapping);
}
return result;
}
private MetaClass getMetaClass(Entity entity) {
return MetadataProvider.getSession().getClass(entity.getClass());
}
public void write(HttpServletResponse response, Object o) {
response.setContentType(MIME_STR);
try {
PrintWriter writer = response.getWriter();
writer.write(o.toString());
writer.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public CommitRequest parseCommitRequest(String content) {
try {
JSONObject jsonContent = new JSONObject(content);
CommitRequest result = new CommitRequest();
if (jsonContent.has("commitInstances")) {
JSONArray entitiesNodeList = jsonContent.getJSONArray("commitInstances");
Set<String> commitIds = new HashSet<>(entitiesNodeList.length());
for (int i = 0; i < entitiesNodeList.length(); i++) {
String id = entitiesNodeList.getJSONObject(i).getString("id");
if (id.startsWith("NEW-"))
id = id.substring(id.indexOf('-') + 1);
commitIds.add(id);
}
result.setCommitIds(commitIds);
result.setCommitInstances(parseIntoList(result, entitiesNodeList));
}
if (jsonContent.has("removeInstances")) {
JSONArray entitiesNodeList = jsonContent.getJSONArray("removeInstances");
result.setRemoveInstances(parseIntoList(result, entitiesNodeList));
}
if (jsonContent.has("softDeletion")) {
result.setSoftDeletion(jsonContent.getBoolean("softDeletion"));
}
return result;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private List<BaseUuidEntity> parseIntoList(CommitRequest commitRequest, JSONArray nodeList)
throws JSONException, InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, IntrospectionException, ParseException {
List<BaseUuidEntity> result = new ArrayList<BaseUuidEntity>(nodeList.length());
for (int j = 0; j < nodeList.length(); j++) {
JSONObject jsonObject = nodeList.getJSONObject(j);
InstanceRef ref = commitRequest.parseInstanceRefAndRegister(jsonObject.getString("id"));
MetaClass metaClass = ref.getMetaClass();
BaseUuidEntity instance = ref.getInstance();
asJavaTree(commitRequest, instance, metaClass, jsonObject);
result.add(instance);
}
return result;
}
private void asJavaTree(CommitRequest commitRequest, Object bean, MetaClass metaClass, JSONObject json)
throws JSONException, InvocationTargetException, IllegalAccessException, NoSuchMethodException, InstantiationException, IntrospectionException, ParseException {
Iterator iter = json.keys();
while (iter.hasNext()) {
String key = (String) iter.next();
//version is readonly property
if ("version".equals(key))
continue;
MetaProperty property = metaClass.getProperty(key);
if (!attrModifyPermitted(metaClass, property.getName()))
continue;
if (json.get(key) == null) {
setField(bean, key, new Object[]{null});
continue;
}
if ("id".equals(key)) {
// id was parsed already
continue;
}
switch (property.getType()) {
case DATATYPE:
String value = json.getString(key);
String typeName = property.getRange().<Object>asDatatype().getName();
if (!StringDatatype.NAME.equals(typeName) && "null".equals(value))
value = null;
setField(bean, key, property.getRange().<Object>asDatatype().parse(value));
break;
case ENUM:
setField(bean, key, property.getRange().asEnumeration().parse(json.getString(key)));
break;
case COMPOSITION:
case ASSOCIATION:
if ("null".equals(json.getString(key))) {
setField(bean, key, null);
break;
}
MetaClass propertyMetaClass = propertyMetaClass(property);
//checks if the user permitted to read and update a property
if (!updatePermitted(propertyMetaClass) && !readPermitted(propertyMetaClass))
break;
if (!property.getRange().getCardinality().isMany()) {
JSONObject jsonChild = json.getJSONObject(key);
Object child;
MetaClass childMetaClass;
if (jsonChild.has("id")) {
String id = jsonChild.getString("id");
//reference to an entity that also a commit instance
//will be registered later
if (commitRequest.getCommitIds().contains(id)) {
EntityLoadInfo loadInfo = EntityLoadInfo.parse(id);
BaseUuidEntity ref = loadInfo.getMetaClass().createInstance();
ref.setValue("id", loadInfo.getId());
setField(bean, key, ref);
break;
}
InstanceRef ref = commitRequest.parseInstanceRefAndRegister(id);
childMetaClass = ref.getMetaClass();
child = ref.getInstance();
} else {
childMetaClass = property.getRange().asClass();
child = childMetaClass.createInstance();
}
asJavaTree(commitRequest, child, childMetaClass, jsonChild);
setField(bean, key, child);
} else {
JSONArray jsonArray = json.getJSONArray(key);
Collection<Object> coll = property.getRange().isOrdered()
? new ArrayList<Object>()
: new HashSet<Object>();
setField(bean, key, coll);
for (int i = 0; i < jsonArray.length(); i++) {
Object arrayValue = jsonArray.get(i);
if (arrayValue == null)
coll.add(null);
else {
//assuming no simple type here
JSONObject jsonChild = (JSONObject) arrayValue;
InstanceRef ref = commitRequest.parseInstanceRefAndRegister(jsonChild.getString("id"));
Object child = ref.getInstance();
coll.add(child);
asJavaTree(commitRequest, child, ref.getMetaClass(), jsonChild);
}
}
}
break;
default:
throw new IllegalStateException("Unknown property type");
}
}
}
private void setField(Object bean, String field, Object value)
throws IllegalAccessException, InvocationTargetException, IntrospectionException {
new PropertyDescriptor(field, bean.getClass()).getWriteMethod().invoke(bean, value);
}
/**
* Encodes the closure of a persistent instance into JSON.
*
* @param entity the managed instance to be encoded. Can be null.
* @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 MyJSONObject encodeInstance(final Entity entity, final Set<Entity> visited, MetaClass metaClass)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
if (visited == null) {
throw new IllegalArgumentException("null closure for encoder");
}
if (entity == null) {
return null;
}
boolean ref = !visited.add(entity);
MyJSONObject root = new MyJSONObject(idof(entity), ref);
if (ref) {
return root;
}
MetadataTools metadataTools = AppBeans.get(MetadataTools.class);
List<MetaProperty> properties = ConvertorHelper.getOrderedProperties(metaClass);
for (MetaProperty property : properties) {
if (metadataTools.isTransient(property))
continue;
if (!attrViewPermitted(metaClass, property.getName()))
continue;
Object value = entity.getValue(property.getName());
if (property.getAnnotatedElement().isAnnotationPresent(Id.class)) {
//skipping: we encoded it before
continue;
}
switch (property.getType()) {
case DATATYPE:
root.set(property.getName(), property.getRange().asDatatype().format(value));
break;
case ENUM:
root.set(property.getName(), property.getRange().asEnumeration().format(value));
break;
case COMPOSITION:
case ASSOCIATION: {
MetaClass meta = propertyMetaClass(property);
//checks if the user permitted to read a property's entity
if (!readPermitted(meta))
break;
if (!property.getRange().getCardinality().isMany()) {
if (value == null) {
root.set(property.getName(), null);
} else {
root.set(property.getName(), encodeInstance((Entity) value, visited,
property.getRange().asClass()));
}
} else {
if (value == null) {
root.set(property.getName(), null);
break;
}
MyJSONObject.Array array = new MyJSONObject.Array();
root.set(property.getName(), array);
Collection<?> members = (Collection<?>) value;
for (Object o : members) {
if (o == null) {
array.add(null);
} else {
array.add(encodeInstance((Entity) o, visited,
property.getRange().asClass()));
}
}
}
break;
}
default:
throw new IllegalStateException("Unknown property type");
}
}
return root;
}
private String idof(Entity entity) {
return EntityLoadInfo.create(entity).toString();
}
private boolean attrViewPermitted(MetaClass metaClass, String property) {
return attrPermitted(metaClass, property, EntityAttrAccess.VIEW);
}
private boolean attrModifyPermitted(MetaClass metaClass, String property) {
return attrPermitted(metaClass, property, EntityAttrAccess.MODIFY);
}
private boolean attrPermitted(MetaClass metaClass, String property, EntityAttrAccess entityAttrAccess) {
UserSession session = UserSessionProvider.getUserSession();
return session.isEntityAttrPermitted(metaClass, property, entityAttrAccess);
}
private boolean readPermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.READ);
}
private boolean updatePermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.UPDATE);
}
private boolean entityOpPermitted(MetaClass metaClass, EntityOp entityOp) {
UserSession session = UserSessionProvider.getUserSession();
return session.isEntityOpPermitted(metaClass, entityOp);
}
private MetaClass propertyMetaClass(MetaProperty property) {
return property.getRange().asClass();
}
}

View File

@ -0,0 +1,119 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.cuba.core.global.AppBeans;
import com.haulmont.cuba.core.global.PasswordEncryption;
import com.haulmont.cuba.security.app.LoginWorker;
import com.haulmont.cuba.security.global.LoginException;
import com.haulmont.cuba.security.global.UserSession;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.json.JSONException;
import org.json.JSONObject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.activation.MimeType;
import javax.activation.MimeTypeParseException;
import javax.inject.Inject;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
/**
* @author chevelev
* @version $Id$
*/
@Controller
@RequestMapping(value = "/login")
public class LoginServiceController {
@Inject
private PasswordEncryption passwordEncryption;
private static Log log = LogFactory.getLog(LoginServiceController.class);
private static MimeType FORM_TYPE;
static {
try {
FORM_TYPE = new MimeType("application/x-www-form-urlencoded;charset=UTF-8");
} catch (MimeTypeParseException e) {
throw new RuntimeException(e);
}
}
@RequestMapping(method = RequestMethod.POST)
public void loginByPost(@RequestBody String requestBody,
@RequestHeader(value = "Content-Type") MimeType contentType,
HttpServletResponse response) throws IOException, JSONException {
response.addHeader("Access-Control-Allow-Origin", "*");
String username;
String password;
Locale locale;
if (contentType.match(JSONConvertor.MIME_TYPE_JSON)) {
JSONObject json = new JSONObject(requestBody);
username = json.getString("username");
password = json.getString("password");
locale = new Locale(json.getString("locale"));
} else if (contentType.match(FORM_TYPE)) {
String[] pairs = requestBody.split("\\&");
Map<String, String> name2value = new HashMap<>();
for (String pair : pairs) {
String[] fields = pair.split("=");
String name = URLDecoder.decode(fields[0], "UTF-8");
String value = URLDecoder.decode(fields[1], "UTF-8");
name2value.put(name, value);
}
username = name2value.get("username");
password = name2value.get("password");
locale = new Locale(name2value.get("locale"));
} else {
throw new IllegalStateException("Unsupported content type: " + contentType);
}
try {
LoginWorker svc = AppBeans.get(LoginWorker.NAME);
UserSession userSession = svc.login(username, passwordEncryption.getPlainHash(password), locale);
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = new PrintWriter(response.getOutputStream());
writer.write(userSession.getId().toString());
writer.close();
log.debug(String.format("User %s logged in with REST-API, session id: %s", username, userSession.getId()));
} catch (LoginException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
@RequestMapping(method = RequestMethod.GET)
public void loginByGet(@RequestParam(value = "u") String username,
@RequestParam(value = "p") String password,
@RequestParam(value = "l") String localeStr,
HttpServletResponse response) throws IOException, JSONException {
response.addHeader("Access-Control-Allow-Origin", "*");
Locale locale = StringUtils.isBlank(localeStr) ? new Locale("en") : new Locale(localeStr);
try {
LoginWorker svc = AppBeans.get(LoginWorker.NAME);
UserSession userSession = svc.login(username, passwordEncryption.getPlainHash(password), locale);
response.setStatus(HttpServletResponse.SC_OK);
PrintWriter writer = new PrintWriter(response.getOutputStream());
writer.write(userSession.getId().toString());
writer.close();
log.debug(String.format("User %s logged in with REST-API, session id: %s", username, userSession.getId()));
} catch (LoginException e) {
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
}

View File

@ -0,0 +1,29 @@
package com.haulmont.cuba.core.sys.restapi;
public interface MyJSON {
/**
* 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,186 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import java.io.PrintWriter;
import java.util.*;
public class MyJSONObject implements MyJSON {
private final String _id;
private final boolean _ref;
private final Map<String, Object> _values;
public MyJSONObject(Object id, boolean ref) {
_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;
}
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);
}
String ior() {
return _id;
}
private static StringBuilder quoteFieldName(String s) {
return new StringBuilder().append(QUOTE).append(s).append(QUOTE);
}
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 MyJSON) return ((MyJSON)o).asString(indent);
return quoted(o.toString());
}
private static StringBuilder quoted(Object o) {
if (o == null) return new StringBuilder(NULL_LITERAL);
String escaped = o.toString().replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\r", "\\r")
.replace("\n", "\\n");
return new StringBuilder().append(QUOTE).append(escaped).append(QUOTE);
}
public static class Array implements MyJSON {
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 = MyJSONObject.newIndent(indent+1);
for (Object o : _members) {
if (buf.length() > 1) buf.append(MEMBER_SEPARATOR);
buf.append(NEWLINE).append(tab);
if (o instanceof MyJSON)
buf.append(((MyJSON)o).asString(indent+1));
else
buf.append(o);
}
buf.append(NEWLINE)
.append(MyJSONObject.newIndent(indent))
.append(ARRAY_END);
return buf;
}
}
public static class KVMap implements MyJSON {
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 = MyJSONObject.newIndent(indent+1);
for (Map.Entry<Object, Object> e : _entries.entrySet()) {
if (buf.length() > 1) buf.append(MEMBER_SEPARATOR);
buf.append(NEWLINE).append(tab);
Object key = e.getKey();
if (key instanceof MyJSON) {
buf.append(((MyJSON)key).asString(indent+1));
} else {
buf.append(key);
}
buf.append(VALUE_SEPARATOR);
Object value = e.getValue();
if (value instanceof MyJSON) {
buf.append(((MyJSON)value).asString(indent+2));
} else {
buf.append(value);
}
}
buf.append(NEWLINE)
.append(MyJSONObject.newIndent(indent))
.append(ARRAY_END);
return buf;
}
}
public static void main(String[] args) throws Exception {
MyJSONObject o = new MyJSONObject("Person-1234", false);
MyJSONObject r = new MyJSONObject("Person-1234", true);
MyJSONObject f = new MyJSONObject("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,79 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.cuba.core.sys.AppContext;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.text.StrTokenizer;
import org.springframework.context.ApplicationContext;
import org.springframework.util.ResourceUtils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import java.io.File;
/**
* <p>$Id$</p>
*
* @author chevelev
*/
public class RestApiServlet extends DispatcherServlet {
public static final String SPRING_CONTEXT_CONFIG = "cuba.restApiSpringContextConfig";
private static final long serialVersionUID = -917577256568108225L;
@Override
public String getContextConfigLocation() {
String configProperty = AppContext.getProperty(SPRING_CONTEXT_CONFIG);
if (StringUtils.isBlank(configProperty)) {
throw new IllegalStateException("Missing " + SPRING_CONTEXT_CONFIG + " application property");
}
File baseDir = new File(AppContext.getProperty("cuba.confDir"));
StrTokenizer tokenizer = new StrTokenizer(configProperty);
String[] tokenArray = tokenizer.getTokenArray();
StringBuilder locations = new StringBuilder();
for (String token : tokenArray) {
String location;
if (ResourceUtils.isUrl(token)) {
location = token;
} else {
if (token.startsWith("/"))
token = token.substring(1);
File file = new File(baseDir, token);
if (file.exists()) {
location = file.toURI().toString();
} else {
location = "classpath:" + token;
}
}
locations.append(location).append(" ");
}
return locations.toString();
}
@Override
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext wac = findWebApplicationContext();
if (wac == null) {
ApplicationContext parent = AppContext.getApplicationContext();
wac = createWebApplicationContext(parent);
}
onRefresh(wac);
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
if (this.logger.isDebugEnabled()) {
this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
"' as ServletContext attribute with name [" + attrName + "]");
}
return wac;
}
}

View File

@ -0,0 +1,636 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi;
import com.haulmont.chile.core.datatypes.impl.StringDatatype;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.cuba.core.entity.BaseUuidEntity;
import com.haulmont.cuba.core.entity.Entity;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.security.entity.EntityAttrAccess;
import com.haulmont.cuba.security.entity.EntityOp;
import com.haulmont.cuba.security.global.UserSession;
import org.w3c.dom.*;
import org.w3c.dom.bootstrap.DOMImplementationRegistry;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSInput;
import org.w3c.dom.ls.LSParser;
import org.w3c.dom.ls.LSParserFilter;
import org.w3c.dom.traversal.NodeFilter;
import javax.activation.MimeType;
import javax.persistence.Embedded;
import javax.persistence.Id;
import javax.persistence.Version;
import javax.servlet.http.HttpServletResponse;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.text.ParseException;
import java.util.*;
/**
* @version $Id$
*/
public class XMLConvertor implements Convertor {
public static final MimeType MIME_TYPE_XML;
public static final String MIME_STR = "text/xml;charset=UTF-8";
public static final String ELEMENT_INSTANCE = "instance";
public static final String ELEMENT_URI = "uri";
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 ATTR_ID = "id";
public static final String ATTR_NAME = "name";
public static final String ATTR_VERSION = "version";
public static final String ATTR_NULL = "null";
public static final String ATTR_MEMBER_TYPE = "member-type";
public static final String NULL_VALUE = "null";
private static final String EMPTY_TEXT = " ";
public static final char DASH = '-';
public static final char UNDERSCORE = '_';
public static final String ROOT_ELEMENT_INSTANCE = "instances";
private static DocumentBuilder _builder;
public static final String MAPPING_ROOT_ELEMENT_INSTANCE = "mapping";
public static final String PAIR_ELEMENT = "pair";
private static final Transformer _transformer;
private static LSParser requestConfigParser;
private static DOMImplementationLS lsImpl;
static {
try {
MIME_TYPE_XML = new MimeType(MIME_STR);
_builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();
lsImpl = (DOMImplementationLS) registry.getDOMImplementation("LS");
requestConfigParser = lsImpl.createLSParser(DOMImplementationLS.MODE_SYNCHRONOUS,
null);
// Set options on the parser
DOMConfiguration config = requestConfigParser.getDomConfig();
config.setParameter("validate", Boolean.TRUE);
config.setParameter("element-content-whitespace", Boolean.FALSE);
config.setParameter("comments", Boolean.FALSE);
requestConfigParser.setFilter(new LSParserFilter() {
public short startElement(Element elementArg) {
return LSParserFilter.FILTER_ACCEPT;
}
public short acceptNode(Node nodeArg) {
return "".equals(nodeArg.getTextContent().trim()) ?
LSParserFilter.FILTER_REJECT :
LSParserFilter.FILTER_ACCEPT;
}
public int getWhatToShow() {
return NodeFilter.SHOW_TEXT;
}
});
_transformer = TransformerFactory.newInstance().newTransformer();
_transformer.setOutputProperty(OutputKeys.METHOD, "xml");
_transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
_transformer.setOutputProperty(OutputKeys.STANDALONE, "no");
_transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
_transformer.setOutputProperty(OutputKeys.INDENT, "no");
_transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public MimeType getMimeType() {
return MIME_TYPE_XML;
}
public Document process(Entity entity, MetaClass metaclass, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Element root = newDocument(ROOT_ELEMENT_INSTANCE);
encodeEntityInstance(new HashSet<Entity>(), entity, root, false, metaclass);
Document doc = root.getOwnerDocument();
decorate(doc, requestURI);
return doc;
}
public Document process(List<Entity> entities, MetaClass metaClass, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Element root = newDocument(ROOT_ELEMENT_INSTANCE);
for (Entity entity : entities) {
encodeEntityInstance(new HashSet(), entity, root, false, metaClass);
}
Document doc = root.getOwnerDocument();
decorate(doc, requestURI);
return doc;
}
public Object process(Map<Entity, Entity> entityMap, String requestURI)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
Element root = newDocument(MAPPING_ROOT_ELEMENT_INSTANCE);
Document doc = root.getOwnerDocument();
for (Map.Entry<Entity, Entity> entry : entityMap.entrySet()) {
Element pair = doc.createElement(PAIR_ELEMENT);
root.appendChild(pair);
encodeEntityInstance(
new HashSet(), entry.getKey(),
pair, false,
getMetaClass(entry.getKey())
);
encodeEntityInstance(
new HashSet(), entry.getValue(),
pair, false,
getMetaClass(entry.getValue())
);
}
return doc;
}
public void write(HttpServletResponse response, Object o) throws IOException {
Document doc = (Document) o;
response.setContentType(MIME_STR);
try {
_transformer.transform(new DOMSource(doc), new StreamResult(response.getOutputStream()));
} catch (Exception e) {
throw new IOException(e);
}
}
public CommitRequest parseCommitRequest(String content) {
try {
LSInput lsInput = lsImpl.createLSInput();
lsInput.setStringData(content);
Document commitRequestDoc = requestConfigParser.parse(lsInput);
Node rootNode = commitRequestDoc.getFirstChild();
if (!"CommitRequest".equals(rootNode.getNodeName()))
throw new IllegalArgumentException("Not a CommitRequest xml passed: " + rootNode.getNodeName());
CommitRequest result = new CommitRequest();
NodeList children = rootNode.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
String childNodeName = child.getNodeName();
if ("commitInstances".equals(childNodeName)) {
NodeList entitiesNodeList = child.getChildNodes();
Set<String> commitIds = new HashSet<>(entitiesNodeList.getLength());
for (int j = 0; j < entitiesNodeList.getLength(); j++) {
Node idNode = entitiesNodeList.item(j).getAttributes().getNamedItem("id");
if (idNode == null)
continue;
String id = idNode.getTextContent();
if (id.startsWith("NEW-"))
id = id.substring(id.indexOf('-') + 1);
commitIds.add(id);
}
result.setCommitIds(commitIds);
result.setCommitInstances(parseNodeList(result, entitiesNodeList));
} else if ("removeInstances".equals(childNodeName)) {
NodeList entitiesNodeList = child.getChildNodes();
List removeInstances = parseNodeList(result, entitiesNodeList);
result.setRemoveInstances(removeInstances);
} else if ("softDeletion".equals(childNodeName)) {
result.setSoftDeletion(Boolean.parseBoolean(child.getTextContent()));
}
}
return result;
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (IntrospectionException e) {
throw new RuntimeException(e);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (ParseException e) {
throw new RuntimeException(e);
}
}
private List parseNodeList(CommitRequest commitRequest, NodeList entitiesNodeList) throws InstantiationException, IllegalAccessException, InvocationTargetException, IntrospectionException, ParseException {
List entities = new ArrayList(entitiesNodeList.getLength());
for (int j = 0; j < entitiesNodeList.getLength(); j++) {
Node entityNode = entitiesNodeList.item(j);
if (ELEMENT_INSTANCE.equals(entityNode.getNodeName())) {
InstanceRef ref = commitRequest.parseInstanceRefAndRegister(getIdAttribute(entityNode));
MetaClass metaClass = ref.getMetaClass();
Object instance = ref.getInstance();
parseEntity(commitRequest, instance, metaClass, entityNode);
entities.add(instance);
}
}
return entities;
}
private void parseEntity(CommitRequest commitRequest, Object bean, MetaClass metaClass, Node node)
throws InstantiationException, IllegalAccessException, InvocationTargetException, IntrospectionException, ParseException {
MetadataTools metadataTools = AppBeans.get(MetadataTools.class);
NodeList fields = node.getChildNodes();
for (int i = 0; i < fields.getLength(); i++) {
Node fieldNode = fields.item(i);
String fieldName = getFieldName(fieldNode);
MetaProperty property = metaClass.getProperty(fieldName);
if (!attrModifyPermitted(metaClass, property.getName()))
continue;
if (metadataTools.isTransient(bean, fieldName))
continue;
String xmlValue = fieldNode.getTextContent();
if (isNullValue(fieldNode)) {
setNullField(bean, fieldName);
}
Object value;
switch (property.getType()) {
case DATATYPE:
case ENUM:
if (property.getAnnotatedElement().isAnnotationPresent(Id.class)) {
// it was parsed in the beginning
continue;
}
String typeName = property.getRange().<Object>asDatatype().getName();
if (property.getType() == MetaProperty.Type.DATATYPE)
if (!StringDatatype.NAME.equals(typeName) && "null".equals(xmlValue))
value = null;
else
value = property.getRange().<Object>asDatatype().parse(xmlValue);
else
value = property.getRange().asEnumeration().parse(xmlValue);
setField(bean, fieldName, value);
break;
case COMPOSITION:
case ASSOCIATION: {
if ("null".equals(xmlValue)) {
setField(bean, fieldName, null);
break;
}
MetaClass propertyMetaClass = propertyMetaClass(property);
//checks if the user permitted to read and update a property
if (!updatePermitted(propertyMetaClass) && !readPermitted(propertyMetaClass))
break;
if (!property.getRange().getCardinality().isMany()) {
if (property.getAnnotatedElement().isAnnotationPresent(Embedded.class)) {
MetaClass embeddedMetaClass = property.getRange().asClass();
value = embeddedMetaClass.createInstance();
parseEntity(commitRequest, value, embeddedMetaClass, fieldNode);
} else {
String id = getRefId(fieldNode);
//reference to an entity that also a commit instance
//will be registered later
if (commitRequest.getCommitIds().contains(id)) {
EntityLoadInfo loadInfo = EntityLoadInfo.parse(id);
BaseUuidEntity ref = loadInfo.getMetaClass().createInstance();
ref.setValue("id", loadInfo.getId());
setField(bean, fieldName, ref);
break;
}
value = parseEntityReference(fieldNode, commitRequest);
}
setField(bean, fieldName, value);
} else {
NodeList memberNodes = fieldNode.getChildNodes();
Collection<Object> members = property.getRange().isOrdered() ? new ArrayList<Object>() : new HashSet<Object>();
for (int memberIndex = 0; memberIndex < memberNodes.getLength(); memberIndex++) {
Node memberNode = memberNodes.item(memberIndex);
members.add(parseEntityReference(memberNode, commitRequest));
}
setField(bean, fieldName, members);
}
break;
}
default:
throw new IllegalStateException("Unknown property type");
}
}
}
private String getRefId(Node refNode) {
Node childNode = refNode.getFirstChild();
do {
if (ELEMENT_REF.equals(childNode.getNodeName())) {
Node idNode = childNode.getAttributes().getNamedItem(ATTR_ID);
return idNode != null ? idNode.getTextContent() : null;
}
childNode = childNode.getNextSibling();
} while (childNode != null);
return null;
}
private Object parseEntityReference(Node node, CommitRequest commitRequest)
throws InstantiationException, IllegalAccessException, InvocationTargetException, IntrospectionException {
Node childNode = node.getFirstChild();
if (ELEMENT_NULL_REF.equals(childNode.getNodeName())) {
return null;
}
InstanceRef ref = commitRequest.parseInstanceRefAndRegister(getIdAttribute(childNode));
return ref.getInstance();
}
private void setField(Object result, String fieldName, Object value) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
new PropertyDescriptor(fieldName, result.getClass()).
getWriteMethod().invoke(result, value);
}
private void setNullField(Object bean, String fieldName) throws IllegalAccessException, InvocationTargetException, IntrospectionException {
setField(bean, fieldName, null);
}
private String getFieldName(Node fieldNode) {
return getAttributeValue(fieldNode, ATTR_NAME);
}
private String getIdAttribute(Node node) {
return getAttributeValue(node, ATTR_ID);
}
private String getAttributeValue(Node node, String name) {
return node.getAttributes().getNamedItem(name).getNodeValue();
}
/**
* 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", INSTANCE_XSD,
ATTR_VERSION, "1.0",
};
for (int i = 0; i < nvpairs.length; i += 2) {
root.setAttribute(nvpairs[i], nvpairs[i + 1]);
}
return root;
}
Document decorate(Document doc, String 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);
root.insertBefore(uriElement, instance);
return doc;
}
/**
* Encodes the closure of a persistent instance into a XML element.
*
* @param visited
* @param entity 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 isRef
* @param metaClass @return the new element. The element has been appended as a child to the given parent in this method.
*/
private Element encodeEntityInstance(HashSet<Entity> visited, final Entity entity, final Element parent,
boolean isRef, MetaClass metaClass)
throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
if (!readPermitted(metaClass))
return null;
if (parent == null)
throw new NullPointerException("No parent specified");
Document doc = parent.getOwnerDocument();
if (doc == null)
throw new NullPointerException("No document specified");
if (entity == null) {
return encodeRef(parent, entity);
}
isRef |= !visited.add(entity);
if (isRef) {
return encodeRef(parent, entity);
}
Element root = doc.createElement(ELEMENT_INSTANCE);
parent.appendChild(root);
root.setAttribute(ATTR_ID, ior(entity));
MetadataTools metadataTools = AppBeans.get(MetadataTools.class);
List<MetaProperty> properties = ConvertorHelper.getOrderedProperties(metaClass);
for (MetaProperty property : properties) {
Element child;
if (metadataTools.isTransient(entity, property.getName()))
continue;
if (!attrViewPermitted(metaClass, property.getName()))
continue;
Object value = entity.getValue(property.getName());
switch (property.getType()) {
case DATATYPE:
String nodeType;
if (property.getAnnotatedElement().isAnnotationPresent(Id.class)) {
continue;
} else if (property.getAnnotatedElement().isAnnotationPresent(Version.class)) {
nodeType = "version";
} else {
nodeType = "basic";
}
child = doc.createElement(nodeType);
child.setAttribute(ATTR_NAME, property.getName());
if (value == null) {
encodeNull(child);
} else {
String str = property.getRange().<Object>asDatatype().format(value);
encodeBasic(child, str, property.getJavaType());
}
break;
case ENUM:
child = doc.createElement("enum");
child.setAttribute(ATTR_NAME, property.getName());
if (value == null) {
encodeNull(child);
} else {
String str = property.getRange().asEnumeration().format(value);
encodeBasic(child, str, property.getJavaType());
}
break;
case COMPOSITION:
case ASSOCIATION: {
MetaClass meta = propertyMetaClass(property);
//checks if the user permitted to read a property
if (!readPermitted(meta)) {
child = null;
break;
}
if (!property.getRange().getCardinality().isMany()) {
boolean isEmbedded = property.getAnnotatedElement().isAnnotationPresent(Embedded.class);
child = doc.createElement(isEmbedded ?
"embedded" :
property.getRange().getCardinality().name().replace(UNDERSCORE, DASH).toLowerCase()
);
child.setAttribute(ATTR_NAME, property.getName());
if (isEmbedded) {
encodeEntityInstance(visited, (Entity) value, child, false, property.getRange().asClass());
} else {
encodeEntityInstance(visited, (Entity) value, child, false, property.getRange().asClass());
}
} else {
child = doc.createElement(getCollectionReferenceTag(property));
child.setAttribute(ATTR_NAME, property.getName());
child.setAttribute(ATTR_MEMBER_TYPE, typeOfEntityProperty(property));
if (value == null) {
encodeNull(child);
break;
}
Collection<?> members = (Collection<?>) value;
for (Object o : members) {
Element member = doc.createElement(ELEMENT_MEMBER);
child.appendChild(member);
if (o == null) {
encodeNull(member);
} else {
encodeEntityInstance(visited, (Entity) o, member, true, property.getRange().asClass());
}
}
}
break;
}
default:
throw new IllegalStateException("Unknown property type");
}
if (child != null) {
root.appendChild(child);
}
}
return root;
}
private String typeOfEntityProperty(MetaProperty property) {
return property.getRange().asClass().getName();
}
private MetaClass propertyMetaClass(MetaProperty property) {
return property.getRange().asClass();
}
/**
* 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 boolean isNullValue(Node fieldNode) {
Node nullAttr = fieldNode.getAttributes().getNamedItem(ATTR_NULL);
return nullAttr == null ?
false :
"true".equals(nullAttr.getNodeValue());
}
private Element encodeRef(Element parent, Entity entity) {
Element ref = parent.getOwnerDocument().createElement(entity == null ? ELEMENT_NULL_REF : ELEMENT_REF);
if (entity != null)
ref.setAttribute(ATTR_ID, ior(entity));
// 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.
* @param runtimeType attribute type
*/
private void encodeBasic(Element element, Object obj, Class<?> runtimeType) {
element.setTextContent(obj == null ? NULL_VALUE : obj.toString());
}
String ior(Entity entity) {
return EntityLoadInfo.create(entity).toString();
}
String typeOf(Class<?> cls) {
return cls.getSimpleName();
}
private String getCollectionReferenceTag(MetaProperty property) {
return property.getRange().getCardinality().name().replace(UNDERSCORE, DASH).toLowerCase();
}
private MetaClass getMetaClass(Entity entity) {
return MetadataProvider.getSession().getClass(entity.getClass());
}
private boolean attrViewPermitted(MetaClass metaClass, String property) {
return attrPermitted(metaClass, property, EntityAttrAccess.VIEW);
}
private boolean attrModifyPermitted(MetaClass metaClass, String property) {
return attrPermitted(metaClass, property, EntityAttrAccess.MODIFY);
}
private boolean attrPermitted(MetaClass metaClass, String property, EntityAttrAccess entityAttrAccess) {
UserSession session = UserSessionProvider.getUserSession();
return session.isEntityAttrPermitted(metaClass, property, entityAttrAccess);
}
private boolean readPermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.READ);
}
private boolean updatePermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.UPDATE);
}
private boolean entityOpPermitted(MetaClass metaClass, EntityOp entityOp) {
UserSession session = UserSessionProvider.getUserSession();
return session.isEntityOpPermitted(metaClass, entityOp);
}
}

View File

@ -0,0 +1,143 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!--
~ Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
~ Haulmont Technology proprietary and confidential.
~ Use is subject to license terms.
-->
<!-- ========================================================================= -->
<!-- Schema for serialized persistence instance. -->
<!-- ========================================================================= -->
<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema"
attributeFormDefault="unqualified" elementFormDefault="qualified"
version="1.0">
<!-- 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. -->
<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="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="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: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="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="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,281 @@
/*
* Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
* Haulmont Technology proprietary and confidential.
* Use is subject to license terms.
*/
package com.haulmont.cuba.core.sys.restapi.template;
import com.haulmont.chile.core.model.MetaClass;
import com.haulmont.chile.core.model.MetaProperty;
import com.haulmont.cuba.core.global.*;
import com.haulmont.cuba.security.entity.EntityAttrAccess;
import com.haulmont.cuba.security.entity.EntityOp;
import com.haulmont.cuba.security.global.UserSession;
import javax.persistence.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* Author: Alexander Chevelev
* Date: 26.05.2011
* Time: 0:11:34
*
* @version $Id$
*/
public class MetaClassRepresentation {
private MetaClass meta;
private List<View> views;
public MetaClassRepresentation(MetaClass meta, List<View> views) {
this.meta = meta;
this.views = views;
getTableName();
}
public String getTableName() {
boolean isEmbeddable = meta.getJavaClass().isAnnotationPresent(Embeddable.class);
if (isEmbeddable)
return "not defined for embeddable entities";
Table tableAnn = (Table) meta.getJavaClass().getAnnotation(Table.class);
return tableAnn != null ? tableAnn.name() : "not defined";
}
public String getName() {
return meta.getName();
}
public String getParent() {
MetaClass ancestor = meta.getAncestor();
if (ancestor == null || !ancestor.getName().contains("$") ||
ancestor.getJavaClass().isAnnotationPresent(MappedSuperclass.class))
return "";
if (!readPermitted(ancestor)) {
return null;
}
return "Parent is " + asHref(ancestor);
}
public String getDescription() {
String result = AppBeans.get(MessageTools.class).getEntityCaption(meta);
return result == null ? "" : result;
}
public Collection<MetaClassRepProperty> getProperties() {
List<MetaClassRepProperty> result = new ArrayList<MetaClassRepProperty>();
for (MetaProperty property : meta.getProperties()) {
MetaProperty.Type propertyType = property.getType();
//don't show property if user don't have permissions to view it
if (!attrViewPermitted(meta, property.getName())) {
continue;
}
//don't show property if it's reference and user
//don't have permissions to view it's entity class
if (propertyType == MetaProperty.Type.COMPOSITION
|| propertyType == MetaProperty.Type.ASSOCIATION) {
MetaClass propertyMetaClass = propertyMetaClass(property);
if (!readPermitted(propertyMetaClass))
continue;
}
MetaClassRepProperty prop = new MetaClassRepProperty(property);
result.add(prop);
}
return result;
}
public static class MetaClassRepProperty {
private MetaProperty property;
public MetaClassRepProperty(MetaProperty property) {
this.property = property;
}
public String getTableName() {
Column column = property.getAnnotatedElement().getAnnotation(Column.class);
if (column != null)
return column.name();
JoinColumn joinColumn = property.getAnnotatedElement().getAnnotation(JoinColumn.class);
return joinColumn != null ? joinColumn.name() : "";
}
public String getName() {
return property.getName();
}
public String getDescription() {
String result = AppBeans.get(MessageTools.class).getPropertyCaption(property);
return result == null ? "" : result;
}
public String getEnum() {
return property.getRange().isEnum() ? "(enum)" : "";
}
public String getJavaType() {
String type = property.getJavaType().getName();
String simpleName = property.getJavaType().getSimpleName();
return type.startsWith("java.lang.") && ("java.lang.".length() + simpleName.length() == type.length()) ?
simpleName :
property.getRange().isClass() ?
asHref(property.getRange().asClass()) :
type;
}
public String getCardinality() {
switch (property.getRange().getCardinality()) {
case ONE_TO_ONE:
return property.getRange().isClass() ? "1:1" : "";
case ONE_TO_MANY:
return "1:N";
case MANY_TO_ONE:
return "N:1";
case MANY_TO_MANY:
return "N:N";
default:
return property.getRange().getCardinality().toString();
}
}
public String getOrdered() {
return property.getRange().isOrdered() ? "Ordered" : "";
}
public String getMandatory() {
return property.isMandatory() ? "Mandatory" : "Optional";
}
public String getReadOnly() {
return property.isReadOnly() ? "Read Only" : "Read/Write";
}
public Collection<String> getAnnotations() {
Collection<String> result = new ArrayList<String>();
Map<String, Object> map = property.getAnnotations();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String annotationName = entry.getKey();
if ("length".equals(annotationName) && !String.class.equals(property.getJavaType()))
continue;
result.add(annotationName + ": " + entry.getValue());
}
return result;
}
public boolean isPersistent() {
return AppBeans.get(MetadataTools.class).isPersistent(property);
}
}
public Collection<MetaClassRepView> getViews() {
if (views == null)
return null;
Collection<MetaClassRepView> result = new ArrayList<MetaClassRepView>();
for (View view : views) {
if (!viewAccessPermitted(view))
continue;
result.add(new MetaClassRepView(view));
}
return result;
}
private static boolean viewAccessPermitted(View view) {
Class clazz = view.getEntityClass();
MetaClass meta = getMetaClass(clazz);
return MetaClassRepresentation.readPermitted(meta);
}
private static MetaClass getMetaClass(Class clazz) {
return AppBeans.get(Metadata.class).getSession().getClass(clazz);
}
private static boolean viewPropertyReadPermitted(MetaClass meta, ViewProperty viewProperty) {
if (!attrViewPermitted(meta, viewProperty.getName()))
return false;
MetaProperty metaProperty = meta.getProperty(viewProperty.getName());
if (metaProperty.getType() == MetaProperty.Type.DATATYPE
|| metaProperty.getType() == MetaProperty.Type.ENUM)
return true;
MetaClass propertyMeta = metaProperty.getRange().asClass();
return readPermitted(propertyMeta);
}
public static class MetaClassRepView {
private View view;
public MetaClassRepView(View view) {
this.view = view;
}
public String getName() {
return view.getName();
}
public Collection<MetaClassRepViewProperty> getProperties() {
Collection<MetaClassRepViewProperty> result = new ArrayList<MetaClassRepViewProperty>();
MetaClass meta = getMetaClass(view.getEntityClass());
for (ViewProperty property : view.getProperties()) {
if (!MetaClassRepresentation.viewPropertyReadPermitted(meta, property))
continue;
result.add(new MetaClassRepViewProperty(property));
}
return result;
}
}
public static class MetaClassRepViewProperty {
private ViewProperty property;
public MetaClassRepViewProperty(ViewProperty property) {
this.property = property;
}
public String getName() {
return property.getName();
}
public String getLazy() {
return property.isLazy() ? "LAZY" : "";
}
public MetaClassRepView getView() {
return property.getView() == null ? null : new MetaClassRepView(property.getView());
}
}
private MetaClass propertyMetaClass(MetaProperty property) {
return property.getRange().asClass();
}
private static boolean attrViewPermitted(MetaClass metaClass, String property) {
return attrPermitted(metaClass, property, EntityAttrAccess.VIEW);
}
private static boolean attrPermitted(MetaClass metaClass, String property, EntityAttrAccess entityAttrAccess) {
UserSession session = AppBeans.get(UserSessionSource.class).getUserSession();
return session.isEntityAttrPermitted(metaClass, property, entityAttrAccess);
}
private static boolean readPermitted(MetaClass metaClass) {
return entityOpPermitted(metaClass, EntityOp.READ);
}
private static boolean entityOpPermitted(MetaClass metaClass, EntityOp entityOp) {
UserSession session = AppBeans.get(UserSessionSource.class).getUserSession();
return session.isEntityOpPermitted(metaClass, entityOp);
}
private static String asHref(MetaClass metaClass) {
return "<a href=\"#" + metaClass.getName() + "\">" + metaClass.getName() + "</a>";
}
}

View File

@ -0,0 +1,120 @@
<!--
~ Copyright (c) 2011 Haulmont Technology Ltd. All Rights Reserved.
~ Haulmont Technology proprietary and confidential.
~ Use is subject to license terms.
-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<title>Data model description</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
</head>
<style>
h1{
font-size: 1.75em;
}
table {
background-color:#eee;
}
table td {
padding: 4px;
background-color:white;
vertical-align: top;
}
table .propertyName{
font-weight: bold;
padding-top: 10px;
}
</style>
<#macro printView view>
<ul>
<#list view.properties as property>
<li>${property.name} ${property.lazy}</li>
<#if property.view ??>
<@printView view = property.view/>
</#if>
</#list>
</ul>
</#macro>
<body style="margin: 40px;">
<h1>Domain model description</h1>
<h2>Available basic types:</h2>
<ul>
<#list availableTypes as type>
<li>${type}</li>
</#list>
</ul>
<h2>Known entities:</h2>
<ul>
<#list knownEntities as entity>
<li><a href="#${entity.name}">${entity.name} - ${entity.description}</a></li>
</#list>
</ul>
<#list knownEntities as entity>
<a name="${entity.name}"></a>
<h2>${entity.name}</h2>
<p>Table: ${entity.tableName}</p>
<#if entity.parent ??>
${entity.parent}
</#if>
<p>${entity.description}</p>
<h3>Fields</h3>
<table border="1" bordercolor="lightgray" cellspacing="0" cellpadding="0" width="90%">
<col width="15%">
<col width="20%">
<col width="15%">
<col width="25%">
<col width="25%">
<tr>
<th>Property</th>
<th>Column</th>
<th>Type</th>
<th>Description</th>
<th>Cardinality</th>
<th>Misc</th>
</tr>
<#list entity.properties as property>
<#if property.persistent>
<tr>
<td class="propertyName">${property.name}</td>
<td>${property.tableName}</td>
<td>${property.javaType} ${property.enum}</td>
<td>${property.description}</td>
<td><i>${property.cardinality} ${property.ordered} ${property.mandatory}. ${property.readOnly}.</i></td>
<td>
<#list property.annotations as ann>
${ann}<br>
</#list>
</td>
</tr>
</#if>
</#list>
</table>
<#if entity.views ??>
<h3>Views</h3>
<ul>
<#list entity.views as aview>
<li><b>${aview.name}</b></li>
<@printView view=aview/>
</#list>
</ul>
</#if>
<p>&nbsp;</p>
</#list>
</body>
</html>