View Javadoc

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; // because the root object is null
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()) { // can't use spring's bean binding to bind new objects to sets
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()) { // can't use spring's bean binding to bind new objects to sets
510  					        	Object object = objects.get(index);
511  					        	String property = header.getPaths()[0].getProperty();
512  					        	// Cheat because we know that maps only use Language as keys
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 			// We can't edit local objects using this method - the user should just use the grid
721 			this.leaveMessage("org.cateproject.controller.flow.action.AbstractUploadAction.localObject", requestContext);
722 			return "edit";
723 		} catch(NumberFormatException nfe) { /* Swallow the exception */ } 
724 		UUID uuid = null;
725 		try {
726 		    uuid = UUID.fromString(matrix.getMatrixValue(row, column));
727 		} catch(IllegalArgumentException iae) {	/* Swallow the exception */}
728 		  catch(NullPointerException npe) {	/* Swallow the exception */}
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 }