1 package org.cateproject.controller.flow.action;
2
3 import java.io.File;
4 import java.io.FileInputStream;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.lang.reflect.Field;
8 import java.util.ArrayList;
9 import java.util.Collection;
10 import java.util.HashMap;
11 import java.util.HashSet;
12 import java.util.List;
13 import java.util.Map;
14 import java.util.Set;
15 import java.util.UUID;
16
17 import org.apache.poi.hssf.usermodel.HSSFCell;
18 import org.apache.poi.hssf.usermodel.HSSFDataFormatter;
19 import org.apache.poi.hssf.usermodel.HSSFSheet;
20 import org.apache.poi.hssf.usermodel.HSSFWorkbook;
21 import org.apache.poi.poifs.filesystem.POIFSFileSystem;
22 import org.apache.poi.ss.usermodel.DataFormatter;
23 import org.cateproject.controller.editor.IdentifiableEntityPropertyEditor;
24 import org.cateproject.controller.flow.action.upload.AbstractMatrixHeader;
25 import org.cateproject.controller.flow.action.upload.AbstractMatrixHeaderType;
26 import org.cateproject.controller.flow.action.upload.CurrentCell;
27 import org.cateproject.controller.flow.action.upload.LocalObjectDataBinder;
28 import org.cateproject.controller.flow.action.upload.MatrixHeader;
29 import org.cateproject.controller.flow.action.upload.ParsedMatrix;
30 import org.cateproject.controller.flow.action.upload.UploadForm;
31 import org.cateproject.service.event.CdmObjectEvent;
32 import org.hibernate.LockMode;
33 import org.springframework.beans.BeanUtils;
34 import org.springframework.beans.MutablePropertyValues;
35 import org.springframework.beans.PropertyValue;
36 import org.springframework.beans.factory.annotation.Autowired;
37 import org.springframework.beans.propertyeditors.CustomCollectionEditor;
38 import org.springframework.beans.propertyeditors.StringTrimmerEditor;
39 import org.springframework.binding.message.MessageBuilder;
40 import org.springframework.binding.message.MessageContext;
41 import org.springframework.core.convert.ConversionService;
42 import org.springframework.core.convert.TypeDescriptor;
43 import org.springframework.core.convert.converter.Converter;
44 import org.springframework.core.convert.converter.GenericConverter;
45 import org.springframework.util.ReflectionUtils;
46 import org.springframework.validation.BindingResult;
47 import org.springframework.validation.DataBinder;
48 import org.springframework.validation.DefaultMessageCodesResolver;
49 import org.springframework.validation.FieldError;
50 import org.springframework.validation.MessageCodesResolver;
51 import org.springframework.validation.ObjectError;
52 import org.springframework.web.bind.WebDataBinder;
53 import org.springframework.web.multipart.MultipartFile;
54 import org.springframework.webflow.context.ExternalContext;
55 import org.springframework.webflow.execution.RequestContext;
56
57 import eu.etaxonomy.cdm.api.service.IAgentService;
58 import eu.etaxonomy.cdm.api.service.ICollectionService;
59 import eu.etaxonomy.cdm.api.service.IFeatureTreeService;
60 import eu.etaxonomy.cdm.api.service.IIdentifiableEntityService;
61 import eu.etaxonomy.cdm.api.service.IMediaService;
62 import eu.etaxonomy.cdm.api.service.INameService;
63 import eu.etaxonomy.cdm.api.service.IOccurrenceService;
64 import eu.etaxonomy.cdm.api.service.IReferenceService;
65 import eu.etaxonomy.cdm.api.service.IService;
66 import eu.etaxonomy.cdm.api.service.ITaxonService;
67 import eu.etaxonomy.cdm.api.service.ITermService;
68 import eu.etaxonomy.cdm.model.agent.AgentBase;
69 import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;
70 import eu.etaxonomy.cdm.model.agent.Institution;
71 import eu.etaxonomy.cdm.model.agent.Person;
72 import eu.etaxonomy.cdm.model.agent.Team;
73 import eu.etaxonomy.cdm.model.common.CdmBase;
74 import eu.etaxonomy.cdm.model.common.DefinedTermBase;
75 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;
76 import eu.etaxonomy.cdm.model.common.Language;
77 import eu.etaxonomy.cdm.model.common.LanguageString;
78 import eu.etaxonomy.cdm.model.common.OrderedTermVocabulary;
79 import eu.etaxonomy.cdm.model.common.Representation;
80 import eu.etaxonomy.cdm.model.common.TermBase;
81 import eu.etaxonomy.cdm.model.common.TermVocabulary;
82 import eu.etaxonomy.cdm.model.description.AbsenceTerm;
83 import eu.etaxonomy.cdm.model.description.Feature;
84 import eu.etaxonomy.cdm.model.description.FeatureTree;
85 import eu.etaxonomy.cdm.model.description.MeasurementUnit;
86 import eu.etaxonomy.cdm.model.description.MediaKey;
87 import eu.etaxonomy.cdm.model.description.Modifier;
88 import eu.etaxonomy.cdm.model.description.PresenceTerm;
89 import eu.etaxonomy.cdm.model.description.State;
90 import eu.etaxonomy.cdm.model.description.StatisticalMeasure;
91 import eu.etaxonomy.cdm.model.description.TextFormat;
92 import eu.etaxonomy.cdm.model.location.NamedArea;
93 import eu.etaxonomy.cdm.model.location.NamedAreaLevel;
94 import eu.etaxonomy.cdm.model.location.NamedAreaType;
95 import eu.etaxonomy.cdm.model.location.ReferenceSystem;
96 import eu.etaxonomy.cdm.model.location.WaterbodyOrCountry;
97 import eu.etaxonomy.cdm.model.media.Media;
98 import eu.etaxonomy.cdm.model.media.Rights;
99 import eu.etaxonomy.cdm.model.media.RightsTerm;
100 import eu.etaxonomy.cdm.model.molecular.DnaSample;
101 import eu.etaxonomy.cdm.model.molecular.PhylogeneticTree;
102 import eu.etaxonomy.cdm.model.name.BacterialName;
103 import eu.etaxonomy.cdm.model.name.BotanicalName;
104 import eu.etaxonomy.cdm.model.name.CultivarPlantName;
105 import eu.etaxonomy.cdm.model.name.HybridRelationshipType;
106 import eu.etaxonomy.cdm.model.name.NameRelationshipType;
107 import eu.etaxonomy.cdm.model.name.NameTypeDesignation;
108 import eu.etaxonomy.cdm.model.name.NameTypeDesignationStatus;
109 import eu.etaxonomy.cdm.model.name.NomenclaturalStatusType;
110 import eu.etaxonomy.cdm.model.name.NonViralName;
111 import eu.etaxonomy.cdm.model.name.Rank;
112 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignation;
113 import eu.etaxonomy.cdm.model.name.SpecimenTypeDesignationStatus;
114 import eu.etaxonomy.cdm.model.name.TaxonNameBase;
115 import eu.etaxonomy.cdm.model.name.TypeDesignationBase;
116 import eu.etaxonomy.cdm.model.name.ViralName;
117 import eu.etaxonomy.cdm.model.name.ZoologicalName;
118 import eu.etaxonomy.cdm.model.occurrence.DerivationEventType;
119 import eu.etaxonomy.cdm.model.occurrence.DerivedUnit;
120 import eu.etaxonomy.cdm.model.occurrence.DeterminationModifier;
121 import eu.etaxonomy.cdm.model.occurrence.FieldObservation;
122 import eu.etaxonomy.cdm.model.occurrence.Fossil;
123 import eu.etaxonomy.cdm.model.occurrence.LivingBeing;
124 import eu.etaxonomy.cdm.model.occurrence.Specimen;
125 import eu.etaxonomy.cdm.model.occurrence.SpecimenOrObservationBase;
126 import eu.etaxonomy.cdm.model.reference.IReferenceBase;
127 import eu.etaxonomy.cdm.model.reference.ReferenceBase;
128 import eu.etaxonomy.cdm.model.taxon.SynonymRelationshipType;
129 import eu.etaxonomy.cdm.model.taxon.TaxonBase;
130 import eu.etaxonomy.cdm.model.taxon.TaxonRelationshipType;
131
132 public abstract class AbstractUploadAction<T extends IdentifiableEntity,SERVICE extends IIdentifiableEntityService<T>> extends AbstractFlowAction<T,SERVICE> {
133
134 private static final String CURRENT_CELL_KEY = "currentCell";
135 private Class<T> type;
136 private File tmpDir = new File(System.getProperty("java.io.tmpdir"));
137 private String[] disallowedFields = {"uuid","id","created", "createdBy", "updated","updatedBy"};
138 private ConversionService conversionService;
139 private IReferenceService referenceService;
140 private IAgentService agentService;
141 private DataBinder dataBinder;
142 private ICollectionService collectionService;
143 private IFeatureTreeService featureTreeService;
144 private INameService nameService;
145 private IMediaService mediaService;
146 private IOccurrenceService occurrenceService;
147 private ITaxonService taxonService;
148 private ITermService termService;
149 private MessageCodesResolver messageCodesResolver = new DefaultMessageCodesResolver();
150
151 public AbstractUploadAction(Class<T> type) {
152 this.type = type;
153 }
154
155 @Autowired
156 public void setReferenceService(IReferenceService referenceService) {
157 this.referenceService = referenceService;
158 }
159
160 @Autowired
161 public void setAgentService(IAgentService agentService) {
162 this.agentService = agentService;
163 }
164
165 @Autowired
166 public void setCollectionService(ICollectionService collectionService) {
167 this.collectionService = collectionService;
168 }
169
170 @Autowired
171 public void setFeatureTreeService(IFeatureTreeService featureTreeService) {
172 this.featureTreeService = featureTreeService;
173 }
174
175 @Autowired
176 public void setNameService(INameService nameService) {
177 this.nameService = nameService;
178 }
179
180 @Autowired
181 public void setMediaService(IMediaService mediaService) {
182 this.mediaService = mediaService;
183 }
184
185 @Autowired
186 public void setOccurrenceService(IOccurrenceService occurrenceService) {
187 this.occurrenceService = occurrenceService;
188 }
189
190 @Autowired
191 public void setTaxonService(ITaxonService taxonService) {
192 this.taxonService = taxonService;
193 }
194
195 @Autowired
196 public void setTermService(ITermService termService) {
197 this.termService = termService;
198 }
199
200 @Autowired
201 public void setConversionService(ConversionService conversionService) {
202 this.conversionService = conversionService;
203 }
204
205 public void setMessageCodesResolver(MessageCodesResolver messageCodesResolver) {
206 this.messageCodesResolver = messageCodesResolver;
207 }
208
209 public void setTmpDir(String tmpDirString) {
210 this.tmpDir = new File(tmpDirString);
211 assert tmpDir.exists() : "Temp Dir must exist";
212 assert tmpDir.isDirectory() : "Temp Dir must be a directory";
213 }
214
215 public abstract void setService(SERVICE service);
216
217 public static Map<Class,List<Class>> TYPES;
218 static {
219 TYPES = new HashMap<Class,List<Class>>();
220
221 List<Class> types = new ArrayList<Class>();
222 types.add(Person.class);
223 types.add(Team.class);
224 types.add(Institution.class);
225 TYPES.put(AgentBase.class, types);
226
227 types = new ArrayList<Class>();
228 types.add(Person.class);
229 types.add(Team.class);
230 TYPES.put(INomenclaturalAuthor.class, types);
231
232 types = new ArrayList<Class>();
233 types.add(BotanicalName.class);
234 types.add(BacterialName.class);
235 types.add(CultivarPlantName.class);
236 types.add(NonViralName.class);
237 types.add(ViralName.class);
238 types.add(ZoologicalName.class);
239 TYPES.put(TaxonNameBase.class, types);
240
241 types = new ArrayList<Class>();
242 types.add(SpecimenTypeDesignation.class);
243 types.add(NameTypeDesignation.class);
244 TYPES.put(TypeDesignationBase.class, types);
245
246 types = new ArrayList<Class>();
247 types.add(FieldObservation.class);
248 types.add(LivingBeing.class);
249 types.add(DerivedUnit.class);
250 types.add(Specimen.class);
251 types.add(Fossil.class);
252 types.add(DnaSample.class);
253 TYPES.put(SpecimenOrObservationBase.class, types);
254
255 types = new ArrayList<Class>();
256 types.add(TermBase.class);
257 types.add(Language.class);
258 types.add(AbsenceTerm.class);
259 types.add(MeasurementUnit.class);
260 types.add(PresenceTerm.class);
261 types.add(StatisticalMeasure.class);
262 types.add(TextFormat.class);
263 types.add(NamedArea.class);
264 types.add(NamedAreaLevel.class);
265 types.add(NamedAreaType.class);
266 types.add(ReferenceSystem.class);
267 types.add(WaterbodyOrCountry.class);
268 types.add(RightsTerm.class);
269 types.add(HybridRelationshipType.class);
270 types.add(NameRelationshipType.class);
271 types.add(NameTypeDesignationStatus.class);
272 types.add(NomenclaturalStatusType.class);
273 types.add(Rank.class);
274 types.add(SpecimenTypeDesignationStatus.class);
275 types.add(DerivationEventType.class);
276 types.add(DeterminationModifier.class);
277 types.add(SynonymRelationshipType.class);
278 types.add(TaxonRelationshipType.class);
279 types.add(Feature.class);
280 types.add(State.class);
281 types.add(Modifier.class);
282 TYPES.put(DefinedTermBase.class, types);
283
284 types = new ArrayList<Class>();
285 types.add(Media.class);
286 types.add(MediaKey.class);
287 types.add(PhylogeneticTree.class);
288 TYPES.put(Media.class, types);
289
290 types = new ArrayList<Class>();
291 types.add(TermVocabulary.class);
292 types.add(TermBase.class);
293 types.add(OrderedTermVocabulary.class);
294 TYPES.put(TermVocabulary.class, types);
295
296 types = new ArrayList<Class>();
297 types.add(Representation.class);
298 TYPES.put(Representation.class, types);
299
300 types = new ArrayList<Class>();
301 types.add(LanguageString.class);
302 TYPES.put(LanguageString.class, types);
303
304 types = new ArrayList<Class>();
305 types.add(Rights.class);
306 TYPES.put(Rights.class, types);
307
308
309 }
310
311 protected AbstractMatrixHeader[] getExampleHeaders() {
312
313 String[] headers = doGetHeaders();
314
315 AbstractMatrixHeader[] exampleHeaders = new AbstractMatrixHeader[headers.length];
316 try{
317 for(int i = 0; i < headers.length; i++) {
318 exampleHeaders[i] = new AbstractMatrixHeader<T>(headers[i],i,getType(),TYPES,termService);
319 }
320 logger.info("Returning " + exampleHeaders.length + " headers");
321 } catch(Exception e) {
322 logger.error(e.getClass() + " " + e.getMessage());
323 for(StackTraceElement ste : e.getStackTrace()) {
324 logger.error(ste);
325 }
326 }
327 return exampleHeaders;
328 }
329
330 protected String[] doGetHeaders() {
331 return new String[] {"class","titleCache","protectedTitleCache"};
332 }
333
334 public Class<T> getType() {
335 return type;
336 }
337
338 public UploadForm setupUploadObject() {
339 return new UploadForm();
340 }
341
342 public boolean createDownloadForm(ExternalContext externalContext) {
343 logger.info("createDownloadForm(ExternalContext externalContext) ");
344 externalContext.getSessionMap().put("download", getExampleHeaders());
345 logger.info("returning " + Boolean.TRUE);
346 return Boolean.TRUE;
347 }
348
349 public ParsedMatrix constructMatrixFromFile(File file) throws IOException {
350 ParsedMatrix<T> parsedMatrix = new ParsedMatrix<T>();
351 InputStream inputStream = new FileInputStream(file);
352 HSSFWorkbook workbook = new HSSFWorkbook(new POIFSFileSystem(inputStream));
353 HSSFSheet sheet = workbook.getSheetAt(0);
354 int physicalNumberOfRows = sheet.getPhysicalNumberOfRows();
355 int numberOfColumns = sheet.getRow(0).getPhysicalNumberOfCells();
356 int numberOfRows = 0;
357 for(int i = 0; i < physicalNumberOfRows; i++) {
358 if(sheet.getRow(i).getCell(0) == null || sheet.getRow(i).getCell(0).getRichStringCellValue() == null) {
359 break;
360 } else {
361 String cellValue = sheet.getRow(i).getCell(0).getRichStringCellValue().getString();
362 if(cellValue == null || cellValue.trim().equals("")) {
363 break;
364 } else {
365 numberOfRows++;
366 }
367 }
368 }
369
370 logger.info("Sheet has " + numberOfRows + " rows and " + numberOfColumns + " columns");
371
372 List<AbstractMatrixHeader<T>> headers = new ArrayList<AbstractMatrixHeader<T>>();
373 for(int i = 0; i < numberOfColumns; i++) {
374
375 HSSFCell cell = sheet.getRow(0).getCell(i);
376 if(cell != null && cell.getRichStringCellValue() != null) {
377 headers.add(new AbstractMatrixHeader<T>(cell.getRichStringCellValue().getString(),i,getType(),TYPES,termService));
378 }
379
380 }
381
382 parsedMatrix.setHeaders(headers);
383
384 DataFormatter dataFormatter = new HSSFDataFormatter();
385
386 String[][] matrix = new String[numberOfRows - 1][numberOfColumns];
387 for(int i = 1; i < numberOfRows; i++) {
388
389 for(int j = 0; j < numberOfColumns; j++){
390 HSSFCell cell = sheet.getRow(i).getCell(j);
391 if(cell != null) {
392 matrix[i-1][j] = dataFormatter.formatCellValue(cell);
393 }
394 }
395 }
396 if(logger.isInfoEnabled()) {
397 logger.info("matrix parsed, values are ");
398 for(String[] row : matrix) {
399 logger.info(row);
400 }
401 }
402 parsedMatrix.setMatrix(matrix);
403 return parsedMatrix;
404 }
405
406 public ParsedMatrix<T> handleUpload(UploadForm uploadForm, RequestContext requestContext, MessageContext messageContext) throws Exception {
407 File tmpFile = null;
408
409 try {
410
411 logger.info("parsing matrix");
412 MultipartFile multipartFile = uploadForm.getFile();
413 UUID uuid = UUID.randomUUID();
414
415 String extension = multipartFile.getOriginalFilename().substring(multipartFile.getOriginalFilename().lastIndexOf("."));
416 tmpFile = new File(tmpDir, uuid.toString() + extension);
417 uploadForm.getFile().transferTo(tmpFile);
418
419 ParsedMatrix parsedMatrix = constructMatrixFromFile(tmpFile);
420
421 tmpFile.delete();
422
423 return parsedMatrix;
424 } catch (IllegalStateException ise) {
425 logger.error(ise);
426 messageContext.addMessage(new MessageBuilder().error()
427 .source("file").code("UploadAction.illegalStateException")
428 .defaultText(ise.getLocalizedMessage()).build());
429 logger.error(ise);
430 for(StackTraceElement ste : ise.getStackTrace()) {
431 logger.error(ste);
432 }
433 if(tmpFile != null) {
434 tmpFile.delete();
435 }
436 throw ise;
437 } catch (IOException ioe) {
438 messageContext.addMessage(new MessageBuilder().error()
439 .source("file").code("UploadAction.ioException")
440 .defaultText(ioe.getLocalizedMessage()).build());
441 logger.error(ioe);
442 for(StackTraceElement ste : ioe.getStackTrace()) {
443 logger.error(ste);
444 }
445 if(tmpFile != null) {
446 tmpFile.delete();
447 }
448 throw ioe;
449 } catch (Exception e) {
450 messageContext.addMessage(new MessageBuilder().error()
451 .source("file").code("UploadAction.exception")
452 .defaultText(e.getLocalizedMessage()).build());
453 logger.error(e);
454 for(StackTraceElement ste : e.getStackTrace()) {
455 logger.error(ste);
456 }
457 if(tmpFile != null) {
458 tmpFile.delete();
459 }
460 throw e;
461 }
462 }
463
464 public void bindObject(String prefix,List<T> objects,Integer index, MutablePropertyValues classes, String[] values, Collection<AbstractMatrixHeader> headers) {
465 for(AbstractMatrixHeader header : headers) {
466 if(header.getType().equals(AbstractMatrixHeaderType.ROOT_CLASS)) {
467 if(values[header.getIndex()] != null) {
468 Class clazz;
469 try {
470 clazz = Class.forName(values[header.getIndex()]);
471 T t = (T)BeanUtils.instantiateClass(clazz);
472 logger.info("Setting object at index " + index + " to " + t);
473 if(objects.size() > index) {
474 objects.set(index, t);
475 } else {
476 objects.add(t);
477 }
478 } catch (ClassNotFoundException e) {
479 logger.error(e);
480 }
481 } else {
482 objects.set(index, null);
483 break;
484 }
485 } else if(header.isApplicable(objects.get(index),conversionService)) {
486 if(header.getType().equals(AbstractMatrixHeaderType.CLASS)) {
487 if(values[header.getIndex()] != null) {
488 Class clazz;
489 try {
490 clazz = Class.forName(values[header.getIndex()]);
491 Object o = BeanUtils.instantiateClass(clazz);
492 if(logger.isInfoEnabled()) {
493 logger.info("Adding " + header.getPath(prefix) + ": " + o + ", class " + clazz + " is null? " + (o == null) + " is set? " + header.isSet() + " is map? " + header.isMap());
494 }
495 if(header.isSet()) {
496 Object object = objects.get(index);
497 String property = header.getPaths()[0].getProperty();
498 Field field = ReflectionUtils.findField(object.getClass(), property);
499 ReflectionUtils.makeAccessible(field);
500 Set set = (Set) ReflectionUtils.getField(field, object);
501 if(set == null) {
502 set = new HashSet();
503 field.set(object, set);
504 }
505 set.add(o);
506 if(logger.isInfoEnabled()) {
507 logger.info("Property is a Set: Set " + property + " for " + object + " to " + o);
508 }
509 } else if(header.isMap()) {
510 Object object = objects.get(index);
511 String property = header.getPaths()[0].getProperty();
512
513 Object key = conversionService.convert(header.getPaths()[0].getIndex(), Language.class);
514 Field field = ReflectionUtils.findField(object.getClass(), property);
515 ReflectionUtils.makeAccessible(field);
516 Map map = (Map) ReflectionUtils.getField(field, object);
517 if(map == null) {
518 map = new HashMap();
519 field.set(object, map);
520 }
521 map.put(key, o);
522 if(logger.isInfoEnabled()) {
523 logger.info("Property is a Map: Set " + property + " for " + object + " with key " + key + " to " + o);
524 }
525 } else {
526 classes.addPropertyValue(header.getPath(prefix),o);
527 }
528 } catch (ClassNotFoundException e) {
529 logger.error(e);
530 } catch (IllegalArgumentException e) {
531 logger.error(e);
532 } catch (IllegalAccessException e) {
533 logger.error(e);
534 }
535 } else {
536 if(!header.getPaths()[0].isCollection()) {
537 logger.info("Setting object value '" + header.getPath(prefix) + "' to " + null );
538 classes.addPropertyValue(header.getPath(prefix),null);
539 }
540 }
541 }
542
543 }
544 }
545 }
546
547 public void bindProperties(String prefix,List<T> objects,Integer index, MutablePropertyValues properties, String[] values, Collection<AbstractMatrixHeader> headers, MutablePropertyValues classProperties) {
548 for(AbstractMatrixHeader header : headers) {
549 if(logger.isInfoEnabled()) {
550 logger.info(header.getPath(prefix)
551 + " Property Type? " + header.getType().equals(AbstractMatrixHeaderType.PROPERTY)
552 + " applicable? " + header.isApplicable(objects.get(index),conversionService)
553 + " not set by class " + !classProperties.contains(header.getPath(prefix))
554 + " value " + classProperties.getPropertyValue(header.getPath(prefix)));
555 }
556 if(header.getType().equals(AbstractMatrixHeaderType.PROPERTY)
557 && header.isApplicable(objects.get(index),conversionService)
558 && (!classProperties.contains(header.getPath(prefix)) ||
559 classProperties.getPropertyValue(header.getPath(prefix)).getValue() == null)) {
560
561 if(values[header.getIndex()] != null) {
562 if(logger.isInfoEnabled()) {
563 logger.info("Adding " + header.getPath(prefix) + " " + values[header.getIndex()] );
564 }
565 properties.add(header.getPath(prefix), values[header.getIndex()]);
566 } else {
567 if(header.getHeaderForObject(objects.get(index),conversionService).isBooleanType()) {
568 if(logger.isInfoEnabled()) {
569 logger.info("Adding " + header.getPath(prefix) + " " + false );
570 }
571 properties.add(header.getPath(prefix), "false");
572 } else {
573 if(logger.isInfoEnabled()) {
574 logger.info("Setting " + header.getPath(prefix) + " to " + null );
575 }
576 properties.add(header.getPath(prefix), null);
577 }
578 }
579 }
580 }
581 }
582
583 protected BindingResult doBindObjects(ParsedMatrix parsedMatrix, MessageContext messageContext, MutablePropertyValues classes) {
584
585 for(int i = 0; i < parsedMatrix.getMatrix().length; i++) {
586 logger.info("Adding values for object[" + i + "]");
587 String[] row = parsedMatrix.getMatrix()[i];
588 String prefix = "objects["+i+"]";
589 bindObject(prefix,parsedMatrix.getObjects(),i,classes,row,parsedMatrix.getHeaders());
590 }
591 for(PropertyValue propertyValue : classes.getPropertyValues()) {
592 logger.info(propertyValue.getName() + " " + propertyValue.getValue());
593 }
594 dataBinder.bind(classes);
595 logger.info("Bound objects");
596 return dataBinder.getBindingResult();
597 }
598
599 protected BindingResult doBindProperties(ParsedMatrix parsedMatrix, MessageContext messageContext, MutablePropertyValues classProperties) {
600 MutablePropertyValues properties = new MutablePropertyValues();
601 for(int i = 0; i < parsedMatrix.getMatrix().length; i++) {
602 logger.info("Adding values for object[" + i + "]");
603 String[] row = parsedMatrix.getMatrix()[i];
604 String prefix = "objects["+i+"]";
605 bindProperties(prefix,parsedMatrix.getObjects(),i,properties,row,parsedMatrix.getHeaders(),classProperties);
606 }
607
608 dataBinder.bind(properties);
609 return dataBinder.getBindingResult();
610 }
611
612 public Boolean bindMatrix(ParsedMatrix parsedMatrix, MessageContext messageContext, Boolean ignoreBindingErrors) {
613 Boolean result = bindMatrix(parsedMatrix,messageContext);
614 if(!ignoreBindingErrors) {
615 return result;
616 } else {
617 return Boolean.TRUE;
618 }
619 }
620
621 public Boolean bindMatrix(ParsedMatrix parsedMatrix, MessageContext messageContext) {
622 logger.info("binding matrix");
623 try {
624 dataBinder = new WebDataBinder(parsedMatrix);
625 dataBinder.setAutoGrowNestedPaths(true);
626 dataBinder.setDisallowedFields(disallowedFields);
627 dataBinder.setConversionService(new ConversionServiceWrapper<T>(conversionService, new IdentifiableEntityPropertyEditor<T,SERVICE>(type, service), parsedMatrix, type));
628 dataBinder.registerCustomEditor(String.class, new StringTrimmerEditor(true));
629 dataBinder.registerCustomEditor(Set.class, new CustomCollectionEditor(Set.class) {
630 protected Object convertElement(Object element) {
631 logger.info("Calling convertElement " + element);
632 return null;
633 }
634 });
635
636 MutablePropertyValues classes = new MutablePropertyValues();
637 doBindObjects(parsedMatrix,messageContext, classes);
638 BindingResult bindingResult = doBindProperties(parsedMatrix,messageContext, classes);
639 if(bindingResult.hasErrors()) {
640 logger.info("Binding has resulted in errors");
641 for(ObjectError error : bindingResult.getAllErrors()) {
642 if(error instanceof FieldError) {
643 FieldError fieldError = (FieldError) error;
644
645 String field = fieldError.getField().substring(fieldError.getField().indexOf("]") + 2);
646 logger.error(fieldError.getField() + " " + field + " " + fieldError.getDefaultMessage());
647 AbstractMatrixHeader header = parsedMatrix.getHeaderForField(fieldError.getField());
648 Class fieldType = header.getFieldType();
649 String[] messageCodes = messageCodesResolver.resolveMessageCodes(error.getCode(), "object", field, fieldType);
650 if(logger.isInfoEnabled()) {
651 logger.info("Field " + field + " Message Codes " + messageCodes + " resolvableArg " + field + " defaultText \"" + error.getCode() + " on " + field + "\"");
652 for(String messageCode : messageCodes) {
653 logger.info("MessageCode : " + messageCode);
654 }
655 }
656 messageContext.addMessage(new MessageBuilder().error().source(fieldError.getField()).codes(messageCodes).resolvableArg(field).defaultText(
657 error.getCode() + " on " + field).build());
658
659 } else {
660 messageContext.addMessage(new MessageBuilder().error().code(error.getCode()).defaultText(error.getDefaultMessage()).build());
661 }
662 }
663 return Boolean.FALSE;
664 }
665 } catch(Throwable t) {
666 logger.error(t.getClass());
667 logger.error(t.getMessage());
668 for(StackTraceElement ste : t.getStackTrace()) {
669 logger.error(ste.toString());
670 }
671 return Boolean.FALSE;
672 }
673 logger.info("Matrix bound " + parsedMatrix.getObjects());
674 return Boolean.TRUE;
675 }
676
677 protected boolean validateMatrix(ParsedMatrix parsedMatrix, MessageContext messageContext) {
678 boolean allValid = true;
679 for(int i = 0; i < parsedMatrix.getObjects().size(); i++) {
680 boolean thisValid = validateIgnoringProperties(parsedMatrix.getObjects().get(i), messageContext,null, "objects["+i+"]");
681 if(!thisValid) {
682 allValid = false;
683 }
684 }
685 return allValid;
686 }
687
688 public abstract boolean validate(ParsedMatrix parsedMatrix, MessageContext messageContext);
689
690 public abstract boolean updateTitleCache(ParsedMatrix parsedMatrix);
691
692 public abstract Boolean saveOrUpdate(ParsedMatrix parsedMatrix, MessageContext messageContext);
693
694 protected Boolean isCompatibleWithType(Class requiredType) {
695 if(this.type.isAssignableFrom(requiredType)) {
696 return true;
697 }
698 return false;
699 }
700
701
702 public Boolean doSave(ParsedMatrix parsedMatrix, MessageContext messageContext) {
703 for(T t : (List<T>)parsedMatrix.getObjects()) {
704 t.setProtectedTitleCache(true);
705 service.saveOrUpdate(t);
706 applicationContext.publishEvent(new CdmObjectEvent<T>(this,t));
707 }
708 return Boolean.TRUE;
709 }
710
711 public Boolean removeObject(Integer row, Integer column, ParsedMatrix matrix) {
712 matrix.setMatrixValue(row,column,null);
713 return Boolean.TRUE;
714 }
715
716 public String route(Integer row, Integer column, ParsedMatrix matrix, RequestContext requestContext) {
717 MatrixHeader header = matrix.getHeader(row, column,conversionService);
718 try{
719 Integer localId = Integer.parseInt(matrix.getMatrixValue(row, column));
720
721 this.leaveMessage("org.cateproject.controller.flow.action.AbstractUploadAction.localObject", requestContext);
722 return "edit";
723 } catch(NumberFormatException nfe) {
724 UUID uuid = null;
725 try {
726 uuid = UUID.fromString(matrix.getMatrixValue(row, column));
727 } catch(IllegalArgumentException iae) {
728 catch(NullPointerException npe) {
729 CurrentCell currentCell = new CurrentCell(row,column,matrix.getMatrixValue(row, column),uuid);
730 logger.debug("Row " + row + " column " + column + " type " + header.getType());
731 if(header.isObjectType()) {
732 Class type = header.getClazz();
733 currentCell.setType(type);
734 requestContext.getFlowScope().put(AbstractUploadAction.CURRENT_CELL_KEY, currentCell);
735 logger.debug("Cell is object type " + type);
736 if(ReferenceBase.class.isAssignableFrom(type)) {
737 return "reference";
738 } else if(IReferenceBase.class.isAssignableFrom(type)) {
739 return "reference";
740 } else if(AgentBase.class.isAssignableFrom(type)) {
741 return "agent";
742 } else if(INomenclaturalAuthor.class.isAssignableFrom(type)) {
743 return "agent";
744 } else if(eu.etaxonomy.cdm.model.occurrence.Collection.class.isAssignableFrom(type)) {
745 return "collection";
746 } else if(FeatureTree.class.isAssignableFrom(type)) {
747 return "featureTree";
748 } else if(TaxonNameBase.class.isAssignableFrom(type)) {
749 return "name";
750 } else if(Media.class.isAssignableFrom(type)) {
751 return "media";
752 } else if(SpecimenOrObservationBase.class.isAssignableFrom(type)) {
753 return "occurrence";
754 } else if(TaxonBase.class.isAssignableFrom(type)) {
755 return "taxon";
756 }
757 }
758 return "edit";
759 }
760
761 public Boolean setCell(UUID uuid,ParsedMatrix matrix,CurrentCell currentCell) {
762 if(ReferenceBase.class.isAssignableFrom(currentCell.getType())) {
763 refresh(uuid,referenceService);
764 } else if(IReferenceBase.class.isAssignableFrom(currentCell.getType())) {
765 refresh(uuid,referenceService);
766 }else if(AgentBase.class.isAssignableFrom(currentCell.getType())) {
767 refresh(uuid,agentService);
768 } else if(INomenclaturalAuthor.class.isAssignableFrom(currentCell.getType())) {
769 refresh(uuid,agentService);
770 }else if(eu.etaxonomy.cdm.model.occurrence.Collection.class.isAssignableFrom(currentCell.getType())) {
771 refresh(uuid,collectionService);
772 } else if(FeatureTree.class.isAssignableFrom(currentCell.getType())) {
773 refresh(uuid,featureTreeService);
774 } else if(TaxonNameBase.class.isAssignableFrom(currentCell.getType())) {
775 refresh(uuid,nameService);
776 } else if(Media.class.isAssignableFrom(currentCell.getType())) {
777 refresh(uuid,mediaService);
778 } else if(SpecimenOrObservationBase.class.isAssignableFrom(currentCell.getType())) {
779 refresh(uuid,occurrenceService);
780 } else if(TaxonBase.class.isAssignableFrom(currentCell.getType())) {
781 refresh(uuid,taxonService);
782 }
783 matrix.setMatrixValue(currentCell.getRow(), currentCell.getColumn(), uuid.toString());
784 return Boolean.TRUE;
785 }
786
787 private <T extends CdmBase> void refresh(UUID uuid, IService<T,UUID> service) {
788 T t = service.find(uuid);
789 service.refresh(t, LockMode.READ,null);
790 }
791
792 class ConversionServiceWrapper<T extends IdentifiableEntity> implements ConversionService {
793
794 private ConversionService delegate;
795 private Converter<String,T> converter;
796 private LocalObjectDataBinder<T> localObjectDataBinder;
797 private Class<T> type;
798
799 public ConversionServiceWrapper(ConversionService delegate, GenericConverter converter, ParsedMatrix parsedMatrix, Class<T> type) {
800 this.delegate = delegate;
801 this.type = type;
802 this.localObjectDataBinder = new LocalObjectDataBinder<T>(converter, parsedMatrix, type);
803 }
804
805 public boolean canConvert(Class<?> sourceType, Class<?> targetType) {
806 logger.info("Can convert? " + sourceType + " " + targetType);
807 if(sourceType.equals(String.class) && type.isAssignableFrom(targetType)) {
808 return true;
809 }
810 return delegate.canConvert(sourceType, targetType);
811 }
812
813 public boolean canConvert(TypeDescriptor sourceType, TypeDescriptor targetType) {
814 logger.info("Can convert? " + sourceType.getType() + " " + targetType.getType());
815 if(sourceType.getType().equals(String.class) && type.isAssignableFrom(targetType.getType())) {
816 return true;
817 }
818 return delegate.canConvert(sourceType, targetType);
819 }
820
821 public <T> T convert(Object source, Class<T> targetType) {
822 logger.info("convert " + source + " " + targetType);
823 if(source != null) {
824 if(source.getClass().equals(String.class) && type.isAssignableFrom(targetType)) {
825 try {
826 logger.info("Using localObjectDataBinder");
827 return (T) localObjectDataBinder.convert((String)source);
828 } catch(NumberFormatException nfe) { }
829 }
830 if(delegate.canConvert(source.getClass(), targetType)) {
831 logger.info("Using delegate conversion service");
832 return delegate.convert(source, targetType);
833 }
834 }
835 return null;
836 }
837
838 public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) {
839 logger.info("convert " + source + " " + sourceType.getType() + " " + targetType.getType());
840 if(source != null) {
841 if(source.getClass().equals(String.class) && type.isAssignableFrom(targetType.getType())) {
842 try {
843 logger.info("Using localObjectDataBinder");
844 return (T) localObjectDataBinder.convert((String)source);
845 } catch(NumberFormatException nfe) { }
846 }
847 if(delegate.canConvert(sourceType, targetType)) {
848 logger.info("Using delegate conversion service");
849 return delegate.convert(source,sourceType, targetType);
850 }
851 }
852 return null;
853 }
854
855 }
856 }