cleanup
[cdmlib.git] / cdmlib-remote / src / main / java / eu / etaxonomy / cdm / remote / controller / dto / OccurrenceCatalogueController.java
1 /**
2 * Copyright (C) 2009 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.dto;
10 import java.io.IOException;
11 import java.io.InputStream;
12 import java.util.ArrayList;
13 import java.util.Arrays;
14 import java.util.HashMap;
15 import java.util.List;
16 import java.util.Map;
17 import java.util.UUID;
18
19 import javax.servlet.http.HttpServletRequest;
20 import javax.servlet.http.HttpServletResponse;
21
22 import org.apache.logging.log4j.LogManager;
23 import org.apache.logging.log4j.Logger;
24 import org.springframework.beans.factory.annotation.Autowired;
25 import org.springframework.context.ResourceLoaderAware;
26 import org.springframework.core.io.Resource;
27 import org.springframework.core.io.ResourceLoader;
28 import org.springframework.stereotype.Controller;
29 import org.springframework.web.bind.annotation.RequestMapping;
30 import org.springframework.web.bind.annotation.RequestMethod;
31 import org.springframework.web.bind.annotation.RequestParam;
32 import org.springframework.web.servlet.ModelAndView;
33
34 import eu.etaxonomy.cdm.api.facade.DerivedUnitFacade;
35 import eu.etaxonomy.cdm.api.facade.DerivedUnitFacadeNotSupportedException;
36 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
37 import eu.etaxonomy.cdm.api.service.ITaxonService;
38 import eu.etaxonomy.cdm.api.service.pager.Pager;
39 import eu.etaxonomy.cdm.api.service.pager.impl.DefaultPagerImpl;
40 import eu.etaxonomy.cdm.common.DocUtils;
41 import eu.etaxonomy.cdm.model.common.CdmBase;
42 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
43 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
44 import eu.etaxonomy.cdm.model.taxon.Taxon;
45 import eu.etaxonomy.cdm.persistence.query.OrderHint;
46 import eu.etaxonomy.cdm.remote.controller.AbstractController;
47 import eu.etaxonomy.cdm.remote.controller.util.PagerParameters;
48 import eu.etaxonomy.cdm.remote.dto.common.ErrorResponse;
49 import eu.etaxonomy.cdm.remote.dto.occurrencecatalogue.OccurrenceSearch;
50 import eu.etaxonomy.cdm.remote.dto.occurrencecatalogue.OccurrenceSearch.OccurrenceSearchResponse;
51 import eu.etaxonomy.cdm.remote.view.HtmlView;
52 import io.swagger.annotations.Api;
53
54 /**
55 * The controller class for the namespace 'occurrence_catalogue'. This web service namespace
56 * is an add-on to the already existing CDM REST API and provides information relating
57 * to scientific names as well as taxa present in the underlying datasource.
58 *
59 * @author p.kelbert
60 * @since March-2014
61 */
62 @Controller
63 @Api("occurrence_catalogue")
64 @RequestMapping(value = { "/occurrence_catalogue" })
65 public class OccurrenceCatalogueController extends AbstractController<SpecimenOrObservationBase, IOccurrenceService> implements ResourceLoaderAware{
66
67 private static final Logger logger = LogManager.getLogger();
68
69 private ResourceLoader resourceLoader;
70
71 public static final String DEFAULT_PAGE_NUMBER = "0";
72
73 public static final String DEFAULT_PAGE_SIZE = "50";
74
75 @Autowired
76 private ITaxonService taxonService;
77
78 @Autowired
79 private IOccurrenceService occurrenceService;
80
81
82 private static final List<String> OCCURRENCE_INIT_STRATEGY = Arrays.asList(new String []{
83 "sources"
84 });
85
86 private static final List<String> FACADE_INIT_STRATEGY = Arrays.asList(new String []{
87 "collector",
88 "collection.institute.name",
89 "fieldNotes",
90 "type",
91 "individualCount",
92 "kindOfUnit.label",
93 "absoluteElevation",
94 "absoluteElevationMaximum",
95 "distanceToGround",
96 "distanceToGroundMax",
97 "gatheringPeriod.start",
98 "gatheringPeriod.end",
99 "exactLocation.latitude",
100 "exactLocation.longitude",
101 "exactLocation.errorRadius",
102 "exactLocation.referenceSystem",
103 "country",
104 "locality",
105 "sources.citation",
106 "sources.citationMicroReference",
107 "rights.abbreviatedText"
108 });
109
110
111
112 public OccurrenceCatalogueController() {
113 super();
114 setInitializationStrategy(Arrays.asList(new String[] { "$" }));
115 }
116
117 /**
118 * Returns a documentation page for the Occurrence Search API.
119 * <p>
120 * URI: <b>&#x002F;{datasource-name}&#x002F;occurrence_catalogue</b>
121 *
122 * @param request
123 * @param response
124 * @return Html page describing the Name Search API
125 * @throws IOException
126 */
127 @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {})
128 public ModelAndView doGetNameSearchDocumentation(
129 HttpServletRequest request, HttpServletResponse response)
130 throws IOException {
131
132 logger.info("doGetNameSearchDocumentation() " + requestPathAndQuery(request));
133
134 ModelAndView mv = new ModelAndView();
135 // Read apt documentation file.
136 Resource resource = resourceLoader.getResource("classpath:eu/etaxonomy/cdm/doc/remote/apt/occurrence-catalogue-default.apt");
137 // using input stream as this works for both files in the classes directory
138 // as well as files inside jars
139 InputStream aptInputStream = resource.getInputStream();
140 // Build Html View
141 Map<String, String> modelMap = new HashMap<String, String>();
142 // Convert Apt to Html
143 modelMap.put("html", DocUtils.convertAptToHtml(aptInputStream));
144 mv.addAllObjects(modelMap);
145
146 HtmlView hv = new HtmlView();
147 mv.setView(hv);
148 return mv;
149 }
150
151 /**
152 * Returns a list of occurrences matching the <code>{query}</code>
153 * Taxon UUID.
154 * <p>
155 * Endpoint documentation can be found <a href="{@docRoot}/../remote/occurrence-catalogue-default.html">here</a>
156 * <p>
157 * URI: <b>&#x002F;{datasource-name}&#x002F;occurrence_catalogue</b>
158 *
159 * @param query
160 * The UUID of the taxon to query for. The query can
161 * not contain wildcard characters.
162 *
163 * @param request Http servlet request.
164 * @param response Http servlet response.
165 * @return a List of {@link OccurrenceSearch} objects each corresponding to a
166 * single query. These are built from {@link SpecimenOrObservationBase} entities
167 * which are in turn initialized using the {@link #OCCURRENCE_INIT_STRATEGY} and {@link #FACADE_INIT_STRATEGY}
168 * Redirect the query with the default page size and the default page number.
169 * @throws IOException
170 */
171 @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {"taxonUuid"})
172 public ModelAndView doGetOccurrenceSearch(@RequestParam(value = "taxonUuid", required = true) String query,
173 HttpServletRequest request, HttpServletResponse response) throws IOException {
174 return doGetOccurrenceSearch(query, DEFAULT_PAGE_NUMBER, DEFAULT_PAGE_SIZE, request, response);
175 }
176
177 /**
178 * Returns a list of occurrences matching the <code>{query}</code>
179 * Taxon UUID.
180 * <p>
181 * Endpoint documentation can be found <a href="{@docRoot}/../remote/occurrence-catalogue-default.html">here</a>
182 * <p>
183 * URI: <b>&#x002F;{datasource-name}&#x002F;occurrence_catalogue</b>
184 *
185 * @param query
186 * The UUID of the taxon to query for. The query can
187 * not contain wildcard characters.
188 *
189 * @param pageNumber
190 * The number of the page to be returned.
191 * * @param pageSize
192 * The number of responses per page to be returned.
193 * @param request Http servlet request.
194 * @param response Http servlet response.
195 * @return a List of {@link OccurrenceSearch} objects each corresponding to a
196 * single query. These are built from {@link SpecimenOrObservationBase} entities
197 * which are in turn initialized using the {@link #OCCURRENCE_INIT_STRATEGY} and {@link #FACADE_INIT_STRATEGY}
198 * @throws IOException
199 */
200 @SuppressWarnings({ "rawtypes", "unchecked" })
201 @RequestMapping(value = { "" }, method = RequestMethod.GET, params = {"taxonUuid","pageNumber","pageSize"})
202 public ModelAndView doGetOccurrenceSearch(
203 @RequestParam(value = "taxonUuid", required = true) String taxonUuid,
204 @RequestParam(value = "pageNumber", required = false, defaultValue = DEFAULT_PAGE_NUMBER) String pageNumber,
205 @RequestParam(value = "pageSize", required = false, defaultValue = DEFAULT_PAGE_SIZE) String pageSize,
206 HttpServletRequest request, HttpServletResponse response) throws IOException {
207
208 logger.info("doGetOccurrenceSearch() " + requestPathAndQuery(request));
209
210 ModelAndView mv = new ModelAndView();
211
212 Integer pS = null;
213 Integer pN = null;
214
215 try {
216 pN=Integer.valueOf(pageNumber);
217 } catch (Exception e) {
218 pN=Integer.valueOf(DEFAULT_PAGE_NUMBER);
219 logger.debug("pagenumber is not a number");
220 }
221 try {
222 pS=Integer.valueOf(pageSize);
223 } catch (Exception e) {
224 pS=Integer.valueOf(DEFAULT_PAGE_SIZE);
225 logger.debug("pagesize is not a number");
226 }
227
228 PagerParameters pagerParams = new PagerParameters(pS, pN);
229 pagerParams.normalizeAndValidate(response);
230
231 List<DerivedUnit> records = new ArrayList<DerivedUnit>();
232
233 OccurrenceSearch ns = new OccurrenceSearch();
234 ns.setRequest(taxonUuid);
235
236 Pager<DerivedUnit> specimenOrObs = null;
237 OccurrenceSearch os = new OccurrenceSearch();
238
239 if(taxonUuid.equals("") || !isValid(taxonUuid)) {
240 ErrorResponse er = new ErrorResponse();
241 er.setErrorMessage("Empty or invalid taxon uuid ");
242 mv.addObject(er);
243 return mv;
244 } else {
245 Taxon taxon = null;
246 try {
247 taxon = (Taxon) taxonService.find(UUID.fromString(taxonUuid));
248 } catch(ClassCastException cce) {
249 ErrorResponse er = new ErrorResponse();
250 er.setErrorMessage("Given UUID is not that of an accepted taxon");
251 mv.addObject(er);
252 return mv;
253 }
254 if(taxon == null) {
255 ErrorResponse er = new ErrorResponse();
256 er.setErrorMessage("No Taxon for given UUID : " + taxonUuid);
257 mv.addObject(er);
258 return mv;
259 }
260 pagerParams.normalizeAndValidate(response);
261 List<OrderHint> orderHints = null;
262
263 // Only derived units are requested in the pager method since we expect
264 // occurrences to be of type DerivedUnit
265 // The only possibility for an occurrence to be of type FieldUnit is the
266 // describedSpecimenOrObservation in the DescriptionBase class, which ideally
267 // should not be used for storing occurrences
268 specimenOrObs= service.pageByAssociatedTaxon(DerivedUnit.class,
269 null,
270 taxon,
271 null,
272 pagerParams.getPageSize(),
273 pagerParams.getPageIndex(),
274 orderHints,
275 null);
276
277 records = specimenOrObs.getRecords();
278
279 DerivedUnit derivedUnit=null;
280 List<DerivedUnitFacade> facades = new ArrayList<DerivedUnitFacade>();
281 for (SpecimenOrObservationBase<?> specimen:records){
282 derivedUnit = CdmBase.deproxy(specimen, DerivedUnit.class);
283 DerivedUnitFacade derivedUnitFacade =null;
284 try {
285 //TODO : This is a potential performance hurdle (especially for large number
286 // of derived units).
287 // A possible solution is to already initialise the required derived unit facade
288 // in the 'pageByAssociatedTaxon' call. This can be done either via the initialisation
289 // strategy technique or by writing a new optimised hql query for this.
290 // This will ensure that the DerivedUnits are loaded with required fields already
291 // initialised and will not require the loading and initialisation of each DerivedUnit
292 // object as is done in the 'getDerivedUnitFacade' method.
293 derivedUnitFacade = occurrenceService.getDerivedUnitFacade(derivedUnit, FACADE_INIT_STRATEGY);
294 os.addToResponse(taxon.getTitleCache(), taxonUuid, derivedUnitFacade);
295 } catch (DerivedUnitFacadeNotSupportedException e) {
296 derivedUnitFacade=null;
297 }
298 }
299 }
300
301
302 if (os.getResponse().isEmpty()){
303 ErrorResponse er = new ErrorResponse();
304 er.setErrorMessage("No specimen or observation for given taxon uuid");
305 mv.addObject(er);
306 return mv;
307 } else {
308 DefaultPagerImpl<OccurrenceSearchResponse> dpi =
309 new DefaultPagerImpl<OccurrenceSearchResponse>(specimenOrObs.getCurrentIndex(),
310 specimenOrObs.getCount(),
311 specimenOrObs.getPageSize(),
312 os.getResponse());
313 mv.addObject(dpi);
314 }
315
316 return mv;
317 }
318
319
320 private boolean isValid(String uuid){
321 if( uuid == null) {
322 return false;
323 }
324 try {
325 // we have to convert to object and back to string because the built in fromString does not have
326 // good validation logic.
327
328 UUID fromStringUUID = UUID.fromString(uuid);
329 String toStringUUID = fromStringUUID.toString();
330
331 logger.debug("input uuid : " + uuid + " , parsed uuid : " + toStringUUID);
332 return toStringUUID.equals(uuid);
333 } catch(IllegalArgumentException e) {
334 return false;
335 }
336 }
337
338
339 /* (non-Javadoc)
340 * @see eu.etaxonomy.cdm.remote.controller.BaseListController#setService(eu.etaxonomy.cdm.api.service.IService)
341 */
342 @Override
343 @Autowired
344 public void setService(IOccurrenceService service) {
345 this.service = service;
346 }
347
348 @Override
349 public void setResourceLoader(ResourceLoader resourceLoader) {
350 this.resourceLoader = resourceLoader;
351
352 }
353 }