Project

General

Profile

Download (23.2 KB) Statistics
| Branch: | Tag: | Revision:
1
package eu.etaxonomy.cdm.remote.controller.oaipmh;
2

    
3
import java.util.ArrayList;
4
import java.util.HashSet;
5
import java.util.List;
6
import java.util.UUID;
7

    
8
import javax.servlet.http.HttpServletRequest;
9

    
10
import org.hibernate.envers.query.AuditEntity;
11
import org.hibernate.envers.query.criteria.AuditCriterion;
12
import org.joda.time.DateTime;
13
import org.springframework.beans.TypeMismatchException;
14
import org.springframework.beans.factory.annotation.Autowired;
15
import org.springframework.http.HttpStatus;
16
import org.springframework.web.bind.MissingServletRequestParameterException;
17
import org.springframework.web.bind.WebDataBinder;
18
import org.springframework.web.bind.annotation.ExceptionHandler;
19
import org.springframework.web.bind.annotation.InitBinder;
20
import org.springframework.web.bind.annotation.RequestMapping;
21
import org.springframework.web.bind.annotation.RequestMethod;
22
import org.springframework.web.bind.annotation.RequestParam;
23
import org.springframework.web.bind.annotation.ResponseStatus;
24
import org.springframework.web.servlet.ModelAndView;
25
import org.springmodules.cache.CachingModel;
26
import org.springmodules.cache.provider.CacheProviderFacade;
27

    
28
import eu.etaxonomy.cdm.api.service.IAuditEventService;
29
import eu.etaxonomy.cdm.api.service.IIdentifiableEntityService;
30
import eu.etaxonomy.cdm.api.service.pager.Pager;
31
import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
32
import eu.etaxonomy.cdm.model.common.LSID;
33
import eu.etaxonomy.cdm.model.view.AuditEvent;
34
import eu.etaxonomy.cdm.model.view.AuditEventRecord;
35
import eu.etaxonomy.cdm.persistence.dao.common.AuditEventSort;
36
import eu.etaxonomy.cdm.remote.controller.BadResumptionTokenException;
37
import eu.etaxonomy.cdm.remote.controller.IdDoesNotExistException;
38
import eu.etaxonomy.cdm.remote.dto.oaipmh.DeletedRecord;
39
import eu.etaxonomy.cdm.remote.dto.oaipmh.ErrorCode;
40
import eu.etaxonomy.cdm.remote.dto.oaipmh.Granularity;
41
import eu.etaxonomy.cdm.remote.dto.oaipmh.MetadataPrefix;
42
import eu.etaxonomy.cdm.remote.dto.oaipmh.ResumptionToken;
43
import eu.etaxonomy.cdm.remote.dto.oaipmh.SetSpec;
44
import eu.etaxonomy.cdm.remote.dto.oaipmh.Verb;
45
import eu.etaxonomy.cdm.remote.editor.IsoDateTimeEditor;
46
import eu.etaxonomy.cdm.remote.editor.LSIDPropertyEditor;
47
import eu.etaxonomy.cdm.remote.editor.MetadataPrefixEditor;
48
import eu.etaxonomy.cdm.remote.editor.SetSpecEditor;
49
import eu.etaxonomy.cdm.remote.editor.UUIDPropertyEditor;
50
import eu.etaxonomy.cdm.remote.exception.CannotDisseminateFormatException;
51
import eu.etaxonomy.cdm.remote.exception.NoRecordsMatchException;
52

    
53
public abstract class AbstractOaiPmhController<T extends IdentifiableEntity, SERVICE extends IIdentifiableEntityService<T>> {
54

    
55
    protected SERVICE service;
56

    
57
    protected IAuditEventService auditEventService;
58

    
59
    private String repositoryName;
60

    
61
    private String baseURL;
62

    
63
    private String protocolVersion;
64

    
65
    private String adminEmail;
66

    
67
    private String description;
68

    
69
    private Integer pageSize;
70

    
71
    public abstract void setService(SERVICE service);
72

    
73
    private CacheProviderFacade cacheProviderFacade;
74

    
75
    private CachingModel cachingModel;
76

    
77
    private boolean onlyItemsWithLsid = false;
78

    
79
    public boolean isRestrictToLsid() {
80
        return onlyItemsWithLsid;
81
    }
82

    
83
    public void setRestrictToLsid(boolean restrictToLsid) {
84
        this.onlyItemsWithLsid = restrictToLsid;
85
    }
86

    
87
    /**
88
     * sets cache name to be used
89
     */
90
    @Autowired
91
    public void setCacheProviderFacade(CacheProviderFacade cacheProviderFacade) {
92
        this.cacheProviderFacade = cacheProviderFacade;
93
    }
94

    
95
    @Autowired
96
    public void setCachingModel(CachingModel cachingModel) {
97
        this.cachingModel = cachingModel;
98
    }
99

    
100
    /**
101
     * Subclasses should override this method to return a list of property
102
     * paths that should be initialized for the getRecord, listRecords methods
103
     * @return
104
     */
105
    protected List<String> getPropertyPaths() {
106
        return new ArrayList<String>();
107
    }
108

    
109
    /**
110
     * Subclasses should override this method and add a collection of
111
     * eu.etaxonomy.cdm.remote.dto.oaipmh.Set objects  called "sets" that
112
     * will be returned in the response
113
     * @param modelAndView
114
     */
115
    protected void addSets(ModelAndView modelAndView) {
116
        modelAndView.addObject("sets",new HashSet<SetSpec>());
117
    }
118

    
119
    @Autowired
120
    public void setAuditEventService(IAuditEventService auditEventService) {
121
        this.auditEventService = auditEventService;
122
    }
123

    
124
    public void setRepositoryName(String repositoryName) {
125
        this.repositoryName = repositoryName;
126
    }
127

    
128
    public void setBaseURL(String baseURL) {
129
        this.baseURL = baseURL;
130
    }
131

    
132
    public void setProtocolVersion(String protocolVersion) {
133
        this.protocolVersion = protocolVersion;
134
    }
135

    
136
    public void setAdminEmail(String adminEmail) {
137
        this.adminEmail = adminEmail;
138
    }
139

    
140
    public void setDescription(String description) {
141
        this.description = description;
142
    }
143

    
144
    public void setPageSize(Integer pageSize) {
145
        this.pageSize = pageSize;
146
    }
147

    
148
    @InitBinder
149
    public void initBinder(WebDataBinder binder) {
150
        binder.registerCustomEditor(DateTime.class, new IsoDateTimeEditor());
151
        binder.registerCustomEditor(LSID.class, new LSIDPropertyEditor());
152
        binder.registerCustomEditor(MetadataPrefix.class, new MetadataPrefixEditor());
153
        binder.registerCustomEditor(SetSpec.class, new SetSpecEditor());
154
        binder.registerCustomEditor(UUID.class, new UUIDPropertyEditor());
155
    }
156

    
157

    
158
    /**
159
     * CannotDisseminateFormatException thrown by MetadataPrefixEditor
160
     *
161
     * @throws IdDoesNotExistException
162
     */
163
// FIXME has same mapping as the other getRecord method: do we really need to support LSIDs or shall we skip this
164
//    @RequestMapping(method = RequestMethod.GET, params = "verb=GetRecord")
165
//    public ModelAndView getRecord(
166
//            @RequestParam(value = "identifier", required = true) LSID identifier,
167
//            @RequestParam(value = "metadataPrefix", required = true) MetadataPrefix metadataPrefix)
168
//            throws IdDoesNotExistException {
169
//
170
//        ModelAndView modelAndView = new ModelAndView();
171
//        modelAndView.addObject("metadataPrefix", metadataPrefix);
172
//
173
//        finishModelAndView(identifier, metadataPrefix, modelAndView);
174
//
175
//        return modelAndView;
176
//    }
177

    
178
    @RequestMapping(method = RequestMethod.GET, params = "verb=GetRecord")
179
    public ModelAndView getRecord(
180
            @RequestParam(value = "identifier", required = true) UUID identifier,
181
            @RequestParam(value = "metadataPrefix", required = true) MetadataPrefix metadataPrefix)
182
            throws IdDoesNotExistException {
183

    
184
        ModelAndView modelAndView = new ModelAndView();
185
        modelAndView.addObject("metadataPrefix", metadataPrefix);
186

    
187
        return modelAndView;
188
    }
189

    
190
    /**
191
     * @param identifier
192
     * @param metadataPrefix
193
     * @param modelAndView
194
     * @throws IdDoesNotExistException
195
     */
196
    protected void finishModelAndView(LSID identifier,
197
            MetadataPrefix metadataPrefix, ModelAndView modelAndView)
198
            throws IdDoesNotExistException {
199

    
200
        switch (metadataPrefix) {
201
            case RDF:
202
                modelAndView.addObject("object", obtainCdmEntity(identifier));
203
                modelAndView.setViewName("oai/getRecord.rdf");
204
                break;
205
            case OAI_DC:
206
            default:
207
                modelAndView.addObject("object", obtainCdmEntity(identifier));
208
                modelAndView.setViewName("oai/getRecord.dc");
209
        }
210
    }
211

    
212
    /**
213
     * @param identifier
214
     * @return
215
     * @throws IdDoesNotExistException
216
     */
217
    protected AuditEventRecord<T> obtainCdmEntity(LSID identifier)
218
            throws IdDoesNotExistException {
219
        T object = service.find(identifier);
220
        if(object == null){
221
            throw new IdDoesNotExistException(identifier);
222
        }
223

    
224
        Pager<AuditEventRecord<T>> results = service.pageAuditEvents(object, 1,
225
                0, AuditEventSort.BACKWARDS, getPropertyPaths());
226

    
227
        if (results.getCount() == 0) {
228
            throw new IdDoesNotExistException(identifier);
229
        }
230
        return results.getRecords().get(0);
231
    }
232

    
233

    
234
    /**
235
     *  CannotDisseminateFormatException thrown by MetadataPrefixEditor
236
     * @throws IdDoesNotExistException
237
     */
238
    @RequestMapping(method = RequestMethod.GET,params = "verb=ListMetadataFormats")
239
    public ModelAndView listMetadataFormats(@RequestParam(value = "identifier", required = false) LSID identifier) throws IdDoesNotExistException {
240

    
241
        ModelAndView modelAndView = new ModelAndView("oai/listMetadataFormats");
242

    
243
        if(identifier != null) {
244
            T  object = service.find(identifier);
245
            if(object == null) {
246
                throw new IdDoesNotExistException(identifier);
247
            }
248
        }
249

    
250
        return modelAndView;
251
    }
252

    
253
    /**
254
     *  CannotDisseminateFormatException thrown by MetadataPrefixEditor
255
     */
256
    @RequestMapping(method = RequestMethod.GET,params = "verb=ListSets")
257
    public ModelAndView listSets() {
258

    
259
        ModelAndView modelAndView = new ModelAndView("oai/listSets");
260

    
261
        addSets(modelAndView);
262

    
263
        return modelAndView;
264
    }
265

    
266
    @RequestMapping(method = RequestMethod.GET,params = "verb=Identify")
267
    public ModelAndView identify() {
268
        ModelAndView modelAndView = new ModelAndView("oai/identify");
269
        modelAndView.addObject("repositoryName", repositoryName);
270
        modelAndView.addObject("baseURL",baseURL);
271
        modelAndView.addObject("protocolVersion",protocolVersion);
272
        modelAndView.addObject("deletedRecord",DeletedRecord.PERSISTENT);
273
        modelAndView.addObject("granularity",Granularity.YYYY_MM_DD_THH_MM_SS_Z);
274

    
275
        Pager<AuditEvent> auditEvents = auditEventService.list(0,1,AuditEventSort.FORWARDS);
276
        modelAndView.addObject("earliestDatestamp",auditEvents.getRecords().get(0).getDate());
277
        modelAndView.addObject("adminEmail",adminEmail);
278
        modelAndView.addObject("description",description);
279

    
280
        return modelAndView;
281
    }
282

    
283
    @RequestMapping(method = RequestMethod.GET, params = {"verb=ListIdentifiers", "!resumptionToken"})
284
    public ModelAndView listIdentifiers(
285
            @RequestParam(value = "from", required = false) DateTime from,
286
            @RequestParam(value = "until", required = false) DateTime until,
287
            @RequestParam(value = "metadataPrefix",required = true) MetadataPrefix metadataPrefix,
288
            @RequestParam(value = "set", required = false) SetSpec set) {
289

    
290
        ModelAndView modelAndView = new ModelAndView("oai/listIdentifiers");
291
        modelAndView.addObject("metadataPrefix",metadataPrefix);
292

    
293
        AuditEvent fromAuditEvent = null;
294
        if(from != null) { // if from is specified, use the event at that date
295
            modelAndView.addObject("from",from);
296
            fromAuditEvent = auditEventService.find(from);
297
        }
298

    
299
        AuditEvent untilAuditEvent = null;
300
        if(until != null) {
301
            modelAndView.addObject("until",until);
302
            untilAuditEvent = auditEventService.find(until);
303
        }
304

    
305
        Class clazz = null;
306
        if(set != null) {
307
            modelAndView.addObject("set",set);
308
            clazz = set.getSetClass();
309
        }
310

    
311
        List<AuditCriterion> criteria = new ArrayList<AuditCriterion>();
312
        if(onlyItemsWithLsid){
313
            //criteria.add(AuditEntity.property("lsid_lsid").isNotNull());
314
            //TODO this isNotNull criterion did not work with mysql, so using a like statement as interim solution
315
            criteria.add(AuditEntity.property("lsid_lsid").like("urn:lsid:%"));
316
        }
317
        Pager<AuditEventRecord<T>> results = service.pageAuditEvents(clazz, fromAuditEvent, untilAuditEvent, criteria, pageSize, 0, AuditEventSort.FORWARDS, null);
318

    
319
        if(results.getCount() == 0) {
320
            throw new NoRecordsMatchException("No records match");
321
        }
322

    
323
        modelAndView.addObject("pager",results);
324

    
325
        if(results.getCount() > results.getRecords().size() && cacheProviderFacade != null) {
326
            ResumptionToken resumptionToken = new ResumptionToken(results, from, until, metadataPrefix, set);
327
            modelAndView.addObject("resumptionToken",resumptionToken);
328
            cacheProviderFacade.putInCache(resumptionToken.getValue(), cachingModel, resumptionToken);
329
        }
330

    
331
        return modelAndView;
332
    }
333

    
334
    @RequestMapping(method = RequestMethod.GET, params = {"verb=ListIdentifiers", "resumptionToken"})
335
    public ModelAndView listIdentifiers(@RequestParam(value = "resumptionToken",required = true) String rToken) {
336
        ResumptionToken resumptionToken;
337
        if(cacheProviderFacade != null && cacheProviderFacade.getFromCache(rToken, cachingModel) != null) {
338
            resumptionToken = (ResumptionToken) cacheProviderFacade.getFromCache(rToken, cachingModel);
339
            ModelAndView modelAndView = new ModelAndView("oai/listIdentifiers");
340
            modelAndView.addObject("metadataPrefix",resumptionToken.getMetadataPrefix());
341

    
342
            AuditEvent fromAuditEvent = null;
343
            if(resumptionToken.getFrom() != null) { // if from is specified, use the event at that date
344
                modelAndView.addObject("from",resumptionToken.getFrom());
345
                fromAuditEvent = auditEventService.find(resumptionToken.getFrom());
346
            }
347

    
348
            AuditEvent untilAuditEvent = null;
349
            if(resumptionToken.getUntil() != null) {
350
                modelAndView.addObject("until",resumptionToken.getUntil());
351
                untilAuditEvent = auditEventService.find(resumptionToken.getUntil());
352
            }
353

    
354
            Class clazz = null;
355
            if(resumptionToken.getSet() != null) {
356
                modelAndView.addObject("set",resumptionToken.getSet());
357
                clazz = resumptionToken.getSet().getSetClass();
358
            }
359

    
360
            List<AuditCriterion> criteria = new ArrayList<AuditCriterion>();
361
            if(onlyItemsWithLsid){
362
                //criteria.add(AuditEntity.property("lsid_lsid").isNotNull());
363
                //TODO this isNotNull criterion did not work with mysql, so using a like statement as interim solution
364
                criteria.add(AuditEntity.property("lsid_lsid").like("urn:lsid:%"));
365
            }
366
            Pager<AuditEventRecord<T>> results = service.pageAuditEvents(clazz,fromAuditEvent,untilAuditEvent,criteria, pageSize, (resumptionToken.getCursor().intValue() / pageSize) + 1, AuditEventSort.FORWARDS,null);
367

    
368
            if(results.getCount() == 0) {
369
                throw new NoRecordsMatchException("No records match");
370
            }
371

    
372
            modelAndView.addObject("pager",results);
373

    
374
            if(results.getCount() > ((results.getPageSize() * results.getCurrentIndex()) + results.getRecords().size())) {
375
                resumptionToken.updateResults(results);
376
                modelAndView.addObject("resumptionToken",resumptionToken);
377
                cacheProviderFacade.putInCache(resumptionToken.getValue(),cachingModel, resumptionToken);
378
            } else {
379
                resumptionToken = ResumptionToken.emptyResumptionToken();
380
                modelAndView.addObject("resumptionToken",resumptionToken);
381
                cacheProviderFacade.removeFromCache(rToken,cachingModel);
382
            }
383

    
384
            return modelAndView;
385
        } else {
386
            throw new BadResumptionTokenException();
387
        }
388
    }
389

    
390
    @RequestMapping(method = RequestMethod.GET, params = {"verb=ListRecords", "!resumptionToken"})
391
    public ModelAndView listRecords(@RequestParam(value = "from", required = false) DateTime from,
392
            @RequestParam(value = "until", required = false) DateTime until,
393
            @RequestParam(value = "metadataPrefix", required = true) MetadataPrefix metadataPrefix,
394
            @RequestParam(value = "set", required = false) SetSpec set) {
395

    
396
        ModelAndView modelAndView = new ModelAndView();
397
        modelAndView.addObject("metadataPrefix",metadataPrefix);
398

    
399
        switch(metadataPrefix) {
400
        case RDF:
401
            modelAndView.setViewName("oai/listRecords.rdf");
402
            break;
403
        case OAI_DC:
404
            default:
405
            modelAndView.setViewName("oai/listRecords.dc");
406
        }
407

    
408
        AuditEvent fromAuditEvent = null;
409
        if(from != null) { // if from is specified, use the event at that date
410
            modelAndView.addObject("from",from);
411
            fromAuditEvent = auditEventService.find(from);
412
        }
413

    
414
        AuditEvent untilAuditEvent = null;
415
        if(until != null) {
416
            modelAndView.addObject("until",until);
417
            untilAuditEvent = auditEventService.find(until);
418
        }
419

    
420
        Class clazz = null;
421
        if(set != null) {
422
            modelAndView.addObject("set",set);
423
            clazz = set.getSetClass();
424
        }
425

    
426
        List<AuditCriterion> criteria = new ArrayList<AuditCriterion>();
427
        if(onlyItemsWithLsid){
428
            //criteria.add(AuditEntity.property("lsid_lsid").isNotNull());
429
            //TODO this isNotNull criterion did not work with mysql, so using a like statement as interim solution
430
            criteria.add(AuditEntity.property("lsid_lsid").like("urn:lsid:%"));
431
        }
432
        Pager<AuditEventRecord<T>> results = service.pageAuditEvents(clazz, fromAuditEvent, untilAuditEvent, criteria, pageSize, 0, AuditEventSort.FORWARDS, getPropertyPaths());
433

    
434
        if(results.getCount() == 0) {
435
            throw new NoRecordsMatchException("No records match");
436
        }
437

    
438
        modelAndView.addObject("pager",results);
439

    
440
        if(results.getCount() > results.getRecords().size() && cacheProviderFacade != null) {
441
            ResumptionToken resumptionToken = new ResumptionToken(results, from, until, metadataPrefix, set);
442
            modelAndView.addObject("resumptionToken",resumptionToken);
443
            cacheProviderFacade.putInCache(resumptionToken.getValue(), cachingModel, resumptionToken);
444
        }
445

    
446
        return modelAndView;
447
    }
448

    
449
    @RequestMapping(method = RequestMethod.GET, params = {"verb=ListRecords", "resumptionToken"})
450
    public ModelAndView listRecords(@RequestParam("resumptionToken") String rToken) {
451

    
452
       ResumptionToken resumptionToken;
453
       if(cacheProviderFacade != null && cacheProviderFacade.getFromCache(rToken,cachingModel) != null) {
454
               resumptionToken = (ResumptionToken) cacheProviderFacade.getFromCache(rToken,cachingModel);
455
            ModelAndView modelAndView = new ModelAndView();
456
            modelAndView.addObject("metadataPrefix",resumptionToken.getMetadataPrefix());
457

    
458
            switch (resumptionToken.getMetadataPrefix()) {
459
            case RDF:
460
                modelAndView.setViewName("oai/listRecords.rdf");
461
                break;
462
            case OAI_DC:
463
                default:
464
                modelAndView.setViewName("oai/listRecords.dc");
465
            }
466

    
467
            AuditEvent fromAuditEvent = null;
468
            if(resumptionToken.getFrom() != null) { // if from is specified, use the event at that date
469
                modelAndView.addObject("from",resumptionToken.getFrom());
470
                fromAuditEvent = auditEventService.find(resumptionToken.getFrom());
471
            }
472

    
473
            AuditEvent untilAuditEvent = null;
474
            if(resumptionToken.getUntil() != null) {
475
                modelAndView.addObject("until",resumptionToken.getUntil());
476
                untilAuditEvent = auditEventService.find(resumptionToken.getUntil());
477
            }
478

    
479
            Class clazz = null;
480
            if(resumptionToken.getSet() != null) {
481
              modelAndView.addObject("set",resumptionToken.getSet());
482
              clazz = resumptionToken.getSet().getSetClass();
483
            }
484
            List<AuditCriterion> criteria = new ArrayList<AuditCriterion>();
485
            if(onlyItemsWithLsid){
486
                //criteria.add(AuditEntity.property("lsid_lsid").isNotNull());
487
                //TODO this isNotNull criterion did not work with mysql, so using a like statement as interim solution
488
                criteria.add(AuditEntity.property("lsid_lsid").like("urn:lsid:%"));
489
            }
490
            Pager<AuditEventRecord<T>> results = service.pageAuditEvents(clazz,fromAuditEvent,untilAuditEvent,criteria, pageSize, (resumptionToken.getCursor().intValue()  / pageSize) + 1, AuditEventSort.FORWARDS,getPropertyPaths());
491

    
492
            if(results.getCount() == 0) {
493
                throw new NoRecordsMatchException("No records match");
494
            }
495

    
496
            modelAndView.addObject("pager",results);
497

    
498
            if(results.getCount() > ((results.getPageSize() * results.getCurrentIndex()) + results.getRecords().size())) {
499
                resumptionToken.updateResults(results);
500
                modelAndView.addObject("resumptionToken",resumptionToken);
501
                cacheProviderFacade.putInCache(resumptionToken.getValue(),cachingModel,resumptionToken);
502
            } else {
503
                resumptionToken = ResumptionToken.emptyResumptionToken();
504
                modelAndView.addObject("resumptionToken",resumptionToken);
505
                cacheProviderFacade.removeFromCache(rToken,cachingModel);
506
            }
507

    
508
            return modelAndView;
509
       } else {
510
           throw new BadResumptionTokenException();
511
       }
512
    }
513

    
514
    private ModelAndView doException(Exception ex, HttpServletRequest request, ErrorCode code) {
515
        ModelAndView modelAndView = new ModelAndView("oai/exception");
516
        modelAndView.addObject("message", ex.getMessage());
517
        if(request.getParameter("verb") != null) {
518
            try {
519
              modelAndView.addObject("verb", Verb.fromValue(request.getParameter("verb")));
520
            } catch(Exception e) {// prevent endless recursion
521

    
522
            }
523
        }
524
        modelAndView.addObject("code",code);
525
        return modelAndView;
526
    }
527

    
528
    @ResponseStatus(HttpStatus.BAD_REQUEST)
529
    @ExceptionHandler({IllegalArgumentException.class,TypeMismatchException.class,MissingServletRequestParameterException.class})
530
    public ModelAndView handleBadArgument(Exception ex, HttpServletRequest request) {
531
        return doException(ex,request,ErrorCode.BAD_ARGUMENT);
532
    }
533

    
534
    @ResponseStatus(HttpStatus.BAD_REQUEST)
535
    @ExceptionHandler(CannotDisseminateFormatException.class)
536
    public ModelAndView handleCannotDisseminateFormat(Exception ex, HttpServletRequest request) {
537
        return doException(ex,request,ErrorCode.CANNOT_DISSEMINATE_FORMAT);
538
    }
539

    
540
    @ResponseStatus(HttpStatus.BAD_REQUEST)
541
    @ExceptionHandler(BadResumptionTokenException.class)
542
    public ModelAndView handleBadResumptionToken(Exception ex, HttpServletRequest request) {
543
        return doException(ex,request,ErrorCode.BAD_RESUMPTION_TOKEN);
544
    }
545

    
546
    @ExceptionHandler(NoRecordsMatchException.class)
547
    public ModelAndView handleNoRecordsMatch(Exception ex, HttpServletRequest request) {
548
        return doException(ex,request,ErrorCode.NO_RECORDS_MATCH);
549
    }
550

    
551
    @ResponseStatus(HttpStatus.NOT_FOUND)
552
    @ExceptionHandler(IdDoesNotExistException.class)
553
    public ModelAndView handleIdDoesNotExist(Exception ex, HttpServletRequest request) {
554
        return doException(ex,request,ErrorCode.ID_DOES_NOT_EXIST);
555
    }
556

    
557

    
558
}
(1-1/3)