Changeset 3606

Show
Ignore:
Timestamp:
11/02/08 13:53:31 (4 years ago)
Author:
michael
Message:

Huge refactor to better support child entities and make cleaner customisation for complex entities. Refs #4, spent 8

Files:

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 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  
    171package org.sarugo.restlet.jpa; 
    182 
    19 import java.lang.reflect.Constructor; 
    20 import java.lang.reflect.Field; 
    21 import java.lang.reflect.InvocationTargetException; 
    22 import java.lang.reflect.Method; 
    233import java.util.ArrayList; 
    24 import java.util.Collections; 
    254import 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; 
    315 
    326import org.restlet.Finder; 
     
    359import org.restlet.data.Request; 
    3610import org.restlet.data.Response; 
    37 import org.sarugo.restlet.jpa.converter.ConverterHelper; 
    3811import org.sarugo.restlet.jpa.converter.InputConverter; 
    3912import org.sarugo.restlet.jpa.converter.OutputConverter; 
    40 import org.sarugo.restlet.jpa.resource.EntityCollection; 
    4113import org.sarugo.restlet.jpa.resource.EntityResource; 
    4214 
    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; 
     15public abstract class EntityFinder<E> extends Finder { 
    5716 
    58     private final Class<E> entityClass
     17    protected final Class<? extends EntityResource> resource
    5918 
    60     private final Class<? extends Object> entityIdClass; 
     19    protected final Class<E> entityClass; 
    6120 
    62     private final EntityRouter router; 
     21    protected final EntityRouter router; 
    6322 
    64     private final List<InputConverter<E>> inputConverters; 
     23    protected final List<InputConverter<E>> inputConverters; 
    6524 
    66     private final List<OutputConverter<E>> outputConverters; 
     25    protected final List<OutputConverter<E>> outputConverters; 
    6726 
    68     private Route route; 
     27    protected Route route; 
    6928 
    70     private final List<Route> aliases; 
     29    protected final List<Route> aliases; 
    7130 
    72     private int maxResults; 
    73  
    74     /** 
    75      * Creates an entity finder. 
    76      *  
    77      * @param router 
    78      *            The {@link EntityRouter} this finder will be be attached 
    79      *            to. 
    80      * @param entityClass 
    81      *            The entity class this finder operates on. 
    82      * @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      */ 
    8731    public EntityFinder(EntityRouter router, Class<E> entityClass, 
    8832            Class<? extends EntityResource> resource) { 
     
    9135        this.router = router; 
    9236        this.entityClass = entityClass; 
    93         this.entityIdClass = EntityHelper.findIdType(entityClass); 
    9437        this.inputConverters = new ArrayList<InputConverter<E>>(); 
    9538        this.outputConverters = new ArrayList<OutputConverter<E>>(); 
     
    11154     * {@link EntityRouter}. 
    11255     *  
    113      * @see EntityRouter#attach(String, EntityFinder) 
    114      * @see EntityRouter#attachCollection(String, EntityFinder) 
     56     * @see EntityRouter#attachInstance(String, EntityCollectionFinder) 
     57     * @see EntityRouter#attachCollection(String, EntityCollectionFinder) 
    11558     */ 
    11659    void setRoute(Route route) { 
     
    15598    /** 
    15699     * Constructs the associated resource target, invoking the 
    157      * {@link EntityFinder}, {@link Request}, {@link Response} constructor. 
     100     * {@link EntityCollectionFinder}, {@link Request}, {@link Response} 
     101     * constructor. 
    158102     */ 
    159103    @Override 
    160104    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) { 
    165107            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); 
    179108        } 
    180         return null
     109        return resource
    181110    } 
    182111 
    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(); 
    196113 
    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. 
    201      *  
    202      * @param entity 
    203      *            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 implementation 
    245      * applies the URL template in the {@link #getRoute() attached route} with 
    246      * parameter "id" and the result of {@link #getIdString(Object)}. 
    247      *  
    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     } 
    338114} 
  • trunk/src/main/java/org/sarugo/restlet/jpa/EntityHelper.java

    r3563 r3606  
    6565     * or classes without any id fields/methods the type is set to 
    6666     * {@link String}. This makes the default 
    67      * {@link EntityFinder#convertIdString(String)} a no-op for compound or 
     67     * {@link EntityInstanceFinder#convertIdString(String)} a no-op for compound or 
    6868     * undefined keys. 
    6969     *  
  • trunk/src/main/java/org/sarugo/restlet/jpa/EntityInstanceFinder.java

    r3577 r3606  
    1818 
    1919import java.lang.reflect.Constructor; 
    20 import java.lang.reflect.Field; 
    2120import 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; 
     21import java.util.Map; 
    2622import java.util.logging.Level; 
    2723 
    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; 
     24import org.restlet.data.Reference; 
    3525import org.restlet.data.Request; 
    3626import org.restlet.data.Response; 
    37 import org.sarugo.restlet.jpa.converter.ConverterHelper; 
    3827import org.sarugo.restlet.jpa.converter.InputConverter; 
    3928import org.sarugo.restlet.jpa.converter.OutputConverter; 
    4029import org.sarugo.restlet.jpa.resource.EntityCollection; 
     30import org.sarugo.restlet.jpa.resource.EntityInstance; 
    4131import org.sarugo.restlet.jpa.resource.EntityResource; 
    4232 
     
    5242 *            The entity type. 
    5343 */ 
    54 public class EntityFinder<E> extends Finder { 
    55      
    56     private final Class<? extends EntityResource> resource; 
    57  
    58     private final Class<E> entityClass; 
     44public class EntityInstanceFinder<E> extends EntityFinder<E> { 
     45 
     46    private final EntityRouter children; 
    5947 
    6048    private final Class<? extends Object> entityIdClass; 
    6149 
    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; 
    7351 
    7452    /** 
     
    7654     *  
    7755     * @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. 
    8057     * @param entityClass 
    8158     *            The entity class this finder operates on. 
    8259     * @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"; 
    9368        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)}. 
    20187     *  
    20288     * @param entity 
    20389     *            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); 
    220104                } 
    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; 
    237110                } 
    238111            } 
    239112        } 
    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. 
    247180     *  
    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 
    338253} 
  • trunk/src/main/java/org/sarugo/restlet/jpa/EntityRouter.java

    r3602 r3606  
    1919import java.util.HashMap; 
    2020import java.util.Map; 
    21 import java.util.logging.Level; 
     21import java.util.Set; 
     22import java.util.concurrent.CopyOnWriteArraySet; 
    2223 
    2324import javax.persistence.EntityManager; 
    2425import javax.persistence.EntityManagerFactory; 
    25 import javax.persistence.PersistenceException; 
    2626 
    2727import org.restlet.Context; 
     
    2929import org.restlet.Route; 
    3030import org.restlet.Router; 
     31import org.restlet.data.Method; 
    3132import org.restlet.data.Request; 
    3233import org.restlet.data.Response; 
     34import org.restlet.data.Status; 
    3335import org.sarugo.restlet.jpa.resource.EntityCollection; 
    34 import org.sarugo.restlet.jpa.resource.EntityResource; 
     36import org.sarugo.restlet.jpa.resource.EntityInstance; 
    3537 
    3638/** 
    3739 * 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. 
    3941 *  
    40  * {@link EntityFinder}s are attached to handle entity instances or 
     42 * {@link EntityInstanceFinder}s are attached to handle entity instances or 
    4143 * 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}. 
    4946 *  
    5047 * @author Michael Terrington 
    5148 */ 
    52 public class EntityRouter extends Restlet { 
    53  
    54     private final Router router; 
     49public class EntityRouter extends Router { 
    5550 
    5651    private final EntityManagerFactory entityManagerFactory; 
     
    5853    private final ThreadLocal<EntityManager> entityManager; 
    5954 
    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}. 
    6866     *  
    6967     * @param context 
    70      *            Restlet context for this PersistenceRouter. 
     68     *            Restlet context for this router. 
    7169     * @param entityManagerFactory 
    7270     *            The entity manager factory. 
     
    7573            EntityManagerFactory entityManagerFactory) { 
    7674        super(context); 
    77         this.router = new Router(context); 
    7875        this.entityManagerFactory = entityManagerFactory; 
    7976        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. 
    8693     */ 
    8794    public EntityManagerFactory getEntityManagerFactory() { 
     
    9299     * Get a thread-local {@link EntityManager}. Typically during request 
    93100     * 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. 
    99107     */ 
    100108    public EntityManager getEntityManager() { 
     
    113121 
    114122    /** 
    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}. 
    116125     *  
    117126     * @param entity 
    118127     *            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) 
    121130     */ 
    122131    @SuppressWarnings("unchecked") 
    123132    public String getEntityURL(Object entity) { 
    124         EntityFinder finder = entities.get(entity.getClass()); 
     133        EntityInstanceFinder finder = entities.get(entity.getClass()); 
    125134        String url = null; 
    126135        if (finder != null) { 
     136            // this router holds the entity instance finder 
    127137            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            } 
    128154        } 
    129155        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; 
    130186    } 
    131187 
     
    136192    @Override 
    137193    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}. 
    145219     *  
    146220     * @param entityClass 
    147221     *            The entity class to attach. 
    148222     * @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 
    162238     * "{entityName}" is formed by calling 
    163      * {@link EntityHelper#getEntityName(Class)}. 
     239     * {@link EntityURLHelper#getEntityName(Class)}. 
    164240     *  
    165241     * @param finder 
     
    168244     * @throws IllegalStateException 
    169245     *             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}. 
    183262     *  
    184263     * @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)} 
    187267     *            expects the URL to contain "{id}" to define the entity id. 
    188268     * @param entityClass 
     
    190270     * @return The finder for the entity. 
    191271     * @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. 
    203284     *  
    204285     * @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)} 
    207289     *            expects the URL to contain "{id}" to define the entity id. 
    208290     * @param finder 
     
    212294     *             If the finder has already been attached. 
    213295     * @see #attachCollection(String, EntityFinder)) for attaching an 
    214      *      {@link EntityFinder} for the entity collection. 
    215      */ 
    216     public <E extends Object> EntityFinder<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) { 
    218300        if (entities.containsKey(finder.getEntityClass())) { 
    219301            throw new IllegalStateException("Entity class is already mapped."); 
    220302        } 
    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); 
    222308        finder.setRoute(route); 
    223309        entities.put(finder.getEntityClass(), finder); 
     
    226312 
    227313    /** 
    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}. 
    232319     *  
    233320     * @param entityClass 
     
    235322     * @return The finder for the entity. 
    236323     */ 
    237     public <E extends Object> EntityFinder<E> attachCollection( 
     324    public <E extends Object> EntityCollectionFinder<E> attachCollection( 
    238325            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)}. 
    249338     *  
    250339     * @param finder 
     
    254343     *             If the finder has already been attached. 
    255344     */ 
    256     public <E extends Object> EntityFinder<E> attachCollection( 
    257             EntityFinder<E> finder) { 
     345    public <E extends Object> EntityCollectionFinder<E> attachCollection( 
     346            EntityCollectionFinder<E> finder) { 
    258347        return attachCollection("/" 
    259348                + EntityHelper.getEntityName(finder.getEntityClass()), finder); 
     
    261350 
    262351    /** 
    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}. 
    267357     *  
    268358     * @param uriPattern 
    269      *            The URL pattern to attach the {@link EntityFinder} at. 
     359     *            The URL pattern to attach the {@link EntityInstanceFinder} at. 
    270360     * @param entityClass 
    271361     *            The entity class to attach. 
    272362     * @return The finder for the entity. 
    273363     */ 
    274     public <E extends Object> EntityFinder<E> attachCollection( 
     364    public <E extends Object> EntityCollectionFinder<E> attachCollection( 
    275365            String uriPattern, Class<E> entityClass) { 
    276         return attachCollection(uriPattern, new EntityFinder<E>(this, 
     366        return attachCollection(uriPattern, new EntityCollectionFinder<E>(this, 
    277367                entityClass, EntityCollection.class)); 
    278368    } 
    279369 
    280370    /** 
    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. 
    283374     *  
    284375     * @param uriPattern 
    285      *            The URL pattern to attach the {@link EntityFinder} at. 
     376     *            The URL pattern to attach the {@link EntityInstanceFinder} at. 
    286377     * @param finder 
    287378     *            The entity finder to attach. 
     
    290381     *             If the finder has already been attached. 
    291382     */ 
    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); 
    295390        finder.setRoute(route); 
    296391        return finder; 
     
    299394    /** 
    300395     * Associate an {@link EntityFinder} with an extra URL. For 
    301      * {@link EntityResource} handling finders, the URL by default should 
    302      * 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})
    303398     *  
    304399     * @param uriPattern 
     
    310405    public <E extends Object> Route alias(String uriPattern, 
    311406            EntityFinder<E> finder) { 
    312         Route route = router.attach(uriPattern, finder); 
     407        Route route = attach(uriPattern, finder); 
    313408        finder.getAliases().add(route); 
    314409        return route; 
    315410    } 
    316411 
    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); 
    337434            } 
    338         } 
    339         return null; 
    340     } 
    341  
     435            parents.add(child); 
     436        } 
     437    } 
    342438} 
  • trunk/src/main/java/org/sarugo/restlet/jpa/converter/ConverterHelper.java

    r3577 r3606  
    6666        // Otherwise check if the type is an entity type 
    6767        if (router != null) { 
    68             T entity = router.findEntity(type, value); 
     68            @SuppressWarnings("unchecked") 
     69            T entity = (T) router.getEntity(value); 
    6970            if (entity != null) { 
    7071                return entity; 
  • trunk/src/main/java/org/sarugo/restlet/jpa/converter/json/JSONConverter.java

    r3577 r3606  
    101101                        .getEntityList().size()); 
    102102                for (E entity : entityCollection.getEntityList()) { 
    103                     urlList.add(entityCollection.getFinder().getEntityURL( 
    104                             entity)); 
     103                    urlList.add(entityCollection.getFinder().getRouter() 
     104                            .getEntityURL(entity)); 
    105105                } 
    106106                Map<String, Object> listObject = new HashMap<String, Object>(1, 
  • trunk/src/main/java/org/sarugo/restlet/jpa/converter/text/ReferenceListConverter.java

    r3577 r3606  
    4343    public Representation represent(EntityResource<E> entityResource, 
    4444            Variant variant) { 
    45         if (VARIANTS.contains(variant.getMediaType()) && entityResource instanceof EntityCollection) { 
     45        if (VARIANTS.contains(variant.getMediaType()) 
     46                && entityResource instanceof EntityCollection) { 
    4647            EntityCollection<E> entityCollection = (EntityCollection<E>) entityResource; 
    4748            ReferenceList list = new ReferenceList(entityCollection 
     
    4950            list.setIdentifier(entityCollection.getRequest().getResourceRef()); 
    5051            for (E o : entityCollection.getEntityList()) { 
    51                 list.add(entityCollection.getFinder().getEntityURL(o)); 
     52                list.add(entityCollection.getFinder().getRouter().getEntityURL( 
     53                        o)); 
    5254            } 
    5355            if (MediaType.TEXT_URI_LIST.equals(variant.getMediaType())) { 
  • trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityCollection.java

    r3577 r3606  
    3030import org.restlet.resource.Resource; 
    3131import org.restlet.resource.ResourceException; 
    32 import org.sarugo.restlet.jpa.EntityFinder; 
     32import org.sarugo.restlet.jpa.EntityCollectionFinder; 
     33import org.sarugo.restlet.jpa.EntityInstanceFinder; 
    3334import org.sarugo.restlet.jpa.converter.InputConverter; 
    3435 
    3536/** 
    3637 * 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. 
    3840 *  
    3941 * @author Michael Terrington 
     
    4648    /** 
    4749     * The number of results to retrieve. Defaults to 
    48      * {@link EntityFinder#getMaxResults()}. 
     50     * {@link EntityInstanceFinder#getMaxResults()}. 
    4951     */ 
    5052    protected int maxResults; 
     
    6567     *            The finder this collection will use to get its entity list. 
    6668     */ 
    67     public EntityCollection(EntityFinder<E> finder) { 
     69    public EntityCollection(EntityCollectionFinder<E> finder) { 
    6870        super(finder); 
    6971        start = 1; 
     
    7779     * initialise the base class.</li> 
    7880     * <li>Calls {@link #initMaxAndStart()}.</li> 
     81     * <li>Calls {@link #getEntityList(Request, Response)} which: 
     82     * <ol> 
    7983     * <li>Gets the JPA query from 
    80      * {@link EntityFinder#getEntityListQuery(Request, Response)}.</li> 
     84     * {@link #getEntityListQuery(Request, Response)}.</li> 
    8185     * <li>Sets {@link Query#setFirstResult(int)} to <code>start - 1</code>).</li> 
    8286     * <li>Sets {@link Query#setMaxResults(int)} to <code>maxResults + 1</code>.</li> 
     
    8690     * Otherwise, truncates the list to {@link #maxResults} (to remove the +1).</li> 
    8791     * </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. 
    9197     *  
    9298     * Override {@link #initMaxAndStart()} if you want to change how 
    9399     * {@link #start} and {@link #maxResults} are initialised. 
    94100     *  
    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). 
    100104     */ 
    101105    public void init(Request request, Response response) { 
    102106        init(getFinder().getContext(), request, response); 
    103107        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); 
    105113        q.setFirstResult(start - 1); 
    106114        q.setMaxResults(maxResults + 1); 
     
    113121            entities = entities.subList(0, maxResults); 
    114122        } 
    115         entityList = entities; 
     123        return entities; 
    116124    } 
    117125 
     
    120128     * parameters <tt>max</tt> and <tt>start</tt>. {@link #maxResults} will 
    121129     * not be changed if the query parameter is greater than the value returned 
    122      * by {@link EntityFinder#getMaxResults()} to prevent DoS attacks. 
     130     * by {@link EntityInstanceFinder#getMaxResults()} to prevent DoS attacks. 
    123131     */ 
    124132    protected void initMaxAndStart() { 
     
    132140            } 
    133141        } 
     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()); 
    134169    } 
    135170 
  • trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityInstance.java

    r3577 r3606  
    1717package org.sarugo.restlet.jpa.resource; 
    1818 
     19import java.lang.reflect.Field; 
     20import java.lang.reflect.InvocationTargetException; 
     21import java.lang.reflect.Method; 
     22import java.util.Collections; 
     23import java.util.Map; 
     24import java.util.logging.Level; 
     25 
     26import javax.persistence.EntityManager; 
     27import javax.persistence.Id; 
     28 
    1929import org.restlet.data.Request; 
    2030import org.restlet.data.Response; 
     
    2232import org.restlet.resource.Representation; 
    2333import org.restlet.resource.ResourceException; 
    24 import org.sarugo.restlet.jpa.EntityFinder; 
     34import org.sarugo.restlet.jpa.EntityInstanceFinder; 
     35import org.sarugo.restlet.jpa.converter.ConverterHelper; 
    2536import org.sarugo.restlet.jpa.converter.InputConverter; 
    2637 
    2738/** 
    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. 
    3042 *  
    3143 * @author Michael Terrington 
     
    4153     *            The finder this instance will use to get its entity. 
    4254     */ 
    43     public EntityInstance(EntityFinder<E> finder) { 
     55    public EntityInstance(EntityInstanceFinder<E> finder) { 
    4456        super(finder); 
    4557    } 
    4658 
    4759    /** 
    48      * Initialise this resource. By default this calls 
    49      * {@link EntityFinder#getEntity(Request, Response)}. 
     60     * Initialise this resource from a request. By default this calls 
     61     * {@link #getEntity(Request, Response)}. 
    5062     */ 
    5163    public void init(Request request, Response response) { 
    5264        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(); 
    54198    } 
    55199 
     
    90234    } 
    91235 
    92     /** Remove the entity by calling {@link EntityFinder#removeEntity(Object)}. */ 
     236    /** Remove the entity using the JPA entity manager. */ 
    93237    @Override 
    94238    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); 
    98240    } 
    99241 
  • trunk/src/main/java/org/sarugo/restlet/jpa/resource/EntityResource.java

    r3577 r3606  
    2424import org.restlet.resource.Variant; 
    2525import org.sarugo.restlet.jpa.EntityFinder; 
     26import org.sarugo.restlet.jpa.EntityInstanceFinder; 
    2627import org.sarugo.restlet.jpa.converter.OutputConverter; 
    2728 
    2829/** 
    2930 * A resource which maps to a JPA entity. The behaviour of this class is largely 
    30  * controlled by the {@link EntityFinder} which creates it. 
     31 * controlled by the {@link EntityInstanceFinder} which creates it. 
    3132 *  
    3233 * @author Michael Terrington 
  • trunk/src/test/java/org/sarugo/restlet/jpa/FooEntity.java

    r3539 r3606  
    1212    Integer id; 
    1313 
    14     String bar
     14    String name
    1515} 
  • trunk/src/test/resources/META-INF/persistence.xml

    r3539 r3606  
    77        <persistence-unit name="test"> 
    88                <class>org.sarugo.restlet.jpa.FooEntity</class> 
     9                <class>org.sarugo.restlet.jpa.BarEntity</class> 
    910                <properties> 
    1011                        <property name="hibernate.hbm2ddl.auto" value="create-drop" />