Unifiy name and taxon creation
[cdmlib-apps.git] / app-import / src / main / java / eu / etaxonomy / cdm / io / globis / GlobisImportBase.java
index 2263061fcdca6b2e178b278b005c41c2fca5e34e..87380caec7e316e2111851917f0d02f1235dcfb1 100644 (file)
@@ -1,8 +1,8 @@
 /**\r
 * Copyright (C) 2007 EDIT\r
-* European Distributed Institute of Taxonomy \r
+* European Distributed Institute of Taxonomy\r
 * http://www.e-taxonomy.eu\r
-* \r
+*\r
 * The contents of this file are subject to the Mozilla Public License Version 1.1\r
 * See LICENSE.TXT at the top of this package for the full license terms.\r
 */\r
@@ -11,113 +11,262 @@ package eu.etaxonomy.cdm.io.globis;
 \r
 import java.lang.reflect.Method;\r
 import java.sql.ResultSet;\r
-import java.sql.ResultSetMetaData;\r
 import java.sql.SQLException;\r
 import java.sql.Timestamp;\r
-import java.util.HashMap;\r
 import java.util.HashSet;\r
-import java.util.Map;\r
 import java.util.Set;\r
 import java.util.UUID;\r
 \r
+import org.apache.commons.lang.StringUtils;\r
 import org.apache.log4j.Logger;\r
+import org.hibernate.NonUniqueObjectException;\r
 import org.joda.time.DateTime;\r
 \r
-import eu.etaxonomy.cdm.common.CdmUtils;\r
 import eu.etaxonomy.cdm.io.common.CdmImportBase;\r
 import eu.etaxonomy.cdm.io.common.ICdmIO;\r
+import eu.etaxonomy.cdm.io.common.IImportConfigurator.EDITOR;\r
 import eu.etaxonomy.cdm.io.common.IPartitionedIO;\r
 import eu.etaxonomy.cdm.io.common.ImportHelper;\r
 import eu.etaxonomy.cdm.io.common.ResultSetPartitioner;\r
 import eu.etaxonomy.cdm.io.common.Source;\r
-import eu.etaxonomy.cdm.io.common.IImportConfigurator.EDITOR;\r
 import eu.etaxonomy.cdm.io.common.mapping.DbImportMapping;\r
+import eu.etaxonomy.cdm.io.common.mapping.UndefinedTransformerMethodException;\r
+import eu.etaxonomy.cdm.model.agent.INomenclaturalAuthor;\r
+import eu.etaxonomy.cdm.model.agent.Person;\r
+import eu.etaxonomy.cdm.model.agent.Team;\r
+import eu.etaxonomy.cdm.model.agent.TeamOrPersonBase;\r
 import eu.etaxonomy.cdm.model.common.AnnotatableEntity;\r
 import eu.etaxonomy.cdm.model.common.Annotation;\r
 import eu.etaxonomy.cdm.model.common.AnnotationType;\r
 import eu.etaxonomy.cdm.model.common.CdmBase;\r
-import eu.etaxonomy.cdm.model.common.ExtensionType;\r
 import eu.etaxonomy.cdm.model.common.IdentifiableEntity;\r
 import eu.etaxonomy.cdm.model.common.Language;\r
-import eu.etaxonomy.cdm.model.common.MarkerType;\r
 import eu.etaxonomy.cdm.model.common.User;\r
+import eu.etaxonomy.cdm.model.location.Country;\r
+import eu.etaxonomy.cdm.model.location.NamedArea;\r
+import eu.etaxonomy.cdm.model.name.ZoologicalName;\r
+import eu.etaxonomy.cdm.strategy.exceptions.StringNotParsableException;\r
+import eu.etaxonomy.cdm.strategy.parser.INonViralNameParser;\r
+import eu.etaxonomy.cdm.strategy.parser.NonViralNameParserImpl;\r
 \r
 /**\r
  * @author a.mueller\r
  * @created 20.03.2008\r
- * @version 1.0\r
  */\r
 public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImportBase<GlobisImportConfigurator, GlobisImportState> implements ICdmIO<GlobisImportState>, IPartitionedIO<GlobisImportState> {\r
        private static final Logger logger = Logger.getLogger(GlobisImportBase.class);\r
-       \r
+\r
        public static final UUID ID_IN_SOURCE_EXT_UUID = UUID.fromString("23dac094-e793-40a4-bad9-649fc4fcfd44");\r
-       \r
+\r
        //NAMESPACES\r
-       \r
-       protected static final String AREA_NAMESPACE = "gu";\r
-       protected static final String DR_NAMESPACE = "dr";\r
-       protected static final String IMAGE_NAMESPACE = "Images";\r
-       protected static final String LINKS_NAMESPACE = "Links";\r
-       protected static final String NOTES_NAMESPACE = "Notes";\r
-       protected static final String LANGUAGE_NAMESPACE = "Language";\r
-       protected static final String REFERENCE_NAMESPACE = "Source";\r
-       protected static final String SOURCEUSE_NAMESPACE = "tu_sources";\r
-       protected static final String TAXON_NAMESPACE = "Taxon";\r
-       protected static final String NAME_NAMESPACE = "TaxonName";\r
-       protected static final String VERNACULAR_NAMESPACE = "Vernaculars";\r
-       protected static final String FEATURE_NAMESPACE = "note.type";\r
-       protected static final String EXTENSION_TYPE_NAMESPACE = "ExtensionType";\r
-       \r
-       \r
-\r
-       private String pluralString;\r
-       private String dbTableName;\r
-       //TODO needed?\r
-       private Class cdmTargetClass;\r
-\r
-       \r
-       \r
+\r
+       protected static final String REFERENCE_NAMESPACE = "Literatur";\r
+       protected static final String TAXON_NAMESPACE = "current_species";\r
+       protected static final String COLLECTION_NAMESPACE = "Collection";\r
+       protected static final String IMAGE_NAMESPACE = "Einzelbilder";\r
+       protected static final String SPEC_TAX_NAMESPACE = "specTax";\r
+       protected static final String TYPE_NAMESPACE = "specTax.SpecTypeDepository";\r
+\r
+       private final String pluralString;\r
+       private final String dbTableName;\r
+       private final Class cdmTargetClass;\r
+\r
+       private final INonViralNameParser<?> parser = NonViralNameParserImpl.NewInstance();\r
+\r
+\r
        /**\r
         * @param dbTableName\r
-        * @param dbTableName2 \r
+        * @param dbTableName2\r
         */\r
-       public GlobisImportBase(String pluralString, String dbTableName, Class cdmTargetClass) {\r
+       public GlobisImportBase(String pluralString, String dbTableName, Class<?> cdmTargetClass) {\r
                this.pluralString = pluralString;\r
                this.dbTableName = dbTableName;\r
                this.cdmTargetClass = cdmTargetClass;\r
        }\r
 \r
-       protected boolean doInvoke(GlobisImportState state){\r
+       @Override\r
+    protected void doInvoke(GlobisImportState state){\r
                logger.info("start make " + getPluralString() + " ...");\r
-               boolean success = true ;\r
                GlobisImportConfigurator config = state.getConfig();\r
                Source source = config.getSource();\r
-                       \r
+\r
                String strIdQuery = getIdQuery();\r
                String strRecordQuery = getRecordQuery(config);\r
 \r
                int recordsPerTransaction = config.getRecordsPerTransaction();\r
                try{\r
-                       ResultSetPartitioner partitioner = ResultSetPartitioner.NewInstance(source, strIdQuery, strRecordQuery, recordsPerTransaction);\r
+                       ResultSetPartitioner<GlobisImportState> partitioner = ResultSetPartitioner.NewInstance(source, strIdQuery, strRecordQuery, recordsPerTransaction);\r
                        while (partitioner.nextPartition()){\r
                                partitioner.doPartition(this, state);\r
                        }\r
                } catch (SQLException e) {\r
                        logger.error("SQLException:" +  e);\r
-                       return false;\r
+                       state.setUnsuccessfull();\r
                }\r
-               \r
-               logger.info("end make " + getPluralString() + " ... " + getSuccessString(success));\r
-               return success;\r
+\r
+               logger.info("end make " + getPluralString() + " ... " + getSuccessString(true));\r
+               return;\r
+       }\r
+\r
+       /**\r
+        * @param authorAndYear\r
+        * @param zooName\r
+        */\r
+       protected void handleAuthorAndYear(String authorAndYear, ZoologicalName zooName, Integer id, GlobisImportState state) {\r
+               if (isBlank(authorAndYear)){\r
+                       return;\r
+               }else if ("[Denis & Schifferm\u00FCller], 1775".equals(authorAndYear)){\r
+                       handleDenisSchiffermueller(zooName, state);\r
+                       return;\r
+               }else{\r
+                       try {\r
+                               String doubtfulAuthorAndYear = null;\r
+                               if(authorAndYear.matches(".+\\,\\s\\[\\d{4}\\].*")){\r
+                                       doubtfulAuthorAndYear = authorAndYear;\r
+                                       authorAndYear = authorAndYear.replace("[", "").replace("]", "");\r
+                               }\r
+\r
+                               parser.parseAuthors(zooName, authorAndYear);\r
+                               deduplicateAuthors(zooName, state);\r
+\r
+                               if (doubtfulAuthorAndYear != null){\r
+                                       zooName.setAuthorshipCache(doubtfulAuthorAndYear, true);\r
+                               }\r
+\r
+                       } catch (StringNotParsableException e) {\r
+                               logger.warn("Author could not be parsed: " + authorAndYear + " for id "  +id);\r
+                               zooName.setAuthorshipCache(authorAndYear, true);\r
+                       }\r
+               }\r
+       }\r
+\r
+       /**\r
+        * @param zooName\r
+        * @param state\r
+        */\r
+       private void handleDenisSchiffermueller(ZoologicalName zooName,\r
+                       GlobisImportState state) {\r
+               String teamStr = "Denis & Schifferm\u00FCller";\r
+               Team team = state.getTeam(teamStr);\r
+               if (team == null){\r
+                       team = Team.NewInstance();\r
+                       state.putTeam(teamStr, team);\r
+                       getAgentService().save(team);\r
+               }\r
+               zooName.setCombinationAuthorship(team);\r
+               zooName.setPublicationYear(1775);\r
+               zooName.setAuthorshipCache("[Denis & Schifferm\u00FCller], 1775", true);\r
+       }\r
+\r
+\r
+       private void deduplicateAuthors(ZoologicalName zooName, GlobisImportState state) {\r
+               zooName.setCombinationAuthorship(getExistingAuthor(zooName.getCombinationAuthorship(), state));\r
+               zooName.setExCombinationAuthorship(getExistingAuthor(zooName.getExCombinationAuthorship(), state));\r
+               zooName.setBasionymAuthorship(getExistingAuthor(zooName.getBasionymAuthorship(), state));\r
+               zooName.setExBasionymAuthorship(getExistingAuthor(zooName.getExBasionymAuthorship(), state));\r
+       }\r
+\r
+       private TeamOrPersonBase<?> getExistingAuthor(INomenclaturalAuthor nomAuthor, GlobisImportState state) {\r
+               TeamOrPersonBase<?> author = (TeamOrPersonBase<?>)nomAuthor;\r
+               if (author == null){\r
+                       return null;\r
+               }\r
+               if (author instanceof Person){\r
+                       Person person = state.getPerson(author.getTitleCache());\r
+                       return saveAndDecide(person, author, author.getTitleCache(), state);\r
+               }else if (author instanceof Team){\r
+                       String key = GlobisAuthorImport.makeTeamKey((Team)author, state, getAgentService());\r
+                       Team existingTeam = state.getTeam(key);\r
+                       if (existingTeam == null){\r
+                               Team newTeam = Team.NewInstance();\r
+                               for (Person member :((Team) author).getTeamMembers()){\r
+                                       Person existingPerson = state.getPerson(member.getTitleCache());\r
+                                       if (existingPerson != null){\r
+                                               try {\r
+                                                       getAgentService().update(existingPerson);\r
+                                               } catch (NonUniqueObjectException nuoe){\r
+                                                       // person already exists in\r
+                                                       existingPerson = CdmBase.deproxy(getAgentService().find(existingPerson.getUuid()), Person.class);\r
+                                                       state.putPerson(existingPerson.getTitleCache(), existingPerson);\r
+                                               } catch (Exception e) {\r
+                                                       throw new RuntimeException (e);\r
+                                               }\r
+                                               newTeam.addTeamMember(existingPerson);\r
+//\r
+//                                             logger.warn("newTeam " + newTeam.getId());\r
+                                       }else{\r
+                                               newTeam.addTeamMember(member);\r
+                                       }\r
+                               }\r
+                               author = newTeam;\r
+                       }\r
+\r
+                       return saveAndDecide(existingTeam, author, key, state);\r
+               }else{\r
+                       logger.warn("Author type not supported: " + author.getClass().getName());\r
+                       return author;\r
+               }\r
+       }\r
+\r
+       private TeamOrPersonBase<?> saveAndDecide(TeamOrPersonBase<?> existing, TeamOrPersonBase<?> author, String key, GlobisImportState state) {\r
+               if (existing != null){\r
+                       try {\r
+                               getAgentService().update(existing);\r
+                       } catch (NonUniqueObjectException nuoe){\r
+                               // person already exists in\r
+                               existing = CdmBase.deproxy(getAgentService().find(existing.getUuid()), TeamOrPersonBase.class);\r
+                               putAgent(existing, key, state);\r
+                       } catch (Exception e) {\r
+                               throw new RuntimeException (e);\r
+                       }\r
+                       return existing;\r
+               }else{\r
+                       getAgentService().save(author);\r
+                       putAgent(author, key, state);\r
+                       return author;\r
+               }\r
+       }\r
+\r
+       /**\r
+        * @param author\r
+        * @param key\r
+        * @param state\r
+        */\r
+       private void putAgent(TeamOrPersonBase<?> agent, String key, GlobisImportState state) {\r
+               if (agent instanceof Team){\r
+                       state.putTeam(key, (Team)agent);\r
+               }else{\r
+                       state.putPerson(key, (Person)agent);\r
+               }\r
+       }\r
+\r
+       /**\r
+        * @param state\r
+        * @param countryStr\r
+        * @return\r
+        */\r
+       protected NamedArea getCountry(GlobisImportState state, String countryStr) {\r
+               NamedArea country = Country.getCountryByLabel(countryStr);\r
+               if (country == null){\r
+                       try {\r
+                               country = state.getTransformer().getNamedAreaByKey(countryStr);\r
+                       } catch (UndefinedTransformerMethodException e) {\r
+                               e.printStackTrace();\r
+                       }\r
+               }\r
+               return country;\r
        }\r
-       \r
-       public boolean doPartition(ResultSetPartitioner partitioner, GlobisImportState state) {\r
+\r
+\r
+\r
+       @Override\r
+    public boolean doPartition(ResultSetPartitioner partitioner, GlobisImportState state) {\r
                boolean success = true ;\r
                Set objectsToSave = new HashSet();\r
-               \r
+\r
                DbImportMapping<?, ?> mapping = getMapping();\r
                mapping.initialize(state, cdmTargetClass);\r
-               \r
+\r
                ResultSet rs = partitioner.getResultSet();\r
                try{\r
                        while (rs.next()){\r
@@ -127,19 +276,21 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                        logger.error("SQLException:" +  e);\r
                        return false;\r
                }\r
-       \r
+\r
                partitioner.startDoSave();\r
                getCommonService().save(objectsToSave);\r
                return success;\r
        }\r
 \r
 \r
-       \r
+\r
        /**\r
         * @return\r
         */\r
-       protected abstract DbImportMapping<?, ?> getMapping();\r
-       \r
+       protected /*abstract*/ DbImportMapping<?, ?> getMapping(){\r
+               return null;\r
+       }\r
+\r
        /**\r
         * @return\r
         */\r
@@ -152,11 +303,12 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                String result = " SELECT id FROM " + getTableName();\r
                return result;\r
        }\r
-       \r
+\r
        /* (non-Javadoc)\r
         * @see eu.etaxonomy.cdm.io.berlinModel.in.IPartitionedIO#getPluralString()\r
         */\r
-       public String getPluralString(){\r
+       @Override\r
+    public String getPluralString(){\r
                return pluralString;\r
        }\r
 \r
@@ -166,36 +318,30 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
        protected String getTableName(){\r
                return this.dbTableName;\r
        }\r
-       \r
+\r
        protected boolean doIdCreatedUpdatedNotes(GlobisImportState state, IdentifiableEntity identifiableEntity, ResultSet rs, long id, String namespace)\r
                        throws SQLException{\r
                boolean success = true;\r
                //id\r
-               success &= ImportHelper.setOriginalSource(identifiableEntity, state.getConfig().getSourceReference(), id, namespace);\r
+               success &= ImportHelper.setOriginalSource(identifiableEntity, state.getTransactionalSourceReference(), id, namespace);\r
                //createdUpdateNotes\r
                success &= doCreatedUpdatedNotes(state, identifiableEntity, rs, namespace);\r
                return success;\r
        }\r
-       \r
-       \r
+\r
+\r
        protected boolean doCreatedUpdatedNotes(GlobisImportState state, AnnotatableEntity annotatableEntity, ResultSet rs, String namespace)\r
                        throws SQLException{\r
 \r
                GlobisImportConfigurator config = state.getConfig();\r
                Object createdWhen = rs.getObject("Created_When");\r
                String createdWho = rs.getString("Created_Who");\r
-               Object updatedWhen = null;\r
-               String updatedWho = null;\r
-               try {\r
-                       updatedWhen = rs.getObject("Updated_When");\r
-                       updatedWho = rs.getString("Updated_who");\r
-               } catch (SQLException e) {\r
-                       //Table "Name" has no updated when/who\r
-               }\r
+               Object updatedWhen = rs.getObject("Updated_When");\r
+               String updatedWho = rs.getString("Updated_who");\r
                String notes = rs.getString("notes");\r
-               \r
+\r
                boolean success  = true;\r
-               \r
+\r
                //Created When, Who, Updated When Who\r
                if (config.getEditor() == null || config.getEditor().equals(EDITOR.NO_EDITORS)){\r
                        //do nothing\r
@@ -220,10 +366,10 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                }else {\r
                        logger.warn("Editor type not yet implemented: " + config.getEditor());\r
                }\r
-               \r
-               \r
+\r
+\r
                //notes\r
-               if (CdmUtils.isNotEmpty(notes)){\r
+               if (StringUtils.isNotBlank(notes)){\r
                        String notesString = String.valueOf(notes);\r
                        if (notesString.length() > 65530 ){\r
                                notesString = notesString.substring(0, 65530) + "...";\r
@@ -236,13 +382,13 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                }\r
                return success;\r
        }\r
-       \r
+\r
        private User getUser(String userString, GlobisImportState state){\r
-               if (CdmUtils.isEmpty(userString)){\r
+               if (isBlank(userString)){\r
                        return null;\r
                }\r
                userString = userString.trim();\r
-               \r
+\r
                User user = state.getUser(userString);\r
                if (user == null){\r
                        user = getTransformedUser(userString,state);\r
@@ -255,7 +401,7 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                }\r
                return user;\r
        }\r
-       \r
+\r
        private User getTransformedUser(String userString, GlobisImportState state){\r
                Method method = state.getConfig().getUserTransformationMethod();\r
                if (method == null){\r
@@ -271,19 +417,19 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
        }\r
 \r
        private User makeNewUser(String userString, GlobisImportState state){\r
-               String pwd = getPassword(); \r
+               String pwd = getPassword();\r
                User user = User.NewInstance(userString, pwd);\r
                state.putUser(userString, user);\r
                getUserService().save(user);\r
                logger.info("Added new user: " + userString);\r
                return user;\r
        }\r
-       \r
+\r
        private String getPassword(){\r
                String result = UUID.randomUUID().toString();\r
                return result;\r
        }\r
-       \r
+\r
        private DateTime getDateTime(Object timeString){\r
                if (timeString == null){\r
                        return null;\r
@@ -298,86 +444,8 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                }\r
                return dateTime;\r
        }\r
-       \r
-       protected boolean resultSetHasColumn(ResultSet rs, String columnName){\r
-               try {\r
-                       ResultSetMetaData metaData = rs.getMetaData();\r
-                       for (int i = 0; i < metaData.getColumnCount(); i++){\r
-                               if (metaData.getColumnName(i + 1).equalsIgnoreCase(columnName)){\r
-                                       return true;\r
-                               }\r
-                       }\r
-                       return false;\r
-               } catch (SQLException e) {\r
-            logger.warn("Exception in resultSetHasColumn");\r
-            return false;\r
-               }\r
-       }\r
-       \r
-       protected boolean checkSqlServerColumnExists(Source source, String tableName, String columnName){\r
-               String strQuery = "SELECT  Count(t.id) as n " +\r
-                               " FROM sysobjects AS t " +\r
-                               " INNER JOIN syscolumns AS c ON t.id = c.id " +\r
-                               " WHERE (t.xtype = 'U') AND " + \r
-                               " (t.name = '" + tableName + "') AND " + \r
-                               " (c.name = '" + columnName + "')";\r
-               ResultSet rs = source.getResultSet(strQuery) ;          \r
-               int n;\r
-               try {\r
-                       rs.next();\r
-                       n = rs.getInt("n");\r
-                       return n>0;\r
-               } catch (SQLException e) {\r
-                       e.printStackTrace();\r
-                       return false;\r
-               }\r
-               \r
-       }\r
-       \r
-       /**\r
-        * Returns a map that holds all values of a ResultSet. This is needed if a value needs to\r
-        * be accessed twice\r
-        * @param rs\r
-        * @return\r
-        * @throws SQLException\r
-        */\r
-       protected Map<String, Object> getValueMap(ResultSet rs) throws SQLException{\r
-               try{\r
-                       Map<String, Object> valueMap = new HashMap<String, Object>();\r
-                       int colCount = rs.getMetaData().getColumnCount();\r
-                       for (int c = 0; c < colCount ; c++){\r
-                               Object value = rs.getObject(c+1);\r
-                               String label = rs.getMetaData().getColumnLabel(c+1).toLowerCase();\r
-                               if (value != null && ! CdmUtils.Nz(value.toString()).trim().equals("")){\r
-                                       valueMap.put(label, value);\r
-                               }\r
-                       }\r
-                       return valueMap;\r
-               }catch(SQLException e){\r
-                       throw e;\r
-               }\r
-       }\r
-       \r
-       protected ExtensionType getExtensionType(UUID uuid, String label, String text, String labelAbbrev){\r
-               ExtensionType extensionType = (ExtensionType)getTermService().find(uuid);\r
-               if (extensionType == null){\r
-                       extensionType = ExtensionType.NewInstance(text, label, labelAbbrev);\r
-                       extensionType.setUuid(uuid);\r
-                       getTermService().save(extensionType);\r
-               }\r
-               return extensionType;\r
-       }\r
-       \r
-       protected MarkerType getMarkerType(UUID uuid, String label, String text, String labelAbbrev){\r
-               MarkerType markerType = (MarkerType)getTermService().find(uuid);\r
-               if (markerType == null){\r
-                       markerType = MarkerType.NewInstance(label, text, labelAbbrev);\r
-                       markerType.setUuid(uuid);\r
-                       getTermService().save(markerType);\r
-               }\r
-               return markerType;\r
-       }\r
-       \r
+\r
+\r
 \r
        /**\r
         * Reads a foreign key field from the result set and adds its value to the idSet.\r
@@ -393,7 +461,10 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                        idSet.add(id);\r
                }\r
        }\r
-       \r
+\r
+\r
+\r
+\r
        /**\r
         * Returns true if i is a multiple of recordsPerTransaction\r
         * @param i\r
@@ -404,12 +475,12 @@ public abstract class GlobisImportBase<CDM_BASE extends CdmBase> extends CdmImpo
                startTransaction();\r
                return (i % recordsPerLoop) == 0;\r
        }\r
-       \r
+\r
        protected void doLogPerLoop(int count, int recordsPerLog, String pluralString){\r
                if ((count % recordsPerLog ) == 0 && count!= 0 ){ logger.info(pluralString + " handled: " + (count));}\r
        }\r
-       \r
 \r
 \r
-       \r
+\r
+\r
 }\r