/**
* $Id: EntityDescriptionManager.java 113499 2012-09-25 01:13:56Z azeckoski@unicon.net $
* $URL: https://source.sakaiproject.org/svn/entitybroker/trunk/rest/src/java/org/sakaiproject/entitybroker/rest/EntityDescriptionManager.java $
* EntityDescriptionManager.java - entity-broker - Jul 22, 2008 12:18:48 PM - azeckoski
**************************************************************************
* Copyright (c) 2008, 2009 The Sakai Foundation
*
* Licensed under the Educational Community 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.opensource.org/licenses/ECL-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.sakaiproject.entitybroker.rest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Map.Entry;
import org.sakaiproject.entitybroker.EntityBrokerManager;
import org.sakaiproject.entitybroker.EntityReference;
import org.sakaiproject.entitybroker.EntityView;
import org.sakaiproject.entitybroker.access.AccessFormats;
import org.sakaiproject.entitybroker.access.EntityViewAccessProvider;
import org.sakaiproject.entitybroker.access.EntityViewAccessProviderManager;
import org.sakaiproject.entitybroker.access.HttpServletAccessProvider;
import org.sakaiproject.entitybroker.access.HttpServletAccessProviderManager;
import org.sakaiproject.entitybroker.entityprovider.CoreEntityProvider;
import org.sakaiproject.entitybroker.entityprovider.EntityProvider;
import org.sakaiproject.entitybroker.entityprovider.EntityProviderManager;
import org.sakaiproject.entitybroker.entityprovider.EntityProviderMethodStore;
import org.sakaiproject.entitybroker.entityprovider.annotations.EntityFieldRequired;
import org.sakaiproject.entitybroker.entityprovider.capabilities.CollectionResolvable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Createable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Deleteable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.DescribeDefineable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.DescribePropertiesable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Inputable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Outputable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Resolvable;
import org.sakaiproject.entitybroker.entityprovider.capabilities.Updateable;
import org.sakaiproject.entitybroker.entityprovider.extension.CustomAction;
import org.sakaiproject.entitybroker.entityprovider.extension.Formats;
import org.sakaiproject.entitybroker.entityprovider.extension.URLRedirect;
import org.sakaiproject.entitybroker.providers.EntityPropertiesService;
import org.sakaiproject.entitybroker.providers.EntityRequestHandler;
import org.sakaiproject.entitybroker.util.TemplateParseUtil;
import org.azeckoski.reflectutils.ArrayUtils;
import org.azeckoski.reflectutils.ClassFields;
import org.azeckoski.reflectutils.ConstructorUtils;
import org.azeckoski.reflectutils.ReflectUtils;
import org.azeckoski.reflectutils.ClassFields.FieldsFilter;
/**
* This handles all the methods related to generating descriptions for entities,
* html and xml currently supported
*
* @author Aaron Zeckoski (azeckoski @ gmail.com)
*/
@SuppressWarnings("deprecation")
public class EntityDescriptionManager {
private static final String INPUT_DESCRIBE_KEY = "input";
private static final String OUTPUT_DESCRIBE_KEY = "output";
private static final String VIEW_KEY_PREFIX = "view.";
private static final String FIELD_KEY_PREFIX = "field.";
private static final String REDIRECT_KEY_PREFIX = "redirect.";
protected static final String ACTION_KEY_PREFIX = "action.";
protected static String DESCRIBE = EntityRequestHandler.DESCRIBE;
protected static String SLASH_DESCRIBE = EntityRequestHandler.SLASH_DESCRIBE;
protected static String FAKE_ID = EntityRequestHandler.FAKE_ID;
protected static final String XML_HEADER = "<?xml version=\"1.0\" encoding=\"UTF-8\" ?>\n";
protected static final String XHTML_HEADER = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" " +
"\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\">\n" +
"<head>\n" +
" <meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\" />\n" +
" <title>Describe Entities</title>\n" +
"</head>\n" +
"<body>\n";
// include versions info in the footer now
protected static final String XHTML_FOOTER = "<br/>\n<div style='width:100%;text-align:center;font-style:italic;font-size:0.9em;'>"
+ "REST:: <b>" + EntityHandlerImpl.APP_VERSION + "</b> SVN: " + EntityHandlerImpl.SVN_REVISION + " : " + EntityHandlerImpl.SVN_LAST_UPDATE
+ "</div>\n"
+ "</body>\n</html>\n";
protected EntityDescriptionManager() { }
/**
* Full constructor
* @param entityViewAccessProviderManager
* @param httpServletAccessProviderManager
* @param entityProviderManager
* @param entityProperties
* @param entityBrokerManager
* @param entityProviderMethodStore
*/
public EntityDescriptionManager(
EntityViewAccessProviderManager entityViewAccessProviderManager,
HttpServletAccessProviderManager httpServletAccessProviderManager,
EntityProviderManager entityProviderManager, EntityPropertiesService entityProperties,
EntityBrokerManager entityBrokerManager,
EntityProviderMethodStore entityProviderMethodStore) {
super();
this.entityViewAccessProviderManager = entityViewAccessProviderManager;
this.httpServletAccessProviderManager = httpServletAccessProviderManager;
this.entityProviderManager = entityProviderManager;
this.entityProperties = entityProperties;
this.entityBrokerManager = entityBrokerManager;
this.entityProviderMethodStore = entityProviderMethodStore;
init();
}
private EntityProvider describeEP = null;
public void init() {
System.out.println("INFO: EntityDescriptionManager: init()");
// register the describe prefixes to load up descriptions
describeEP = new DescribePropertiesable() {
public String getEntityPrefix() {
return EntityRequestHandler.DESCRIBE;
}
public String getBaseName() {
return getEntityPrefix();
}
public ClassLoader getResourceClassLoader() {
return EntityDescriptionManager.class.getClassLoader();
}
};
entityProviderManager.registerEntityProvider(describeEP);
}
public void destroy() {
System.out.println("INFO: EntityDescriptionManager: destroy()");
// NOTE: do not try to unregister describe
// if (describeEP != null) {
// try {
// entityProviderManager.unregisterEntityProvider(describeEP);
// } catch (RuntimeException e) {
// System.out.println("WARN: EntityDescriptionManager: Unable to unregister the describe description provider: " + e);
// }
// }
}
private EntityViewAccessProviderManager entityViewAccessProviderManager;
public void setEntityViewAccessProviderManager(
EntityViewAccessProviderManager entityViewAccessProviderManager) {
this.entityViewAccessProviderManager = entityViewAccessProviderManager;
}
private HttpServletAccessProviderManager httpServletAccessProviderManager;
public void setHttpServletAccessProviderManager(
HttpServletAccessProviderManager httpServletAccessProviderManager) {
this.httpServletAccessProviderManager = httpServletAccessProviderManager;
}
private EntityProviderManager entityProviderManager;
public void setEntityProviderManager(EntityProviderManager entityProviderManager) {
this.entityProviderManager = entityProviderManager;
}
private EntityPropertiesService entityProperties;
public void setEntityProperties(EntityPropertiesService entityProperties) {
this.entityProperties = entityProperties;
}
private EntityBrokerManager entityBrokerManager;
public void setEntityBrokerManager(EntityBrokerManager entityBrokerManager) {
this.entityBrokerManager = entityBrokerManager;
}
private EntityProviderMethodStore entityProviderMethodStore;
public void setEntityProviderMethodStore(EntityProviderMethodStore entityProviderMethodStore) {
this.entityProviderMethodStore = entityProviderMethodStore;
}
/**
* Generate a description of all entities in the system,
* this is only available as XML and XHTML
*
* @param format XML or HTML (default is HTML)
* @param locale the locale to use for any translations
* @return the description string for all known entities
*/
public String makeDescribeAll(String format, Locale locale) {
if (locale == null) {
locale = entityProperties.getLocale();
}
Map<String, List<Class<? extends EntityProvider>>> map = entityProviderManager.getRegisteredEntityCapabilities();
// take out the "describe" EP if it is in there
map.remove(DESCRIBE);
// now get to creating the descriptions
String describeURL = entityBrokerManager.getServletContext() + SLASH_DESCRIBE;
String output = "";
if (Formats.XML.equals(format)) {
// XML available in case someone wants to parse this in javascript or whatever
StringBuilder sb = new StringBuilder(200);
sb.append(XML_HEADER);
sb.append("<describe>\n");
sb.append(" <describeURL>" + describeURL + "</describeURL>\n");
sb.append( makeXMLVersion() );
sb.append(" <prefixes>\n");
ArrayList<String> prefixes = new ArrayList<String>(map.keySet());
Collections.sort(prefixes);
for (int i = 0; i < prefixes.size(); i++) {
String prefix = prefixes.get(i);
describeEntity(sb, prefix, FAKE_ID, format, false, map.get(prefix), locale);
}
sb.append(" </prefixes>\n");
sb.append("</describe>\n");
output = sb.toString();
} else {
// just do HTML if not one of the handled ones
StringBuilder sb = new StringBuilder(300);
sb.append(XML_HEADER);
sb.append(XHTML_HEADER);
sb.append("<h1><a href='"+ describeURL +"'>"+entityProperties.getProperty(DESCRIBE, "describe.all", locale)+"</a> "
+ entityProperties.getProperty(DESCRIBE, "describe.registered.entities", locale)
+ makeFormatUrlHtml(describeURL, Formats.XML) +"</h1>\n");
sb.append(" <i>RESTful URLs: <a href='http://microformats.org/wiki/rest/urls'>http://microformats.org/wiki/rest/urls</a></i><br/>\n");
sb.append(" <h2>"+entityProperties.getProperty(DESCRIBE, "describe.all", locale)+" ("
+entityProperties.getProperty(DESCRIBE, "describe.registered.entities", locale)+"): "
+map.size()+"</h2>\n");
sb.append(" <div style='font-style:italic;padding-bottom:0.5em;'>"+entityProperties.getProperty(DESCRIBE, "describe.general.notes", locale)+"</div>\n"); // notes
sb.append(" <div style='font-style:italic;'>"+entityProperties.getProperty(DESCRIBE, "describe.searching", locale)+"</div>\n"); // searching
ArrayList<String> prefixes = new ArrayList<String>(map.keySet());
Collections.sort(prefixes);
for (int i = 0; i < prefixes.size(); i++) {
String prefix = prefixes.get(i);
describeEntity(sb, prefix, FAKE_ID, format, false, map.get(prefix), locale);
}
sb.append(XHTML_FOOTER);
output = sb.toString();
}
return output;
}
/**
* @return the XML tags string that contains the XML version info
*/
private String makeXMLVersion() {
StringBuilder sb = new StringBuilder();
sb.append(" <version>" + EntityHandlerImpl.APP_VERSION + "</version>\n");
sb.append(" <svn>\n");
sb.append(" <revision>"+EntityHandlerImpl.SVN_REVISION+"</revision>\n");
sb.append(" <last-update>"+EntityHandlerImpl.SVN_LAST_UPDATE+"</last-update>\n");
sb.append(" </svn>\n");
return sb.toString();
}
/**
* Generate a description of an entity type
*
* @param prefix an entity prefix
* @param id the entity id to use for generating URLs
* @param format a format to output, HTML and XML supported
* @param locale the locale to use for translations
* @return the description string
* @throws IllegalArgumentException if the entity does not exist
*/
public String makeDescribeEntity(String prefix, String id, String format, Locale locale) {
if (locale == null) {
locale = entityProperties.getLocale();
}
if (entityProviderManager.getProviderByPrefix(prefix) == null) {
throw new IllegalArgumentException("Invalid prefix ("+prefix+"), entity with that prefix does not exist");
}
StringBuilder sb = new StringBuilder(250);
if (Formats.XML.equals(format)) {
sb.append(XML_HEADER);
describeEntity(sb, prefix, id, format, true, null, locale);
} else {
// just do HTML if not one of the handled ones
sb.append(XML_HEADER);
sb.append(XHTML_HEADER);
describeEntity(sb, prefix, id, format, true, null, locale);
sb.append(XHTML_FOOTER);
}
return sb.toString();
}
/**
* This is reducing code duplication
* @param sb
* @param prefix
* @param id
* @param format
* @param extra
* @param caps
* @param locale used for translations
* @return the entity description
*/
@SuppressWarnings("rawtypes")
protected String describeEntity(StringBuilder sb, String prefix, String id, String format, boolean extra, List<Class<? extends EntityProvider>> caps, Locale locale) {
if (caps == null) {
caps = entityProviderManager.getPrefixCapabilities(prefix);
}
if (locale == null) {
locale = entityProperties.getLocale();
}
String servletUrl = entityBrokerManager.getServletContext();
if (Formats.XML.equals(format)) {
// XML available in case someone wants to parse this in javascript or whatever
String describePrefixUrl = servletUrl + "/" + prefix + SLASH_DESCRIBE;
sb.append(" <prefix>\n");
sb.append(" <prefix>" + prefix + "</prefix>\n");
sb.append(" <describeURL>" + describePrefixUrl + "</describeURL>\n");
String summary = getEntityDescription(prefix, null, locale);
if (summary != null) {
sb.append(" <summary>" + summary + "</summary>\n");
}
String description = getEntityDescription(prefix, "description", locale);
if (description != null) {
sb.append(" <description>" + description + "</description>\n");
}
if (extra) {
// URLs
EntityView ev = entityBrokerManager.makeEntityView(new EntityReference(prefix, id), null, null);
if (caps.contains(CollectionResolvable.class)) {
sb.append(" <collectionURL>" + ev.getEntityURL(EntityView.VIEW_LIST, null) + "</collectionURL>\n");
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_LIST, locale);
if (viewDesc != null) {
sb.append(" <collectionDescription>"+viewDesc+"</collectionDescription>\n");
}
}
if (caps.contains(Createable.class)) {
sb.append(" <createURL>" + ev.getEntityURL(EntityView.VIEW_NEW, null) + "</createURL>\n");
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_NEW, locale);
if (viewDesc != null) {
sb.append(" <createDescription>"+viewDesc+"</createDescription>\n");
}
}
if (caps.contains(CoreEntityProvider.class) || caps.contains(Resolvable.class)) {
sb.append(" <showURL>" + ev.getEntityURL(EntityView.VIEW_SHOW, null) + "</showURL>\n");
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_SHOW, locale);
if (viewDesc != null) {
sb.append(" <showDescription>"+viewDesc+"</showDescription>\n");
}
}
if (caps.contains(Updateable.class)) {
sb.append(" <updateURL>" + ev.getEntityURL(EntityView.VIEW_EDIT, null) + "</updateURL>\n");
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_EDIT, locale);
if (viewDesc != null) {
sb.append(" <updateDescription>"+viewDesc+"</updateDescription>\n");
}
}
if (caps.contains(Deleteable.class)) {
sb.append(" <deleteURL>" + ev.getEntityURL(EntityView.VIEW_DELETE, null) + "</deleteURL>\n");
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_DELETE, locale);
if (viewDesc != null) {
sb.append(" <deleteDescription>"+viewDesc+"</deleteDescription>\n");
}
}
// Custom Actions
List<CustomAction> customActions = entityProviderMethodStore.getCustomActions(prefix);
if (! customActions.isEmpty()) {
for (CustomAction customAction : customActions) {
sb.append(" <customActions>\n");
sb.append(" <customAction>\n");
sb.append(" <action>"+customAction.action+"</action>\n");
sb.append(" <url>"+servletUrl+makeActionURL(ev, customAction)+"</url>\n");
if (customAction.viewKey == null || "".equals(customAction.viewKey)) {
sb.append(" <method/>\n");
sb.append(" <viewKey/>\n");
} else {
sb.append(" <method>"+EntityView.translateViewKeyToMethod(customAction.viewKey)+"</method>\n");
sb.append(" <viewKey>"+customAction.viewKey+"</viewKey>\n");
}
String actionDesc = getEntityDescription(prefix, ACTION_KEY_PREFIX + customAction.action, locale);
if (actionDesc != null) {
sb.append(" <description>"+actionDesc+"</description>\n");
}
sb.append(" </customAction>\n");
sb.append(" </customActions>\n");
}
}
// Data and request handling
// Formats
String[] outputFormats = getFormats(prefix, true);
sb.append(" <outputFormats>\n");
if (outputFormats != null) {
if (outputFormats.length == 0) {
sb.append(" <format>*</format>\n");
} else {
for (int i = 0; i < outputFormats.length; i++) {
sb.append(" <format>"+outputFormats[i]+"</format>\n");
}
}
}
String outputDesc = getEntityDescription(prefix, OUTPUT_DESCRIBE_KEY, locale);
if (outputDesc != null) {
sb.append(" <description>"+outputDesc+"</description>\n");
}
sb.append(" </outputFormats>\n");
String[] inputFormats = getFormats(prefix, false);
sb.append(" <inputFormats>\n");
if (inputFormats != null) {
if (inputFormats.length == 0) {
sb.append(" <format>*</format>\n");
} else {
for (int i = 0; i < inputFormats.length; i++) {
sb.append(" <format>"+inputFormats[i]+"</format>\n");
}
}
}
String intputDesc = getEntityDescription(prefix, INPUT_DESCRIBE_KEY, locale);
if (intputDesc != null) {
sb.append(" <description>"+intputDesc+"</description>\n");
}
sb.append(" </inputFormats>\n");
EntityViewAccessProvider evap = entityViewAccessProviderManager.getProvider(prefix);
// httpServletAccessProviderManager is deprecated and can be null
HttpServletAccessProvider hsap = httpServletAccessProviderManager == null ? null : httpServletAccessProviderManager.getProvider(prefix);
if (evap != null || hsap != null) {
sb.append(" <accessProvider>\n");
if (evap != null) {
sb.append(" <type>" + EntityViewAccessProvider.class.getSimpleName() + "</type>\n");
sb.append(" <implementor>" + evap.getClass().getName() + "</implementor>\n");
if (AccessFormats.class.isAssignableFrom(evap.getClass())) {
String[] accessFormats = ((AccessFormats)evap).getHandledAccessFormats();
sb.append(" <accessFormats>\n");
if (accessFormats != null) {
if (accessFormats.length == 0) {
sb.append(" <format>*</format>\n");
} else {
for (int i = 0; i < accessFormats.length; i++) {
sb.append(" <format>"+accessFormats[i]+"</format>\n");
}
}
}
sb.append(" </accessFormats>\n");
}
} else {
sb.append(" <type>" + HttpServletAccessProvider.class.getSimpleName() + "</type>\n");
sb.append(" <implementor>" + hsap.getClass().getName() + "</implementor>\n");
}
sb.append(" </accessProvider>\n");
}
// Resolvable Entity Info
Object entity = entityBrokerManager.getSampleEntityObject(prefix, null);
if (entity != null) {
Class<?> entityType = entity.getClass();
sb.append(" <entityClass>\n");
sb.append(" <class>"+ entityType.getName() +"</class>\n");
if (ConstructorUtils.isClassSimple(entityType)) {
sb.append(" <type>simple</type>\n");
} else if (ConstructorUtils.isClassCollection(entityType)) {
sb.append(" <type>collection</type>\n");
} else if (ConstructorUtils.isClassArray(entityType)) {
sb.append(" <type>array</type>\n");
sb.append(" <componentType>"+ArrayUtils.type((Object[])entity).getName()+"</componentType>\n");
} else if (ConstructorUtils.isClassMap(entityType)) {
sb.append(" <type>map</type>\n");
// get the types of the map keys if possible
Map m = (Map) entity;
if (m.size() > 0) {
Entry entry = (Entry) m.entrySet().iterator().next();
sb.append(" <keyType>"+(entry.getKey()==null?Object.class.getName():entry.getKey().getClass().getName())+"</keyType>\n");
sb.append(" <valueType>"+(entry.getValue()==null?Object.class.getName():entry.getValue().getClass().getName())+"</valueType>\n");
}
} else {
sb.append(" <type>bean</type>\n");
sb.append(" <fields>\n");
// get all the read and write fields from this object
Map<String, Class<?>> readTypes = ReflectUtils.getInstance().getFieldTypes(entity.getClass(), FieldsFilter.SERIALIZABLE);
Map<String, Class<?>> writeTypes = ReflectUtils.getInstance().getFieldTypes(entity.getClass(), FieldsFilter.WRITEABLE);
Map<String, Class<?>> entityTypes = new HashMap<String, Class<?>>(readTypes);
entityTypes.putAll(writeTypes);
ArrayList<String> keys = new ArrayList<String>(entityTypes.keySet());
Collections.sort(keys);
for (String key : keys) {
Class<?> type = entityTypes.get(key);
sb.append(" <field>\n");
sb.append(" <name>"+ key +"</name>\n");
sb.append(" <type>"+ type.getName() +"</type>\n");
sb.append(" <readable>"+ readTypes.containsKey(key) +"</readable>\n");
sb.append(" <writeable>"+ writeTypes.containsKey(key) +"</writeable>\n");
String fieldDesc = getEntityDescription(prefix, FIELD_KEY_PREFIX + key, locale);
if (fieldDesc != null) {
sb.append(" <description>"+ fieldDesc +"</description>\n");
}
sb.append(" </field>\n");
}
sb.append(" </fields>\n");
}
sb.append(" </entityClass>\n");
}
// Redirects
List<URLRedirect> redirects = entityProviderMethodStore.getURLRedirects(prefix);
if (! redirects.isEmpty()) {
sb.append(" <redirects>\n");
for (int i = 0; i < redirects.size(); i++) {
URLRedirect redirect = redirects.get(i);
sb.append(" <redirect>\n");
sb.append(" <template>"+redirect.template+"</template>\n");
if (redirect.outgoingTemplate != null) {
sb.append(" <outgoingTemplate>"+redirect.outgoingTemplate+"</outgoingTemplate>\n");
}
if (redirect.methodName != null) {
sb.append(" <methodName>"+redirect.methodName+"</methodName>\n");
}
String redirectDesc = getEntityDescription(prefix, REDIRECT_KEY_PREFIX + redirect.template, locale);
if (redirectDesc != null) {
sb.append(" <description>"+redirectDesc+"</description>\n");
}
sb.append(" <controllable>"+redirect.controllable+"</controllable>\n");
sb.append(" <order>"+i+"</order>\n");
sb.append(" </redirect>\n");
}
sb.append(" </redirects>\n");
}
}
// now capabilities
sb.append(" <capabilities>\n");
for (Class<? extends EntityProvider> class1 : caps) {
sb.append(" <capability>\n");
sb.append(" <name>"+class1.getSimpleName()+"</name>\n");
sb.append(" <type>"+class1.getName()+"</type>\n");
if (extra) {
String capabilityDescription = getEntityDescription(prefix, class1.getSimpleName(), locale);
if (capabilityDescription != null) {
sb.append(" <description>" + capabilityDescription + "</description>\n");
}
}
sb.append(" </capability>\n");
}
sb.append(" </capabilities>\n");
sb.append(" </prefix>\n");
} else {
// just do HTML if not one of the handled ones
String describePrefixUrl = servletUrl + "/" + prefix + SLASH_DESCRIBE;
sb.append(" <h3 style='margin-bottom: 0.3em;'><a href='"+describePrefixUrl+"'>"+prefix+"</a>"
+ makeFormatUrlHtml(describePrefixUrl, Formats.XML) +"</h3>\n");
String summary = getEntityDescription(prefix, null, locale);
if (summary != null) {
sb.append(" <div style='padding-left:0.5em; padding-bottom:0.2em; width:90%;'>" + summary + "</div>\n");
}
if (extra) {
String description = getEntityDescription(prefix, "description", locale);
if (description != null) {
sb.append(" <div style='padding-left:1em; padding-bottom:0.4em; width:90%; font-size:0.9em;'>" + description + "</div>\n");
}
sb.append(" <div style='font-style: italic; padding-left:1em;'>"
+ "RESTful URLs: <a href='http://microformats.org/wiki/rest/urls'>http://microformats.org/wiki/rest/urls</a></div>\n");
sb.append(" <div style='font-style: italic; padding-left:2em; font-size:0.9em;'>" + entityProperties.getProperty(DESCRIBE, "describe.response.codes", locale) + "</div>\n");
String[] outputFormats = getFormats(prefix, true);
// URLs
EntityView ev = entityBrokerManager.makeEntityView(new EntityReference(prefix, id), null, null);
String url = "";
if (caps.contains(CollectionResolvable.class)
|| caps.contains(Createable.class)
|| caps.contains(CoreEntityProvider.class)
|| caps.contains(Resolvable.class)
|| caps.contains(Updateable.class)
|| caps.contains(Deleteable.class)) {
sb.append(" <h4 style='padding-left:0.5em;margin-bottom:0.2em;'>"+entityProperties.getProperty(DESCRIBE, "describe.entity.sample.urls", locale)
+" (_id='"+id+"') ["
+entityProperties.getProperty(DESCRIBE, "describe.entity.may.be.invalid", locale)+"]:</h4>\n");
sb.append(" <div style='padding-left:1em;padding-bottom:1em;'>\n");
if (caps.contains(CollectionResolvable.class)) {
url = makeEntityURL(ev, EntityView.VIEW_LIST);
sb.append(" <div>\n");
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.collection.url", locale)
+": GET <a href='"+ servletUrl+url +"'>"+url+"</a>"
+ makeFormatsUrlHtml(servletUrl+url, outputFormats)
+ "</div>\n");
sb.append( generateMethodDetails(EntityView.VIEW_LIST, locale) );
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_LIST, locale);
if (viewDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:2em;'>"+viewDesc+"</div>\n");
}
sb.append(" </div>\n");
}
if (caps.contains(Createable.class)) {
url = makeEntityURL(ev, EntityView.VIEW_NEW);
sb.append(" <div>\n");
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.create.url", locale)
+": POST <a href='"+ servletUrl+url +"'>"+url+"</a>"
+ makeFormUrlHtml(servletUrl+url, outputFormats)
+"</div>\n");
sb.append( generateMethodDetails(EntityView.VIEW_NEW, locale) );
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_NEW, locale);
if (viewDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:2em;'>"+viewDesc+"</div>\n");
}
sb.append(" </div>\n");
}
if (caps.contains(CoreEntityProvider.class) || caps.contains(Resolvable.class)) {
url = makeEntityURL(ev, EntityView.VIEW_SHOW);
sb.append(" <div>\n");
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.show.url", locale)
+": GET <a href='"+ servletUrl+url +"'>"+url+"</a>"
+ makeFormatsUrlHtml(servletUrl+url, outputFormats)
+ "</div>\n");
sb.append( generateMethodDetails(EntityView.VIEW_SHOW, locale) );
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_SHOW, locale);
if (viewDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:2em;'>"+viewDesc+"</div>\n");
}
sb.append(" </div>\n");
}
if (caps.contains(Updateable.class)) {
url = makeEntityURL(ev, EntityView.VIEW_EDIT);
sb.append(" <div>\n");
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.update.url", locale)
+": PUT <a href='"+ servletUrl+url +"'>"+url+"</a>"
+ makeFormUrlHtml(servletUrl+url, outputFormats)
+"</div>\n");
sb.append( generateMethodDetails(EntityView.VIEW_EDIT, locale) );
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_EDIT, locale);
if (viewDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:2em;'>"+viewDesc+"</div>\n");
}
sb.append(" </div>\n");
}
if (caps.contains(Deleteable.class)) {
url = makeEntityURL(ev, EntityView.VIEW_DELETE);
sb.append(" <div>\n");
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.delete.url", locale)
+": DELETE <a href='"+ servletUrl+url +"'>"+url+"</a>"
+ makeFormUrlHtml(servletUrl+url, outputFormats)
+"</div>\n");
sb.append( generateMethodDetails(EntityView.VIEW_DELETE, locale) );
String viewDesc = getEntityDescription(prefix, VIEW_KEY_PREFIX + EntityView.VIEW_DELETE, locale);
if (viewDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:2em;'>"+viewDesc+"</div>\n");
}
sb.append(" </div>\n");
}
sb.append(" </div>\n");
}
// Custom Actions
List<CustomAction> customActions = entityProviderMethodStore.getCustomActions(prefix);
if (! customActions.isEmpty()) {
sb.append(" <h4 style='padding-left:0.5em;margin-bottom:0.2em;'>"+entityProperties.getProperty(DESCRIBE, "describe.custom.actions", locale)+"</h4>\n");
sb.append(" <div style='padding-left:1em;padding-bottom:1em;'>\n");
for (CustomAction customAction : customActions) {
sb.append(" <div>\n");
String actionURL = makeActionURL(ev, customAction);
String formatsHtml = "";
if (customAction.viewKey == null
|| EntityView.VIEW_LIST.equals(customAction.viewKey)
|| EntityView.VIEW_SHOW.equals(customAction.viewKey)) {
formatsHtml = makeFormatsUrlHtml(servletUrl+actionURL, outputFormats);
}
sb.append(" <a style='font-weight:bold;' href='"+servletUrl+actionURL+"'>"
+ customAction.action+"</a> : "
+ "<span>"+makeCustomActionKeyMethodText(customAction)+"</span> : "
+ "<span>["+actionURL+"]</span> "
+ formatsHtml
+ "<br/>\n");
String actionDesc = getEntityDescription(prefix, ACTION_KEY_PREFIX + customAction.action, locale);
if (actionDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:1.5em;'>"+actionDesc+"</div>\n");
}
sb.append(" </div>\n");
}
sb.append(" </div>\n");
}
// Redirects
List<URLRedirect> redirects = entityProviderMethodStore.getURLRedirects(prefix);
if (! redirects.isEmpty()) {
sb.append(" <h4 style='padding-left:0.5em;margin-bottom:0.2em;'>"+entityProperties.getProperty(DESCRIBE, "describe.url.redirects", locale)+"</h4>\n");
sb.append(" <div style='padding-left:1em;padding-bottom:1em;'>\n");
for (int i = 0; i < redirects.size(); i++) {
URLRedirect redirect = redirects.get(i);
sb.append(" <div>\n");
String target = replacePrefix(redirect.outgoingTemplate, prefix);
if (target == null) {
target = "<i>" + entityProperties.getProperty(DESCRIBE, "describe.url.redirects.no.outgoing", locale) + "</i>";
}
sb.append(" <span>"+(i+1)+")</span> "
+ makeRedirectLink(replacePrefix(redirect.template, prefix), servletUrl)
+ " ==> <span>"+target+"</span><br/>\n");
String redirectDesc = getEntityDescription(prefix, REDIRECT_KEY_PREFIX + redirect.template, locale);
if (redirectDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:1.5em;'>"+redirectDesc+"</div>\n");
}
sb.append(" </div>\n");
}
sb.append(" </div>\n");
}
// Resolvable Entity Info
Object entity = entityBrokerManager.getSampleEntityObject(prefix, null);
if (entity != null) {
Class<?> entityType = entity.getClass();
sb.append(" <h4 style='padding-left:0.5em;margin-bottom:0.2em;'>"+entityProperties.getProperty(DESCRIBE, "describe.entity.class", locale)+" : "+ entityType.getName() +"</h4>\n");
sb.append(" <div style='padding-left:1em;padding-bottom:1em;'>\n");
if (ConstructorUtils.isClassSimple(entityType)) {
sb.append( makeResolveType("simple", null, locale));
} else if (ConstructorUtils.isClassCollection(entityType)) {
sb.append( makeResolveType("collection", null, locale));
} else if (ConstructorUtils.isClassArray(entityType)) {
String cType = "Component Class: " + ArrayUtils.type((Object[])entity).getName();
sb.append( makeResolveType("array", cType, locale));
} else if (ConstructorUtils.isClassMap(entityType)) {
// get the types of the map keys if possible
String mapTypes = null;
Map m = (Map) entity;
if (m.size() > 0) {
Entry entry = (Entry) m.entrySet().iterator().next();
mapTypes = (entry.getKey()==null?Object.class.getName():entry.getKey().getClass().getName())
+" => "
+(entry.getValue()==null?Object.class.getName():entry.getValue().getClass().getName());
}
sb.append( makeResolveType("map", mapTypes, locale));
} else {
sb.append( makeResolveType("bean", null, locale));
sb.append(" <table width='80%' cellpadding='0' cellspacing='0'>\n");
sb.append(" <thead>\n");
sb.append(" <tr>\n");
sb.append(" <td width='1%'></td>\n");
sb.append(" <td>"+ entityProperties.getProperty(DESCRIBE, "describe.capabilities.name", locale) +"</td>\n");
sb.append(" <td>"+ entityProperties.getProperty(DESCRIBE, "describe.capabilities.type", locale) +"</td>\n");
sb.append(" <td>"+ entityProperties.getProperty(DESCRIBE, "describe.entity.field.status", locale) +"</td>\n");
sb.append(" </tr>\n");
sb.append(" </thead>\n");
sb.append(" <tbody>\n");
// get all the read and write fields from this object
ClassFields<?> cf = ReflectUtils.getInstance().analyzeClass(entity.getClass());
Map<String, Class<?>> readTypes = cf.getFieldTypes(FieldsFilter.SERIALIZABLE);
Map<String, Class<?>> writeTypes = cf.getFieldTypes(FieldsFilter.WRITEABLE);
HashSet<String> requiredFieldNames = new HashSet<String>(cf.getFieldNamesWithAnnotation(EntityFieldRequired.class));
Map<String, Class<?>> entityTypes = new HashMap<String, Class<?>>(readTypes);
entityTypes.putAll(writeTypes);
ArrayList<String> keys = new ArrayList<String>(entityTypes.keySet());
Collections.sort(keys);
for (int i = 0; i < keys.size(); i++) {
String fieldName = keys.get(i);
Class<?> type = entityTypes.get(fieldName);
String status = null;
String trStyle = "";
if (! readTypes.containsKey(fieldName)) {
// write only
status = entityProperties.getProperty(DESCRIBE, "describe.entity.field.write.only", locale);
trStyle = " style='color:blue;'";
} else if (! writeTypes.containsKey(fieldName)) {
// read only
status = entityProperties.getProperty(DESCRIBE, "describe.entity.field.read.only", locale);
trStyle = " style='color:red;'";
} else {
// read/write
status = entityProperties.getProperty(DESCRIBE, "describe.entity.field.read.write", locale);
}
boolean required = requiredFieldNames.contains(fieldName);
if (required) {
status = status + " <b style='color:red;'>* " + entityProperties.getProperty(DESCRIBE, "describe.entity.field.required", locale) + "</b>";
}
// get the printable type names
String typeName = type.getName();
if (String.class.getName().equals(typeName)) {
typeName = "string";
} else if (Boolean.class.getName().equals(typeName)) {
typeName = "boolean";
} else if (Integer.class.getName().equals(typeName)) {
typeName = "int";
} else if (Long.class.getName().equals(typeName)) {
typeName = "long";
}
sb.append(" <tr"+trStyle+"><td>"+(i+1)+") </td>"
+ "<td style='font-weight:bold;'>"+ fieldName +"</td>"
+ "<td>"+ typeName +"</td>"
+ "<td style='font-style:italic;'>"+status+"</td></tr>\n");
String fieldDesc = getEntityDescription(prefix, FIELD_KEY_PREFIX + fieldName, locale);
if (fieldDesc != null) {
sb.append(" <tr><td></td><td colspan='3' style='font-style:italic;font-size:0.9em;'>");
sb.append(fieldDesc);
sb.append("</td></tr>\n");
}
}
sb.append(" </tbody>\n");
sb.append(" </table>\n");
}
sb.append(" </div>\n");
}
// Data Handling
sb.append(" <h4 style='padding-left:0.5em;margin-bottom:0.2em;'>"+entityProperties.getProperty(DESCRIBE, "describe.entity.data.handling", locale)+"</h4>\n");
sb.append(" <div style='padding-left:1em;padding-bottom:1em;'>\n");
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.formats.output", locale)+" : "
+ makeFormatsString(outputFormats, EntityEncodingManager.HANDLED_OUTPUT_FORMATS, locale) +"</div>\n");
String outputDesc = getEntityDescription(prefix, OUTPUT_DESCRIBE_KEY, locale);
if (outputDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:1.5em;'>"+outputDesc+"</div>\n");
}
String[] inputFormats = getFormats(prefix, false);
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.formats.input", locale)+" : "
+ makeFormatsString(inputFormats, EntityEncodingManager.HANDLED_INPUT_FORMATS, locale) +"</div>\n");
String intputDesc = getEntityDescription(prefix, INPUT_DESCRIBE_KEY, locale);
if (intputDesc != null) {
sb.append(" <div style='font-style:italic;font-size:0.9em;padding-left:1.5em;'>"+intputDesc+"</div>\n");
}
EntityViewAccessProvider evap = entityViewAccessProviderManager.getProvider(prefix);
if (evap != null) {
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.data.access.provider", locale)+" : "+ EntityViewAccessProvider.class.getSimpleName() +"</div>\n");
if (AccessFormats.class.isAssignableFrom(evap.getClass())) {
String[] accessFormats = ((AccessFormats)evap).getHandledAccessFormats();
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.formats.access", locale)+" : "
+ makeFormatsString(accessFormats, null, locale) +"</div>\n");
}
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.data.access.provider", locale)+" : "+ EntityViewAccessProvider.class.getSimpleName() +"</div>\n");
} else if (httpServletAccessProviderManager != null
&& httpServletAccessProviderManager.getProvider(prefix) != null) {
sb.append(" <div>"+entityProperties.getProperty(DESCRIBE, "describe.entity.data.access.provider", locale)+" : "+ HttpServletAccessProvider.class.getSimpleName() +"</div>\n");
} else {
sb.append(" <div style='font-style:italic;padding-left:1em'>"+entityProperties.getProperty(DESCRIBE, "describe.entity.data.access.provider.none", locale) +"</div>\n");
}
sb.append(" </div>\n");
}
// Capabilities
if (extra) {
sb.append(" <h4 style='padding-left:0.5em;margin-bottom:0.2em;'>"
+entityProperties.getProperty(DESCRIBE, "describe.capabilities", locale)+"</h4>\n");
sb.append(" <table width='95%' style='padding-left:1.5em;'>\n");
sb.append(" <tr style='font-size:0.9em;'><th width='1%'></th><th width='14%'>"
+entityProperties.getProperty(DESCRIBE, "describe.capabilities.name", locale)
+"</th><th width='30%'>"
+entityProperties.getProperty(DESCRIBE, "describe.capabilities.type", locale)
+"</th>");
if (extra) { sb.append("<th width='55%'>"
+entityProperties.getProperty(DESCRIBE, "describe.capabilities.description", locale)
+"</th>"); }
sb.append("</tr>\n");
int counter = 1;
for (Class<? extends EntityProvider> class1 : caps) {
sb.append(" <tr style='font-size:0.9em;'><td>");
sb.append(counter++);
sb.append("</td><td>");
sb.append(class1.getSimpleName());
sb.append("</td><td>");
sb.append(class1.getName());
sb.append("</td><td>");
if (extra) {
String capabilityDescription = getEntityDescription(prefix, class1.getSimpleName(), locale);
if (capabilityDescription != null) {
sb.append(capabilityDescription);
}
}
sb.append("</td></tr>\n");
}
sb.append(" </table>\n");
}
}
return sb.toString();
}
/**
* Generates the details listing which shows the response types for a view method
* @param methodType the view method (new, show, list, delete, edit)
* @param locale the locale
* @return the html string to place on the description page
*/
protected String generateMethodDetails(String methodType, Locale locale) {
return " <div style='font-style:italic;font-size:0.9em;padding-left:1.5em;'>"
+ entityProperties.getProperty(DESCRIBE, "describe.details.header", locale)
+ " "
+ entityProperties.getProperty(DESCRIBE, "describe.entity."+methodType+".details", locale)
+ "</div>\n";
}
/**
* Replaces the {prefix} value in the template with the actual prefix,
* allows nulls to pass through
*/
protected String replacePrefix(String outgoingTemplate, String prefix) {
if (outgoingTemplate != null) {
outgoingTemplate = outgoingTemplate.replace(TemplateParseUtil.PREFIX_VARIABLE, prefix);
}
return outgoingTemplate;
}
/**
* Turn a redirect template into html for a link if it has no variables in it,
* otherwise output the text of the template in a span with bold
*/
protected String makeRedirectLink(String redirect, String prefixURL) {
String html = redirect;
if (redirect.indexOf("{") > 0 && redirect.indexOf("}") > 0) {
html = "<span style='font-weight:bold;'>"+redirect+"</span>";
} else {
html = "<a style='font-weight:bold;' href='"+prefixURL+redirect+"'>"+redirect+"</a>";
}
return html;
}
// DESCRIBE formatting utilities
protected String makeResolveType(String typeName, String extra, Locale locale) {
StringBuilder sb = new StringBuilder();
sb.append(" <div><b>");
sb.append( entityProperties.getProperty(DESCRIBE, "describe.capabilities.type", locale) );
sb.append(" :: ");
sb.append(typeName);
sb.append("</b>");
if (extra != null) {
sb.append(" (");
sb.append(extra);
sb.append(") ");
}
sb.append("</div>\n");
return sb.toString();
}
/**
* @param ev the entity view
* @param customAction the custom action
* @return a URL for triggering the custom action (without http://server/direct)
*/
protected String makeActionURL(EntityView ev, CustomAction customAction) {
// switched to this since it is more correct
String URL = EntityView.SEPARATOR + ev.getEntityReference().getPrefix() + EntityView.SEPARATOR + customAction.action;
String viewKey = customAction.viewKey;
if (viewKey != null
&& (EntityView.VIEW_SHOW.equals(viewKey) || EntityView.VIEW_EDIT.equals(viewKey) || EntityView.VIEW_DELETE.equals(viewKey))) {
URL = ev.getEntityURL(EntityView.VIEW_SHOW, null) + EntityView.SEPARATOR + customAction.action;
}
return URL;
}
/**
* @param ev entity view
* @param viewType the type of view
* @return a URL for triggering the entity action (without http://server/direct)
*/
protected String makeEntityURL(EntityView ev, String viewType) {
if (viewType == null) {
viewType = EntityView.VIEW_LIST;
}
if (! EntityView.VIEW_LIST.equals(viewType) && ! EntityView.VIEW_NEW.equals(viewType)) {
viewType = EntityView.VIEW_SHOW;
} else {
viewType = EntityView.VIEW_LIST;
}
return ev.getEntityURL(viewType, null);
}
/**
* Create text to display for the CA key and method
* @param customAction
* @return
*/
protected String makeCustomActionKeyMethodText(CustomAction customAction) {
String togo = "*";
if (customAction.viewKey != null && ! "".equals(customAction.viewKey)) {
togo = customAction.viewKey+" ("+EntityView.translateViewKeyToMethod(customAction.viewKey)+")";
}
return togo;
}
/**
* @return all the format extensions handled by this, null if none handled, empty if all
*/
protected String[] getFormats(String prefix, boolean output) {
String[] formats = null;
try {
if (output) {
formats = entityProviderManager.getProviderByPrefixAndCapability(prefix, Outputable.class).getHandledOutputFormats();
} else {
formats = entityProviderManager.getProviderByPrefixAndCapability(prefix, Inputable.class).getHandledInputFormats();
if (formats != null) {
// strip out the FORM element if it was included
if (ArrayUtils.contains(formats, Formats.FORM)) {
ArrayList<String> l = new ArrayList<String>();
for (String format : formats) {
if (! Formats.FORM.equals(format)) {
l.add(format);
}
}
formats = l.toArray(new String[l.size()]);
}
}
}
} catch (NullPointerException e) {
formats = null;
}
EntityViewAccessProvider evap = entityViewAccessProviderManager.getProvider(prefix);
if (evap != null) {
if (AccessFormats.class.isAssignableFrom(evap.getClass())) {
String[] accessFormats = ((AccessFormats)evap).getHandledAccessFormats();
if (accessFormats != null) {
if (accessFormats.length > 0) {
if (formats == null) {
formats = accessFormats;
} else {
for (int i = 0; i < accessFormats.length; i++) {
if (! ReflectUtils.contains(formats, accessFormats[i])) {
ReflectUtils.appendArray(formats, accessFormats[i]);
}
}
}
}
}
}
}
return formats;
}
protected String makeFormatsUrlHtml(String url, String[] formats) {
StringBuilder sb = new StringBuilder();
if (formats != null) {
for (String format : formats) {
sb.append( makeFormatUrlHtml(url, format) );
}
}
return sb.toString();
}
protected String makeFormatsString(String[] formats, String[] extraFormats, Locale locale) {
String s = "";
if (locale == null) {
locale = entityProperties.getLocale();
}
if (formats == null) {
s = "<i>"+entityProperties.getProperty(DESCRIBE, "describe.entity.formats.none", locale)+"</i>";
} else if (formats.length == 0) {
// all
String all = "*";
if (extraFormats != null && extraFormats.length > 0) {
all = makeArrayIntoString(formats) + ",*";
}
s = "<i>" + entityProperties.getProperty(DESCRIBE, "describe.entity.formats.all", locale) + " (" + all + ")</i>";
} else {
s = makeArrayIntoString( formats );
}
return s;
}
protected String makeFormUrlHtml(String url, String[] formats) {
String form = "";
if (formats != null) {
if (ArrayUtils.contains(formats, Formats.FORM)) {
form = makeFormatUrlHtml(url, Formats.FORM);
}
}
return form;
}
protected String makeFormatUrlHtml(String url, String format) {
return " (<a href='"+url+"."+format+"'>"+format+"</a>)";
}
protected String makeArrayIntoString(Object[] array) {
StringBuilder result = new StringBuilder();
if (array != null && array.length > 0) {
for (int i = 0; i < array.length; i++) {
if (i > 0) {
result.append(", ");
}
if (array[i] != null) {
result.append(array[i].toString());
}
}
}
return result.toString();
}
/**
* Get the descriptions for an entity OR its capabilities OR custom actions
* @param prefix an entity prefix
* @param descriptionkey (optional) the key (simplename for capability, action.actionkey for actions)
* @param locale the Locale to use for translations
* @return the description (may be blank) OR null if there is none
*/
protected String getEntityDescription(String prefix, String descriptionkey, Locale locale) {
String value = null;
if (locale == null) {
locale = entityProperties.getLocale();
}
// get from EP first if possible
DescribeDefineable describer = entityProviderManager.getProviderByPrefixAndCapability(prefix, DescribeDefineable.class);
if (describer != null) {
value = describer.getDescription(locale, descriptionkey);
}
// now from the default location if null
if (value == null) {
String key = prefix;
if (descriptionkey != null) {
// try simple name first
key += "." + descriptionkey;
}
value = entityProperties.getProperty(prefix, key, locale);
}
if ("".equals(value)) {
value = null;
}
return value;
}
}