/* * This software is distributed under the terms of the FSF * Gnu Lesser General Public License (see lgpl.txt). * * This program is distributed WITHOUT ANY WARRANTY. See the * GNU General Public License for more details. */ package com.scooterframework.web.util; import java.util.HashMap; import java.util.Map; import com.scooterframework.admin.Constants; import com.scooterframework.admin.EnvConfig; import com.scooterframework.common.util.CurrentThreadCacheClient; import com.scooterframework.common.util.WordUtil; import com.scooterframework.orm.activerecord.ActiveRecord; import com.scooterframework.orm.activerecord.ActiveRecordUtil; import com.scooterframework.orm.sqldataexpress.object.RESTified; import com.scooterframework.web.route.MatchMaker; import com.scooterframework.web.route.Resource; import com.scooterframework.web.route.Route; /** * <p>R(RestfulHelper) class has helper methods for routes and resources. </p> * * @author (Fei) John Chen */ public class R { private static String getRestfulId(Object rest) { if (rest == null) return null; String restfulId = null; if (rest instanceof String) { restfulId = (String)rest; } else if (rest instanceof RESTified) { restfulId = ((RESTified)rest).getRestfulId(); } else { throw new IllegalArgumentException("Only String or RESTified types " + "are allowed in getRestfulId method."); } return restfulId; } private static Map<String, String> getFieldValues() { return CurrentThreadCacheClient.fieldValues(); } /** * Returns resource. * * @param resourceName name of the resource * @return resource */ public static Resource resource(String resourceName) { Resource resource = MatchMaker.getInstance().getResource(resourceName); if (resource == null) { //throw new IllegalArgumentException("\"" + resourceName + "\"" + // " is not defined as a resource."); resource = new Resource(resourceName, true); } return resource; } /** * Returns resource corresponding to a model name. * * @param model model name * @return resource */ public static Resource resourceForModel(String model) { Resource resource = MatchMaker.getInstance().getResourceForModel(model); if (resource == null) { //throw new IllegalArgumentException("\"" + model + "\"" + // " is not defined as a model name for a resource."); resource = new Resource(model, true); } return resource; } /** * <p>Returns resource path based on resource name. </p> * * <p>For example, for resource <tt>"posts"</tt>, the resource path may be * <tt>"/posts"</tt>.</p> * * @param resourceName name of the resource * @return resource path */ public static String resourcePath(String resourceName) { return _resourcePath(resourceName, getFieldValues()); } /** * <p>Returns a resource path. If the <tt>record</tt>'s RESTful id is * empty, an empty string is returned.</p> * * <p>For example, for record <tt>"Post"</tt> with <tt>post.id=1</tt>, the * resource path may be <tt>"/posts/1"</tt>.</p> * * @param resourceName resource name * @param record a restified record * @return resource path */ public static String resourceRecordPath(String resourceName, RESTified record) { return resourceRecordPath(resourceName, getRestfulId(record)); } /** * <p>Returns a resource path for a specific resource. If the * <tt>resourceId</tt> is empty, an empty string is returned.</p> * * <p>For example, for record <tt>"Post"</tt> with <tt>post.id=1</tt>, the * resource path may be <tt>"/posts/1"</tt>.</p> * * @param resourceName resource name * @param resourceId a specific resource * @return resource path */ public static String resourceRecordPath(String resourceName, String resourceId) { return _resourceRecordPath(resourceName, resourceId, getFieldValues()); } private static String _resourcePath(String resourceName, Map<String, String> fieldValues) { return resource(resourceName).getScreenURL(fieldValues); } private static String _resourceRecordPath(String resourceName, String resourceId, Map<String, String> fieldValues) { if (M.isEmpty(resourceId)) return ""; Resource resource = resource(resourceName); String path = ""; if (resource.isSingle()) { path = resource.getScreenURL(fieldValues); } else { path = resource.getScreenURL(fieldValues) + "/" + resourceId; } return path; } private static String _addResourcePath(String pathToResource) { return pathToResource + "/add"; } /** * Returns resource path for adding a record. * * For example, for adding a new record <tt>"Post"</tt>, the resource path * may be <tt>"/posts/add"</tt>. * * @param resourceName name of the resource * @return resource path for adding a new record */ public static String addResourcePath(String resourceName) { return _addResourcePath(resourcePath(resourceName)); } private static String _editResourceRecordPath(String pathToResourceId) { return (M.isEmpty(pathToResourceId))?"":(pathToResourceId + "/edit"); } /** * <p>Returns resource path for editing a record. If the <tt>record</tt>'s * RESTful id is empty, an empty string is returned.</p> * * <p>For example, for editing a record <tt>"Post"</tt> with * <tt>post.id=1</tt>, the resource path may be <tt>"/posts/1/edit"</tt>.<p> * * @param resourceName resource name * @param record a restified record * @return resource path for editing a new record */ public static String editResourceRecordPath(String resourceName, RESTified record) { return _editResourceRecordPath(resourceRecordPath(resourceName, record)); } /** * <p>Returns resource path for editing a specific resource. If the * <tt>resourceId</tt> is empty, an empty string is returned.</p> * * <p>For example, for editing a record <tt>"Post"</tt> with <tt>post.id=1</tt>, * the resource path may be <tt>"/posts/1/edit"</tt>. * * @param resourceName resource name * @param resourceId a specific resource * @return resource path for editing a new record */ public static String editResourceRecordPath(String resourceName, String resourceId) { return _editResourceRecordPath(resourceRecordPath(resourceName, resourceId)); } private static String _formForAddResource(String pathTOResource, String resourceName) { Resource resource = resource(resourceName); String name = "add_" + resource.getModel(); String id = name; StringBuilder sb = new StringBuilder(); sb.append("<form action=\"").append(W.getURL(pathTOResource)); sb.append("\" class=\"").append(name).append("\" id=\"").append(id).append("\" method=\"POST\">"); return sb.toString(); } /** * Returns form element for a resource. * * @param resourceName name of the resource * @param restfulId a specific object in the resource * @return form element for a resource record */ public static String formForResource(String resourceName, String restfulId) { return (restfulId == null)? formForAddResource(resourceName):formForEditResourceRecord(resourceName, restfulId); } private static boolean isAddForm(RESTified record) { boolean addForm = false; if (record == null || record.getRestfulId() == null || (record instanceof ActiveRecord && (((ActiveRecord)record).isNewRecord() || ((ActiveRecord)record).isHomeInstance())) ) { addForm = true; } return addForm; } /** * Returns form element for a resource. * * @param resourceName name of the resource * @param record a restified record * @return form element for a resource record */ public static String formForResource(String resourceName, RESTified record) { return (isAddForm(record))?formForAddResource(resourceName):formForEditResourceRecord(resourceName, record); } /** * Returns form element for a nested resource. * * @param parentResourceName name of parent resource * @param parentRecord parent restified record * @param resourceName name of the resource * @param record a restified record * @return form element for a resource record */ public static String formForNestedResourceRecord(String parentResourceName, RESTified parentRecord, String resourceName, RESTified record) { return (isAddForm(record))? formForAddNestedResource(parentResourceName, parentRecord, resourceName): formForEditNestedResourceRecord(parentResourceName, parentRecord, resourceName, record); } /** * Returns form element for a nested resource. * * @param parentResourceName name of parent resource * @param parentRestfulId parent restful id * @param resourceName name of the resource * @param record a restified record * @return form element for a resource record */ public static String formForNestedResourceRecord(String parentResourceName, String parentRestfulId, String resourceName, RESTified record) { return (isAddForm(record))? formForAddNestedResource(parentResourceName, parentRestfulId, resourceName): formForEditNestedResourceRecord(parentResourceName, parentRestfulId, resourceName, record); } /** * Returns form element for a nested resource. <tt>parentResourceNames</tt> * is an array of ancestors. <tt>parentRestfuls</tt> is an array of either * restful id strings or RESTified records of ancestors. * * @param parentResourceNames names of parent resources * @param parentRestfuls parent restfuls * @param resourceName name of the resource * @param record a restified record * @return form element for a resource record */ public static String formForNestedResourceRecord(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, RESTified record) { return (isAddForm(record))? formForAddNestedResource(parentResourceNames, parentRestfuls, resourceName): formForEditNestedResourceRecord(parentResourceNames, parentRestfuls, resourceName, record); } /** * Returns form element for a nested resource. * * @param parentResourceName name of parent resource * @param parentRecord parent restified record * @param resourceName name of the resource * @param restfulId restful id * @return form element for a resource record */ public static String formForNestedResourceRecord(String parentResourceName, RESTified parentRecord, String resourceName, String restfulId) { return (restfulId == null)? formForAddNestedResource(parentResourceName, parentRecord, resourceName): formForEditNestedResourceRecord(parentResourceName, parentRecord, resourceName, restfulId); } /** * Returns form element for a nested resource. * * @param parentResourceName name of parent resource * @param parentRestfulId parent restful id * @param resourceName name of the resource * @param restfulId restful id * @return form element for a resource record */ public static String formForNestedResourceRecord(String parentResourceName, String parentRestfulId, String resourceName, String restfulId) { return (restfulId == null)? formForAddNestedResource(parentResourceName, parentRestfulId, resourceName): formForEditNestedResourceRecord(parentResourceName, parentRestfulId, resourceName, restfulId); } /** * Returns form element for a nested resource. <tt>parentResourceNames</tt> * is an array of ancestors. <tt>parentRestfuls</tt> is an array of either * restful id strings or RESTified records of ancestors. * * @param parentResourceNames names of parent resources * @param parentRestfuls parent restfuls * @param resourceName name of the resource * @param restfulId restful id * @return form element for a resource record */ public static String formForNestedResourceRecord(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, String restfulId) { return (restfulId == null)? formForAddNestedResource(parentResourceNames, parentRestfuls, resourceName): formForEditNestedResourceRecord(parentResourceNames, parentRestfuls, resourceName, restfulId); } /** * Returns form element for adding a resource record. * * @param resourceName name of the resource * @return form element for adding a resource record */ public static String formForAddResource(String resourceName) { return _formForAddResource(resourcePath(resourceName), resourceName); } /** * Returns form element for editing a resource record. * * @param resourceName resource name * @param record a restified record * @return form element for editing a resource record */ public static String formForEditResourceRecord(String resourceName, RESTified record) { return formForEditResourceRecord(resourceName, getRestfulId(record)); } private static String _formForEditResourceRecord(String pathToResourceId, String resourceName, String resourceId) { Resource resource = resource(resourceName); String name = "edit_" + resource.getModel(); String id = name + "_" + resourceId; StringBuilder sb = new StringBuilder(); sb.append("<form action=\"").append(W.getURL(pathToResourceId)); sb.append("\" class=\"").append(name).append("\" id=\"").append(id).append("\" method=\"POST\">").append("\n"); sb.append("<div style=\"margin:0;padding:0\"><input name=\""); sb.append(Constants.HTTP_METHOD).append("\" type=\"hidden\" value=\"PUT\" /></div>"); return sb.toString(); } /** * Returns form element for editing a specific resource. * * @param resourceName resource name * @param resourceId a specific resource * @return form element for editing a resource record */ public static String formForEditResourceRecord(String resourceName, String resourceId) { return _formForEditResourceRecord(resourceRecordPath(resourceName, resourceId), resourceName, resourceId); } private static Resource _parentResource(String parentResourceName) { Resource resource = MatchMaker.getInstance().getResource(parentResourceName); if (resource == null) { resource = new Resource(parentResourceName, true); } return resource; } private static String getParentResourceIdFormat(String parentResourceName) { return Resource.getNesteeIdFormat(_parentResource(parentResourceName)); } /** * Merges content of <tt>mapTmp</tt> to <tt>mapToKeep</tt>. * * @param mapToKeep * @param mapTmp */ private static void mergeMap(Map<String, String> mapToKeep, Map<String, String> mapTmp) { for(Map.Entry<String, String> entry : mapTmp.entrySet()) { String value = entry.getValue(); if (value != null) { mapToKeep.put(entry.getKey(), value); } } } private static String _nestedResourcePath(String resourceName, Map<String, String> parentsMap) { Map<String, String> map = getFieldValues(); mergeMap(map, parentsMap); return _resourcePath(resourceName, map); } private static String _nestedResourceRecordPath(String resourceName, String resourceId, Map<String, String> parentsMap) { Map<String, String> map = getFieldValues(); mergeMap(map, parentsMap); return _resourceRecordPath(resourceName, resourceId, map); } public static String nestedResourcePath(String parentResourceName, RESTified parentRecord, String resourceName) { return nestedResourcePath(parentResourceName, getRestfulId(parentRecord), resourceName); } public static String nestedResourcePath(String parentResourceName, String parentRestfulId, String resourceName) { String parentPath = resourceRecordPath(parentResourceName, parentRestfulId); if (!parentPath.startsWith("/")) parentPath = "/" + parentPath; Map<String, String> map = new HashMap<String, String>(); map.put(getParentResourceIdFormat(parentResourceName), parentRestfulId); String childrenPath = _nestedResourcePath(resourceName, map); if (!childrenPath.startsWith("/")) childrenPath = "/" + childrenPath; return (childrenPath.startsWith(parentPath))?childrenPath: parentPath + childrenPath; } public static String nestedResourcePath(String[] parentResourceNames, Object[] parentRestfuls, String resourceName) { if (parentResourceNames.length != parentRestfuls.length) throw new IllegalArgumentException("The length of parent resource " + "should be the equal to the length of parent resource ids."); int length = parentResourceNames.length; String parentsPath = ""; Map<String, String> map = new HashMap<String, String>(length); for (int i = 0; i < length; i++) { String parentResourceName = parentResourceNames[i]; String parentRestfulId = getRestfulId(parentRestfuls[i]); map.put(getParentResourceIdFormat(parentResourceName), parentRestfulId); String parentPath = _nestedResourceRecordPath(parentResourceName, parentRestfulId, map); if (!parentPath.startsWith("/")) parentPath = "/" + parentPath; parentsPath = (parentPath.startsWith(parentsPath))?parentPath:(parentsPath + parentPath); } String childrenPath = _nestedResourcePath(resourceName, map); if (!childrenPath.startsWith("/")) childrenPath = "/" + childrenPath; return (childrenPath.startsWith(parentsPath))?childrenPath: parentsPath + childrenPath; } public static String nestedResourceRecordPath(String parentResourceName, RESTified parentRecord, String resourceName, RESTified record) { return nestedResourceRecordPath(parentResourceName, getRestfulId(parentRecord), resourceName, getRestfulId(record)); } public static String nestedResourceRecordPath(String parentResourceName, String parentRestfulId, String resourceName, RESTified record) { return nestedResourceRecordPath(parentResourceName, parentRestfulId, resourceName, getRestfulId(record)); } public static String nestedResourceRecordPath(String parentResourceName, RESTified parentRecord, String resourceName, String resourceId) { return nestedResourceRecordPath(parentResourceName, getRestfulId(parentRecord), resourceName, resourceId); } public static String nestedResourceRecordPath(String parentResourceName, String parentRestfulId, String resourceName, String resourceId) { String parentPath = resourceRecordPath(parentResourceName, parentRestfulId); if (!parentPath.startsWith("/")) parentPath = "/" + parentPath; Map<String, String> map = new HashMap<String, String>(); map.put(getParentResourceIdFormat(parentResourceName), parentRestfulId); String childrenPath = _nestedResourceRecordPath(resourceName, resourceId, map); if (!childrenPath.startsWith("/")) childrenPath = "/" + childrenPath; return (childrenPath.startsWith(parentPath))?childrenPath: parentPath + childrenPath; } public static String nestedResourceRecordPath(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, RESTified record) { return nestedResourceRecordPath(parentResourceNames, parentRestfuls, resourceName, getRestfulId(record)); } public static String nestedResourceRecordPath(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, String resourceId) { if (parentResourceNames.length != parentRestfuls.length) throw new IllegalArgumentException("The length of parent resource " + "should be the equal to the length of parent resource ids."); int length = parentResourceNames.length; String parentsPath = ""; Map<String, String> map = new HashMap<String, String>(length); for (int i = 0; i < length; i++) { String parentResourceName = parentResourceNames[i]; String parentRestfulId = getRestfulId(parentRestfuls[i]); map.put(getParentResourceIdFormat(parentResourceName), parentRestfulId); String parentPath = _nestedResourceRecordPath(parentResourceName, parentRestfulId, map); if (!parentPath.startsWith("/")) parentPath = "/" + parentPath; parentsPath = (parentPath.startsWith(parentsPath))?parentPath:(parentsPath + parentPath); } String childrenPath = _nestedResourceRecordPath(resourceName, resourceId, map); if (!childrenPath.startsWith("/")) childrenPath = "/" + childrenPath; return (childrenPath.startsWith(parentsPath))?childrenPath: parentsPath + childrenPath; } public static String addNestedResourcePath(String parentResourceName, RESTified parentRecord, String resourceName) { return _addResourcePath(nestedResourcePath(parentResourceName, parentRecord, resourceName)); } public static String addNestedResourcePath(String parentResourceName, String parentRestfulId, String resourceName) { return _addResourcePath(nestedResourcePath(parentResourceName, parentRestfulId, resourceName)); } public static String addNestedResourcePath(String[] parentResourceNames, Object[] parentRestfuls, String resourceName) { return _addResourcePath(nestedResourcePath(parentResourceNames, parentRestfuls, resourceName)); } public static String editNestedResourceRecordPath(String parentResourceName, RESTified parentRecord, String resourceName, RESTified record) { return _editResourceRecordPath(nestedResourceRecordPath(parentResourceName, parentRecord, resourceName, record)); } public static String editNestedResourceRecordPath(String parentResourceName, RESTified parentRecord, String resourceName, String resourceId) { return _editResourceRecordPath(nestedResourceRecordPath(parentResourceName, parentRecord, resourceName, resourceId)); } public static String editNestedResourceRecordPath(String parentResourceName, String parentRestfulId, String resourceName, RESTified record) { return _editResourceRecordPath(nestedResourceRecordPath(parentResourceName, parentRestfulId, resourceName, record)); } public static String editNestedResourceRecordPath(String parentResourceName, String parentRestfulId, String resourceName, String resourceId) { return _editResourceRecordPath(nestedResourceRecordPath(parentResourceName, parentRestfulId, resourceName, resourceId)); } public static String editNestedResourceRecordPath(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, RESTified record) { return _editResourceRecordPath(nestedResourceRecordPath(parentResourceNames, parentRestfuls, resourceName, record)); } public static String editNestedResourceRecordPath(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, String resourceId) { return _editResourceRecordPath(nestedResourceRecordPath(parentResourceNames, parentRestfuls, resourceName, resourceId)); } public static String formForAddNestedResource(String parentResourceName, RESTified parentRecord, String resourceName) { return _formForAddResource(nestedResourcePath(parentResourceName, parentRecord, resourceName), resourceName); } public static String formForAddNestedResource(String parentResourceName, String parentRestfulId, String resourceName) { return _formForAddResource(nestedResourcePath(parentResourceName, parentRestfulId, resourceName), resourceName); } public static String formForAddNestedResource(String[] parentResourceNames, Object[] parentRestfuls, String resourceName) { return _formForAddResource(nestedResourcePath(parentResourceNames, parentRestfuls, resourceName), resourceName); } public static String formForEditNestedResourceRecord(String parentResourceName, RESTified parentRecord, String resourceName, RESTified record) { return _formForEditResourceRecord(nestedResourceRecordPath(parentResourceName, parentRecord, resourceName, record), resourceName, getRestfulId(record)); } public static String formForEditNestedResourceRecord(String parentResourceName, RESTified parentRecord, String resourceName, String resourceId) { return _formForEditResourceRecord(nestedResourceRecordPath(parentResourceName, parentRecord, resourceName, resourceId), resourceName, resourceId); } public static String formForEditNestedResourceRecord(String parentResourceName, String parentRestfulId, String resourceName, RESTified record) { return _formForEditResourceRecord(nestedResourceRecordPath(parentResourceName, parentRestfulId, resourceName, record), resourceName, getRestfulId(record)); } public static String formForEditNestedResourceRecord(String parentResourceName, String parentRestfulId, String resourceName, String resourceId) { return _formForEditResourceRecord(nestedResourceRecordPath(parentResourceName, parentRestfulId, resourceName, resourceId), resourceName, resourceId); } public static String formForEditNestedResourceRecord(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, RESTified record) { return _formForEditResourceRecord(nestedResourceRecordPath(parentResourceNames, parentRestfuls, resourceName, record), resourceName, getRestfulId(record)); } public static String formForEditNestedResourceRecord(String[] parentResourceNames, Object[] parentRestfuls, String resourceName, String resourceId) { return _formForEditResourceRecord(nestedResourceRecordPath(parentResourceNames, parentRestfuls, resourceName, resourceId), resourceName, resourceId); } /** * Returns route. * * @param routeName route name * @return route */ public static Route route(String routeName) { //validate the routeName Route route = MatchMaker.getInstance().getRoute(routeName); if (route == null) { throw new IllegalArgumentException("\"" + routeName + "\"" + " is not defined as a route."); } return route; } /** * Returns route path based on a route name. * * For example, for named route <tt>"login"</tt>, the route path may be <tt>"/login"</tt>. * * @param routeName route name * @return route path */ public static String routePath(String routeName) { return route(routeName).getScreenURL(getFieldValues()); } /** * <p>Returns route path based on a record instance. If the record's * RESTful id is empty, an empty string is returned.</p> * * <p>For example, for record <tt>"Post"</tt> with <tt>post.getRestfulId()=1</tt>, * the route path is <tt>"/posts/1"</tt>.</p> * * @param routeName route name * @param record a restified record * @return route path */ public static String routeRecordPath(String routeName, RESTified record) { String s = getRestfulId(record); if (M.isEmpty(s)) return ""; return routePath(routeName) + "/" + s; } /** * <p>Returns a label link on the <tt>columnName</tt> for <tt>columnValue</tt>.</p> * * <p>There are several restrictions of the use of this method. </p> * <p>First, the column name must be of the <tt>'{referencedModelName}_id'</tt> * format where the <tt>referencedModelName</tt> is the model name of the * referenced entity. Second, the primary key of the referenced model must * be <tt>id</tt>. </p> * * <p>If the above conditions are violated, the original * <tt>columnName</tt> is returned.</p> * * <pre> * Examples: * column value link * ------ ----- ---- * user_id 10 <a http="/blog/users/10">10</a> * </pre> * * <p>See method * {@link com.scooterframework.web.util.W#simpleForeignKeyRecordShowActionLink(String, String)} * for action (non resource) case.</p> * * @param columnName a column name ended with "_id" * @param columnValue the value on the column * @return a label link */ public static String simpleForeignKeyResourceRecordLink(String columnName, String columnValue) { if (columnName == null || !columnName.toLowerCase().endsWith("_id")) return columnName; String modelName = columnName.toLowerCase().substring(0, (columnName.length() - 3)); String modelClassName = EnvConfig.getInstance().getModelClassName(modelName); ActiveRecord foreignRecordHome = ActiveRecordUtil.getHomeInstance(modelClassName, modelName, ActiveRecordUtil.DEFAULT_RECORD_CLASS); String[] pkNames = foreignRecordHome.getPrimaryKeyNames(); if (pkNames == null || pkNames.length > 1 || !"id".equalsIgnoreCase(pkNames[0])) return columnValue; String resourceName = WordUtil.pluralize(modelName); return W.labelLink(columnValue, R.resourceRecordPath(resourceName, columnValue)); } }