Project

General

Profile

Download (19.3 KB) Statistics
| Branch: | Tag: | Revision:
1
/**
2
* Copyright (C) 2007 EDIT
3
* European Distributed Institute of Taxonomy
4
* http://www.e-taxonomy.eu
5
*
6
* The contents of this file are subject to the Mozilla Public License Version 1.1
7
* See LICENSE.TXT at the top of this package for the full license terms.
8
*/
9
package eu.etaxonomy.cdm.remote.controller;
10

    
11
import java.beans.PropertyDescriptor;
12
import java.io.IOException;
13
import java.lang.reflect.InvocationTargetException;
14
import java.lang.reflect.Method;
15
import java.lang.reflect.ParameterizedType;
16
import java.lang.reflect.Type;
17
import java.util.ArrayList;
18
import java.util.Arrays;
19
import java.util.Collection;
20
import java.util.Comparator;
21
import java.util.List;
22
import java.util.Set;
23
import java.util.UUID;
24

    
25
import javax.servlet.http.HttpServletRequest;
26
import javax.servlet.http.HttpServletResponse;
27

    
28
import org.apache.commons.beanutils.PropertyUtils;
29
import org.apache.commons.io.FilenameUtils;
30
import org.apache.commons.lang.StringUtils;
31
import org.apache.log4j.Logger;
32
import org.hibernate.mapping.Map;
33
import org.springframework.web.bind.WebDataBinder;
34
import org.springframework.web.bind.annotation.InitBinder;
35
import org.springframework.web.bind.annotation.PathVariable;
36
import org.springframework.web.bind.annotation.RequestMapping;
37
import org.springframework.web.bind.annotation.RequestMethod;
38
import org.springframework.web.bind.annotation.RequestParam;
39

    
40
import eu.etaxonomy.cdm.api.service.IClassificationService;
41
import eu.etaxonomy.cdm.api.service.IService;
42
import eu.etaxonomy.cdm.api.service.ITaxonNodeService;
43
import eu.etaxonomy.cdm.api.service.pager.Pager;
44
import eu.etaxonomy.cdm.hibernate.HibernateProxyHelper;
45
import eu.etaxonomy.cdm.model.common.CdmBase;
46
import eu.etaxonomy.cdm.model.common.IPublishable;
47
import eu.etaxonomy.cdm.model.reference.INomenclaturalReference;
48
import eu.etaxonomy.cdm.model.taxon.Classification;
49
import eu.etaxonomy.cdm.model.taxon.TaxonNode;
50
import eu.etaxonomy.cdm.remote.editor.UUIDPropertyEditor;
51

    
52
/**
53
 * based on org.cateproject.controller.common
54
 * @author b.clark
55
 * @author a.kohlbecker
56
 */
57
public abstract class BaseController<T extends CdmBase, SERVICE extends IService<T>> extends AbstractController<T, SERVICE> {
58

    
59
    private static final Logger logger = Logger.getLogger(BaseController.class);
60

    
61
    protected Class<T> baseClass;
62

    
63
    @SuppressWarnings("unchecked")
64
    public BaseController (){
65

    
66
       Type superClass = this.getClass().getGenericSuperclass();
67
       while(true){
68
           if(superClass instanceof ParameterizedType){
69
               ParameterizedType parametrizedSuperClass = (ParameterizedType) superClass;
70
               Type[] typeArguments = parametrizedSuperClass.getActualTypeArguments();
71

    
72
               if(typeArguments.length > 1 && typeArguments[0] instanceof Class<?>){
73
                   baseClass = (Class<T>) typeArguments[0];
74
               } else {
75
                   logger.error("unable to find baseClass");
76
               }
77
               break;
78
           } else if(superClass instanceof Class<?>){
79
               superClass = ((Class<?>) superClass).getGenericSuperclass();
80
           } else {
81
               // no point digging deeper if neither Class or ParameterizedType
82
               logger.error("unable to find baseClass");
83
               break;
84
           }
85
       }
86
    }
87

    
88
    @InitBinder
89
    public void initBinder(WebDataBinder binder) {
90
        binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());
91
    }
92

    
93
    //TODO implement bulk version of this method
94
    @RequestMapping(method = RequestMethod.GET)
95
    public T doGet(@PathVariable("uuid") UUID uuid,
96
                HttpServletRequest request,
97
                HttpServletResponse response) throws IOException {
98
        if(request != null) {
99
            logger.info("doGet() " + request.getRequestURI());
100
        }
101
        T obj = getCdmBaseInstance(uuid, response, initializationStrategy);
102
        if (obj instanceof IPublishable){
103
            obj = (T)checkExistsAndAccess((IPublishable)obj, NO_UNPUBLISHED, response);
104
        }
105
        return obj;
106
    }
107

    
108
    /**
109
     * @param uuid
110
     * @param request
111
     * @param response
112
     * @return
113
     * @throws IOException
114
     *
115
     * TODO implement bulk version of this method
116
     */
117
    @RequestMapping(value = "*", method = RequestMethod.GET)
118
    public Object doGetMethod(
119
            @PathVariable("uuid") UUID uuid,
120
            // doPage request parameters
121
            @RequestParam(value = "pageIndex", required = false) Integer pageIndex,
122
            @RequestParam(value = "pageSize", required = false) Integer pageSize,
123
            // doList request parameters
124
            @RequestParam(value = "start", required = false) Integer start,
125
            @RequestParam(value = "limit", required = false) Integer limit,
126
            HttpServletRequest request,
127
            HttpServletResponse response) throws IOException {
128

    
129
        String servletPath = request.getServletPath();
130
        String propertyName = FilenameUtils.getBaseName(servletPath);
131

    
132
        logger.info("doGetMethod()[doGet" + StringUtils.capitalize(propertyName) + "] " + requestPathAndQuery(request));
133

    
134
        // <CUT
135
//		T instance = getCdmBaseInstance(uuid, response, (List<String>)null);
136
        //Class<?> propertyClass = propertyClass(instance, baseName);
137
        Object objectFromProperty = getCdmBaseProperty(uuid, propertyName, response);//   invokeProperty(instance, baseName, response);
138
        // CUT>
139
        if(objectFromProperty != null){
140
            if( Collection.class.isAssignableFrom(objectFromProperty.getClass())){
141
                // Map types cannot be returned as list or in a pager!
142
                return pageFromCollection((Collection<CdmBase>)objectFromProperty, pageIndex, pageSize, start, limit, response);
143
            } else {
144
                return objectFromProperty;
145
            }
146
        }
147
        return null;
148
    }
149

    
150
    /**
151
     * Returns a sub-collection of <code>c</code>. A pager object will be returned if the <code>pageNumber</code> and
152
     * <code>pageSize</code> are given. Otherwise a <code>List</code> in case of <code>start</code> and <code>limit</code>.
153
     *
154
     * @param pageNumber
155
     * @param pageSize
156
     * @param start
157
     * @param limit
158
     * @param response
159
     * @param objectFromProperty
160
     * @return either a List or Pager depending on the parameter combination.
161
     *
162
     * @throws IOException
163
     */
164
    protected Object pageFromCollection(Collection<? extends CdmBase> c, Integer pageNumber, Integer pageSize, Integer start,
165
            Integer limit, HttpServletResponse response) throws IOException {
166

    
167
        if(c instanceof Set){
168
            // sets need to be sorted to have a defined order
169
            List<CdmBase> list = new ArrayList<>(c);
170
            java.util.Collections.sort(list, new Comparator<CdmBase>() {
171

    
172
                @Override
173
                public int compare(CdmBase o1, CdmBase o2) {
174
                    if (o1 == null && o2 == null){
175
                        return 0;
176
                    }else if (o1 == null){
177
                        return -1;
178
                    }else if (o2 == null){
179
                        return 1;
180
                    }
181
                    return Integer.compare(o1.getId(), o2.getId());
182
                }
183
            });
184
            c = list;
185
        }
186

    
187
        if(start != null){
188
            // return list
189
            limit = (limit == null ? DEFAULT_PAGE_SIZE : limit);
190
            Collection<CdmBase> sub_c = subCollection(c, start, limit);
191
            return sub_c;
192
        } else {
193
            //FIXME use real paging mechanism of according service class instead of subCollection()
194
            //FIXME use BaseListController.normalizeAndValidatePagerParameters(pageNumber, pageSize, response);
195
            Pager<? extends CdmBase> p = pagerForSubCollectionOf(c, pageNumber, pageSize, response);
196
            return p;
197
        }
198
    }
199

    
200
    public Object getCdmBaseProperty(UUID uuid, String property, HttpServletResponse response) throws IOException{
201

    
202
        T instance = HibernateProxyHelper.deproxy(getCdmBaseInstance(uuid, response, property));
203

    
204
        Object objectFromProperty = invokeProperty(instance, property, response);
205

    
206
        return objectFromProperty;
207
    }
208

    
209
    private Class<?> propertyClass(T instance, String baseName) {
210
        PropertyDescriptor propertyDescriptor = null;
211
        Class<?> c = null;
212
        try {
213
            propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
214
            if(propertyDescriptor != null){
215
                c =  propertyDescriptor.getClass();
216
            }
217
        } catch (IllegalAccessException e) {
218
            // TODO Auto-generated catch block
219
            e.printStackTrace();
220
        } catch (InvocationTargetException e) {
221
            // TODO Auto-generated catch block
222
            e.printStackTrace();
223
        } catch (NoSuchMethodException e) {
224
            // TODO Auto-generated catch block
225
            e.printStackTrace();
226
        }
227
        return c;
228
    }
229

    
230
    /**
231
     * @param <SUB_T>
232
     * @param clazz
233
     * @param uuid
234
     * @param response
235
     * @param pathProperties
236
     * @return
237
     * @throws IOException
238
     */
239
    @SuppressWarnings("unchecked")
240
    protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz,
241
            UUID uuid, HttpServletResponse response, List<String> pathProperties)
242
            throws IOException {
243

    
244
        CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperties);
245
        if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
246
            HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
247
        }
248
        return (SUB_T) cdmBaseObject;
249
    }
250

    
251
    /**
252
     * @param <SUB_T>
253
     * @param clazz
254
     * @param uuid
255
     * @param response
256
     * @param pathProperty
257
     * @return
258
     * @throws IOException
259
     */
260
    @SuppressWarnings("unchecked")
261
    protected final <SUB_T extends T> SUB_T getCdmBaseInstance(Class<SUB_T> clazz, UUID uuid, HttpServletResponse response, String pathProperty)
262
            throws IOException {
263

    
264
        CdmBase cdmBaseObject = getCdmBaseInstance(uuid, response, pathProperty);
265
        if(!clazz.isAssignableFrom(cdmBaseObject.getClass())){
266
            HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
267
        }
268
        return (SUB_T) cdmBaseObject;
269
    }
270

    
271
    /**
272
     * @param uuid
273
     * @param response
274
     * @param pathProperty
275
     * @return
276
     * @throws IOException
277
     */
278
    protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, String pathProperty)
279
            throws IOException {
280
        return getCdmBaseInstance(baseClass, uuid, response, Arrays
281
                .asList(new String[] { pathProperty }));
282
    }
283

    
284

    
285
    /**
286
     * @param uuid
287
     * @param response
288
     * @param pathProperties
289
     * @return
290
     * @throws IOException
291
     */
292
    protected final T getCdmBaseInstance(UUID uuid, HttpServletResponse response, List<String> pathProperties)
293
            throws IOException {
294
        return getCdmBaseInstance(baseClass, service, uuid, response, pathProperties);
295
    }
296

    
297
    /**
298
     * @param <CDM_BASE>
299
     * @param clazz
300
     * @param service
301
     * @param uuid
302
     * @param response
303
     * @param pathProperties
304
     * @return
305
     * @throws IOException
306
     */
307
    protected final <CDM_BASE extends CdmBase> CDM_BASE getCdmBaseInstance(
308
            Class<CDM_BASE> clazz, IService<CDM_BASE> service, UUID uuid,
309
            HttpServletResponse response, List<String> pathProperties)
310
            throws IOException {
311

    
312
        @SuppressWarnings("unused")
313
        boolean includeUnpublished = NO_UNPUBLISHED;
314
        CDM_BASE cdmBaseObject;
315
//        if (service instanceof IPublishableService){
316
//            cdmBaseObject = ((IPublishableService<CDM_BASE>)service).load(uuid, includeUnpublished, pathProperties);
317
//        }else{
318
            pathProperties = complementInitStrategy(clazz, pathProperties);
319
            cdmBaseObject = service.load(uuid, pathProperties);
320
//        }
321
        if (cdmBaseObject == null) {
322
            HttpStatusMessage.UUID_NOT_FOUND.send(response);
323
        }
324
        return cdmBaseObject;
325
    }
326

    
327
    /**
328
     * Implementations of the BaseController can override this method to
329
     * extend the <code>pathProperties</code> to for example avoid
330
     * <code>LazyInitializationExceptions</code> which can happen when
331
     * {@link #doGetMethod(UUID, Integer, Integer, Integer, Integer, HttpServletRequest, HttpServletResponse)} is being used.
332
     *
333
     * @param clazz
334
     * @param pathProperties
335
     */
336
    protected  <CDM_BASE extends CdmBase> List<String> complementInitStrategy(@SuppressWarnings("unused") Class<CDM_BASE> clazz, List<String> pathProperties) {
337
        return pathProperties;
338
    }
339

    
340
    /**
341
     * @param instance
342
     * @param baseName
343
     * @param response
344
     * @return
345
     * @throws IOException
346
     */
347
    private final Object invokeProperty(T instance,
348
            String baseName, HttpServletResponse response) throws IOException {
349

    
350
        Object result = null;
351
        try {
352
            PropertyDescriptor propertyDescriptor = PropertyUtils.getPropertyDescriptor(instance, baseName);
353
            if(propertyDescriptor == null){
354
                throw new NoSuchMethodException("No such method: " + instance.getClass().getSimpleName() + ".get" + baseName);
355
            }
356
            Method method = propertyDescriptor.getReadMethod();
357

    
358
            Class<?> returnType = method.getReturnType();
359

    
360
            if(CdmBase.class.isAssignableFrom(returnType)
361
                    || Collection.class.isAssignableFrom(returnType)
362
                    || Map.class.isAssignableFrom(returnType)
363
                    || INomenclaturalReference.class.isAssignableFrom(returnType)){
364

    
365
                result = method.invoke(instance, (Object[])null);
366

    
367
                result = HibernateProxyHelper.deproxy(result);
368

    
369
            }else{
370
                HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
371
            }
372
        } catch (SecurityException e) {
373
            logger.error("SecurityException: ", e);
374
            HttpStatusMessage.INTERNAL_ERROR.send(response);
375
        } catch (NoSuchMethodException e) {
376
            HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
377
        } catch (IllegalArgumentException e) {
378
            HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
379
        } catch (IllegalAccessException e) {
380
            HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
381
        } catch (InvocationTargetException e) {
382
            HttpStatusMessage.PROPERTY_NOT_FOUND.send(response);
383
        }
384
        return result;
385
    }
386

    
387
    /**
388
     * Checks if an {@link IPublishable} was found and if it is publish.
389
     * If not the according {@link HttpStatusMessage http messages} are added to response.
390
     * @param publishable
391
     * @param includeUnpublished
392
     * @param response
393
     * @return
394
     * @throws IOException
395
     */
396
    protected <S extends IPublishable> S checkExistsAndAccess(S publishable, boolean includeUnpublished,
397
            HttpServletResponse response) throws IOException {
398
        if (publishable == null){
399
            HttpStatusMessage.UUID_NOT_FOUND.send(response);
400
        }else if (!includeUnpublished && !publishable.isPublish()){
401
            HttpStatusMessage.ACCESS_DENIED.send(response);
402
            publishable = null;
403
        }
404
        return publishable;
405
    }
406

    
407
    protected <S extends IPublishable> S checkExistsAccessType(IPublishable publishable, boolean includeUnpublished,
408
            Class<S> clazz, HttpServletResponse response) throws IOException {
409
        IPublishable result = this.checkExistsAndAccess(publishable, includeUnpublished, response);
410
        if (clazz != null && !clazz.isAssignableFrom(result.getClass())){
411
            HttpStatusMessage.UUID_REFERENCES_WRONG_TYPE.send(response);
412
            result = null;
413
        }
414
        return (S)result;
415
    }
416

    
417

    
418
    /**
419
     * @param subtreeUuid
420
     * @param response
421
     * @return
422
     * @throws IOException
423
     */
424
    protected TaxonNode getSubtreeOrError(UUID subtreeUuid, ITaxonNodeService taxonNodeService, HttpServletResponse response) throws IOException {
425
        TaxonNode subtree = null;
426
        if (subtreeUuid != null){
427
            subtree = taxonNodeService.find(subtreeUuid);
428
            if(subtree == null) {
429
                response.sendError(404 , "TaxonNode not found using " + subtreeUuid );
430
                //will not happen
431
                return null;
432
            }
433
        }
434
        return subtree;
435
    }
436

    
437
    protected Classification getClassificationOrError(UUID classificationUuid,
438
            IClassificationService classificationService, HttpServletResponse response) throws IOException {
439
        Classification classification = null;
440
        if (classificationUuid != null){
441
            classification = classificationService.find(classificationUuid);
442
            if(classification == null) {
443
                response.sendError(404 , "Classification not found: " + classificationUuid );
444
                //will not happen
445
                return null;
446
            }
447
        }
448
        return classification;
449
    }
450

    
451
      /* TODO implement
452

    
453
      private Validator validator;
454

    
455
      private javax.validation.Validator javaxValidator;
456

    
457
      @RequestMapping(method = RequestMethod.PUT, headers="content-type=multipart/form-data")
458
      public T doPutForm(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
459
          object.setUuid(uuid);
460
          validator.validate(object, result);
461
          if (result.hasErrors()) {
462
              throw new Error();
463
                // set http status code depending upon what happened, possibly return
464
                // the put object and errors so that they can be rendered into a suitable error response
465
          } else {
466
             // requires merging detached object ?gilead?
467
             service.save(object);
468
          }
469

    
470
            return object;
471
      }
472

    
473
      @RequestMapping(method = RequestMethod.PUT, headers="content-type=text/json")
474
      public T doPutJSON(@PathVariable(value = "uuid") UUID uuid, @RequestBody String jsonMessage) {
475
          JSONObject jsonObject = JSONObject.fromObject(jsonMessage);
476
          T object = (T)JSONObject.toBean(jsonObject, this.getClass());
477

    
478

    
479
          Set<ConstraintViolation<T>> constraintViolations = javaxValidator.validate(object);
480
            if (!constraintViolations.isEmpty()) {
481
                throw new Error();
482
                    // set http status code depending upon what happened, possibly return
483
                // the put object and errors so that they can be rendered into a suitable error response
484
            } else {
485
              // requires merging detached object ?gilead?
486
              service.save(object);
487
            }
488

    
489
            return object;
490
      }
491

    
492
      @RequestMapping(method = RequestMethod.PUT) // the cdm-server may not allow clients to specify the uuid for resources
493
      public T doPut(@PathVariable(value = "uuid") UUID uuid, @ModelAttribute("object") T object, BindingResult result) {
494
            validator.validate(object, result);
495
            if (result.hasErrors()) {
496
                    // set http status code depending upon what happened, possibly return
497
                // the put object and errors so that they can be rendered into a suitable error response
498
            } else {
499
              service.save(object);
500
            }
501
      }
502

    
503
       @RequestMapping(method = RequestMethod.DELETE)
504
       public void doDelete(@PathVariable(value = "uuid") UUID uuid) {
505
           T object = service.find(uuid);
506
           // provided the object exists
507
           service.delete(uuid);
508
           // might return 204 or 200
509
       }
510
    }
511
*/
512

    
513

    
514
}
(10-10/76)