Changeset 3563
- Timestamp:
- 28/01/08 21:21:54 (4 years ago)
- Files:
-
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityCollection.java (modified) (3 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityFinder.java (modified) (10 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityHelper.java (added)
- trunk/src/main/java/org/sarugo/restlet/jpa/EntityResource.java (modified) (4 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/PersistenceRouter.java (modified) (10 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/ConverterHelper.java (modified) (2 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/InputConverter.java (modified) (3 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/OutputConverter.java (modified) (3 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/form (added)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/form/FormEntityUpdateConverter.java (moved) (moved from trunk/src/main/java/org/sarugo/restlet/jpa/converter/FormEntityUpdateConverter.java) (3 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/json (added)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/json/JSONConverter.java (moved) (moved from trunk/src/main/java/org/sarugo/restlet/jpa/converter/JSONConverter.java) (4 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/text (added)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/text/ReferenceListConverter.java (moved) (moved from trunk/src/main/java/org/sarugo/restlet/jpa/converter/ReferenceListConverter.java) (3 diffs)
- trunk/src/main/java/org/sarugo/restlet/jpa/converter/text/StringEntityConverter.java (moved) (moved from trunk/src/main/java/org/sarugo/restlet/jpa/converter/StringEntityConverter.java) (4 diffs)
- trunk/src/test/java/org/sarugo/restlet/jpa/PersistenceResourceTests.java (modified) (2 diffs)
- trunk/src/test/java/org/sarugo/restlet/jpa/PersistenceRouterTest.java (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/src/main/java/org/sarugo/restlet/jpa/EntityCollection.java
r3539 r3563 1 /* *2 * Copyright 2007 Sarugo Pty. Ltd.1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 3 * 4 4 * Licensed under the Common Development and Distribution License, … … 17 17 package org.sarugo.restlet.jpa; 18 18 19 import java.lang.reflect.Field;20 import java.lang.reflect.InvocationTargetException;21 import java.lang.reflect.Method;22 19 import java.util.ArrayList; 23 20 import java.util.List; 24 import java.util.Map;25 import java.util.logging.Level;26 21 27 import javax.persistence.Id;28 22 import javax.persistence.Query; 29 23 30 24 import org.restlet.data.Parameter; 31 import org.restlet.data.Reference;32 25 import org.restlet.data.Request; 33 26 import org.restlet.data.Response; … … 35 28 import org.restlet.resource.Representation; 36 29 import org.restlet.resource.Resource; 30 import org.restlet.resource.ResourceException; 37 31 import org.restlet.resource.Variant; 38 import org.sarugo.restlet.jpa.converter.ConverterHelper; 32 import org.sarugo.restlet.jpa.converter.InputConverter; 33 import org.sarugo.restlet.jpa.converter.OutputConverter; 39 34 40 35 /** 41 * A resource which maps to a JPA entity. The behaviour of this class is largely42 * c ontrolled by the {@link EntityFinder} which creates it.36 * 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. 43 38 * 44 * @author michael39 * @author Michael Terrington 45 40 */ 46 41 public class EntityCollection<E> extends Resource { 47 42 48 /** 49 * Add a {@link Map} to the request attributes ({@link Request#getAttributes()}) 50 * with this name to add parameters to the JPA query. 51 */ 52 public static final String QUERY_PARAMS_ATTRIBUTE = "org.sarugo.restlet.jpa.QueryParams"; 43 private final EntityFinder<E> finder; 53 44 54 private PersistenceRouter router;45 protected final List<E> entityList; 55 46 56 protected ResourceInfo info;47 protected final int start; 57 48 58 protected E entity;49 protected final int maxResults; 59 50 60 protected List<? extends Object> entities;51 protected final boolean listComplete; 61 52 62 protected int start; 53 public EntityCollection(EntityFinder<E> finder, Request request, 54 Response response) { 55 super(finder.getContext(), request, response); 56 this.finder = finder; 57 int start = 1; 58 int maxResults = finder.getMaxResults(); 59 for (Parameter param : getRequest().getResourceRef().getQueryAsForm()) { 60 if (param.getName().equals("max")) { 61 // Set max to minimum of the finder's limit or request value 62 // This prevents DoS consumption of resources 63 maxResults = Math.min(Integer.parseInt(param.getValue()), 64 maxResults); 65 } else if (param.getName().equals("start")) { 66 start = Integer.parseInt(param.getValue()); 67 } 68 } 69 this.start = start; 70 this.maxResults = maxResults; 71 Query q = finder.getEntityListQuery(request, response); 72 q.setFirstResult(start - 1); 73 q.setMaxResults(maxResults + 1); 74 @SuppressWarnings("unchecked") 75 // Copy list to workaround OpenJPA lack of support for subList() 76 List<E> entities = new ArrayList(q.getResultList()); 77 listComplete = (entities.size() <= maxResults); 78 // truncate if the list is more than max results was returned 79 if (!listComplete) { 80 entities = entities.subList(0, maxResults); 81 } 82 entityList = entities; 83 } 63 84 64 protected int maxResults; 85 public List<E> getEntityList() { 86 return entityList; 87 } 65 88 66 protected boolean listComplete; 89 public boolean isListComplete() { 90 return listComplete; 91 } 67 92 68 public EntityCollection(PersistenceRouter router, Request request, 69 Response response, ResourceInfo ri) { 70 super(router.getContext(), request, response); 71 this.router = router; 72 this.info = ri; 73 this.start = 1; 74 this.maxResults = ri.getMaxResults(); 75 initEntity(); 76 initEntityList(); 77 if (entity != null) { 78 for (ConverterHelper converter : info.getConverters()) { 79 converter.initEntityVariants(this); 80 } 81 } else { 82 for (ConverterHelper converter : info.getConverters()) { 83 converter.initEntityListVariants(this); 84 } 85 } 86 } 93 public int getMaxResults() { 94 return maxResults; 95 } 87 96 88 protected void initEntity() { 89 String id = (String) getRequest().getAttributes().get("id"); 90 if (id != null) { 91 entity = router.getEntityManager().find(info.getModel(), 92 getAsIdType(id)); 93 if (entity == null) { 94 getResponse().setStatus(Status.CLIENT_ERROR_NOT_FOUND); 95 } 96 } 97 } 97 public int getStart() { 98 return start; 99 } 98 100 99 @SuppressWarnings("unchecked") 100 protected void initEntityList() { 101 // List of entities 102 StringBuilder search = null; 103 String searchValue = null; 104 for (Parameter param : getRequest().getResourceRef().getQueryAsForm()) { 105 if (param.getName().equals("max")) { 106 maxResults = Integer.parseInt(param.getValue()); 107 } else if (param.getName().equals("start")) { 108 start = Integer.parseInt(param.getValue()); 109 } else if (param.getName().equals("q") 110 && info.getListSearchFields() != null) { 111 search = new StringBuilder(); 112 for (String field : info.getListSearchFields()) { 113 if (search.length() > 0) { 114 search.append(" OR "); 115 } 116 search.append("o."); 117 search.append(field); 118 search.append(" LIKE :searchValue"); 119 } 120 searchValue = "%" + param.getValue() + "%"; 121 } 122 } 123 StringBuilder sb = new StringBuilder(); 124 sb.append("SELECT o FROM "); 125 sb.append(info.getModel().getSimpleName()); 126 sb.append(" o"); 127 if (info.getListJoin() != null && info.getListJoin().length() > 0) { 128 sb.append(" "); 129 sb.append(info.getListJoin()); 130 } 131 if (info.getListWhere() != null && info.getListWhere().length() > 0) { 132 sb.append(" WHERE "); 133 sb.append(info.getListWhere()); 134 if (search != null && searchValue != null) { 135 sb.append(" AND ("); 136 sb.append(search); 137 sb.append(")"); 138 } 139 } else if (search != null && searchValue != null) { 140 sb.append(" WHERE "); 141 sb.append(search); 142 } 143 if (info.getListOrder() != null && info.getListOrder().length() > 0) { 144 sb.append(" ORDER BY "); 145 sb.append(info.getListOrder()); 146 } 147 Query q = router.getEntityManager().createQuery(sb.toString()); 148 Map<String, Object> queryParams = (Map<String, Object>) getRequest() 149 .getAttributes().get(QUERY_PARAMS_ATTRIBUTE); 150 if (queryParams != null) { 151 for (Map.Entry<String, Object> param : queryParams.entrySet()) { 152 q.setParameter(param.getKey(), param.getValue()); 153 } 154 } 155 if (search != null && searchValue != null) { 156 q.setParameter("searchValue", searchValue); 157 } 158 q.setFirstResult(start - 1); 159 q.setMaxResults(maxResults + 1); 160 entities = new ArrayList(q.getResultList()); // workaround OpenJPA 161 listComplete = (entities.size() <= maxResults); 162 // truncate if the list is more than max results was returned 163 if (!listComplete) { 164 entities = entities.subList(0, maxResults); 165 } 166 } 101 public EntityFinder<E> getFinder() { 102 return finder; 103 } 167 104 168 @Override 169 public Representation getRepresentation(Variant variant) { 170 Representation result = null; 171 if (variant instanceof Representation) { 172 result = (Representation) variant; 173 } else if (entity != null) { 174 for (ConverterHelper converter : info.getConverters()) { 175 result = converter.getEntityRepresentation(variant, this); 176 if (result != null) 177 break; 178 } 179 } else { 180 for (ConverterHelper converter : info.getConverters()) { 181 result = converter.getEntityListRepresentation(variant, this); 182 if (result != null) 183 break; 184 } 185 } 186 return result; 187 } 105 @Override 106 public boolean allowPost() { 107 return !getFinder().getInputConverters().isEmpty(); 108 } 188 109 189 public String getEntityURI(Object entity) { 190 Reference ref = getEntityReference(entity); 191 if (ref != null) { 192 return ref.getRelativeRef(getRequest().getResourceRef()).toString(); 193 } else { 194 return null; 195 } 196 } 110 @Override 111 public boolean isReadable() { 112 return !getFinder().getOutputConverters().isEmpty(); 113 } 197 114 198 public Reference getEntityReference(Object entity) { 199 String entityPath = getRequest().getResourceRef() 200 .toString(false, false); 201 if (!entityPath.endsWith("/")) { 202 entityPath = entityPath.substring(0, 203 entityPath.lastIndexOf("/") + 1); 204 } 205 if (!getModel().isInstance(entity)) { 206 String name = getRouter().getName(entity.getClass()); 207 if (name == null) { 208 getLogger().warning( 209 "Attempt to get reference for unknown entity type: " 210 + entity.getClass()); 211 return null; 212 } 213 entityPath += "../" + name + "/"; 214 } 215 String entityId = getIdForEntity(entity); 216 if (entityId != null) { 217 return new Reference(entityPath + entityId).normalize(); 218 } else { 219 return null; 220 } 221 } 115 @Override 116 public Representation represent(Variant variant) throws ResourceException { 117 Representation representation = null; 118 for (OutputConverter<E> converter : getFinder().getOutputConverters()) { 119 representation = converter.represent(this, variant); 120 if (representation != null) { 121 break; 122 } 123 } 124 return representation; 125 } 222 126 223 public String getIdForEntity(Object entity) { 224 if (entity != null) { 225 for (Field f : entity.getClass().getDeclaredFields()) { 226 if (f.isAnnotationPresent(Id.class)) { 227 f.setAccessible(true); 228 try { 229 return String.valueOf(f.get(entity)); 230 } catch (IllegalArgumentException e) { 231 getLogger().log(Level.WARNING, 232 "Unable to get Id for object", e); 233 } catch (IllegalAccessException e) { 234 getLogger().log(Level.WARNING, 235 "Unable to get Id for object", e); 236 } 237 } 238 } 239 for (Method m : entity.getClass().getDeclaredMethods()) { 240 if (m.isAnnotationPresent(Id.class)) { 241 m.setAccessible(true); 242 try { 243 return String.valueOf(m.invoke(entity)); 244 } catch (IllegalArgumentException e) { 245 getLogger().log(Level.WARNING, 246 "Unable to get Id for object", e); 247 } catch (IllegalAccessException e) { 248 getLogger().log(Level.WARNING, 249 "Unable to get Id for object", e); 250 } catch (InvocationTargetException e) { 251 getLogger().log(Level.WARNING, 252 "Unable to get Id for object", e); 253 } 254 } 255 } 256 } 257 return null; 258 } 259 260 protected Object getAsIdType(String value) { 261 return getAsIdType(getModel(), value); 262 } 263 264 public static Object getAsIdType(Class<? extends Object> model, String value) { 265 Class<? extends Object> type = String.class; 266 for (Field f : model.getDeclaredFields()) { 267 if (f.isAnnotationPresent(Id.class)) { 268 type = f.getType(); 269 } 270 } 271 for (Method m : model.getDeclaredMethods()) { 272 if (m.isAnnotationPresent(Id.class)) { 273 type = m.getReturnType(); 274 } 275 } 276 return ConverterHelper.convertToType(type, value, null); 277 } 278 279 public List<? extends Object> getEntities() { 280 return entities; 281 } 282 283 public Object getEntity() { 284 return entity; 285 } 286 287 public boolean isListComplete() { 288 return listComplete; 289 } 290 291 public int getMaxResults() { 292 return maxResults; 293 } 294 295 public Class<? extends Object> getModel() { 296 return info.getModel(); 297 } 298 299 public String getName() { 300 return info.getName(); 301 } 302 303 public PersistenceRouter getRouter() { 304 return router; 305 } 306 307 public int getStart() { 308 return start; 309 } 310 311 @Override 312 public boolean allowDelete() { 313 return !info.isReadOnly() && entity != null; 314 } 315 316 @Override 317 public boolean allowPost() { 318 return !info.isReadOnly() && entity == null; 319 } 320 321 @Override 322 public boolean allowPut() { 323 return !info.isReadOnly() && entity != null; 324 } 325 326 @Override 327 public void handleDelete() { 328 getRouter().getEntityManager().remove(entity); 329 getRouter().getEntityManager().flush(); 330 } 331 332 @Override 333 public void handlePost() { 334 try { 335 entity = getModel().newInstance(); 336 updateBean(entity); 337 } catch (IllegalAccessException e) { 338 getLogger().log(Level.WARNING, "Unable to create entity.", e); 339 getResponse().setStatus(Status.SERVER_ERROR_INTERNAL); 340 } catch (InstantiationException e) { 341 getLogger().log(Level.WARNING, "Unable to create entity.", e); 342 getResponse().setStatus(Status.SERVER_ERROR_INTERNAL); 343 } 344 if (getResponse().getStatus().isSuccess()) { 345 getRouter().getEntityManager().persist(entity); 346 getRouter().getEntityManager().flush(); 347 getResponse().setStatus(Status.SUCCESS_CREATED); 348 getResponse().setRedirectRef(getEntityReference(entity)); 349 } 350 } 351 352 protected void updateBean(Object bean) { 353 for (ConverterHelper converter : info.getConverters()) { 354 if (converter.updateEntity(bean, this)) { 355 break; 356 } 357 } 358 } 359 360 @Override 361 public void handlePut() { 362 updateBean(entity); 363 if (getResponse().getStatus().isSuccess()) { 364 getRouter().getEntityManager().flush(); 365 getResponse().setEntity(getPreferredRepresentation()); 366 } 367 } 368 369 public boolean isReadOnly() { 370 return info.isReadOnly(); 371 } 127 @Override 128 public void acceptRepresentation(Representation representation) 129 throws ResourceException { 130 boolean accepted = false; 131 for (InputConverter<E> converter : getFinder().getInputConverters()) { 132 E entity = converter.acceptRepresentation(representation); 133 if (entity != null) { 134 getFinder().getRouter().getEntityManager().persist(entity); 135 accepted = true; 136 break; 137 } 138 } 139 if (!accepted) { 140 getResponse().setStatus(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); 141 } 142 } 372 143 373 144 } trunk/src/main/java/org/sarugo/restlet/jpa/EntityFinder.java
r3539 r3563 1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 * 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 at 7 * 8 * http://www.sun.com/cddl/ 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 1 17 package org.sarugo.restlet.jpa; 2 18 … … 10 26 import java.util.logging.Level; 11 27 28 import javax.persistence.EntityManager; 12 29 import javax.persistence.Id; 30 import javax.persistence.Query; 13 31 14 32 import org.restlet.Finder; … … 17 35 import org.restlet.data.Request; 18 36 import org.restlet.data.Response; 37 import org.restlet.resource.Resource; 19 38 import org.sarugo.restlet.jpa.converter.ConverterHelper; 20 39 import org.sarugo.restlet.jpa.converter.InputConverter; 21 40 import org.sarugo.restlet.jpa.converter.OutputConverter; 22 41 42 /** 43 * An entity finder maps a request to either an {@link EntityResource} (for 44 * entity instances) or {@link EntityCollection} (for entity collections). 45 * Conversions for request processing are performed by 46 * {@link InputConverter input} and {@link OutputConverter output} converters. 47 * 48 * @author Michael Terrington 49 * 50 * @param <E> 51 * The entity type. 52 */ 23 53 public class EntityFinder<E> extends Finder { 24 54 … … 35 65 private Route route; 36 66 37 public EntityFinder(PersistenceRouter router, Class<E> entityClass) { 38 super(router.getContext(), EntityResource.class); 67 private final List<Route> aliases; 68 69 private int maxResults; 70 71 /** 72 * Creates an entity finder. 73 * 74 * @param router 75 * The {@link PersistenceRouter} this finder will be be attached 76 * to. 77 * @param entityClass 78 * The entity class this finder operates on. 79 * @param resource 80 * Either {@link EntityResource} or {@link EntityCollection} or a 81 * sub-class. Sub-classes must have a constructor: 82 * {@link EntityFinder}, {@link Request}, {@link Response}. 83 */ 84 public EntityFinder(PersistenceRouter router, Class<E> entityClass, 85 Class<? extends Resource> resource) { 86 super(router.getContext(), resource); 39 87 this.router = router; 40 88 this.entityClass = entityClass; 41 this.entityIdClass = findIdType();89 this.entityIdClass = EntityHelper.findIdType(entityClass); 42 90 this.inputConverters = new ArrayList<InputConverter<E>>(); 43 91 this.outputConverters = new ArrayList<OutputConverter<E>>(); 44 } 45 92 this.aliases = new ArrayList<Route>(); 93 } 94 95 /** Get the {@link PersistenceRouter} this finder is attached to. */ 46 96 public PersistenceRouter getRouter() { 47 97 return router; 48 98 } 49 99 100 /** Get the route this finder is attached to. */ 50 101 public Route getRoute() { 51 102 return route; 52 103 } 53 104 54 public void setRoute(Route route) { 105 /** 106 * Attach this finder to a router. Should only be called by 107 * {@link PersistenceRouter}. 108 * 109 * @see PersistenceRouter#attach(String, EntityFinder) 110 * @see PersistenceRouter#attachCollection(String, EntityFinder) 111 */ 112 void setRoute(Route route) { 55 113 if (this.route != null) { 56 114 throw new IllegalStateException("Route already set."); … … 59 117 } 60 118 119 /** Get a list of additional routes this finder is associated with. */ 120 public List<Route> getAliases() { 121 return aliases; 122 } 123 124 /** Get the entity class this finder is associated with. */ 61 125 public Class<E> getEntityClass() { 62 126 return entityClass; 63 127 } 64 128 129 /** Get the list of input converters. */ 65 130 public List<InputConverter<E>> getInputConverters() { 66 131 return inputConverters; 67 132 } 68 133 134 /** Add an input converter. Returns this finder to enable call chaining. */ 135 public EntityFinder<E> addInputConverter(InputConverter<E> inputConverter) { 136 inputConverters.add(inputConverter); 137 return this; 138 } 139 140 /** Get the list of output converters. */ 69 141 public List<OutputConverter<E>> getOutputConverters() { 70 142 return outputConverters; 71 143 } 72 144 145 /** Add an output converter. Returns this finder to enable call chaining. */ 146 public EntityFinder<E> addOutputConverter(OutputConverter<E> outputConverter) { 147 outputConverters.add(outputConverter); 148 return this; 149 } 150 151 /** 152 * Constructs the associated resource target, invoking the 153 * {@link EntityFinder}, {@link Request}, {@link Response} constructor. 154 */ 73 155 @Override 74 156 protected Handler findTarget(Request request, Response response) { … … 93 175 } 94 176 177 /** 178 * Convert an id string to an object suitable for passing to 179 * {@link EntityManager#find(Class, Object)}. For simple entity id's the 180 * default implementation should suffice. Entity's with compound identity 181 * objects should override this method. 182 * 183 * @param value 184 * The id string. 185 * @return The identity object. 186 */ 95 187 public Object convertIdString(String value) { 96 188 return ConverterHelper.convertToType(entityIdClass, value, null); 97 189 } 98 190 191 /** 192 * Get the id string for a given entity. For simple entity id's the default 193 * implementation should suffice. Entity's with compound identity objects 194 * should override this method. 195 * 196 * @param entity 197 * The entity. 198 * @return The id string. 199 */ 99 200 public String getIdString(E entity) { 100 201 if (entity != null) { … … 134 235 } 135 236 237 /** 238 * Get a the attached URL for a given entity. The default implementation 239 * applies the URL template in the {@link #getRoute() attached route} with 240 * parameter "id" and the result of {@link #getIdString(Object)}. 241 * 242 * @param entity 243 * The entity. 244 * @return The URL relative to the attached {@link PersistenceRouter}. 245 */ 136 246 public String getEntityURL(E entity) { 137 247 return route.getTemplate().format( … … 139 249 } 140 250 141 public E findEntity(Request request, Response response) { 142 return findEntity((String) request.getAttributes().get("id")); 143 } 144 145 public E findEntity(String id) { 251 /** 252 * Get the entity identified by the request URL. By default this invokes 253 * {@link #getEntity(String)} with id string retrieved from the request 254 * parameter "id". 255 */ 256 public E getEntity(Request request, Response response) { 257 return getEntity((String) request.getAttributes().get("id")); 258 } 259 260 /** 261 * Get the entity identified by the id string. Invokes 262 * {@link EntityManager#find(Class, Object)} with the result of 263 * {@link #convertIdString(String)}. 264 * 265 * @param id 266 * The id string. 267 * @return The entity or null if not found. 268 */ 269 public E getEntity(String id) { 146 270 E entity = null; 147 271 if (id != null) { … … 152 276 } 153 277 278 /** 279 * Removes an entity from persistence. 280 * 281 * @param entity 282 * The entity to remove. 283 * @return true if the entity was removed. 284 */ 154 285 public boolean removeEntity(E entity) { 155 286 router.getEntityManager().remove(entity); … … 157 288 } 158 289 159 private Class<? extends Object> findIdType() { 160 Class<? extends Object> type = null; 161 for (Field f : entityClass.getDeclaredFields()) { 162 if (f.isAnnotationPresent(Id.class)) { 163 if (type == null) { 164 type = f.getType(); 165 } else { 166 // compound keys default to string type 167 type = String.class; 168 } 169 } 170 } 171 for (Method m : entityClass.getDeclaredMethods()) { 172 if (m.isAnnotationPresent(Id.class)) { 173 if (type == null) { 174 type = m.getReturnType(); 175 } else { 176 // compound keys default to string type 177 type = String.class; 178 } 179 } 180 } 181 if (type == null) { 182 // no key found - default to string 183 type = String.class; 184 } 185 return type; 290 /** 291 * Get the query for the entity collection. If the request defines a 292 * parameter "queryName", that is used to call 293 * {@link #getEntityListQuery(String)}. Alternatively, 294 * {@link #getEntityListQuery()} is called to get the default query. 295 */ 296 public Query getEntityListQuery(Request request, Response response) { 297 String queryName = (String) request.getAttributes().get("queryName"); 298 if (queryName != null) { 299 return getEntityListQuery(queryName); 300 } else { 301 return getEntityListQuery(); 302 } 303 } 304 305 /** Get a named query. */ 306 protected Query getEntityListQuery(String queryName) { 307 return router.getEntityManager().createNamedQuery(queryName); 308 } 309 310 /** Get the default query. */ 311 protected Query getEntityListQuery() { 312 return router.getEntityManager().createQuery( 313 "SELECT o from " + EntityHelper.getEntityName(getEntityClass()) 314 + " o"); 315 } 316 317 public int getMaxResults() { 318 return maxResults; 319 } 320 321 /** 322 * Set the maximum results that should be returned from a query. 323 * {@link EntityCollection} uses this to limit query results. 324 */ 325 public EntityFinder<E> setMaxResults(int maxResults) { 326 this.maxResults = maxResults; 327 if (this.maxResults == Integer.MAX_VALUE) { 328 this.maxResults--; 329 } 330 return this; 186 331 } 187 332 } trunk/src/main/java/org/sarugo/restlet/jpa/EntityResource.java
r3539 r3563 1 /** 1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 2 3 * 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 at 7 * 8 * http://www.sun.com/cddl/ 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 3 15 */ 16 4 17 package org.sarugo.restlet.jpa; 5 18 … … 14 27 import org.sarugo.restlet.jpa.converter.OutputConverter; 15 28 29 /** 30 * A resource which maps to a JPA entity. The behaviour of this class is largely 31 * controlled by the {@link EntityFinder} which creates it. 32 * 33 * @author Michael Terrington 34 */ 16 35 public class EntityResource<E> extends Resource { 17 36 … … 24 43 super(finder.getContext(), request, response); 25 44 this.finder = finder; 26 this.entity = finder. findEntity(request, response);45 this.entity = finder.getEntity(request, response); 27 46 for (OutputConverter<E> converter : finder.getOutputConverters()) { 28 47 getVariants().addAll(converter.getVariants()); … … 73 92 public void storeRepresentation(Representation representation) 74 93 throws ResourceException { 94 boolean accepted = false; 75 95 for (InputConverter<E> converter : getFinder().getInputConverters()) { 76 if (converter.storeRepresentation(this, representation)) 77 ; 96 if (converter.storeRepresentation(getEntity(), representation)) { 97 accepted = true; 98 break; 99 } 100 } 101 if (!accepted) { 102 getResponse().setStatus(Status.CLIENT_ERROR_UNSUPPORTED_MEDIA_TYPE); 78 103 } 79 104 } trunk/src/main/java/org/sarugo/restlet/jpa/PersistenceRouter.java
r3539 r3563 1 /* *2 * Copyright 2007 Sarugo Pty. Ltd.1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 3 * 4 4 * Licensed under the Common Development and Distribution License, … … 21 21 import java.util.logging.Level; 22 22 23 import javax.persistence.Entity;24 23 import javax.persistence.EntityManager; 25 24 import javax.persistence.EntityManagerFactory; … … 36 35 37 36 /** 38 * Root of JPA entity mappings. 39 * 40 * @author michael 37 * Root of JPA entity mappings. All related entities and entity collections 38 * should share a common PersistenceRouter, this enables link resolution. 39 * 40 * {@link EntityFinder}s are attached to handle entity instances or 41 * 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. 49 * 50 * @author Michael Terrington 41 51 */ 42 52 public class PersistenceRouter extends Restlet { … … 50 60 private final Map<Class, EntityFinder> entities; 51 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}. 68 * 69 * @param context 70 * Restlet context for this PersistenceRouter. 71 * @param entityManagerFactory 72 * The entity manager factory. 73 */ 52 74 public PersistenceRouter(Context context, 53 75 EntityManagerFactory entityManagerFactory) { … … 59 81 } 60 82 83 /** 84 * Get the {@link EntityManagerFactory} associated with this 85 * PersistenceRouter. 86 */ 61 87 public EntityManagerFactory getEntityManagerFactory() { 62 88 return entityManagerFactory; 63 89 } 64 90 91 /** 92 * Get a thread-local {@link EntityManager}. Typically during request 93 * 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. 99 */ 65 100 public EntityManager getEntityManager() { 66 101 EntityManager em = entityManager.get(); … … 72 107 } 73 108 109 /** 110 * Convert an entity to a URL based on an attached {@link EntityFinder}. 111 * 112 * @param entity 113 * The entity to get a URL for. 114 * @return The entity's URL relative to this PersistenceRouter. 115 * @see #attach(String, EntityFinder) 116 */ 74 117 @SuppressWarnings("unchecked") 75 118 public String getEntityURL(Object entity) { … … 82 125 } 83 126 127 /** 128 * Handle a request by: 129 * <ol> 130 * <li>Creating an {@link EntityManager} and beginning a transaction.</li> 131 * <li>Sending the request to the {@link EntityFinder} which matches the 132 * request URL.</li> 133 * <li>Commiting (or rolling-back) the transaction and clearing the 134 * {@link EntityManager}.</li> 135 * </ol> 136 */ 84 137 @Override 85 138 public void handle(Request request, Response response) { … … 104 157 } 105 158 159 /** 160 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 161 * That is, a handler for entity instances, not the entity collection. 162 * Invokes {@link #attach(EntityFinder)} with a new {@link EntityFinder}. 163 * 164 * @param entityClass 165 * The entity class to attach. 166 * @return The finder for the entity. 167 * @see #attachCollection(Class) for attaching an {@link EntityFinder} for 168 * the entity collection. 169 */ 106 170 public <E extends Object> EntityFinder<E> attach(Class<E> entityClass) { 107 return attach(new EntityFinder<E>(this, entityClass)); 108 } 109 171 return attach(new EntityFinder<E>(this, entityClass, 172 EntityResource.class)); 173 } 174 175 /** 176 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 177 * That is, a handler for entity instances, not the entity collection. 178 * Invokes {@link #attach(String, EntityFinder)} with the given 179 * {@link EntityFinder} and the URL "/{entityName}/{id}" where 180 * "{entityName}" is formed by calling 181 * {@link EntityHelper#getEntityName(Class)}. 182 * 183 * @param finder 184 * The entity finder to attach. 185 * @return The finder for the entity. 186 * @throws IllegalStateException 187 * If the finder has already been attached. 188 * @see #attachCollection(EntityFinder) for attaching an 189 * {@link EntityFinder} for the entity collection. 190 */ 110 191 public <E extends Object> EntityFinder<E> attach(EntityFinder<E> finder) { 111 return attach("/" + createName(finder.getEntityClass()) + "/{id}", 112 finder); 113 } 114 192 return attach("/" + EntityHelper.getEntityName(finder.getEntityClass()) 193 + "/{id}", finder); 194 } 195 196 /** 197 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 198 * That is, a handler for entity instances, not the entity collection. 199 * Invokes {@link #attach(String, EntityFinder)} with a new 200 * {@link EntityFinder}. 201 * 202 * @param uriPattern 203 * The URL pattern to attach the {@link EntityFinder} at. By 204 * default, {@link EntityFinder#getEntity(Request, Response)} 205 * expects the URL to contain "{id}" to define the entity id. 206 * @param entityClass 207 * The entity class to attach. 208 * @return The finder for the entity. 209 * @see #attachCollection(String, Class)) for attaching an 210 * {@link EntityFinder} for the entity collection. 211 */ 115 212 public <E extends Object> EntityFinder<E> attach(String uriPattern, 116 213 Class<E> entityClass) { 117 return attach(uriPattern, new EntityFinder<E>(this, entityClass)); 118 } 119 214 return attach(uriPattern, new EntityFinder<E>(this, entityClass, 215 EntityResource.class)); 216 } 217 218 /** 219 * Attach an {@link EntityFinder} for handling {@link EntityResource}s. 220 * That is, a handler for entity instances, not the entity collection. 221 * 222 * @param uriPattern 223 * The URL pattern to attach the {@link EntityFinder} at. By 224 * default, {@link EntityFinder#getEntity(Request, Response)} 225 * expects the URL to contain "{id}" to define the entity id. 226 * @param finder 227 * The entity finder to attach. 228 * @return The finder for the entity. 229 * @throws IllegalStateException 230 * If the finder has already been attached. 231 * @see #attachCollection(String, EntityFinder)) for attaching an 232 * {@link EntityFinder} for the entity collection. 233 */ 120 234 public <E extends Object> EntityFinder<E> attach(String uriPattern, 121 235 EntityFinder<E> finder) { … … 129 243 } 130 244 131 public Route alias(String uriPattern, EntityFinder finder) { 245 /** 246 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 247 * That is, a handler for an entity collection, not entity instances. 248 * Invokes {@link #attachCollection(EntityFinder)} with a new 249 * {@link EntityFinder}. 250 * 251 * @param entityClass 252 * The entity class to attach. 253 * @return The finder for the entity. 254 */ 255 public <E extends Object> EntityFinder<E> attachCollection( 256 Class<E> entityClass) { 257 return attachCollection(new EntityFinder<E>(this, entityClass, 258 EntityCollection.class)); 259 } 260 261 /** 262 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 263 * That is, a handler for an entity collection, not entity instances. 264 * Invokes {@link #attachCollection(String, EntityFinder)} with the given 265 * {@link EntityFinder} and the URL "/{entityName}" where "{entityName}" is 266 * formed by calling {@link EntityHelper#getEntityName(Class)}. 267 * 268 * @param finder 269 * The entity finder to attach. 270 * @return The finder for the entity. 271 * @throws IllegalStateException 272 * If the finder has already been attached. 273 */ 274 public <E extends Object> EntityFinder<E> attachCollection( 275 EntityFinder<E> finder) { 276 return attachCollection("/" 277 + EntityHelper.getEntityName(finder.getEntityClass()), finder); 278 } 279 280 /** 281 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 282 * That is, a handler for an entity collection, not entity instances. 283 * Invokes {@link #attachCollection(String, EntityFinder)} with a new 284 * {@link EntityFinder}. 285 * 286 * @param uriPattern 287 * The URL pattern to attach the {@link EntityFinder} at. 288 * @param entityClass 289 * The entity class to attach. 290 * @return The finder for the entity. 291 */ 292 public <E extends Object> EntityFinder<E> attachCollection( 293 String uriPattern, Class<E> entityClass) { 294 return attachCollection(uriPattern, new EntityFinder<E>(this, 295 entityClass, EntityCollection.class)); 296 } 297 298 /** 299 * Attach an {@link EntityFinder} for handling {@link EntityCollection}s. 300 * That is, a handler for an entity collection, not entity instances. 301 * 302 * @param uriPattern 303 * The URL pattern to attach the {@link EntityFinder} at. 304 * @param finder 305 * The entity finder to attach. 306 * @return The finder for the entity. 307 * @throws IllegalStateException 308 * If the finder has already been attached. 309 */ 310 public <E extends Object> EntityFinder<E> attachCollection( 311 String uriPattern, EntityFinder<E> finder) { 132 312 Route route = router.attach(uriPattern, finder); 313 finder.setRoute(route); 314 return finder; 315 } 316 317 /** 318 * Associate an {@link EntityFinder} with an extra URL. For 319 * {@link EntityResource} handling finders, the URL by default should 320 * contain "{id}" to define the entity id. 321 * 322 * @param uriPattern 323 * The URL pattern to alias the {@link EntityFinder} to. 324 * @param finder 325 * The entity finder to alias. 326 * @return The {@link Route} for the alias. 327 */ 328 public <E extends Object> Route alias(String uriPattern, 329 EntityFinder<E> finder) { 330 Route route = router.attach(uriPattern, finder); 331 finder.getAliases().add(route); 133 332 return route; 134 333 } 135 334 136 public <E extends Object> E findEntity(Class<E> entityClass, String value) { 335 /** 336 * Locate an entity instance of the given class by converting the id string. 337 * 338 * @param entityClass 339 * The entity class to locate. 340 * @param id 341 * The entity id as a string. 342 * @return The entity instance matching the given id, or null if none was 343 * found. 344 */ 345 public <E extends Object> E findEntity(Class<E> entityClass, String id) { 137 346 EntityFinder finder = entities.get(entityClass); 138 347 if (finder != null) { 139 Object id = finder.convertIdString(value);140 if (id != null) {348 Object idObj = finder.convertIdString(id); 349 if (idObj != null) { 141 350 try { 142 return getEntityManager().find(entityClass, id );351 return getEntityManager().find(entityClass, idObj); 143 352 } catch (PersistenceException e) { 144 353 getLogger().log(Level.INFO, "Exception finding entity.", e); … … 149 358 } 150 359 151 private String createName(Class<? extends Object> model) {152 String name = null;153 if (model.isAnnotationPresent(Entity.class)) {154 name = model.getSimpleName();155 Entity annotation = model.getAnnotation(Entity.class);156 if (annotation.name() != null && !annotation.name().equals("")) {157 name = annotation.name();158 }159 }160 return name;161 }162 163 360 } trunk/src/main/java/org/sarugo/restlet/jpa/converter/ConverterHelper.java
r3539 r3563 1 /* *2 * Copyright 2007 Sarugo Pty. Ltd.1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 3 * 4 4 * Licensed under the Common Development and Distribution License, … … 28 28 * @author michael 29 29 */ 30 public abstract class ConverterHelper { 30 public class ConverterHelper { 31 32 private ConverterHelper() { 33 } 31 34 32 35 public static <T extends Object> T convertToType(Class<T> type, trunk/src/main/java/org/sarugo/restlet/jpa/converter/InputConverter.java
r3539 r3563 1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 * 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 at 7 * 8 * http://www.sun.com/cddl/ 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 1 17 package org.sarugo.restlet.jpa.converter; 2 18 3 19 import org.restlet.resource.Representation; 4 import org.sarugo.restlet.jpa.EntityResource;5 20 6 21 public interface InputConverter<E> { … … 9 24 * if unable to process the representation. 10 25 */ 11 E acceptRepresentation(EntityResource<E> entityResource, 12 Representation representation); 26 E acceptRepresentation(Representation representation); 13 27 14 28 /** … … 19 33 * @return true if updated 20 34 */ 21 boolean storeRepresentation(E ntityResource<E> entityResource,22 Representation representation); 35 boolean storeRepresentation(E entity, Representation representation); 36 23 37 } trunk/src/main/java/org/sarugo/restlet/jpa/converter/OutputConverter.java
r3539 r3563 1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 * 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 at 7 * 8 * http://www.sun.com/cddl/ 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 1 17 package org.sarugo.restlet.jpa.converter; 2 18 … … 5 21 import org.restlet.resource.Representation; 6 22 import org.restlet.resource.Variant; 23 import org.sarugo.restlet.jpa.EntityCollection; 7 24 import org.sarugo.restlet.jpa.EntityResource; 8 25 … … 16 33 * Produce a representation for the provided entity. 17 34 * 18 * @param entity 35 * @param entityResource 19 36 * the entity to represent. 20 37 * @return 21 38 */ 22 39 Representation represent(EntityResource<E> entityResource, Variant variant); 40 41 /** 42 * Produce a representation for the provided entity collection. 43 * 44 * @param entityCollection 45 * the entity collection to represent. 46 * @return 47 */ 48 Representation represent(EntityCollection<E> entityCollection, 49 Variant variant); 23 50 } trunk/src/main/java/org/sarugo/restlet/jpa/converter/form/FormEntityUpdateConverter.java
r3539 r3563 1 package org.sarugo.restlet.jpa.converter; 1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 * 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 at 7 * 8 * http://www.sun.com/cddl/ 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 13 * implied. See the License for the specific language governing 14 * permissions and limitations under the License. 15 */ 16 17 package org.sarugo.restlet.jpa.converter.form; 2 18 3 19 import java.beans.IntrospectionException; … … 13 29 import org.restlet.data.Form; 14 30 import org.restlet.data.MediaType; 15 import org.restlet.data.Status;16 31 import org.restlet.resource.Representation; 17 import org.restlet.resource.StringRepresentation; 18 import org.sarugo.restlet.jpa.EntityResource; 32 import org.sarugo.restlet.jpa.PersistenceRouter; 33 import org.sarugo.restlet.jpa.converter.ConverterHelper; 34 import org.sarugo.restlet.jpa.converter.InputConverter; 19 35 20 public class FormEntityUpdateConverter extends ConverterHelper{36 public class FormEntityUpdateConverter<E> implements InputConverter<E> { 21 37 22 @Override 23 public boolean updateEntity(Object entity, EntityResource resource) { 24 Representation representation = resource.getRequest().getEntity(); 38 private final PersistenceRouter router; 39 40 private final Class<E> entityClass; 41 42 public FormEntityUpdateConverter(PersistenceRouter router, 43 Class<E> entityClass) { 44 this.router = router; 45 this.entityClass = entityClass; 46 } 47 48 public E acceptRepresentation(Representation representation) { 49 E entity = null; 50 try { 51 entity = entityClass.newInstance(); 52 if (!storeRepresentation(entity, representation)) { 53 entity = null; 54 } 55 } catch (InstantiationException e) { 56 router.getLogger().log(Level.WARNING, 57 "Unable to instantiate entity class.", e); 58 } catch (IllegalAccessException e) { 59 router.getLogger().log(Level.WARNING, 60 "Unable to instantiate entity class.", e); 61 } 62 return entity; 63 } 64 65 public boolean storeRepresentation(E entity, Representation representation) { 25 66 if (!MediaType.APPLICATION_WWW_FORM.equals(representation 26 67 .getMediaType())) { 27 68 return false; 28 69 } 29 Form data = resource.getRequest().getEntityAsForm(); 30 updateEntityFromForm(entity, resource, data); 31 return true; 70 Form data = new Form(representation); 71 return updateEntityFromForm(entity, data); 32 72 } 33 73 34 protected void updateEntityFromForm(Object entity,35 EntityResource resource, Form data) {74 protected boolean updateEntityFromForm(E entity, Form data) { 75 boolean updated = true; 36 76 try { 37 77 for (PropertyDescriptor p : Introspector.getBeanInfo( … … 53 93 entity, 54 94 ConverterHelper.convertToType(p 55 .getPropertyType(), value, r esource));95 .getPropertyType(), value, router)); 56 96 } 57 97 } 58 98 } 59 99 } catch (IntrospectionException e) { 60 r esource.getLogger().log(Level.WARNING,100 router.getLogger().log(Level.WARNING, 61 101 "Unable to update entity from form.", e); 62 resource.getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);102 updated = false; 63 103 } catch (IllegalAccessException e) { 64 r esource.getLogger().log(Level.WARNING,104 router.getLogger().log(Level.WARNING, 65 105 "Unable to update entity from form.", e); 66 resource.getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);106 updated = false; 67 107 } catch (IllegalArgumentException e) { 68 r esource.getLogger().log(Level.WARNING,108 router.getLogger().log(Level.WARNING, 69 109 "Unable to update entity from form.", e); 70 resource.getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);110 updated = false; 71 111 } catch (InvocationTargetException e) { 72 r esource.getLogger().log(Level.WARNING,112 router.getLogger().log(Level.WARNING, 73 113 "Unable to update entity from form.", e); 74 resource.getResponse().setStatus(Status.SERVER_ERROR_INTERNAL);114 updated = false; 75 115 } catch (ParseException e) { 76 r esource.getLogger().log(Level.INFO,116 router.getLogger().log(Level.INFO, 77 117 "Unable to update entity from form.", e); 78 resource.getResponse().setStatus(Status.CLIENT_ERROR_BAD_REQUEST); 79 resource.getResponse().setEntity( 80 new StringRepresentation(e.toString())); 118 updated = false; 81 119 } 120 return updated; 82 121 } 83 122 trunk/src/main/java/org/sarugo/restlet/jpa/converter/json/JSONConverter.java
r3539 r3563 1 /* *2 * Copyright 2007 Sarugo Pty. Ltd.1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 3 * 4 4 * Licensed under the Common Development and Distribution License, … … 15 15 */ 16 16 17 package org.sarugo.restlet.jpa.converter ;17 package org.sarugo.restlet.jpa.converter.json; 18 18 19 19 import java.beans.IntrospectionException; … … 22 22 import java.lang.reflect.InvocationTargetException; 23 23 import java.util.ArrayList; 24 import java.util.Collection; 25 import java.util.Collections; 24 26 import java.util.HashMap; 25 27 import java.util.List; … … 31 33 import org.restlet.resource.StringRepresentation; 32 34 import org.restlet.resource.Variant; 35 import org.sarugo.restlet.jpa.EntityCollection; 33 36 import org.sarugo.restlet.jpa.EntityResource; 37 import org.sarugo.restlet.jpa.converter.OutputConverter; 34 38 35 39 import com.sdicons.json.mapper.JSONMapper; 36 40 import com.sdicons.json.mapper.MapperException; 37 41 38 public class JSONConverter extends ConverterHelper{42 public class JSONConverter<E> implements OutputConverter<E> { 39 43 40 @Override 41 public void initEntityVariants(EntityResource resource) { 42 resource.getVariants().add(new Variant(MediaType.APPLICATION_JSON)); 43 } 44 private static final Collection<Variant> VARIANTS = Collections 45 .singleton(new Variant(MediaType.APPLICATION_JSON)); 44 46 45 @Override 46 public Representation getEntityRepresentation(Variant variant, 47 EntityResource resource) { 48 if (MediaType.APPLICATION_JSON.equals(variant.getMediaType())) { 49 try { 50 // Read each property from the entity and store in a map. 51 // For properties which point to other entities, convert 52 // to a URL and store that instead of the object. 53 Map<String, Object> entityValues = new HashMap<String, Object>(); 54 for (PropertyDescriptor p : Introspector.getBeanInfo( 55 resource.getModel()).getPropertyDescriptors()) { 56 if (p.getWriteMethod() != null && p.getReadMethod() != null) { 57 Object value = p.getReadMethod().invoke( 58 resource.getEntity()); 59 if (resource.getRouter().getName(value.getClass()) != null) { 60 // Convert "known" entities to URLs 61 value = resource.getEntityReference(value) 62 .toString(false, false); 63 } 64 entityValues.put(p.getName(), value); 65 } 66 } 67 return new StringRepresentation(JSONMapper.toJSON(entityValues) 68 .render(true), variant.getMediaType()); 69 } catch (MapperException e) { 70 resource.getLogger().log(Level.WARNING, 71 "Unable to map entity to JSON", e); 72 } catch (IntrospectionException e) { 73 resource.getLogger().log(Level.WARNING, 74 "Unable to map entity to JSON", e); 75 } catch (IllegalArgumentException e) { 76 resource.getLogger().log(Level.WARNING, 77 "Unable to map entity to JSON", e); 78 } catch (IllegalAccessException e) { 79 resource.getLogger().log(Level.WARNING, 80 "Unable to map entity to JSON", e); 81 } catch (InvocationTargetException e) { 82 resource.getLogger().log(Level.WARNING, 83 "Unable to map entity to JSON", e); 84 } 85 } 86 return null; 87 } 47 public Collection<Variant> getVariants() { 48 return VARIANTS; 49 } 88 50 89 @Override 90 public void initEntityListVariants(EntityResource resource) { 91 resource.getVariants().add(new Variant(MediaType.APPLICATION_JSON)); 92 } 51 public Representation represent(EntityResource<E> entityResource, 52 Variant variant) { 53 if (MediaType.APPLICATION_JSON.equals(variant.getMediaType())) { 54 try { 55 // Read each property from the entity and store in a map. 56 // For properties which point to other entities, convert 57 // to a URL and store that instead of the object. 58 Map<String, Object> entityValues = new HashMap<String, Object>(); 59 for (PropertyDescriptor p : Introspector.getBeanInfo( 60 entityResource.getFinder().getEntityClass()) 61 .getPropertyDescriptors()) { 62 if (p.getWriteMethod() != null && p.getReadMethod() != null) { 63 Object value = p.getReadMethod().invoke( 64 entityResource.getEntity()); 65 String url = entityResource.getFinder().getRouter() 66 .getEntityURL(value); 67 if (url != null) { 68 value = url; 69 } 70 entityValues.put(p.getName(), value); 71 } 72 } 73 return new StringRepresentation(JSONMapper.toJSON(entityValues) 74 .render(true), variant.getMediaType()); 75 } catch (MapperException e) { 76 entityResource.getLogger().log(Level.WARNING, 77 "Unable to map entity to JSON", e); 78 } catch (IntrospectionException e) { 79 entityResource.getLogger().log(Level.WARNING, 80 "Unable to map entity to JSON", e); 81 } catch (IllegalArgumentException e) { 82 entityResource.getLogger().log(Level.WARNING, 83 "Unable to map entity to JSON", e); 84 } catch (IllegalAccessException e) { 85 entityResource.getLogger().log(Level.WARNING, 86 "Unable to map entity to JSON", e); 87 } catch (InvocationTargetException e) { 88 entityResource.getLogger().log(Level.WARNING, 89 "Unable to map entity to JSON", e); 90 } 91 } 92 return null; 93 } 93 94 94 @Override 95 public Representation getEntityListRepresentation(Variant variant, 96 EntityResource resource) { 97 if (MediaType.APPLICATION_JSON.equals(variant.getMediaType())) { 98 try { 99 List<String> urlList = new ArrayList<String>(resource 100 .getEntities().size()); 101 for (Object entity : resource.getEntities()) { 102 urlList.add(resource.getEntityURI(entity)); 103 } 104 Map<String, Object> listObject = new HashMap<String, Object>(1, 1.0f); 105 listObject.put("entities", urlList); 106 return new StringRepresentation(JSONMapper.toJSON(listObject) 107 .render(true), variant.getMediaType()); 108 } catch (MapperException e) { 109 resource.getLogger().log(Level.WARNING, 110 "Unable to map entity list to JSON", e); 111 } catch (IllegalArgumentException e) { 112 resource.getLogger().log(Level.WARNING, 113 "Unable to map entity list to JSON", e); 114 } 115 } 116 return null; 117 } 95 public Representation represent(EntityCollection<E> entityCollection, 96 Variant variant) { 97 if (MediaType.APPLICATION_JSON.equals(variant.getMediaType())) { 98 try { 99 List<String> urlList = new ArrayList<String>(entityCollection 100 .getEntityList().size()); 101 for (E entity : entityCollection.getEntityList()) { 102 urlList.add(entityCollection.getFinder().getEntityURL( 103 entity)); 104 } 105 Map<String, Object> listObject = new HashMap<String, Object>(1, 106 1.0f); 107 listObject.put("entities", urlList); 108 return new StringRepresentation(JSONMapper.toJSON(listObject) 109 .render(true), variant.getMediaType()); 110 } catch (MapperException e) { 111 entityCollection.getLogger().log(Level.WARNING, 112 "Unable to map entity list to JSON", e); 113 } catch (IllegalArgumentException e) { 114 entityCollection.getLogger().log(Level.WARNING, 115 "Unable to map entity list to JSON", e); 116 } 117 } 118 return null; 119 } 118 120 119 121 } trunk/src/main/java/org/sarugo/restlet/jpa/converter/text/ReferenceListConverter.java
r3539 r3563 1 /* *2 * Copyright 2007 Sarugo Pty. Ltd.1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 3 * 4 4 * Licensed under the Common Development and Distribution License, … … 15 15 */ 16 16 17 package org.sarugo.restlet.jpa.converter; 17 package org.sarugo.restlet.jpa.converter.text; 18 19 import java.util.ArrayList; 20 import java.util.Collection; 18 21 19 22 import org.restlet.data.MediaType; … … 21 24 import org.restlet.resource.Representation; 22 25 import org.restlet.resource.Variant; 26 import org.sarugo.restlet.jpa.EntityCollection; 23 27 import org.sarugo.restlet.jpa.EntityResource; 28 import org.sarugo.restlet.jpa.converter.OutputConverter; 24 29 25 public class ReferenceListConverter extends ConverterHelper{30 public class ReferenceListConverter<E> implements OutputConverter<E> { 26 31 27 @Override 28 public void initEntityListVariants(EntityResource resource) { 29 resource.getVariants().add(new Variant(MediaType.TEXT_URI_LIST)); 30 resource.getVariants().add(new Variant(MediaType.TEXT_HTML)); 31 } 32 private static final Collection<Variant> VARIANTS = new ArrayList<Variant>( 33 2); 34 static { 35 VARIANTS.add(new Variant(MediaType.TEXT_URI_LIST)); 36 VARIANTS.add(new Variant(MediaType.TEXT_HTML)); 37 } 32 38 33 @Override 34 public Representation getEntityListRepresentation(Variant variant, 35 EntityResource resource) { 36 if (MediaType.TEXT_URI_LIST.equals(variant.getMediaType()) 37 || MediaType.TEXT_HTML.equals(variant.getMediaType())) { 38 ReferenceList list = new ReferenceList(resource.getEntities() 39 .size()); 40 list.setIdentifier(resource.getRequest().getResourceRef()); 41 for (Object o : resource.getEntities()) { 42 list.add(resource.getEntityReference(o)); 43 } 44 if (MediaType.TEXT_URI_LIST.equals(variant.getMediaType())) { 45 return list.getTextRepresentation(); 46 } else { 47 return list.getWebRepresentation(); 48 } 49 } 50 return null; 51 } 39 public Collection<Variant> getVariants() { 40 return VARIANTS; 41 } 42 43 public Representation represent(EntityResource<E> entityResource, 44 Variant variant) { 45 return null; 46 } 47 48 public Representation represent(EntityCollection<E> entityCollection, 49 Variant variant) { 50 if (VARIANTS.contains(variant.getMediaType())) { 51 ReferenceList list = new ReferenceList(entityCollection 52 .getEntityList().size()); 53 list.setIdentifier(entityCollection.getRequest().getResourceRef()); 54 for (E o : entityCollection.getEntityList()) { 55 list.add(entityCollection.getFinder().getEntityURL(o)); 56 } 57 if (MediaType.TEXT_URI_LIST.equals(variant.getMediaType())) { 58 return list.getTextRepresentation(); 59 } else { 60 return list.getWebRepresentation(); 61 } 62 } 63 return null; 64 } 52 65 53 66 } trunk/src/main/java/org/sarugo/restlet/jpa/converter/text/StringEntityConverter.java
r3539 r3563 1 /* *2 * Copyright 2007 Sarugo Pty. Ltd.1 /* 2 * Copyright 2007, 2008 Sarugo Pty Ltd 3 3 * 4 4 * Licensed under the Common Development and Distribution License, … … 15 15 */ 16 16 17 package org.sarugo.restlet.jpa.converter ;17 package org.sarugo.restlet.jpa.converter.text; 18 18 19 19 import java.util.Collection; … … 24 24 import org.restlet.resource.StringRepresentation; 25 25 import org.restlet.resource.Variant; 26 import org.sarugo.restlet.jpa.EntityCollection; 26 27 import org.sarugo.restlet.jpa.EntityResource; 28 import org.sarugo.restlet.jpa.converter.OutputConverter; 27 29 28 30 public class StringEntityConverter<E> implements OutputConverter<E> { … … 44 46 } 45 47 48 public Representation represent(EntityCollection<E> entityCollection, 49 Variant variant) { 50 return null; 51 } 52 46 53 } trunk/src/test/java/org/sarugo/restlet/jpa/PersistenceResourceTests.java
r3539 r3563 17 17 package org.sarugo.restlet.jpa; 18 18 19 import java.util.ArrayList;20 19 import java.util.logging.Level; 21 20 … … 30 29 import org.restlet.data.Request; 31 30 import org.restlet.data.Response; 32 import org.sarugo.restlet.jpa.converter. JSONConverter;33 import org.sarugo.restlet.jpa.converter. StringEntityConverter;31 import org.sarugo.restlet.jpa.converter.json.JSONConverter; 32 import org.sarugo.restlet.jpa.converter.text.StringEntityConverter; 34 33 35 34 public class PersistenceResourceTests extends TestCase { 36 35 37 @Entity(name = "foo")38 public static class FooEntity {39 @SuppressWarnings("unused")40 @Id41 private String id = "A";36 @Entity(name = "foo") 37 public static class FooEntity { 38 @SuppressWarnings("unused") 39 @Id 40 private String id = "A"; 42 41 43 @SuppressWarnings("unused")44 private String info = "I'm a foo.";42 @SuppressWarnings("unused") 43 private String info = "I'm a foo."; 45 44 46 @Override47 public String toString() {48 return id + ":" + info;49 }50 }45 @Override 46 public String toString() { 47 return id + ":" + info; 48 } 49 } 51 50 52 @Entity(name = "bar")53 public static class BarEntity {54 @Id55 private String id = "B";51 @Entity(name = "bar") 52 public static class BarEntity { 53 @Id 54 private String id = "B"; 56 55 57 private String info = "I'm a bar.";56 private String info = "I'm a bar."; 58 57 59 private FooEntity foo = new FooEntity();58 private FooEntity foo = new FooEntity(); 60 59 61 public FooEntity getFoo() {62 return foo;63 }60 public FooEntity getFoo() { 61 return foo; 62 } 64 63 65 public void setFoo(FooEntity foo) {66 this.foo = foo;67 }64 public void setFoo(FooEntity foo) { 65 this.foo = foo; 66 } 68 67 69 public String getId() {70 return id;71 }68 public String getId() { 69 return id; 70 } 72 71 73 public void setId(String id) {74 this.id = id;75 }72 public void setId(String id) { 73 this.id = id; 74 } 76 75 77 public String getInfo() {78 return info;79 }76 public String getInfo() { 77 return info; 78 } 80 79 81 public void setInfo(String info) {82 this.info = info;83 }80 public void setInfo(String info) { 81 this.info = info; 82 } 84 83 85 }84 } 86 85 87 private static class TestResource extends EntityResource{86 private static class TestFinder<E> extends EntityFinder<E> { 88 87 89 public TestResource(PersistenceRouter router, Request request, 90 Response response, ResourceInfo ri) { 91 super(router, request, response, ri); 92 } 88 private final E testEntity; 93 89 94 @Override 95 protected void initEntity() { 96 String id = (String) getRequest().getAttributes().get("id"); 97 if (id != null) { 98 try { 99 entity = getModel().newInstance(); 100 } catch (Exception e) { 101 throw new RuntimeException(e); 102 } 103 } 104 } 90 public TestFinder(PersistenceRouter router, Class<E> entityClass) 91 throws InstantiationException, IllegalAccessException { 92 super(router, entityClass, EntityResource.class); 93 testEntity = entityClass.newInstance(); 94 } 105 95 106 @Override 107 protected void initEntityList() { 108 entities = new ArrayList<Object>(1); 109 try { 110 ((ArrayList<Object>) entities).add(getModel().newInstance()); 111 } catch (Exception e) { 112 throw new RuntimeException(e); 113 } 114 } 115 } 96 @Override 97 public E getEntity(String id) { 98 return testEntity; 99 } 100 } 116 101 117 private PersistenceRouter router;102 private PersistenceRouter router; 118 103 119 @Override 120 protected void setUp() throws Exception { 121 super.setUp(); 122 router = new PersistenceRouter(null, Persistence 123 .createEntityManagerFactory("test")); 124 router.getLogger().setLevel(Level.OFF); 125 router.attach(FooEntity.class).setResourceClass(TestResource.class) 126 .addConverter(new StringEntityConverter()); 127 router.attach(BarEntity.class).setResourceClass(TestResource.class) 128 .addConverter(new JSONConverter()); 129 } 104 @Override 105 protected void setUp() throws Exception { 106 super.setUp(); 107 router = new PersistenceRouter(null, Persistence 108 .createEntityManagerFactory("test")); 109 router.getLogger().setLevel(Level.OFF); 110 router.attach(new TestFinder<FooEntity>(router, FooEntity.class)) 111 .getOutputConverters().add( 112 new StringEntityConverter<FooEntity>()); 113 router.attach(new TestFinder<BarEntity>(router, BarEntity.class)) 114 .getOutputConverters().add(new JSONConverter<BarEntity>()); 115 } 130 116 131 public void testEntityReferences() throws Exception { 132 Request request = new Request(Method.GET, "http://localhost/foo/A"); 133 EntityResource resource = new TestResource(router, request, null, 134 new ResourceInfo("foo", FooEntity.class)); 135 assertEquals(".", resource.getEntityURI(new FooEntity())); 136 assertEquals("http://localhost/foo/A", resource.getEntityReference( 137 new FooEntity()).toString()); 138 assertEquals("../bar/B", resource.getEntityURI(new BarEntity())); 139 assertEquals("http://localhost/bar/B", resource.getEntityReference( 140 new BarEntity()).toString()); 141 } 117 public void testEntityReferences() throws Exception { 118 assertEquals("/foo/A", router.getEntityURL(new FooEntity())); 119 assertEquals("/bar/B", router.getEntityURL(new BarEntity())); 120 } 142 121 143 public void testEntityRepresentation() throws Exception {144 Request request = new Request(Method.GET, "/foo/A");145 Response response = router.handle(request);146 assertEquals("A:I'm a foo.", response.getEntity().getText());147 }122 public void testEntityRepresentation() throws Exception { 123 Request request = new Request(Method.GET, "/foo/A"); 124 Response response = router.handle(request); 125 assertEquals("A:I'm a foo.", response.getEntity().getText()); 126 } 148 127 149 public void testJSONEntityRepresentation() throws Exception {150 Reference ref = new Reference("http://localhost/bar/B");151 ref.setBaseRef(new Reference("http://localhost"));152 Request request = new Request(Method.GET, ref);153 Response response = router.handle(request);154 String expected = "{\n \"foo\" : \"http://localhost/foo/A\",\n \"info\" : \"I'm a bar.\",\n \"id\" : \"B\"\n}";155 assertEquals(expected, response.getEntity().getText());156 }128 public void testJSONEntityRepresentation() throws Exception { 129 Reference ref = new Reference("http://localhost/bar/B"); 130 ref.setBaseRef(new Reference("http://localhost")); 131 Request request = new Request(Method.GET, ref); 132 Response response = router.handle(request); 133 String expected = "{\n \"foo\" : \"/foo/A\",\n \"info\" : \"I'm a bar.\",\n \"id\" : \"B\"\n}"; 134 assertEquals(expected, response.getEntity().getText()); 135 } 157 136 158 137 } trunk/src/test/java/org/sarugo/restlet/jpa/PersistenceRouterTest.java
r3539 r3563 6 6 import org.restlet.Component; 7 7 import org.restlet.data.Protocol; 8 import org.sarugo.restlet.jpa.converter. StringEntityConverter;8 import org.sarugo.restlet.jpa.converter.text.StringEntityConverter; 9 9 10 10 public class PersistenceRouterTest {
