Changeset 3606
- Timestamp:
- 11/02/08 13:53:31 (4 years ago)
- Files:
-
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityCollectionFinder.java (added)
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityFinder.java (modified) (5 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityHelper.java (modified) (1 diff)
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityInstanceFinder.java (copied) (copied from trunk/src/main/java/org/sarugo/restlet/jpa/EntityFinder.java) (3 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityRouter.java (modified) (17 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/ConverterHelper.java (modified) (1 diff)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/json/JSONConverter.java (modified) (1 diff)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/text/ReferenceListConverter.java (modified) (2 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityCollection.java (modified) (8 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityInstance.java (modified) (4 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityResource.java (modified) (1 diff)
- trunk/src/test/java/org/sarugo/restlet/jpa/BarEntity.java (added)
- trunk/src/test/java/org/sarugo/restlet/jpa/FooEntity.java (modified) (1 diff)
- trunk/src/test/java/org/sarugo/restlet/jpa/PersistenceResourceTests.java (deleted)
- trunk/src/test/java/org/sarugo/restlet/jpa/PersistenceRouterTest.java (deleted)
- trunk/src/test/java/org/sarugo/restlet/jpa/URLMappingTests.java (added)
- trunk/src/test/resources/META-INF/persistence.xml (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/src/main/java/org/sarugo/restlet/jpa/EntityFinder.java
r3577 r3606 1 /*2 * Copyright 2007, 2008 Sarugo Pty Ltd3 *4 * Licensed under the Common Development and Distribution License,5 * you may not use this file except in compliance with the License.6 * You may obtain a copy of the License at7 *8 * http://www.sun.com/cddl/9 *10 * Unless required by applicable law or agreed to in writing, software11 * distributed under the License is distributed on an "AS IS" BASIS,12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or13 * implied. See the License for the specific language governing14 * permissions and limitations under the License.15 */16 17 1 package org.sarugo.restlet.jpa; 18 2 19 import java.lang.reflect.Constructor;20 import java.lang.reflect.Field;21 import java.lang.reflect.InvocationTargetException;22 import java.lang.reflect.Method;23 3 import java.util.ArrayList; 24 import java.util.Collections;25 4 import java.util.List; 26 import java.util.logging.Level;27 28 import javax.persistence.EntityManager;29 import javax.persistence.Id;30 import javax.persistence.Query;31 5 32 6 import org.restlet.Finder; … … 35 9 import org.restlet.data.Request; 36 10 import org.restlet.data.Response; 37 import org.sarugo.restlet.jpa.converter.ConverterHelper;38 11 import org.sarugo.restlet.jpa.converter.InputConverter; 39 12 import org.sarugo.restlet.jpa.converter.OutputConverter; 40 import org.sarugo.restlet.jpa.resource.EntityCollection;41 13 import org.sarugo.restlet.jpa.resource.EntityResource; 42 14 43 /** 44 * An entity finder maps a request to either an {@link EntityResource} (for 45 * entity instances) or {@link EntityCollection} (for entity collections). 46 * Conversions for request processing are performed by 47 * {@link InputConverter input} and {@link OutputConverter output} converters. 48 * 49 * @author Michael Terrington 50 * 51 * @param <E> 52 * The entity type. 53 */ 54 public class EntityFinder<E> extends Finder { 55 56 private final Class<? extends EntityResource> resource; 15 public abstract class EntityFinder<E> extends Finder { 57 16 58 pr ivate final Class<E> entityClass;17 protected final Class<? extends EntityResource> resource; 59 18 60 pr ivate final Class<? extends Object> entityIdClass;19 protected final Class<E> entityClass; 61 20 62 pr ivatefinal EntityRouter router;21 protected final EntityRouter router; 63 22 64 pr ivatefinal List<InputConverter<E>> inputConverters;23 protected final List<InputConverter<E>> inputConverters; 65 24 66 pr ivatefinal List<OutputConverter<E>> outputConverters;25 protected final List<OutputConverter<E>> outputConverters; 67 26 68 pr ivateRoute route;27 protected Route route; 69 28 70 pr ivatefinal List<Route> aliases;29 protected final List<Route> aliases; 71 30 72 private int maxResults;73 74 /**75 * Creates an entity finder.76 *77 * @param router78 * The {@link EntityRouter} this finder will be be attached79 * to.80 * @param entityClass81 * The entity class this finder operates on.82 * @param resource83 * Either {@link EntityResource} or {@link EntityCollection} or a84 * sub-class. Sub-classes must have a constructor:85 * {@link EntityFinder}, {@link Request}, {@link Response}.86 */87 31 public EntityFinder(EntityRouter router, Class<E> entityClass, 88 32 Class<? extends EntityResource> resource) { … … 91 35 this.router = router; 92 36 this.entityClass = entityClass; 93 this.entityIdClass = EntityHelper.findIdType(entityClass);94 37 this.inputConverters = new ArrayList<InputConverter<E>>(); 95 38 this.outputConverters = new ArrayList<OutputConverter<E>>(); … … 111 54 * {@link EntityRouter}. 112 55 * 113 * @see EntityRouter#attach (String, EntityFinder)114 * @see EntityRouter#attachCollection(String, Entity Finder)56 * @see EntityRouter#attachInstance(String, EntityCollectionFinder) 57 * @see EntityRouter#attachCollection(String, EntityCollectionFinder) 115 58 */ 116 59 void setRoute(Route route) { … … 155 98 /** 156 99 * Constructs the associated resource target, invoking the 157 * {@link EntityFinder}, {@link Request}, {@link Response} constructor. 100 * {@link EntityCollectionFinder}, {@link Request}, {@link Response} 101 * constructor. 158 102 */ 159 103 @Override 160 104 protected Handler findTarget(Request request, Response response) { 161 try { 162 Constructor<? extends EntityResource> c = resource.getConstructor( 163 EntityFinder.class); 164 EntityResource resource = c.newInstance(this); 105 EntityResource resource = initResource(); 106 if (resource != null) { 165 107 resource.init(request, response); 166 return resource;167 } catch (SecurityException e) {168 getLogger().log(Level.WARNING, "Error constructing resource.", e);169 } catch (NoSuchMethodException e) {170 getLogger().log(Level.WARNING, "Error constructing resource.", e);171 } catch (IllegalArgumentException e) {172 getLogger().log(Level.WARNING, "Error constructing resource.", e);173 } catch (InstantiationException e) {174 getLogger().log(Level.WARNING, "Error constructing resource.", e);175 } catch (IllegalAccessException e) {176 getLogger().log(Level.WARNING, "Error constructing resource.", e);177 } catch (InvocationTargetException e) {178 getLogger().log(Level.WARNING, "Error constructing resource.", e);179 108 } 180 return null;109 return resource; 181 110 } 182 111 183 /** 184 * Convert an id string to an object suitable for passing to 185 * {@link EntityManager#find(Class, Object)}. For simple entity id's the 186 * default implementation should suffice. Entity's with compound identity 187 * objects should override this method. 188 * 189 * @param value 190 * The id string. 191 * @return The identity object. 192 */ 193 public Object convertIdString(String value) { 194 return ConverterHelper.convertToType(entityIdClass, value, null); 195 } 112 protected abstract EntityResource initResource(); 196 113 197 /**198 * Get the id string for a given entity. For simple entity id's the default199 * implementation should suffice. Entity's with compound identity objects200 * should override this method.201 *202 * @param entity203 * The entity.204 * @return The id string.205 */206 public String getIdString(E entity) {207 if (entity != null) {208 for (Field f : entity.getClass().getDeclaredFields()) {209 if (f.isAnnotationPresent(Id.class)) {210 f.setAccessible(true);211 try {212 return String.valueOf(f.get(entity));213 } catch (IllegalArgumentException e) {214 getLogger().log(Level.WARNING,215 "Unable to get Id for object", e);216 } catch (IllegalAccessException e) {217 getLogger().log(Level.WARNING,218 "Unable to get Id for object", e);219 }220 }221 }222 for (Method m : entity.getClass().getDeclaredMethods()) {223 if (m.isAnnotationPresent(Id.class)) {224 m.setAccessible(true);225 try {226 return String.valueOf(m.invoke(entity));227 } catch (IllegalArgumentException e) {228 getLogger().log(Level.WARNING,229 "Unable to get Id for object", e);230 } catch (IllegalAccessException e) {231 getLogger().log(Level.WARNING,232 "Unable to get Id for object", e);233 } catch (InvocationTargetException e) {234 getLogger().log(Level.WARNING,235 "Unable to get Id for object", e);236 }237 }238 }239 }240 return null;241 }242 243 /**244 * Get a the attached URL for a given entity. The default implementation245 * applies the URL template in the {@link #getRoute() attached route} with246 * parameter "id" and the result of {@link #getIdString(Object)}.247 *248 * @param entity249 * The entity.250 * @return The URL relative to the attached {@link EntityRouter}.251 */252 public String getEntityURL(E entity) {253 return route.getTemplate().format(254 Collections.singletonMap("id", (Object) getIdString(entity)));255 }256 257 /**258 * Get the entity identified by the request URL. By default this invokes259 * {@link #getEntity(String)} with id string retrieved from the request260 * parameter "id".261 */262 public E getEntity(Request request, Response response) {263 return getEntity((String) request.getAttributes().get("id"));264 }265 266 /**267 * Get the entity identified by the id string. Invokes268 * {@link EntityManager#find(Class, Object)} with the result of269 * {@link #convertIdString(String)}.270 *271 * @param id272 * The id string.273 * @return The entity or null if not found.274 */275 public E getEntity(String id) {276 E entity = null;277 if (id != null) {278 entity = router.getEntityManager().find(getEntityClass(),279 convertIdString(id));280 }281 return entity;282 }283 284 /**285 * Removes an entity from persistence.286 *287 * @param entity288 * The entity to remove.289 * @return true if the entity was removed.290 */291 public boolean removeEntity(E entity) {292 router.getEntityManager().remove(entity);293 return true;294 }295 296 /**297 * Get the query for the entity collection. If the request defines a298 * parameter "queryName", that is used to call299 * {@link #getEntityListQuery(String)}. Alternatively,300 * {@link #getEntityListQuery()} is called to get the default query.301 */302 public Query getEntityListQuery(Request request, Response response) {303 String queryName = (String) request.getAttributes().get("queryName");304 if (queryName != null) {305 return getEntityListQuery(queryName);306 } else {307 return getEntityListQuery();308 }309 }310 311 /** Get a named query. */312 protected Query getEntityListQuery(String queryName) {313 return router.getEntityManager().createNamedQuery(queryName);314 }315 316 /** Get the default query. */317 protected Query getEntityListQuery() {318 return router.getEntityManager().createQuery(319 "SELECT o from " + EntityHelper.getEntityName(getEntityClass())320 + " o");321 }322 323 public int getMaxResults() {324 return maxResults;325 }326 327 /**328 * Set the maximum results that should be returned from a query.329 * {@link EntityCollection} uses this to limit query results.330 */331 public EntityFinder<E> setMaxResults(int maxResults) {332 this.maxResults = maxResults;333 if (this.maxResults == Integer.MAX_VALUE) {334 this.maxResults--;335 }336 return this;337 }338 114 } trunk/src/main/java/org/sarugo/restlet/jpa/EntityHelper.java
r3563 r3606 65 65 * or classes without any id fields/methods the type is set to 66 66 * {@link String}. This makes the default 67 * {@link Entity Finder#convertIdString(String)} a no-op for compound or67 * {@link EntityInstanceFinder#convertIdString(String)} a no-op for compound or 68 68 * undefined keys. 69 69 * trunk/src/main/java/org/sarugo/restlet/jpa/EntityInstanceFinder.java
r3577 r3606 18 18 19 19 import java.lang.reflect.Constructor; 20 import java.lang.reflect.Field;21 20 import java.lang.reflect.InvocationTargetException; 22 import java.lang.reflect.Method; 23 import java.util.ArrayList; 24 import java.util.Collections; 25 import java.util.List; 21 import java.util.Map; 26 22 import java.util.logging.Level; 27 23 28 import javax.persistence.EntityManager; 29 import javax.persistence.Id; 30 import javax.persistence.Query; 31 32 import org.restlet.Finder; 33 import org.restlet.Handler; 34 import org.restlet.Route; 24 import org.restlet.data.Reference; 35 25 import org.restlet.data.Request; 36 26 import org.restlet.data.Response; 37 import org.sarugo.restlet.jpa.converter.ConverterHelper;38 27 import org.sarugo.restlet.jpa.converter.InputConverter; 39 28 import org.sarugo.restlet.jpa.converter.OutputConverter; 40 29 import org.sarugo.restlet.jpa.resource.EntityCollection; 30 import org.sarugo.restlet.jpa.resource.EntityInstance; 41 31 import org.sarugo.restlet.jpa.resource.EntityResource; 42 32 … … 52 42 * The entity type. 53 43 */ 54 public class EntityFinder<E> extends Finder { 55 56 private final Class<? extends EntityResource> resource; 57 58 private final Class<E> entityClass; 44 public class EntityInstanceFinder<E> extends EntityFinder<E> { 45 46 private final EntityRouter children; 59 47 60 48 private final Class<? extends Object> entityIdClass; 61 49 62 private final EntityRouter router; 63 64 private final List<InputConverter<E>> inputConverters; 65 66 private final List<OutputConverter<E>> outputConverters; 67 68 private Route route; 69 70 private final List<Route> aliases; 71 72 private int maxResults; 50 private String idAttribute; 73 51 74 52 /** … … 76 54 * 77 55 * @param router 78 * The {@link EntityRouter} this finder will be be attached 79 * to. 56 * The {@link EntityRouter} this finder will be be attached to. 80 57 * @param entityClass 81 58 * The entity class this finder operates on. 82 59 * @param resource 83 * Either {@link EntityResource} or {@link EntityCollection} or a 84 * sub-class. Sub-classes must have a constructor: 85 * {@link EntityFinder}, {@link Request}, {@link Response}. 86 */ 87 public EntityFinder(EntityRouter router, Class<E> entityClass, 88 Class<? extends EntityResource> resource) { 89 super(router.getContext()); 90 this.resource = resource; 91 this.router = router; 92 this.entityClass = entityClass; 60 * The entity instance resource. 61 */ 62 @SuppressWarnings("unchecked") 63 public EntityInstanceFinder(EntityRouter router, Class<E> entityClass, 64 Class<? extends EntityInstance> resource) { 65 super(router, entityClass, resource); 66 this.children = new EntityRouter(this); 67 this.idAttribute = "id"; 93 68 this.entityIdClass = EntityHelper.findIdType(entityClass); 94 this.inputConverters = new ArrayList<InputConverter<E>>(); 95 this.outputConverters = new ArrayList<OutputConverter<E>>(); 96 this.aliases = new ArrayList<Route>(); 97 } 98 99 /** Get the {@link EntityRouter} this finder is attached to. */ 100 public EntityRouter getRouter() { 101 return router; 102 } 103 104 /** Get the route this finder is attached to. */ 105 public Route getRoute() { 106 return route; 107 } 108 109 /** 110 * Attach this finder to a router. Should only be called by 111 * {@link EntityRouter}. 112 * 113 * @see EntityRouter#attach(String, EntityFinder) 114 * @see EntityRouter#attachCollection(String, EntityFinder) 115 */ 116 void setRoute(Route route) { 117 if (this.route != null) { 118 throw new IllegalStateException("Route already set."); 119 } 120 this.route = route; 121 } 122 123 /** Get a list of additional routes this finder is associated with. */ 124 public List<Route> getAliases() { 125 return aliases; 126 } 127 128 /** Get the entity class this finder is associated with. */ 129 public Class<E> getEntityClass() { 130 return entityClass; 131 } 132 133 /** Get the list of input converters. */ 134 public List<InputConverter<E>> getInputConverters() { 135 return inputConverters; 136 } 137 138 /** Add an input converter. Returns this finder to enable call chaining. */ 139 public EntityFinder<E> addInputConverter(InputConverter<E> inputConverter) { 140 inputConverters.add(inputConverter); 141 return this; 142 } 143 144 /** Get the list of output converters. */ 145 public List<OutputConverter<E>> getOutputConverters() { 146 return outputConverters; 147 } 148 149 /** Add an output converter. Returns this finder to enable call chaining. */ 150 public EntityFinder<E> addOutputConverter(OutputConverter<E> outputConverter) { 151 outputConverters.add(outputConverter); 152 return this; 153 } 154 155 /** 156 * Constructs the associated resource target, invoking the 157 * {@link EntityFinder}, {@link Request}, {@link Response} constructor. 158 */ 159 @Override 160 protected Handler findTarget(Request request, Response response) { 161 try { 162 Constructor<? extends EntityResource> c = resource.getConstructor( 163 EntityFinder.class); 164 EntityResource resource = c.newInstance(this); 165 resource.init(request, response); 166 return resource; 167 } catch (SecurityException e) { 168 getLogger().log(Level.WARNING, "Error constructing resource.", e); 169 } catch (NoSuchMethodException e) { 170 getLogger().log(Level.WARNING, "Error constructing resource.", e); 171 } catch (IllegalArgumentException e) { 172 getLogger().log(Level.WARNING, "Error constructing resource.", e); 173 } catch (InstantiationException e) { 174 getLogger().log(Level.WARNING, "Error constructing resource.", e); 175 } catch (IllegalAccessException e) { 176 getLogger().log(Level.WARNING, "Error constructing resource.", e); 177 } catch (InvocationTargetException e) { 178 getLogger().log(Level.WARNING, "Error constructing resource.", e); 179 } 180 return null; 181 } 182 183 /** 184 * Convert an id string to an object suitable for passing to 185 * {@link EntityManager#find(Class, Object)}. For simple entity id's the 186 * default implementation should suffice. Entity's with compound identity 187 * objects should override this method. 188 * 189 * @param value 190 * The id string. 191 * @return The identity object. 192 */ 193 public Object convertIdString(String value) { 194 return ConverterHelper.convertToType(entityIdClass, value, null); 195 } 196 197 /** 198 * Get the id string for a given entity. For simple entity id's the default 199 * implementation should suffice. Entity's with compound identity objects 200 * should override this method. 69 } 70 71 @SuppressWarnings("unchecked") 72 private EntityInstance<E> getHelper(E entity) { 73 EntityInstance<E> helper = null; 74 if (EntityInstance.class.isAssignableFrom(resource)) { 75 helper = (EntityInstance<E>) initResource(); 76 if (helper != null) { 77 helper.init(entity); 78 } 79 } 80 return helper; 81 } 82 83 /** 84 * Get a the attached URL for a given entity by applying the URL template in 85 * the {@link #getRoute() attached route} with the id map returned by 86 * {@link EntityURLHelper#getEntityId(Object)}. 201 87 * 202 88 * @param entity 203 89 * The entity. 204 * @return The id string. 205 */ 206 public String getIdString(E entity) { 207 if (entity != null) { 208 for (Field f : entity.getClass().getDeclaredFields()) { 209 if (f.isAnnotationPresent(Id.class)) { 210 f.setAccessible(true); 211 try { 212 return String.valueOf(f.get(entity)); 213 } catch (IllegalArgumentException e) { 214 getLogger().log(Level.WARNING, 215 "Unable to get Id for object", e); 216 } catch (IllegalAccessException e) { 217 getLogger().log(Level.WARNING, 218 "Unable to get Id for object", e); 219 } 90 * @return The URL relative to the top-level {@link EntityRouter}. 91 */ 92 @SuppressWarnings("unchecked") 93 public String getEntityURL(E entity) { 94 String entityUrl = null; 95 EntityInstance<E> helper = getHelper(entity); 96 if (helper != null) { 97 Map<String, Object> attributes = helper.getIdAttributes(); 98 entityUrl = route.getTemplate().format(attributes); 99 if (isChild()) { 100 String parentUrl = null; 101 Object parent = helper.getParentEntity(); 102 if (parent != null) { 103 parentUrl = getParent().getEntityURL(parent); 220 104 } 221 } 222 for (Method m : entity.getClass().getDeclaredMethods()) { 223 if (m.isAnnotationPresent(Id.class)) { 224 m.setAccessible(true); 225 try { 226 return String.valueOf(m.invoke(entity)); 227 } catch (IllegalArgumentException e) { 228 getLogger().log(Level.WARNING, 229 "Unable to get Id for object", e); 230 } catch (IllegalAccessException e) { 231 getLogger().log(Level.WARNING, 232 "Unable to get Id for object", e); 233 } catch (InvocationTargetException e) { 234 getLogger().log(Level.WARNING, 235 "Unable to get Id for object", e); 236 } 105 if (parentUrl == null) { 106 // bomb out if we can't find a parent url 107 entityUrl = null; 108 } else { 109 entityUrl = parentUrl + entityUrl; 237 110 } 238 111 } 239 112 } 240 return null; 241 } 242 243 /** 244 * Get a the attached URL for a given entity. The default implementation 245 * applies the URL template in the {@link #getRoute() attached route} with 246 * parameter "id" and the result of {@link #getIdString(Object)}. 113 return entityUrl; 114 } 115 116 /** 117 * Convert a URL in the request to an entity instance. Works by first 118 * checking if any children match the requested URL. If no children match 119 * and this finder maps to an {@link EntityInstance}, then the 120 * {@link EntityInstance} is constructed and 121 * {@link EntityInstance#getEntity()} returned. 122 */ 123 public Object getEntity(Request request) { 124 Object entity = null; 125 String remainingPart = request.getResourceRef().getRemainingPart(); 126 int matchedLength = getRoute().getTemplate().parse(remainingPart, 127 request); 128 if (matchedLength != -1) { 129 // Update the remaining part 130 String matchedPart = remainingPart.substring(0, matchedLength); 131 Reference baseRef = request.getResourceRef().getBaseRef(); 132 133 if (baseRef == null) { 134 baseRef = new Reference(matchedPart); 135 } else { 136 baseRef = new Reference(baseRef.toString(false, false) 137 + matchedPart); 138 } 139 140 request.getResourceRef().setBaseRef(baseRef); 141 142 entity = children.getEntity(request); 143 if (entity == null 144 && EntityInstance.class.isAssignableFrom(resource)) { 145 try { 146 Constructor<? extends EntityResource> c = resource 147 .getConstructor(EntityInstanceFinder.class); 148 EntityInstance resource = (EntityInstance) c 149 .newInstance(this); 150 resource.init(request, new Response(request)); 151 entity = resource.getEntity(); 152 } catch (Exception e) { 153 getLogger().log(Level.WARNING, 154 "Unable to convert request to entity instance", e); 155 } 156 } 157 } 158 return entity; 159 } 160 161 public Class getEntityIdClass() { 162 return entityIdClass; 163 } 164 165 public String getIdAttribute() { 166 return idAttribute; 167 } 168 169 /** 170 * Set the URI template variable used to map URI's to entities and back. For 171 * entities with complex keys, provide a custom {@link EntityInstance}. 172 */ 173 public EntityInstanceFinder<E> setIdAttribute(String idAttribute) { 174 this.idAttribute = idAttribute; 175 return this; 176 } 177 178 /** 179 * Attaches a child finder to this finder. 247 180 * 248 * @param entity 249 * The entity. 250 * @return The URL relative to the attached {@link EntityRouter}. 251 */ 252 public String getEntityURL(E entity) { 253 return route.getTemplate().format( 254 Collections.singletonMap("id", (Object) getIdString(entity))); 255 } 256 257 /** 258 * Get the entity identified by the request URL. By default this invokes 259 * {@link #getEntity(String)} with id string retrieved from the request 260 * parameter "id". 261 */ 262 public E getEntity(Request request, Response response) { 263 return getEntity((String) request.getAttributes().get("id")); 264 } 265 266 /** 267 * Get the entity identified by the id string. Invokes 268 * {@link EntityManager#find(Class, Object)} with the result of 269 * {@link #convertIdString(String)}. 270 * 271 * @param id 272 * The id string. 273 * @return The entity or null if not found. 274 */ 275 public E getEntity(String id) { 276 E entity = null; 277 if (id != null) { 278 entity = router.getEntityManager().find(getEntityClass(), 279 convertIdString(id)); 280 } 281 return entity; 282 } 283 284 /** 285 * Removes an entity from persistence. 286 * 287 * @param entity 288 * The entity to remove. 289 * @return true if the entity was removed. 290 */ 291 public boolean removeEntity(E entity) { 292 router.getEntityManager().remove(entity); 293 return true; 294 } 295 296 /** 297 * Get the query for the entity collection. If the request defines a 298 * parameter "queryName", that is used to call 299 * {@link #getEntityListQuery(String)}. Alternatively, 300 * {@link #getEntityListQuery()} is called to get the default query. 301 */ 302 public Query getEntityListQuery(Request request, Response response) { 303 String queryName = (String) request.getAttributes().get("queryName"); 304 if (queryName != null) { 305 return getEntityListQuery(queryName); 306 } else { 307 return getEntityListQuery(); 308 } 309 } 310 311 /** Get a named query. */ 312 protected Query getEntityListQuery(String queryName) { 313 return router.getEntityManager().createNamedQuery(queryName); 314 } 315 316 /** Get the default query. */ 317 protected Query getEntityListQuery() { 318 return router.getEntityManager().createQuery( 319 "SELECT o from " + EntityHelper.getEntityName(getEntityClass()) 320 + " o"); 321 } 322 323 public int getMaxResults() { 324 return maxResults; 325 } 326 327 /** 328 * Set the maximum results that should be returned from a query. 329 * {@link EntityCollection} uses this to limit query results. 330 */ 331 public EntityFinder<E> setMaxResults(int maxResults) { 332 this.maxResults = maxResults; 333 if (this.maxResults == Integer.MAX_VALUE) { 334 this.maxResults--; 335 } 336 return this; 337 } 181 * @param uriPattern 182 * The URL pattern to attach the child at (relative to this 183 * finder's URL). 184 * @param childClass 185 * The child entity class. 186 * @param resource 187 * The resource class for the child entity. 188 * @return This child finder. 189 */ 190 public <C extends Object> EntityInstanceFinder<C> attachChild( 191 String uriPattern, Class<C> childClass, 192 Class<? extends EntityInstance> resource) { 193 EntityInstanceFinder<C> child = new EntityInstanceFinder<C>(children, 194 childClass, resource); 195 children.attachInstance(uriPattern, child); 196 getRouter().addChildFinder(child); 197 return child; 198 } 199 200 /** True if this finder has children. */ 201 public boolean isParent() { 202 return children.size() > 0; 203 } 204 205 /** The router child finders should be attached to. */ 206 public EntityRouter getChildRouter() { 207 return children; 208 } 209 210 /** True if this finder is a child. */ 211 public boolean isChild() { 212 return router.isChild(); 213 } 214 215 /** Gets the parent finder (if this is a child). */ 216 public EntityInstanceFinder getParent() { 217 return router.getParent(); 218 } 219 220 /** 221 * Passes the request to children if possible or handles directly if no 222 * children map to the URL. 223 */ 224 @Override 225 public void handle(Request request, Response response) { 226 if (!isParent() || !children.handleIfPossible(request, response)) { 227 super.handle(request, response); 228 } 229 } 230 231 protected EntityResource initResource() { 232 EntityResource resource = null; 233 try { 234 Constructor<? extends EntityResource> c = this.resource 235 .getConstructor(EntityInstanceFinder.class); 236 resource = c.newInstance(this); 237 } catch (SecurityException e) { 238 getLogger().log(Level.WARNING, "Error constructing resource.", e); 239 } catch (NoSuchMethodException e) { 240 getLogger().log(Level.WARNING, "Error constructing resource.", e); 241 } catch (IllegalArgumentException e) { 242 getLogger().log(Level.WARNING, "Error constructing resource.", e); 243 } catch (InstantiationException e) { 244 getLogger().log(Level.WARNING, "Error constructing resource.", e); 245 } catch (IllegalAccessException e) { 246 getLogger().log(Level.WARNING, "Error constructing resource.", e); 247 } catch (InvocationTargetException e) { 248 getLogger().log(Level.WARNING, "Error constructing resource.", e); 249 } 250 return resource; 251 } 252 338 253 } trunk/src/main/java/org/sarugo/restlet/jpa/EntityRouter.java
r3602 r3606 19 19 import java.util.HashMap; 20 20 import java.util.Map; 21 import java.util.logging.Level; 21 import java.util.Set; 22 import java.util.concurrent.CopyOnWriteArraySet; 22 23 23 24 import javax.persistence.EntityManager; 24 25 import javax.persistence.EntityManagerFactory; 25 import javax.persistence.PersistenceException;26 26 27 27 import org.restlet.Context; … … 29 29 import org.restlet.Route; 30 30 import org.restlet.Router; 31 import org.restlet.data.Method; 31 32 import org.restlet.data.Request; 32 33 import org.restlet.data.Response; 34 import org.restlet.data.Status; 33 35 import org.sarugo.restlet.jpa.resource.EntityCollection; 34 import org.sarugo.restlet.jpa.resource.Entity Resource;36 import org.sarugo.restlet.jpa.resource.EntityInstance; 35 37 36 38 /** 37 39 * Root of JPA entity mappings. All related entities and entity collections 38 * should share a common PersistenceRouter, this enables link resolution.40 * should share a common router, this enables link resolution. 39 41 * 40 * {@link Entity Finder}s are attached to handle entity instances or42 * {@link EntityInstanceFinder}s are attached to handle entity instances or 41 43 * collections. Instances are attached independently from collections. An 42 * {@link EntityFinder} can only be attached to one URL, however it may have 43 * several {@link #alias(String, EntityFinder) aliases}. 44 * 45 * <b>Known Issue:</b><br> 46 * Currently only a single PersistenceRouter is supported and it must be 47 * attached to the root of the URL-space due to the way 48 * {@link #getEntityURL(Object)} works. 44 * {@link EntityInstanceFinder} can only be attached to one URL, however it may 45 * have several {@link #alias(String, EntityInstanceFinder) aliases}. 49 46 * 50 47 * @author Michael Terrington 51 48 */ 52 public class EntityRouter extends Restlet { 53 54 private final Router router; 49 public class EntityRouter extends Router { 55 50 56 51 private final EntityManagerFactory entityManagerFactory; … … 58 53 private final ThreadLocal<EntityManager> entityManager; 59 54 60 private final Map<Class, EntityFinder> entities; 61 62 /** 63 * Create a PersistenceRouter associated with an 64 * {@link EntityManagerFactory}. The {@link EntityManagerFactory} must 65 * manage any entity types associated with this PersistenceRouter. For each 66 * request handled an {@link EntityManager} will be created from the 67 * {@link EntityManagerFactory}. 55 private final Map<Class, EntityInstanceFinder> entities; 56 57 private final Map<Class, Set<EntityInstanceFinder>> childEntities; 58 59 private final EntityInstanceFinder<? extends Object> parent; 60 61 /** 62 * Create a router associated with an {@link EntityManagerFactory}. The 63 * {@link EntityManagerFactory} must manage any entity types associated with 64 * this router. For each request handled an {@link EntityManager} will be 65 * created from the {@link EntityManagerFactory}. 68 66 * 69 67 * @param context 70 * Restlet context for this PersistenceRouter.68 * Restlet context for this router. 71 69 * @param entityManagerFactory 72 70 * The entity manager factory. … … 75 73 EntityManagerFactory entityManagerFactory) { 76 74 super(context); 77 this.router = new Router(context);78 75 this.entityManagerFactory = entityManagerFactory; 79 76 this.entityManager = new ThreadLocal<EntityManager>(); 80 this.entities = new HashMap<Class, EntityFinder>(); 81 } 82 83 /** 84 * Get the {@link EntityManagerFactory} associated with this 85 * PersistenceRouter. 77 this.entities = new HashMap<Class, EntityInstanceFinder>(); 78 this.childEntities = new HashMap<Class, Set<EntityInstanceFinder>>(); 79 this.parent = null; 80 } 81 82 public EntityRouter(EntityInstanceFinder<? extends Object> parent) { 83 super(parent.getContext()); 84 this.parent = parent; 85 this.entityManagerFactory = parent.getRouter().entityManagerFactory; 86 this.entityManager = parent.getRouter().entityManager; 87 this.entities = new HashMap<Class, EntityInstanceFinder>(); 88 this.childEntities = null; 89 } 90 91 /** 92 * Get the {@link EntityManagerFactory} associated with this router. 86 93 */ 87 94 public EntityManagerFactory getEntityManagerFactory() { … … 92 99 * Get a thread-local {@link EntityManager}. Typically during request 93 100 * processing an {@link EntityManager} is created and a transaction begun 94 * before passing control to an {@link EntityFinder}. During processing 95 * (which is always in the same thread), the {@link EntityFinder} can call 96 * this method to get the {@link EntityManager} associated with the request. 97 * After request processing is complete, the transaction is completed and 98 * the {@link EntityManager} closed and cleared from the thread-local. 101 * before passing control to an {@link EntityInstanceFinder}. During 102 * processing (which is always in the same thread), the 103 * {@link EntityInstanceFinder} can call this method to get the 104 * {@link EntityManager} associated with the request. After request 105 * processing is complete, the transaction is completed and the 106 * {@link EntityManager} closed and cleared from the thread-local. 99 107 */ 100 108 public EntityManager getEntityManager() { … … 113 121 114 122 /** 115 * Convert an entity to a URL based on an attached {@link EntityFinder}. 123 * Convert an entity to a URL based on an attached 124 * {@link EntityInstanceFinder}. 116 125 * 117 126 * @param entity 118 127 * The entity to get a URL for. 119 * @return The entity's URL relative to this PersistenceRouter.120 * @see #attach (String, EntityFinder)128 * @return The entity's URL relative to this router. 129 * @see #attachInstance(String, EntityInstanceFinder) 121 130 */ 122 131 @SuppressWarnings("unchecked") 123 132 public String getEntityURL(Object entity) { 124 Entity Finder finder = entities.get(entity.getClass());133 EntityInstanceFinder finder = entities.get(entity.getClass()); 125 134 String url = null; 126 135 if (finder != null) { 136 // this router holds the entity instance finder 127 137 url = finder.getEntityURL(entity); 138 } else if (isChild()) { 139 // check if the parent router can find it 140 getParent().getRouter().getEntityURL(entity); 141 } else { 142 // else - we must be the top ancestor, so see if there's a child 143 // finder that maps 144 Set<EntityInstanceFinder> parents = childEntities.get(entity 145 .getClass()); 146 if (parents != null) { 147 for (EntityInstanceFinder parent : parents) { 148 url = parent.getEntityURL(entity); 149 if (url != null) { 150 break; 151 } 152 } 153 } 128 154 } 129 155 return url; 156 } 157 158 /** 159 * Locate an entity instance of the given class by its URL. 160 * 161 * @param url 162 * The entity URL as returned by {@link #getEntityURL(Object)}. 163 * @return The entity instance matching the given URL, or null if none was 164 * found. 165 */ 166 public Object getEntity(String url) { 167 return getEntity(new Request(Method.GET, url)); 168 } 169 170 /** 171 * Locate an entity instance of the given class by its URL. 172 * 173 * @param url 174 * The entity URL as returned by {@link #getEntityURL(Object)}. 175 * @return The entity instance matching the given URL, or null if none was 176 * found. 177 */ 178 public Object getEntity(Request request) { 179 Object entity = null; 180 Route next = (Route) getNext(request, new Response(request)); 181 if (next != null && next.getNext() instanceof EntityInstanceFinder) { 182 EntityInstanceFinder finder = (EntityInstanceFinder) next.getNext(); 183 entity = finder.getEntity(request); 184 } 185 return entity; 130 186 } 131 187 … … 136 192 @Override 137 193 public void handle(Request request, Response response) { 138 router.handle(request, response); 139 } 140 141 /** 142 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 143 * That is, a handler for entity instances, not the entity collection. 144 * Invokes {@link #attach(EntityFinder)} with a new {@link EntityFinder}. 194 handleIfPossible(request, response); 195 } 196 197 /** 198 * Handle a request by delegating to the internal router which holds the 199 * mappings. For transation support see {@link TransactionFilter}. 200 * 201 * @return true if the request was handled. Response status will also be set 202 * to {@link Status#CLIENT_ERROR_NOT_FOUND} if request was not 203 * handled. 204 */ 205 public boolean handleIfPossible(Request request, Response response) { 206 Restlet next = getNext(request, response); 207 if (next != null) { 208 next.handle(request, response); 209 } 210 return next != null; 211 } 212 213 /** 214 * Attach an {@link EntityInstanceFinder} for handling 215 * {@link EntityInstance}s. That is, a handler for entity instances, not 216 * the entity collection. Invokes 217 * {@link #attachInstance(EntityInstanceFinder)} with a new 218 * {@link EntityInstanceFinder}. 145 219 * 146 220 * @param entityClass 147 221 * The entity class to attach. 148 222 * @return The finder for the entity. 149 * @see #attachCollection(Class) for attaching an {@link EntityFinder} for 150 * the entity collection. 151 */ 152 public <E extends Object> EntityFinder<E> attach(Class<E> entityClass) { 153 return attach(new EntityFinder<E>(this, entityClass, 154 EntityResource.class)); 155 } 156 157 /** 158 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 159 * That is, a handler for entity instances, not the entity collection. 160 * Invokes {@link #attach(String, EntityFinder)} with the given 161 * {@link EntityFinder} and the URL "/{entityName}/{id}" where 223 * @see #attachCollection(Class) for attaching an 224 * {@link EntityInstanceFinder} for the entity collection. 225 */ 226 public <E extends Object> EntityInstanceFinder<E> attachInstance( 227 Class<E> entityClass) { 228 return attachInstance(new EntityInstanceFinder<E>(this, entityClass, 229 EntityInstance.class)); 230 } 231 232 /** 233 * Attach an {@link EntityInstanceFinder} for handling 234 * {@link EntityInstance}s. That is, a handler for entity instances, not 235 * the entity collection. Invokes 236 * {@link #attachInstance(String, EntityInstanceFinder)} with the given 237 * {@link EntityInstanceFinder} and the URL "/{entityName}/{id}" where 162 238 * "{entityName}" is formed by calling 163 * {@link Entity Helper#getEntityName(Class)}.239 * {@link EntityURLHelper#getEntityName(Class)}. 164 240 * 165 241 * @param finder … … 168 244 * @throws IllegalStateException 169 245 * If the finder has already been attached. 170 * @see #attachCollection(EntityFinder) for attaching an 171 * {@link EntityFinder} for the entity collection. 172 */ 173 public <E extends Object> EntityFinder<E> attach(EntityFinder<E> finder) { 174 return attach("/" + EntityHelper.getEntityName(finder.getEntityClass()) 175 + "/{id}", finder); 176 } 177 178 /** 179 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 180 * That is, a handler for entity instances, not the entity collection. 181 * Invokes {@link #attach(String, EntityFinder)} with a new 182 * {@link EntityFinder}. 246 * @see #attachCollection(EntityInstanceFinder) for attaching an 247 * {@link EntityInstanceFinder} for the entity collection. 248 */ 249 public <E extends Object> EntityInstanceFinder<E> attachInstance( 250 EntityInstanceFinder<E> finder) { 251 return attachInstance( 252 "/" + EntityHelper.getEntityName(finder.getEntityClass()) 253 + "/{id}", finder); 254 } 255 256 /** 257 * Attach an {@link EntityInstanceFinder} for handling 258 * {@link EntityInstance}s. That is, a handler for entity instances, not 259 * the entity collection. Invokes 260 * {@link #attachInstance(String, EntityInstanceFinder)} with a new 261 * {@link EntityInstanceFinder}. 183 262 * 184 263 * @param uriPattern 185 * The URL pattern to attach the {@link EntityFinder} at. By 186 * default, {@link EntityFinder#getEntity(Request, Response)} 264 * The URL pattern to attach the {@link EntityInstanceFinder} at. 265 * By default, 266 * {@link EntityInstanceFinder#getEntity(Request, Response)} 187 267 * expects the URL to contain "{id}" to define the entity id. 188 268 * @param entityClass … … 190 270 * @return The finder for the entity. 191 271 * @see #attachCollection(String, Class)) for attaching an 192 * {@link EntityFinder} for the entity collection. 193 */ 194 public <E extends Object> EntityFinder<E> attach(String uriPattern, 195 Class<E> entityClass) { 196 return attach(uriPattern, new EntityFinder<E>(this, entityClass, 197 EntityResource.class)); 198 } 199 200 /** 201 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 202 * That is, a handler for entity instances, not the entity collection. 272 * {@link EntityInstanceFinder} for the entity collection. 273 */ 274 public <E extends Object> EntityInstanceFinder<E> attachInstance( 275 String uriPattern, Class<E> entityClass) { 276 return attachInstance(uriPattern, new EntityInstanceFinder<E>(this, 277 entityClass, EntityInstance.class)); 278 } 279 280 /** 281 * Attach an {@link EntityInstanceFinder} for handling 282 * {@link EntityInstance}s. That is, a handler for entity instances, not 283 * the entity collection. 203 284 * 204 285 * @param uriPattern 205 * The URL pattern to attach the {@link EntityFinder} at. By 206 * default, {@link EntityFinder#getEntity(Request, Response)} 286 * The URL pattern to attach the {@link EntityInstanceFinder} at. 287 * By default, 288 * {@link EntityInstanceFinder#getEntity(Request, Response)} 207 289 * expects the URL to contain "{id}" to define the entity id. 208 290 * @param finder … … 212 294 * If the finder has already been attached. 213 295 * @see #attachCollection(String, EntityFinder)) for attaching an 214 * {@link Entity Finder} for the entity collection.215 */ 216 public <E extends Object> Entity Finder<E> attach(String uriPattern,217 EntityFinder<E> finder) {296 * {@link EntityInstanceFinder} for the entity collection. 297 */ 298 public <E extends Object> EntityInstanceFinder<E> attachInstance( 299 String uriPattern, EntityInstanceFinder<E> finder) { 218 300 if (entities.containsKey(finder.getEntityClass())) { 219 301 throw new IllegalStateException("Entity class is already mapped."); 220 302 } 221 Route route = router.attach(uriPattern, finder); 303 if (finder.getRouter() != this) { 304 throw new IllegalStateException( 305 "Finder is already attached to another router"); 306 } 307 Route route = attach(uriPattern, finder); 222 308 finder.setRoute(route); 223 309 entities.put(finder.getEntityClass(), finder); … … 226 312 227 313 /** 228 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 229 * That is, a handler for an entity collection, not entity instances. 230 * Invokes {@link #attachCollection(EntityFinder)} with a new 231 * {@link EntityFinder}. 314 * Attach an {@link EntityInstanceFinder} for handling 315 * {@link EntityCollection}s. That is, a handler for an entity collection, 316 * not entity instances. Invokes 317 * {@link #attachCollection(EntityInstanceFinder)} with a new 318 * {@link EntityInstanceFinder}. 232 319 * 233 320 * @param entityClass … … 235 322 * @return The finder for the entity. 236 323 */ 237 public <E extends Object> Entity Finder<E> attachCollection(324 public <E extends Object> EntityCollectionFinder<E> attachCollection( 238 325 Class<E> entityClass) { 239 return attachCollection(new EntityFinder<E>(this, entityClass, 240 EntityCollection.class)); 241 } 242 243 /** 244 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 245 * That is, a handler for an entity collection, not entity instances. 246 * Invokes {@link #attachCollection(String, EntityFinder)} with the given 247 * {@link EntityFinder} and the URL "/{entityName}" where "{entityName}" is 248 * formed by calling {@link EntityHelper#getEntityName(Class)}. 326 return attachCollection(new EntityCollectionFinder<E>(this, 327 entityClass, EntityCollection.class)); 328 } 329 330 /** 331 * Attach an {@link EntityInstanceFinder} for handling 332 * {@link EntityCollection}s. That is, a handler for an entity collection, 333 * not entity instances. Invokes 334 * {@link #attachCollection(String, EntityInstanceFinder)} with the given 335 * {@link EntityInstanceFinder} and the URL "/{entityName}" where 336 * "{entityName}" is formed by calling 337 * {@link EntityURLHelper#getEntityName(Class)}. 249 338 * 250 339 * @param finder … … 254 343 * If the finder has already been attached. 255 344 */ 256 public <E extends Object> Entity Finder<E> attachCollection(257 Entity Finder<E> finder) {345 public <E extends Object> EntityCollectionFinder<E> attachCollection( 346 EntityCollectionFinder<E> finder) { 258 347 return attachCollection("/" 259 348 + EntityHelper.getEntityName(finder.getEntityClass()), finder); … … 261 350 262 351 /** 263 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 264 * That is, a handler for an entity collection, not entity instances. 265 * Invokes {@link #attachCollection(String, EntityFinder)} with a new 266 * {@link EntityFinder}. 352 * Attach an {@link EntityInstanceFinder} for handling 353 * {@link EntityCollection}s. That is, a handler for an entity collection, 354 * not entity instances. Invokes 355 * {@link #attachCollection(String, EntityInstanceFinder)} with a new 356 * {@link EntityInstanceFinder}. 267 357 * 268 358 * @param uriPattern 269 * The URL pattern to attach the {@link Entity Finder} at.359 * The URL pattern to attach the {@link EntityInstanceFinder} at. 270 360 * @param entityClass 271 361 * The entity class to attach. 272 362 * @return The finder for the entity. 273 363 */ 274 public <E extends Object> Entity Finder<E> attachCollection(364 public <E extends Object> EntityCollectionFinder<E> attachCollection( 275 365 String uriPattern, Class<E> entityClass) { 276 return attachCollection(uriPattern, new Entity Finder<E>(this,366 return attachCollection(uriPattern, new EntityCollectionFinder<E>(this, 277 367 entityClass, EntityCollection.class)); 278 368 } 279 369 280 370 /** 281 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 282 * That is, a handler for an entity collection, not entity instances. 371 * Attach an {@link EntityInstanceFinder} for handling 372 * {@link EntityCollection}s. That is, a handler for an entity collection, 373 * not entity instances. 283 374 * 284 375 * @param uriPattern 285 * The URL pattern to attach the {@link Entity Finder} at.376 * The URL pattern to attach the {@link EntityInstanceFinder} at. 286 377 * @param finder 287 378 * The entity finder to attach. … … 290 381 * If the finder has already been attached. 291 382 */ 292 public <E extends Object> EntityFinder<E> attachCollection( 293 String uriPattern, EntityFinder<E> finder) { 294 Route route = router.attach(uriPattern, finder); 383 public <E extends Object> EntityCollectionFinder<E> attachCollection( 384 String uriPattern, EntityCollectionFinder<E> finder) { 385 if (finder.getRouter() != this) { 386 throw new IllegalStateException( 387 "Finder is already attached to another router"); 388 } 389 Route route = attach(uriPattern, finder); 295 390 finder.setRoute(route); 296 391 return finder; … … 299 394 /** 300 395 * Associate an {@link EntityFinder} with an extra URL. For 301 * {@link Entity Resource} handling finders, the URL by default should302 * contain "{id}" to define the entity id.396 * {@link EntityInstanceFinder}s, the URL must contain the template 397 * variables required to find the instance (by default {id}). 303 398 * 304 399 * @param uriPattern … … 310 405 public <E extends Object> Route alias(String uriPattern, 311 406 EntityFinder<E> finder) { 312 Route route = router.attach(uriPattern, finder);407 Route route = attach(uriPattern, finder); 313 408 finder.getAliases().add(route); 314 409 return route; 315 410 } 316 411 317 /** 318 * Locate an entity instance of the given class by converting the id string. 319 * 320 * @param entityClass 321 * The entity class to locate. 322 * @param id 323 * The entity id as a string. 324 * @return The entity instance matching the given id, or null if none was 325 * found. 326 */ 327 public <E extends Object> E findEntity(Class<E> entityClass, String id) { 328 EntityFinder finder = entities.get(entityClass); 329 if (finder != null) { 330 Object idObj = finder.convertIdString(id); 331 if (idObj != null) { 332 try { 333 return getEntityManager().find(entityClass, idObj); 334 } catch (PersistenceException e) { 335 getLogger().log(Level.INFO, "Exception finding entity.", e); 336 } 412 public int size() { 413 return entities.size(); 414 } 415 416 public EntityInstanceFinder<? extends Object> getParent() { 417 return parent; 418 } 419 420 public boolean isChild() { 421 return parent != null; 422 } 423 424 public void addChildFinder(EntityInstanceFinder child) { 425 if (isChild()) { 426 // add to top ancestor 427 getParent().getRouter().addChildFinder(child); 428 } else { 429 Set<EntityInstanceFinder> parents = childEntities.get(child 430 .getEntityClass()); 431 if (parents == null) { 432 parents = new CopyOnWriteArraySet<EntityInstanceFinder>(); 433 childEntities.put(child.getEntityClass(), parents); 337 434 } 338 } 339 return null; 340 } 341 435 parents.add(child); 436 } 437 } 342 438 } trunk/src/main/java/org/sarugo/restlet/jpa/converter/ConverterHelper.java
r3577 r3606 66 66 // Otherwise check if the type is an entity type 67 67 if (router != null) { 68 T entity = router.findEntity(type, value); 68 @SuppressWarnings("unchecked") 69 T entity = (T) router.getEntity(value); 69 70 if (entity != null) { 70 71 return entity; trunk/src/main/java/org/sarugo/restlet/jpa/converter/json/JSONConverter.java
r3577 r3606 101 101 .getEntityList().size()); 102 102 for (E entity : entityCollection.getEntityList()) { 103 urlList.add(entityCollection.getFinder().get EntityURL(104 entity));103 urlList.add(entityCollection.getFinder().getRouter() 104 .getEntityURL(entity)); 105 105 } 106 106 Map<String, Object> listObject = new HashMap<String, Object>(1, trunk/src/main/java/org/sarugo/restlet/jpa/converter/text/ReferenceListConverter.java
r3577 r3606 43 43 public Representation represent(EntityResource<E> entityResource, 44 44 Variant variant) { 45 if (VARIANTS.contains(variant.getMediaType()) && entityResource instanceof EntityCollection) { 45 if (VARIANTS.contains(variant.getMediaType()) 46 && entityResource instanceof EntityCollection) { 46 47 EntityCollection<E> entityCollection = (EntityCollection<E>) entityResource; 47 48 ReferenceList list = new ReferenceList(entityCollection … … 49 50 list.setIdentifier(entityCollection.getRequest().getResourceRef()); 50 51 for (E o : entityCollection.getEntityList()) { 51 list.add(entityCollection.getFinder().getEntityURL(o)); 52 list.add(entityCollection.getFinder().getRouter().getEntityURL( 53 o)); 52 54 } 53 55 if (MediaType.TEXT_URI_LIST.equals(variant.getMediaType())) { trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityCollection.java
r3577 r3606 30 30 import org.restlet.resource.Resource; 31 31 import org.restlet.resource.ResourceException; 32 import org.sarugo.restlet.jpa.EntityFinder; 32 import org.sarugo.restlet.jpa.EntityCollectionFinder; 33 import org.sarugo.restlet.jpa.EntityInstanceFinder; 33 34 import org.sarugo.restlet.jpa.converter.InputConverter; 34 35 35 36 /** 36 37 * A resource which maps to a collection of JPA entities. The behaviour of this 37 * class is largely controlled by the {@link EntityFinder} which creates it. 38 * class is largely controlled by the {@link EntityInstanceFinder} which creates 39 * it. 38 40 * 39 41 * @author Michael Terrington … … 46 48 /** 47 49 * The number of results to retrieve. Defaults to 48 * {@link Entity Finder#getMaxResults()}.50 * {@link EntityInstanceFinder#getMaxResults()}. 49 51 */ 50 52 protected int maxResults; … … 65 67 * The finder this collection will use to get its entity list. 66 68 */ 67 public EntityCollection(Entity Finder<E> finder) {69 public EntityCollection(EntityCollectionFinder<E> finder) { 68 70 super(finder); 69 71 start = 1; … … 77 79 * initialise the base class.</li> 78 80 * <li>Calls {@link #initMaxAndStart()}.</li> 81 * <li>Calls {@link #getEntityList(Request, Response)} which: 82 * <ol> 79 83 * <li>Gets the JPA query from 80 * {@link EntityFinder#getEntityListQuery(Request, Response)}.</li>84 * {@link #getEntityListQuery(Request, Response)}.</li> 81 85 * <li>Sets {@link Query#setFirstResult(int)} to <code>start - 1</code>).</li> 82 86 * <li>Sets {@link Query#setMaxResults(int)} to <code>maxResults + 1</code>.</li> … … 86 90 * Otherwise, truncates the list to {@link #maxResults} (to remove the +1).</li> 87 91 * </ol> 88 * 89 * Override {@link EntityFinder#getEntityListQuery(Request, Response)} if 90 * you want to change the query. 92 * </li> 93 * </ol> 94 * 95 * Override {@link #getEntityListQuery(Request, Response)} if you want to 96 * change the query. 91 97 * 92 98 * Override {@link #initMaxAndStart()} if you want to change how 93 99 * {@link #start} and {@link #maxResults} are initialised. 94 100 * 95 * Override this method if you want to retrieve the entity list from a 96 * non-query source (e.g. from a containing entity). Do not call this 97 * implementation (<tt>super.init</tt>) if you override it, instead 98 * ensure you call {@link Resource#init(Context, Request, Response)} first 99 * and {@link #initMaxAndStart()} if required. 101 * Override {@link #getEntityList(Request, Response)} method if you want to 102 * retrieve the entity list from a non-query source (e.g. from a containing 103 * entity). 100 104 */ 101 105 public void init(Request request, Response response) { 102 106 init(getFinder().getContext(), request, response); 103 107 initMaxAndStart(); 104 Query q = getFinder().getEntityListQuery(request, response); 108 entityList = getEntityList(request, response); 109 } 110 111 protected List<E> getEntityList(Request request, Response response) { 112 Query q = getEntityListQuery(request, response); 105 113 q.setFirstResult(start - 1); 106 114 q.setMaxResults(maxResults + 1); … … 113 121 entities = entities.subList(0, maxResults); 114 122 } 115 entityList =entities;123 return entities; 116 124 } 117 125 … … 120 128 * parameters <tt>max</tt> and <tt>start</tt>. {@link #maxResults} will 121 129 * not be changed if the query parameter is greater than the value returned 122 * by {@link Entity Finder#getMaxResults()} to prevent DoS attacks.130 * by {@link EntityInstanceFinder#getMaxResults()} to prevent DoS attacks. 123 131 */ 124 132 protected void initMaxAndStart() { … … 132 140 } 133 141 } 142 } 143 144 /** 145 * Get the query for the entity collection. If the request defines a 146 * parameter "queryName", that is used to call 147 * {@link #getEntityListQuery(String)}. Alternatively, 148 * {@link #getEntityListQuery()} is called to get the default query. 149 */ 150 protected Query getEntityListQuery(Request request, Response response) { 151 String queryName = (String) request.getAttributes().get("queryName"); 152 if (queryName != null) { 153 return getEntityListQuery(queryName); 154 } else { 155 return getEntityListQuery(); 156 } 157 } 158 159 /** Get a named query. */ 160 protected Query getEntityListQuery(String queryName) { 161 return getFinder().getRouter().getEntityManager().createNamedQuery( 162 queryName); 163 } 164 165 /** Get the default query. */ 166 protected Query getEntityListQuery() { 167 return getFinder().getRouter().getEntityManager().createQuery( 168 ((EntityCollectionFinder<E>)getFinder()).getDefaultQuery()); 134 169 } 135 170 trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityInstance.java
r3577 r3606 17 17 package org.sarugo.restlet.jpa.resource; 18 18 19 import java.lang.reflect.Field; 20 import java.lang.reflect.InvocationTargetException; 21 import java.lang.reflect.Method; 22 import java.util.Collections; 23 import java.util.Map; 24 import java.util.logging.Level; 25 26 import javax.persistence.EntityManager; 27 import javax.persistence.Id; 28 19 29 import org.restlet.data.Request; 20 30 import org.restlet.data.Response; … … 22 32 import org.restlet.resource.Representation; 23 33 import org.restlet.resource.ResourceException; 24 import org.sarugo.restlet.jpa.EntityFinder; 34 import org.sarugo.restlet.jpa.EntityInstanceFinder; 35 import org.sarugo.restlet.jpa.converter.ConverterHelper; 25 36 import org.sarugo.restlet.jpa.converter.InputConverter; 26 37 27 38 /** 28 * A resource which maps to a JPA entity. The behaviour of this class is largely 29 * controlled by the {@link EntityFinder} which creates it. 39 * A resource which maps to a JPA entity. For simple entities, the behaviour of 40 * this class is largely controlled by the {@link EntityInstanceFinder} which 41 * creates it. 30 42 * 31 43 * @author Michael Terrington … … 41 53 * The finder this instance will use to get its entity. 42 54 */ 43 public EntityInstance(Entity Finder<E> finder) {55 public EntityInstance(EntityInstanceFinder<E> finder) { 44 56 super(finder); 45 57 } 46 58 47 59 /** 48 * Initialise this resource . By default this calls49 * {@link EntityFinder#getEntity(Request, Response)}.60 * Initialise this resource from a request. By default this calls 61 * {@link #getEntity(Request, Response)}. 50 62 */ 51 63 public void init(Request request, Response response) { 52 64 super.init(getFinder().getContext(), request, response); 53 this.entity = getFinder().getEntity(request, response); 65 this.entity = getEntity(request, response); 66 } 67 68 /** 69 * Initialise this resource from an entity instance. Request and response 70 * will not be available during processing. This is used by 71 * {@link EntityInstanceFinder} when calling {@link #getParentEntity()} or 72 * {@link #getIdAttributes()}. 73 */ 74 public void init(E entity) { 75 super.init(getFinder().getContext(), null, null); 76 this.entity = entity; 77 } 78 79 /** 80 * Get the entity identified by the request URL. By default this invokes 81 * {@link EntityManager#find(Class, Object)} with the conversion of the 82 * request attributes using {@link #getId(Map)}. 83 * 84 * When to override: 85 * <ul> 86 * <li>If you need to load the entity from a parent rather than by primary 87 * key.</li> 88 * </ul> 89 */ 90 protected E getEntity(Request request, Response response) { 91 E entity = null; 92 Object id = getId(request.getAttributes()); 93 if (id != null) { 94 entity = getFinder().getRouter().getEntityManager().find( 95 getFinder().getEntityClass(), id); 96 } 97 return entity; 98 } 99 100 /** 101 * Convert the id attributes to an object suitable for passing to 102 * {@link EntityManager#find(Class, Object)}. For simple entity id's the 103 * default implementation should suffice. 104 * 105 * When to override: 106 * <ul> 107 * <li>If the entity has a compound primary key.</li> 108 * <li>If the primary key is contained in multiple request attributes.</li> 109 * </ul> 110 * 111 * @param attributes 112 * The request attributes. 113 * @return The identity object. 114 */ 115 @SuppressWarnings("unchecked") 116 protected Object getId(Map<String, Object> attributes) { 117 Object id = null; 118 String value = (String) attributes.get(getIdAttribute()); 119 if (value != null) { 120 id = ConverterHelper.convertToType( 121 ((EntityInstanceFinder<E>) getFinder()).getEntityIdClass(), 122 value, null); 123 } 124 return id; 125 } 126 127 /** 128 * Get the parent entity for the given child entity instance. Finders for 129 * child entities should override this method to return the parent object 130 * for the given child instance. This method is used by 131 * {@link EntityInstanceFinder#getEntityURL(Object)} when 132 * {@link EntityInstanceFinder#isChild()} returns true. 133 * 134 * @param child 135 * The child to locate the parent for. 136 * @return The parent entity instance or null if unknown / unavaiable. 137 */ 138 public Object getParentEntity() { 139 return null; 140 } 141 142 /** 143 * Get the id attributes for a given entity. For simple entity id's the 144 * default implementation should suffice. Entity's with compound identity 145 * objects should override this method. This method is used by 146 * {@link EntityInstanceFinder#getEntityURL(Object)}. 147 * 148 * @return A map of attribute names to values. 149 */ 150 public Map<String, Object> getIdAttributes() { 151 Map<String, Object> attributes = Collections.emptyMap(); 152 Object idValue = null; 153 if (entity != null) { 154 for (Field f : entity.getClass().getDeclaredFields()) { 155 if (f.isAnnotationPresent(Id.class)) { 156 f.setAccessible(true); 157 try { 158 idValue = String.valueOf(f.get(entity)); 159 break; 160 } catch (IllegalArgumentException e) { 161 getLogger().log(Level.WARNING, 162 "Unable to get Id for object", e); 163 } catch (IllegalAccessException e) { 164 getLogger().log(Level.WARNING, 165 "Unable to get Id for object", e); 166 } 167 } 168 } 169 if (idValue == null) { 170 for (Method m : entity.getClass().getDeclaredMethods()) { 171 if (m.isAnnotationPresent(Id.class)) { 172 m.setAccessible(true); 173 try { 174 idValue = String.valueOf(m.invoke(entity)); 175 break; 176 } catch (IllegalArgumentException e) { 177 getLogger().log(Level.WARNING, 178 "Unable to get Id for object", e); 179 } catch (IllegalAccessException e) { 180 getLogger().log(Level.WARNING, 181 "Unable to get Id for object", e); 182 } catch (InvocationTargetException e) { 183 getLogger().log(Level.WARNING, 184 "Unable to get Id for object", e); 185 } 186 } 187 } 188 } 189 } 190 if (idValue != null) { 191 attributes = Collections.singletonMap(getIdAttribute(), idValue); 192 } 193 return attributes; 194 } 195 196 private String getIdAttribute() { 197 return ((EntityInstanceFinder<E>) getFinder()).getIdAttribute(); 54 198 } 55 199 … … 90 234 } 91 235 92 /** Remove the entity by calling {@link EntityFinder#removeEntity(Object)}. */236 /** Remove the entity using the JPA entity manager. */ 93 237 @Override 94 238 public void removeRepresentations() throws ResourceException { 95 if (!getFinder().removeEntity(entity)) { 96 getResponse().setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED); 97 } 239 getFinder().getRouter().getEntityManager().remove(entity); 98 240 } 99 241 trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityResource.java
r3577 r3606 24 24 import org.restlet.resource.Variant; 25 25 import org.sarugo.restlet.jpa.EntityFinder; 26 import org.sarugo.restlet.jpa.EntityInstanceFinder; 26 27 import org.sarugo.restlet.jpa.converter.OutputConverter; 27 28 28 29 /** 29 30 * A resource which maps to a JPA entity. The behaviour of this class is largely 30 * controlled by the {@link Entity Finder} which creates it.31 * controlled by the {@link EntityInstanceFinder} which creates it. 31 32 * 32 33 * @author Michael Terrington trunk/src/test/java/org/sarugo/restlet/jpa/FooEntity.java
r3539 r3606 12 12 Integer id; 13 13 14 String bar;14 String name; 15 15 } trunk/src/test/resources/META-INF/persistence.xml
r3539 r3606 7 7 <persistence-unit name="test"> 8 8 <class>org.sarugo.restlet.jpa.FooEntity</class> 9 <class>org.sarugo.restlet.jpa.BarEntity</class> 9 10 <properties> 10 11 <property name="hibernate.hbm2ddl.auto" value="create-drop" />
