From 90cd720c42905903de4aaa06998730aae2b834d6 Mon Sep 17 00:00:00 2001 From: rwetherall Date: Mon, 26 Jun 2017 16:03:49 +1000 Subject: [PATCH 01/11] Search browse performance improvements (cherry picked from commit 089df63e27b03efbfb97c369faad80c8e8c9885c) --- .../org_alfresco_module_rm/log4j.properties | 2 +- .../capability/RMSecurityCommon.java | 7 ++- .../declarative/DeclarativeCapability.java | 47 ++++++++++++------- 3 files changed, 35 insertions(+), 21 deletions(-) diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties index 701dae81e6..a8fbf50130 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/log4j.properties @@ -13,7 +13,7 @@ log4j.logger.org.alfresco.module.org_alfresco_module_rm.patch=info # Set to 'debug' to see details of capability failures when AccessDenied is thrown. May be # removed to enhance performance. # -log4j.logger.org.alfresco.module.org_alfresco_module_rm.security.RMMethodSecurityInterceptor=debug +log4j.logger.org.alfresco.module.org_alfresco_module_rm.security.RMMethodSecurityInterceptor=info # # RM permission debug diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMSecurityCommon.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMSecurityCommon.java index 5a60bf1994..8e38d62e00 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMSecurityCommon.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/RMSecurityCommon.java @@ -149,8 +149,11 @@ public class RMSecurityCommon implements ApplicationContextAware protected int getTransactionCache(String prefix, NodeRef nodeRef) { int result = NOSET_VALUE; - String user = AuthenticationUtil.getRunAsUser(); - Integer value = (Integer)AlfrescoTransactionSupport.getResource(prefix + nodeRef.toString() + user); + StringBuffer key = new StringBuffer(prefix) + .append(nodeRef) + .append(AuthenticationUtil.getRunAsUser()); + + Integer value = (Integer)AlfrescoTransactionSupport.getResource(key); if (value != null) { result = value.intValue(); diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java index 7f4c4f97e4..0e920f8e69 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/capability/declarative/DeclarativeCapability.java @@ -29,9 +29,12 @@ package org.alfresco.module.org_alfresco_module_rm.capability.declarative; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; +import net.sf.acegisecurity.vote.AccessDecisionVoter; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.module.org_alfresco_module_rm.capability.AbstractCapability; import org.alfresco.module.org_alfresco_module_rm.capability.Capability; @@ -44,8 +47,6 @@ import org.alfresco.service.cmr.security.AccessStatus; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import net.sf.acegisecurity.vote.AccessDecisionVoter; - /** * Declarative capability implementation. * @@ -70,6 +71,9 @@ public class DeclarativeCapability extends AbstractCapability /** Indicates whether to return an undetermined result */ protected boolean isUndetermined = false; + + /** List of available kinds */ + private Set availableKinds; /** * @param permissions permissions @@ -258,6 +262,27 @@ public class DeclarativeCapability extends AbstractCapability { return checkConditions(nodeRef, conditions); } + + /** + * Get list of available kinds + * + * @return list of available kinds + */ + + private Set getAvailableKinds() + { + if (kinds != null && availableKinds == null) + { + availableKinds = new HashSet(kinds.size()); + for (String kindString : kinds) + { + FilePlanComponentKind kind = FilePlanComponentKind.valueOf(kindString); + availableKinds.add(kind); + } + } + + return availableKinds; + } /** * Checks that the node ref is of the expected kind @@ -273,23 +298,9 @@ public class DeclarativeCapability extends AbstractCapability if (actualKind != null) { - if (kinds != null && !kinds.isEmpty()) + Set availableKinds = getAvailableKinds(); + if (availableKinds == null || availableKinds.contains(actualKind)) { - // need to check the actual file plan kind is in the list specified - for (String kindString : kinds) - { - FilePlanComponentKind kind = FilePlanComponentKind.valueOf(kindString); - if (actualKind.equals(kind)) - { - result = true; - break; - } - } - } - else - { - // we don't have any specific kinds to check, so we pass since we have a file - // plan component in our hands result = true; } } From d030ede3776d975fd9eb8d874bd5c4fa73261147 Mon Sep 17 00:00:00 2001 From: rwetherall Date: Tue, 27 Jun 2017 13:13:25 +1000 Subject: [PATCH 02/11] Transaction cache on BaseEvaluator.evaluate method --- .../rm-ui-evaluators-context.xml | 1 + .../jscript/app/BaseEvaluator.java | 35 ++++++++++++++----- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml index 3d8ea5c7e4..daccb826a1 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml @@ -38,6 +38,7 @@ + results = transactionalResourceHelper.getMap("BaseEvaluator.evaluate"); + + if (!results.containsKey(nodeRef)) { - result = evaluateImpl(nodeRef); + boolean result = false; + + // Check that we are dealing with the correct kind of RM object + if ((kinds == null || checkKinds(nodeRef)) && + // Check we have the required capabilities + (capabilities == null || checkCapabilities(nodeRef))) + { + result = evaluateImpl(nodeRef); + } + + results.put(nodeRef, result); } - return result; + return results.get(nodeRef); } /** From 1a13253d572728d5e586e2b2f1b35d76e10eed25 Mon Sep 17 00:00:00 2001 From: rwetherall Date: Tue, 27 Jun 2017 13:38:01 +1000 Subject: [PATCH 03/11] By pass public services to reduce bottle necks where appropriate --- .../module/org_alfresco_module_rm/rm-ui-evaluators-context.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml index daccb826a1..3c12a64720 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/rm-ui-evaluators-context.xml @@ -35,7 +35,7 @@ - + From fcab3c3bdf87ab82e8e3dbe4ba155475394d2b0c Mon Sep 17 00:00:00 2001 From: rwetherall Date: Tue, 27 Jun 2017 16:11:25 +1000 Subject: [PATCH 04/11] Fix BaseEvaluator cache. Name missing from key. --- .../jscript/app/BaseEvaluator.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java index ec417b988a..8f53134cb3 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java @@ -41,6 +41,7 @@ import org.alfresco.module.org_alfresco_module_rm.model.RecordsManagementModel; import org.alfresco.module.org_alfresco_module_rm.record.RecordService; import org.alfresco.module.org_alfresco_module_rm.recordfolder.RecordFolderService; import org.alfresco.module.org_alfresco_module_rm.util.TransactionalResourceHelper; +import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessStatus; @@ -229,9 +230,10 @@ public abstract class BaseEvaluator implements RecordsManagementModel */ public boolean evaluate(NodeRef nodeRef) { - Map results = transactionalResourceHelper.getMap("BaseEvaluator.evaluate"); + Map results = transactionalResourceHelper.getMap("BaseEvaluator.evaluate"); + String key = new StringBuffer(nodeRef.toString()).append(AuthenticationUtil.getRunAsUser()).append(name).toString(); - if (!results.containsKey(nodeRef)) + if (!results.containsKey(key)) { boolean result = false; @@ -243,10 +245,10 @@ public abstract class BaseEvaluator implements RecordsManagementModel result = evaluateImpl(nodeRef); } - results.put(nodeRef, result); + results.put(key, result); } - return results.get(nodeRef); + return results.get(key); } /** From db19519dc73104451a88faa3f3fd76853ecddf1f Mon Sep 17 00:00:00 2001 From: rwetherall Date: Tue, 27 Jun 2017 21:42:08 +1000 Subject: [PATCH 05/11] Use the bean id in the cache key to prevent actions and identifiers clashing --- .../jscript/app/BaseEvaluator.java | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java index 8f53134cb3..a75ed6d3d2 100644 --- a/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java +++ b/rm-community/rm-community-repo/source/java/org/alfresco/module/org_alfresco_module_rm/jscript/app/BaseEvaluator.java @@ -46,16 +46,20 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.namespace.NamespaceService; +import org.springframework.beans.factory.BeanNameAware; /** * Base evaluator. * * @author Roy Wetherall */ -public abstract class BaseEvaluator implements RecordsManagementModel +public abstract class BaseEvaluator implements RecordsManagementModel, BeanNameAware { /** Name */ protected String name; + + /** bean name */ + protected String beanName; /** JSON conversion component */ protected JSONConversionComponent jsonConversionComponent; @@ -90,6 +94,15 @@ public abstract class BaseEvaluator implements RecordsManagementModel /** transactional resource helper */ protected TransactionalResourceHelper transactionalResourceHelper; + /** + * @param beanName bean name + */ + @Override + public void setBeanName(String beanName) + { + this.beanName = beanName; + } + /** * @param jsonConversionComponent json conversion component */ @@ -231,7 +244,7 @@ public abstract class BaseEvaluator implements RecordsManagementModel public boolean evaluate(NodeRef nodeRef) { Map results = transactionalResourceHelper.getMap("BaseEvaluator.evaluate"); - String key = new StringBuffer(nodeRef.toString()).append(AuthenticationUtil.getRunAsUser()).append(name).toString(); + String key = new StringBuffer(nodeRef.toString()).append(AuthenticationUtil.getRunAsUser()).append(beanName).toString(); if (!results.containsKey(key)) { From 1afeabb314aaddcdce10207d46538a13e749aaf9 Mon Sep 17 00:00:00 2001 From: alfresco-build Date: Tue, 27 Jun 2017 16:01:00 +0100 Subject: [PATCH 06/11] [maven-release-plugin] prepare release V2.5.0.3 --- pom.xml | 4 ++-- rm-automation/pom.xml | 2 +- rm-community/pom.xml | 2 +- rm-community/rm-community-repo/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index a3e618d68f..aa1ec5edcf 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.alfresco alfresco-rm pom - 2.5.0.3-SNAPSHOT + 2.5.0.3 Alfresco Records Management @@ -24,7 +24,7 @@ scm:git:https://git.alfresco.com/records-management/records-management.git scm:git:https://git.alfresco.com/records-management/records-management.git https://git.alfresco.com/records-management/records-management - HEAD + V2.5.0.3 diff --git a/rm-automation/pom.xml b/rm-automation/pom.xml index 1efa9b7889..c16f0a874f 100644 --- a/rm-automation/pom.xml +++ b/rm-automation/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.3-SNAPSHOT + 2.5.0.3 diff --git a/rm-community/pom.xml b/rm-community/pom.xml index a75c9eb640..30d1d61ae0 100644 --- a/rm-community/pom.xml +++ b/rm-community/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.3-SNAPSHOT + 2.5.0.3 diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index 98406408a2..2ccbc4037c 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-rm-community - 2.5.0.3-SNAPSHOT + 2.5.0.3 From 674864c48a6b533173a219cee8f50255357b7abf Mon Sep 17 00:00:00 2001 From: alfresco-build Date: Tue, 27 Jun 2017 16:01:03 +0100 Subject: [PATCH 07/11] [maven-release-plugin] prepare for next development iteration --- pom.xml | 4 ++-- rm-automation/pom.xml | 2 +- rm-community/pom.xml | 2 +- rm-community/rm-community-repo/pom.xml | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index aa1ec5edcf..948dbb269c 100644 --- a/pom.xml +++ b/pom.xml @@ -4,7 +4,7 @@ org.alfresco alfresco-rm pom - 2.5.0.3 + 2.5.0.4-SNAPSHOT Alfresco Records Management @@ -24,7 +24,7 @@ scm:git:https://git.alfresco.com/records-management/records-management.git scm:git:https://git.alfresco.com/records-management/records-management.git https://git.alfresco.com/records-management/records-management - V2.5.0.3 + HEAD diff --git a/rm-automation/pom.xml b/rm-automation/pom.xml index c16f0a874f..2c98865dee 100644 --- a/rm-automation/pom.xml +++ b/rm-automation/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.3 + 2.5.0.4-SNAPSHOT diff --git a/rm-community/pom.xml b/rm-community/pom.xml index 30d1d61ae0..31cb866d83 100644 --- a/rm-community/pom.xml +++ b/rm-community/pom.xml @@ -8,7 +8,7 @@ org.alfresco alfresco-rm - 2.5.0.3 + 2.5.0.4-SNAPSHOT diff --git a/rm-community/rm-community-repo/pom.xml b/rm-community/rm-community-repo/pom.xml index 2ccbc4037c..75c39249d1 100644 --- a/rm-community/rm-community-repo/pom.xml +++ b/rm-community/rm-community-repo/pom.xml @@ -9,7 +9,7 @@ org.alfresco alfresco-rm-community - 2.5.0.3 + 2.5.0.4-SNAPSHOT From e8591d0cfb9e046c6145022182b041348a15b308 Mon Sep 17 00:00:00 2001 From: Andy Healey Date: Wed, 12 Jul 2017 14:33:22 +0100 Subject: [PATCH 08/11] DOCS-3531 - Update action-service.properties --- .../messages/action-service.properties | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties index 3d36f0ec7c..4484b4fcf2 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties @@ -39,10 +39,10 @@ rm.action.parameter-not-supplied=Add a ''{0}'' to continue. rm.action.delete-not-hold-type=We couldn't delete the hold because {1} isn't of type {0}. rm.action.cast-to-rm-type=You can't upload a custom folder type to the records management file plan. rm.action.record-folder-create=You can't create a record folder in another record folder. -rm.action.unique.child.type-error-message=Operation failed. Multiple children of this type are not allowed. -rm.action.multiple.children.type-error-message=Operation failed. Children of type {0} are not allowed -rm.action.create.transfer.container.child-error-message=Operation failed. Creation is not allowed in Transfer Container. -rm.action.create.transfer.child-error-message=Operation failed. Creation is not allowed in Transfer Folders. -rm.action.create.record.folder.child-error-message=Only records can be created in record folders but it was {0} -rm.action.transfer-non-editable=The metadata of transfer nodes is not editable. +rm.action.unique.child.type-error-message=You can't create multiple items of this type here. +rm.action.multiple.children.type-error-message=You can't create {0} here. +rm.action.create.transfer.container.child-error-message=You can't create items in the Transfer container. +rm.action.create.transfer.child-error-message=You can't create items in Transfer Folders. +rm.action.create.record.folder.child-error-message=You can only create records in record folders. +rm.action.transfer-non-editable=You can't edit transfer folder or container metadata. From 96e5b3bb8ff2f15de4216d056a57e0030b8a2f39 Mon Sep 17 00:00:00 2001 From: Andy Healey Date: Wed, 12 Jul 2017 14:35:09 +0100 Subject: [PATCH 09/11] DOCS-3531 - Update records-management-service.properties --- .../messages/records-management-service.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-management-service.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-management-service.properties index a42ee061f7..3287a0cbab 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-management-service.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/records-management-service.properties @@ -20,5 +20,5 @@ rm.service.node-has-aspect=The record type {1} is already showing for record {0} rm.service.final-version=Final rm.service.final-version-description=The final archived record version rm.service.enable-autoversion-on-record-creation=Auto Version on Record Creation -rm.service.add-children-to-closed-record-folder=You can't add new items to a closed record folder -rm.service.update-record-content=Could not update content property as it's immutable for records. \ No newline at end of file +rm.service.add-children-to-closed-record-folder=You can't add new items to a closed record folder. +rm.service.update-record-content=You can't update a record's content property. \ No newline at end of file From 2fa6760a264a1b907ff94033231df4265eafde0f Mon Sep 17 00:00:00 2001 From: Andy Healey Date: Thu, 13 Jul 2017 09:31:56 +0100 Subject: [PATCH 10/11] Update action-service.properties --- .../org_alfresco_module_rm/messages/action-service.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties index 4484b4fcf2..eeac19f2e6 100644 --- a/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties +++ b/rm-community/rm-community-repo/config/alfresco/module/org_alfresco_module_rm/messages/action-service.properties @@ -43,6 +43,6 @@ rm.action.unique.child.type-error-message=You can't create multiple items of thi rm.action.multiple.children.type-error-message=You can't create {0} here. rm.action.create.transfer.container.child-error-message=You can't create items in the Transfer container. rm.action.create.transfer.child-error-message=You can't create items in Transfer Folders. -rm.action.create.record.folder.child-error-message=You can only create records in record folders. +rm.action.create.record.folder.child-error-message=You can only create records in record folders and this was a {0}. rm.action.transfer-non-editable=You can't edit transfer folder or container metadata. From cb31194cb1323a5c021622b94a189aaaf53cfcab Mon Sep 17 00:00:00 2001 From: Tom Page Date: Mon, 24 Jul 2017 15:53:35 +0100 Subject: [PATCH 11/11] RM-5420 Code review fixes including move toIndentedString into a helper method. --- .../java/org/alfresco/util/StringUtils.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 rm-community/rm-community-repo/source/java/org/alfresco/util/StringUtils.java diff --git a/rm-community/rm-community-repo/source/java/org/alfresco/util/StringUtils.java b/rm-community/rm-community-repo/source/java/org/alfresco/util/StringUtils.java new file mode 100644 index 0000000000..829c03c197 --- /dev/null +++ b/rm-community/rm-community-repo/source/java/org/alfresco/util/StringUtils.java @@ -0,0 +1,51 @@ +/* + * #%L + * Alfresco Records Management Module + * %% + * Copyright (C) 2005 - 2017 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * - + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * - + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * - + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * - + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ +package org.alfresco.util; + +/** + * String utility methods. + * + * @author Tom Page + * @since 2.6 + */ +public class StringUtils +{ + /** Private constructor for the helper class. */ + private StringUtils() {} + + /** + * Convert the given object to string with each line indented by 4 spaces (except the first line). + */ + public static String toIndentedString(Object o) + { + if (o == null) + { + return "null"; + } + return o.toString().replace("\n", "\n "); + } +}