diff --git a/.externalToolBuilders/JibX.launch b/.externalToolBuilders/JibX.launch index 209a3cdd97..e0266a6cbc 100644 --- a/.externalToolBuilders/JibX.launch +++ b/.externalToolBuilders/JibX.launch @@ -11,17 +11,15 @@ - - - + + - diff --git a/config/alfresco/action-services-context.xml b/config/alfresco/action-services-context.xml index 9f750e8a40..6632dd0938 100644 --- a/config/alfresco/action-services-context.xml +++ b/config/alfresco/action-services-context.xml @@ -319,9 +319,12 @@ - - - + + + + + + @@ -534,23 +537,6 @@ - - - - - - - - - - - - - {http://www.alfresco.org/model/content/1.0}content - - - - diff --git a/config/alfresco/application-context-core.xml b/config/alfresco/application-context-core.xml index 1f99d423cb..3352c1962f 100644 --- a/config/alfresco/application-context-core.xml +++ b/config/alfresco/application-context-core.xml @@ -6,6 +6,8 @@ + + diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml index 23b69ae263..7fb1cf81cd 100644 --- a/config/alfresco/application-context-highlevel.xml +++ b/config/alfresco/application-context-highlevel.xml @@ -26,6 +26,7 @@ + diff --git a/config/alfresco/bootstrap-context.xml b/config/alfresco/bootstrap-context.xml index 43008c4aef..a24909bff1 100644 --- a/config/alfresco/bootstrap-context.xml +++ b/config/alfresco/bootstrap-context.xml @@ -84,27 +84,6 @@ - - - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-RepoTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-LockTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-ContentTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-PropertyValueTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-AuditTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-AvmTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-ActivityTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-UsageTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-SubscriptionTables.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-TenantTables.sql - - - - - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-Extra.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql - classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-varinst-indexes.sql - - classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-ALF.xml @@ -113,73 +92,8 @@ classpath:alfresco/dbscripts/create/${db.script.dialect}/Schema-Reference-ACT.xml - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + @@ -649,12 +563,14 @@ - + + diff --git a/config/alfresco/bootstrap/imapSpacesTemplates.xml b/config/alfresco/bootstrap/imapSpacesTemplates.xml index 95c5e54881..30e47fc4f8 100644 --- a/config/alfresco/bootstrap/imapSpacesTemplates.xml +++ b/config/alfresco/bootstrap/imapSpacesTemplates.xml @@ -423,5 +423,211 @@ - + + + + + + + + + true + ${spaces.imap_templates.emailbody_textplain.description} for share - ${version.dutch} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_nl.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=nl + emailbody_textplain_share_nl.ftl + + emailbody_textplain_share_nl.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_textplain.description} for explorer - ${version.dutch} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_nl.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=nl + emailbody_textplain_alfresco_nl.ftl + + emailbody_textplain_alfresco_nl.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_texthtml.description} for explorer - ${version.dutch} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_nl.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=nl + emailbody_texthtml_alfresco_nl.ftl + + emailbody_texthtml_alfresco_nl.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_texthtml.description} for share - ${version.dutch} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_nl.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=nl + emailbody_texthtml_share_nl.ftl + + emailbody_texthtml_share_nl.ftl + + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_textplain.description} for share - ${version.russian} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_ru.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=ru + emailbody_textplain_share_ru.ftl + + emailbody_textplain_share_ru.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_textplain.description} for explorer - ${version.russian} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_ru.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=ru + emailbody_textplain_alfresco_ru.ftl + + emailbody_textplain_alfresco_ru.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_texthtml.description} for explorer - ${version.russian} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_ru.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=ru + emailbody_texthtml_alfresco_ru.ftl + + emailbody_texthtml_alfresco_ru.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_texthtml.description} for share - ${version.russian} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_ru.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=ru + emailbody_texthtml_share_ru.ftl + + emailbody_texthtml_share_ru.ftl + + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_textplain.description} for share - ${version.chinese} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_zh_CN.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=zh_CN + emailbody_textplain_share_zh_CN.ftl + + emailbody_textplain_share_zh_CN.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_textplain.description} for explorer - ${version.chinese} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_zh_CN.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=zh_CN + emailbody_textplain_alfresco_zh_CN.ftl + + emailbody_textplain_alfresco_zh_CN.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_texthtml.description} for explorer - ${version.chinese} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_zh_CN.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=zh_CN + emailbody_texthtml_alfresco_zh_CN.ftl + + emailbody_texthtml_alfresco_zh_CN.ftl + + + + + + + + + + + + true + ${spaces.imap_templates.emailbody_texthtml.description} for share - ${version.chinese} + contentUrl=classpath:alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_zh_CN.ftl|mimetype=text/plain|size=|encoding=UTF-8|locale=zh_CN + emailbody_texthtml_share_zh_CN.ftl + + emailbody_texthtml_share_zh_CN.ftl + + + + \ No newline at end of file diff --git a/config/alfresco/bootstrap/notification/wf-email.html_de.ftl b/config/alfresco/bootstrap/notification/wf-email_de.html.ftl similarity index 100% rename from config/alfresco/bootstrap/notification/wf-email.html_de.ftl rename to config/alfresco/bootstrap/notification/wf-email_de.html.ftl diff --git a/config/alfresco/bootstrap/notification/wf-email.html_es.ftl b/config/alfresco/bootstrap/notification/wf-email_es.html.ftl similarity index 100% rename from config/alfresco/bootstrap/notification/wf-email.html_es.ftl rename to config/alfresco/bootstrap/notification/wf-email_es.html.ftl diff --git a/config/alfresco/bootstrap/notification/wf-email.html_fr.ftl b/config/alfresco/bootstrap/notification/wf-email_fr.html.ftl similarity index 100% rename from config/alfresco/bootstrap/notification/wf-email.html_fr.ftl rename to config/alfresco/bootstrap/notification/wf-email_fr.html.ftl diff --git a/config/alfresco/bootstrap/notification/wf-email.html_it.ftl b/config/alfresco/bootstrap/notification/wf-email_it.html.ftl similarity index 100% rename from config/alfresco/bootstrap/notification/wf-email.html_it.ftl rename to config/alfresco/bootstrap/notification/wf-email_it.html.ftl diff --git a/config/alfresco/bootstrap/notification/wf-email.html_ja.ftl b/config/alfresco/bootstrap/notification/wf-email_ja.html.ftl similarity index 100% rename from config/alfresco/bootstrap/notification/wf-email.html_ja.ftl rename to config/alfresco/bootstrap/notification/wf-email_ja.html.ftl diff --git a/config/alfresco/bootstrap/notification/wf-email.html_nl.ftl b/config/alfresco/bootstrap/notification/wf-email_nl.html.ftl similarity index 100% rename from config/alfresco/bootstrap/notification/wf-email.html_nl.ftl rename to config/alfresco/bootstrap/notification/wf-email_nl.html.ftl diff --git a/config/alfresco/bootstrap/notification/workflow-email-notification.xml b/config/alfresco/bootstrap/notification/workflow-email-notification.xml index 6178de4a89..4823d2c9c8 100644 --- a/config/alfresco/bootstrap/notification/workflow-email-notification.xml +++ b/config/alfresco/bootstrap/notification/workflow-email-notification.xml @@ -45,7 +45,7 @@ wf-email_de.html.ftl wf-email_de.html.ftl ${spaces.templates.email.generate_the_wf_notification_email.description} - ${version.default} - contentUrl=classpath:alfresco\bootstrap\notification\wf-email.html_de.ftl|mimetype=text/plain|encoding=UTF-8 + contentUrl=classpath:alfresco\bootstrap\notification\wf-email_de.html.ftl|mimetype=text/plain|encoding=UTF-8 @@ -60,7 +60,7 @@ wf-email_es.html.ftl wf-email_es.html.ftl ${spaces.templates.email.generate_the_wf_notification_email.description} - ${version.default} - contentUrl=classpath:alfresco\bootstrap\notification\wf-email.html_es.ftl|mimetype=text/plain|encoding=UTF-8 + contentUrl=classpath:alfresco\bootstrap\notification\wf-email_es.html.ftl|mimetype=text/plain|encoding=UTF-8 @@ -75,7 +75,7 @@ wf-email_fr.html.ftl wf-email_fr.html.ftl ${spaces.templates.email.generate_the_wf_notification_email.description} - ${version.default} - contentUrl=classpath:alfresco\bootstrap\notification\wf-email.html_fr.ftl|mimetype=text/plain|encoding=UTF-8 + contentUrl=classpath:alfresco\bootstrap\notification\wf-email_fr.html.ftl|mimetype=text/plain|encoding=UTF-8 @@ -90,7 +90,7 @@ wf-email_it.html.ftl wf-email_it.html.ftl ${spaces.templates.email.generate_the_wf_notification_email.description} - ${version.default} - contentUrl=classpath:alfresco\bootstrap\notification\wf-email.html_it.ftl|mimetype=text/plain|encoding=UTF-8 + contentUrl=classpath:alfresco\bootstrap\notification\wf-email_it.html.ftl|mimetype=text/plain|encoding=UTF-8 @@ -105,7 +105,7 @@ wf-email_ja.html.ftl wf-email_ja.html.ftl ${spaces.templates.email.generate_the_wf_notification_email.description} - ${version.default} - contentUrl=classpath:alfresco\bootstrap\notification\wf-email.html_ja.ftl|mimetype=text/plain|encoding=UTF-8 + contentUrl=classpath:alfresco\bootstrap\notification\wf-email_ja.html.ftl|mimetype=text/plain|encoding=UTF-8 @@ -120,7 +120,7 @@ wf-email_nl.html.ftl wf-email_nl.html.ftl ${spaces.templates.email.generate_the_wf_notification_email.description} - ${version.default} - contentUrl=classpath:alfresco\bootstrap\notification\wf-email.html_nl.ftl|mimetype=text/plain|encoding=UTF-8 + contentUrl=classpath:alfresco\bootstrap\notification\wf-email_nl.html.ftl|mimetype=text/plain|encoding=UTF-8 diff --git a/config/alfresco/content-services-context.xml b/config/alfresco/content-services-context.xml index 92ea231226..dd82c3ee33 100644 --- a/config/alfresco/content-services-context.xml +++ b/config/alfresco/content-services-context.xml @@ -121,9 +121,6 @@ ${policy.content.update.ignoreEmpty} - - ${content.transformer.failover} - @@ -312,9 +309,21 @@ + + + + + + + + + + + + @@ -341,14 +350,56 @@ + + + + + + + + org.alfresco.repo.content.transform.TransformerConfigMBean + + + + + + + + + + + transformerDebugLog + + + + org.apache.commons.logging.Log + + + + + + + + + + + transformerLog + + + + org.apache.commons.logging.Log + + + + - - + + true @@ -366,6 +417,9 @@ + + + @@ -420,7 +474,8 @@ + ImageMagick transformation to png or two ImageMagick transformation via png. + Adobe Illustrator (ai) files are, in fact, PDF files --> @@ -432,20 +487,6 @@ - - - - - - - - - - - diff --git a/config/alfresco/dao/dao-context.xml b/config/alfresco/dao/dao-context.xml index b3cc499ac4..6413b106da 100644 --- a/config/alfresco/dao/dao-context.xml +++ b/config/alfresco/dao/dao-context.xml @@ -35,6 +35,8 @@ + + @@ -123,6 +125,7 @@ + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml index 2ab3f95a00..4cd591a32c 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.MySQLInnoDBDialect/Schema-Reference-ACT.xml @@ -1,5 +1,12 @@ - + diff --git a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml index 17aa902065..4894019c57 100644 --- a/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml +++ b/config/alfresco/dbscripts/create/org.hibernate.dialect.PostgreSQLDialect/Schema-Reference-ACT.xml @@ -1,5 +1,12 @@ - + diff --git a/config/alfresco/dbscripts/db-schema-context.xml b/config/alfresco/dbscripts/db-schema-context.xml new file mode 100644 index 0000000000..5db72dbf92 --- /dev/null +++ b/config/alfresco/dbscripts/db-schema-context.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-RepoTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-LockTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-ContentTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-PropertyValueTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-AuditTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-ActivityTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-UsageTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-SubscriptionTables.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-TenantTables.sql + + + + + + + + + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoCreate-AvmTables.sql + + + + + + + + + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-Extra.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-FK-indexes.sql + classpath:alfresco/dbscripts/create/${db.script.dialect}/AlfrescoPostCreate-JBPM-varinst-indexes.sql + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/extension/metadata-embedding-context.xml.sample b/config/alfresco/extension/metadata-embedding-context.xml.sample new file mode 100644 index 0000000000..30cdeb1e1a --- /dev/null +++ b/config/alfresco/extension/metadata-embedding-context.xml.sample @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + {http://www.alfresco.org/model/content/1.0}content + + + + + diff --git a/config/alfresco/favourites-service-context.xml b/config/alfresco/favourites-service-context.xml new file mode 100644 index 0000000000..e4a16b1454 --- /dev/null +++ b/config/alfresco/favourites-service-context.xml @@ -0,0 +1,49 @@ + + + + + + + + org.alfresco.service.cmr.favourites.FavouritesService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml index 1a6e901bf1..7922f8476f 100644 --- a/config/alfresco/ibatis/alfresco-SqlMapConfig.xml +++ b/config/alfresco/ibatis/alfresco-SqlMapConfig.xml @@ -16,7 +16,10 @@ Inbound settings from iBatis - + + + + @@ -42,7 +45,6 @@ Inbound settings from iBatis - @@ -105,7 +107,6 @@ Inbound settings from iBatis - @@ -170,7 +171,6 @@ Inbound settings from iBatis - @@ -188,7 +188,7 @@ Inbound settings from iBatis - + diff --git a/config/alfresco/ibatis/ibatis-context.xml b/config/alfresco/ibatis/ibatis-context.xml index ccc0302bd4..d1592f130e 100644 --- a/config/alfresco/ibatis/ibatis-context.xml +++ b/config/alfresco/ibatis/ibatis-context.xml @@ -19,47 +19,8 @@ - - - - - - - + + diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml index 1a875859ae..b2915439dd 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/activities-common-SqlMap.xml @@ -92,6 +92,7 @@ #{maxFeedSize} ]]> @@ -170,7 +171,7 @@ @@ -180,7 +181,7 @@ @@ -218,20 +219,20 @@ delete from alf_activity_feed - where feed_user_id = #{feedUserId} + where (feed_user_id IS NULL and #{feedUserId} IS NULL OR feed_user_id = #{feedUserId}) diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-test-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-test-common-SqlMap.xml index b851e20076..10e35b7d2b 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-test-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/query-test-common-SqlMap.xml @@ -84,4 +84,17 @@ ]]> + + \ No newline at end of file diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_nl.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_nl.ftl new file mode 100644 index 0000000000..82b5122d4a --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_nl.ftl @@ -0,0 +1,79 @@ + + + + + + + + + +
+

Document (naam): ${document.name}

+
+

Metagegevens

+ + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
Titel: ${document.properties.title}
Titel: 
Beschrijving: ${document.properties.description}
Beschrijving: 
Maker: ${document.properties.creator}
Gemaakt op: ${document.properties.created?datetime}
Gewijzigd door: ${document.properties.modifier}
Gewijzigd: ${document.properties.modified?datetime}
Grootte: ${document.size / 1024} kB
+
+

Contentkoppelingen

+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_ru.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_ru.ftl new file mode 100644 index 0000000000..68f064a2a7 --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_ru.ftl @@ -0,0 +1,79 @@ + + + + + + + + + +
+

Документ (имя): ${document.name}

+
+

Метаданные

+ + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
Заголовок: ${document.properties.title}
Заголовок: 
Описание: ${document.properties.description}
Описание: 
Создатель: ${document.properties.creator}
Создано: ${document.properties.created?datetime}
Модификатор: ${document.properties.modifier}
Изменено: ${document.properties.modified?datetime}
Размер: ${document.size / 1024} КБ
+
+

Ссылки на контент

+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_zh_CN.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_zh_CN.ftl new file mode 100644 index 0000000000..423cc789ac --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_alfresco_zh_CN.ftl @@ -0,0 +1,79 @@ + + + + + + + + + +
+

文档(名称):

+
+

元数据

+ + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
标题:
标题: 
说明:
说明: 
创建者:
创建时间:
修改者:
修改时间:
大小: KB
+
+

内容链接

+ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_nl.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_nl.ftl new file mode 100644 index 0000000000..d1a5fb284d --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_nl.ftl @@ -0,0 +1,96 @@ + + + + + + + + +
+

Document (naam): ${document.name}

+
+
+ Metagegevens + + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
Titel: ${document.properties.title}
Titel: 
Beschrijving: ${document.properties.description}
Beschrijving: 
Maker: ${document.properties.creator}
Gemaakt op: ${document.properties.created?datetime}
Gewijzigd door: ${document.properties.modifier}
Gewijzigd: ${document.properties.modified?datetime}
Grootte: ${document.size / 1024} kB
+
+
+ Contentkoppelingen + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_ru.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_ru.ftl new file mode 100644 index 0000000000..5e69e74aae --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_ru.ftl @@ -0,0 +1,96 @@ + + + + + + + + +
+

Документ (имя): ${document.name}

+
+
+ Метаданные + + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
Заголовок: ${document.properties.title}
Заголовок: 
Описание: ${document.properties.description}
Описание: 
Создатель: ${document.properties.creator}
Создано: ${document.properties.created?datetime}
Модификатор: ${document.properties.modifier}
Изменено: ${document.properties.modified?datetime}
Размер: ${document.size / 1024} КБ
+
+
+ Ссылки на контент + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_zh_CN.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_zh_CN.ftl new file mode 100644 index 0000000000..a82732e09a --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_texthtml_share_zh_CN.ftl @@ -0,0 +1,96 @@ + + + + + + + + +
+

文档(名称):

+
+
+ 元数据 + + <#if document.properties.title?exists> + + <#else> + + + <#if document.properties.description?exists> + + <#else> + + + + + + + +
标题:
标题: 
说明:
说明: 
创建者:
创建时间:
修改者:
修改时间:
大小: KB
+
+
+ 内容链接 + + + + + + + + + + + +
+ + \ No newline at end of file diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_nl.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_nl.ftl new file mode 100644 index 0000000000..f2b175baad --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_nl.ftl @@ -0,0 +1,27 @@ +------------------------------------------------------------------------------ +Documentnaam: ${document.name} +------------------------------------------------------------------------------ + + <#if document.properties.title?exists> +Titel: ${document.properties.title} + <#else> +Titel: GEEN + + <#if document.properties.description?exists> +Beschrijving: ${document.properties.description} + <#else> +Beschrijving: GEEN + +Maker: ${document.properties.creator} +Gemaakt op: ${document.properties.created?datetime} +Gewijzigd door: ${document.properties.modifier} +Gewijzigd: ${document.properties.modified?datetime} +Grootte: ${document.size / 1024} Kb + + +CONTENTKOPPELINGEN + +Contentmap: ${contextUrl}/navigate/browse${document.parent.webdavUrl} +Content-URL: ${contextUrl}${document.url} +Download-URL: ${contextUrl}${document.downloadUrl} +WebDAV-URL: ${contextUrl}${document.webdavUrl} diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_ru.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_ru.ftl new file mode 100644 index 0000000000..c06bf8acfa --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_ru.ftl @@ -0,0 +1,27 @@ +------------------------------------------------------------------------------ +Имя документа: ${document.name} +------------------------------------------------------------------------------ + + <#if document.properties.title?exists> +Заголовок: ${document.properties.title} + <#else> +Заголовок: НЕТ + + <#if document.properties.description?exists> +Описание: ${document.properties.description} + <#else> +Описание: НЕТ + +Создатель: ${document.properties.creator} +Создано: ${document.properties.created?datetime} +Модификатор: ${document.properties.modifier} +Изменено: ${document.properties.modified?datetime} +Размер: ${document.size / 1024} КБ + + +ССЫЛКИ НА КОНТЕНТ + +Папка с контентом: ${contextUrl}/navigate/browse${document.parent.webdavUrl} +URL-адрес контента: ${contextUrl}${document.url} +Загрузить с URL-адреса: ${contextUrl}${document.downloadUrl} +URL-адрес WebDAV: ${contextUrl}${document.webdavUrl} diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_zh_CN.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_zh_CN.ftl new file mode 100644 index 0000000000..97caeb0753 --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_alfresco_zh_CN.ftl @@ -0,0 +1,27 @@ +------------------------------------------------------------------------------ +文档名称: ${document.name} +------------------------------------------------------------------------------ + + <#if document.properties.title?exists> +标题: ${document.properties.title} + <#else> +标题: 无 + + <#if document.properties.description?exists> +说明: ${document.properties.description} + <#else> +说明: 无 + +创建者: ${document.properties.creator} +创建时间: ${document.properties.created?datetime} +修改者: ${document.properties.modifier} +修改时间: ${document.properties.modified?datetime} +大小: ${document.size / 1024} Kb + + +内容链接 + +内容文件夹: ${contextUrl}/navigate/browse${document.parent.webdavUrl} +内容 URL: ${contextUrl}${document.url} +下载 URL: ${contextUrl}${document.downloadUrl} +WebDAV URL: ${contextUrl}${document.webdavUrl} diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_nl.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_nl.ftl new file mode 100644 index 0000000000..ec33087a6f --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_nl.ftl @@ -0,0 +1,26 @@ +------------------------------------------------------------------------------ +Documentnaam: ${document.name} +------------------------------------------------------------------------------ + + <#if document.properties.title?exists> +Titel: ${document.properties.title} + <#else> +Titel: GEEN + + <#if document.properties.description?exists> +Beschrijving: ${document.properties.description} + <#else> +Beschrijving: GEEN + +Maker: ${document.properties.creator} +Gemaakt op: ${document.properties.created?datetime} +Gewijzigd door: ${document.properties.modifier} +Gewijzigd: ${document.properties.modified?datetime} +Grootte: ${document.size / 1024} Kb + + +CONTENTKOPPELINGEN + +Contentmap: ${shareContextUrl}/page/site/${parentPathFromSites} +Content-URL: ${shareContextUrl}/proxy/alfresco/api/node/content/${document.storeType}/${document.storeId}/${document.id}/${document.name} +Download-URL: ${shareContextUrl}/proxy/alfresco/api/node/content/${document.storeType}/${document.storeId}/${document.id}/${document.name}?a=true diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_ru.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_ru.ftl new file mode 100644 index 0000000000..b7067f05f5 --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_ru.ftl @@ -0,0 +1,26 @@ +------------------------------------------------------------------------------ +Имя документа: ${document.name} +------------------------------------------------------------------------------ + + <#if document.properties.title?exists> +Заголовок: ${document.properties.title} + <#else> +Заголовок: НЕТ + + <#if document.properties.description?exists> +Описание: ${document.properties.description} + <#else> +Описание: НЕТ + +Создатель: ${document.properties.creator} +Создано: ${document.properties.created?datetime} +Модификатор: ${document.properties.modifier} +Изменено: ${document.properties.modified?datetime} +Размер: ${document.size / 1024} КБ + + +ССЫЛКИ НА КОНТЕНТ + +Папка с контентом: ${shareContextUrl}/page/site/${parentPathFromSites} +URL-адрес контента: ${shareContextUrl}/proxy/alfresco/api/node/content/${document.storeType}/${document.storeId}/${document.id}/${document.name} +Загрузить с URL-адреса: ${shareContextUrl}/proxy/alfresco/api/node/content/${document.storeType}/${document.storeId}/${document.id}/${document.name}?a=true diff --git a/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_zh_CN.ftl b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_zh_CN.ftl new file mode 100644 index 0000000000..78cfe2c274 --- /dev/null +++ b/config/alfresco/imap/imapSpacesTemplates/emailbody_textplain_share_zh_CN.ftl @@ -0,0 +1,26 @@ +------------------------------------------------------------------------------ +文档名称: ${document.name} +------------------------------------------------------------------------------ + + <#if document.properties.title?exists> +标题: ${document.properties.title} + <#else> +标题: 无 + + <#if document.properties.description?exists> +说明: ${document.properties.description} + <#else> +说明: 无 + +创建者: ${document.properties.creator} +创建时间: ${document.properties.created?datetime} +修改者: ${document.properties.modifier} +修改时间: ${document.properties.modified?datetime} +大小: ${document.size / 1024} Kb + + +内容链接 + +内容文件夹: ${shareContextUrl}/page/site/${parentPathFromSites} +内容 URL: ${shareContextUrl}/proxy/alfresco/api/node/content/${document.storeType}/${document.storeId}/${document.id}/${document.name} +下载 URL: ${shareContextUrl}/proxy/alfresco/api/node/content/${document.storeType}/${document.storeId}/${document.id}/${document.name}?a=true diff --git a/config/alfresco/messages/activiti-engine-messages_fr.properties b/config/alfresco/messages/activiti-engine-messages_fr.properties index 9939bc2359..766be7d31e 100755 --- a/config/alfresco/messages/activiti-engine-messages_fr.properties +++ b/config/alfresco/messages/activiti-engine-messages_fr.properties @@ -1,8 +1,8 @@ -activiti.engine.mandatory.properties.missing=Les propri\u00e9t\u00e9s de t\u00e2che obligatoires n'ont pas \u00e9t\u00e9 renseign\u00e9es. {0} +activiti.engine.mandatory.properties.missing=Les propri\u00e9t\u00e9s de t\u00e2che obligatoires n''ont pas \u00e9t\u00e9 renseign\u00e9es. {0} activiti.engine.deploy.workflow.error=Impossible de d\u00e9ployer la d\u00e9finition de workflow. activiti.engine.is.workflow.deployed.error=Impossible de d\u00e9terminer si la d\u00e9finition de workflow est d\u00e9j\u00e0 d\u00e9ploy\u00e9e. activiti.engine.undeploy.workflow.error=Impossible d''annuler le d\u00e9ploiement de la d\u00e9finition de workflow {0}. -activiti.engine.undeploy.workflow.unexisting.error=Impossible d''annuler le d\u00e9ploiement d'une d\u00e9finition de workflow inexistante {0}. +activiti.engine.undeploy.workflow.unexisting.error=Impossible d''annuler le d\u00e9ploiement d''une d\u00e9finition de workflow inexistante {0}. activiti.engine.get.workflow.definition.error=Impossible de r\u00e9cup\u00e9rer les d\u00e9finitions de workflow. activiti.engine.get.workflow.definition.by.id.error=Impossible de r\u00e9cup\u00e9rer la d\u00e9finition de workflow pour l''ID {0}. activiti.engine.get.workflow.definition.by.name.error=Impossible de r\u00e9cup\u00e9rer la d\u00e9finition de workflow pour le nom {0}. @@ -32,7 +32,7 @@ activiti.engine.get.timers.error=Impossible de r\u00e9cup\u00e9rer l''une des ho activiti.engine.find.completed.task.instances.error=Impossible de r\u00e9cup\u00e9rer la liste des instances de t\u00e2ches achev\u00e9es pour l''acteur {0}. activiti.engine.get.assigned.tasks.error=Impossible de r\u00e9cup\u00e9rer les t\u00e2ches assign\u00e9es \u00e0 l''autorit\u00e9 {0} de l''\u00e9tat {1}. activiti.engine.get.pooled.tasks.error=Impossible de r\u00e9cup\u00e9rer les t\u00e2ches partag\u00e9es pour les autorit\u00e9s {0}. -activiti.engine.query.tasks.error=Impossible d'interroger les t\u00e2ches. Requ\u00eate : {0}. +activiti.engine.query.tasks.error=Impossible d''interroger les t\u00e2ches. Requ\u00eate : {0}. activiti.engine.get.task.instance.error=L''instance de t\u00e2che {0} n''existe pas. activiti.engine.update.task.error=Impossible de mettre \u00e0 jour la t\u00e2che de workflow {0}. activiti.engine.update.task.unexisting.error=Impossible de mettre \u00e0 jour la t\u00e2che de workflow {0} car cette t\u00e2che n''existe pas. @@ -50,5 +50,5 @@ activiti.engine.get.workflow.token.is.null=Le chemin de workflow {0} n''existe p activiti.engine.set.task.properties.invalid.value=La valeur {0} n''est pas valide pour la propri\u00e9t\u00e9 de t\u00e2che {1}. activiti.engine.package.already.associated.error=Impossible d''associer le paquetage de workflow {0} avec l''instance de workflow {1} car il est d\u00e9j\u00e0 associ\u00e9 \u00e0 l''instance de workflow {2}. activiti.engine.convert.value.error=Impossible de convertir la valeur Activiti {0} en valeur Alfresco car elle n''est pas s\u00e9rialisable. -activiti.engine.get.company.home.invalid=Chemin d''acc\u00e8s \u00e0 l'espace racine {0} non valide. -activiti.engine.get.company.home.multiple=Chemin d''acc\u00e8s \u00e0 l'espace racine {0} non valide. 1 correspondance \u00e9tait attendue mais {1} correspondances ont \u00e9t\u00e9 trouv\u00e9es. \ No newline at end of file +activiti.engine.get.company.home.invalid=Chemin d''acc\u00e8s \u00e0 l''espace racine {0} non valide. +activiti.engine.get.company.home.multiple=Chemin d''acc\u00e8s \u00e0 l''espace racine {0} non valide. 1 correspondance \u00e9tait attendue mais {1} correspondances ont \u00e9t\u00e9 trouv\u00e9es. \ No newline at end of file diff --git a/config/alfresco/messages/authentication_de.properties b/config/alfresco/messages/authentication_de.properties new file mode 100644 index 0000000000..730348a1eb --- /dev/null +++ b/config/alfresco/messages/authentication_de.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=Authentifizierung fehlgeschlagen, Authentifizierer mit Namen {0} nicht gefunden +authentication.err.validation.authenticator.notactive=Der Authentifizierer ist nicht aktiv. + +authentication.err.authentication=Authentifizierung fehlgeschlagen, Benutzername oder Passwort falsch. Benutzername:{0} Grund: {1} +authentication.err.connection=Verbindung mit {0} fehlgeschlagen. Grund: {1} +authentication.err.communication=Kommunikation mit {0} fehlgeschlage. Grund: {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=Verbindung fehlgeschlagen, LDAP-Host {0} nicht bekannt +authentication.err.connection.ldap.user.notfound=LDAP-Benutzer {0} nicht gefunden +authentication.err.connection.ldap.manager.notfound=LDAP Manager-Benutzer {0} nicht gefunden +authentication.err.connection.ldap.search=LDAP kann nicht durchsucht werden. Grund: {0} + +# PASSTHRU +authentication.err.connection.passthru.server=Session mit Passthru-Server konnte nicht ge\u00f6ffnet werden +authentication.err.passthru.token.unsupported=Nicht unterst\u00fctzter Authentifizierungs-Token +authentication.err.passthru.guest.notenabled=G\u00e4ste-Anmeldungen deaktiviert +authentication.err.passthru.user.disabled=Konto deaktiviert +authentication.err.passthru.user.notfound=Passthru-Benutzer {0} nicht gefunden + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=\u00dcberpr\u00fcfung der Anfrage +authentication.step.ldap.connecting=Verbindungsaufbau zum LDAP-Server {0} +authentication.step.ldap.connected=Verbunden mit LDAP-Server {0} mit Prinzipalserver {1} +authentication.step.ldap.lookup=Suche nach Testbenutzer mit Benutzer-ID {0}, Anfrage: {1} +authentication.step.ldap.lookedup=Gesucht nach Testbenutzer mit Benutzer-ID {0}, definierter Name (DN) gefunden: {1} +authentication.step.ldap.format.user=Format f\u00fcr Benutzername, Benutzernamenformat: {2} Format f\u00fcr Benutzer mit Benutzer-ID {0}, um folgenden definierten Namen (DN) zu erstellen: {1} +authentication.step.ldap.authentication=Benutzer-ID authentifizieren: {0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=Authentifizierer nicht gefunden + + diff --git a/config/alfresco/messages/authentication_es.properties b/config/alfresco/messages/authentication_es.properties new file mode 100644 index 0000000000..f9df70b2f2 --- /dev/null +++ b/config/alfresco/messages/authentication_es.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=Fallo de autenticaci\u00f3n, no se encontr\u00f3 el autenticador {0} +authentication.err.validation.authenticator.notactive=El autenticador no est\u00e1 activo + +authentication.err.authentication=Fallo de autenticaci\u00f3n, nombre de usuario o contrase\u00f1a incorrectos. Nombre de usuario:{0} Raz\u00f3n {1} +authentication.err.connection=Fallo de conexi\u00f3n a {0}. Raz\u00f3n {1} +authentication.err.communication=Fallo de comunicaci\u00f3n con {0}. Raz\u00f3n {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=Fallo de conexi\u00f3n, host {0} desconocido +authentication.err.connection.ldap.user.notfound=No se encontr\u00f3 el usuario LDAP {0} +authentication.err.connection.ldap.manager.notfound=No se encontr\u00f3 el usuario administrador LDAP {0} +authentication.err.connection.ldap.search=No se pudo buscar LDAP. Raz\u00f3n {0} + +# PASSTHRU +authentication.err.connection.passthru.server=No se pudo abrir sesi\u00f3n con servidor passthru +authentication.err.passthru.token.unsupported=Tipo de token de autenticaci\u00f3n no compatible +authentication.err.passthru.guest.notenabled=Inicios de sesi\u00f3n como invitado deshabilitados +authentication.err.passthru.user.disabled=Cuenta deshabilitada +authentication.err.passthru.user.notfound=No se encontr\u00f3 el usuario passthru {0} + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=Validaci\u00f3n de solicitud +authentication.step.ldap.connecting=Conectando al servidor LDAP {0} +authentication.step.ldap.connected=Conectado al servidor LDAP {0} con principal: {1} +authentication.step.ldap.lookup=Buscar Id. de usuario de prueba:{0} con consulta: {1} +authentication.step.ldap.lookedup=ID de usuario de prueba buscado:{0}, se encontr\u00f3 nombre distintivo (DN):{1} +authentication.step.ldap.format.user=Formato de nombre de usuario especificado, formato de nombre de usuario:{2} formatear ID de usuario de usuario:{0} para crear nombre distintivo (DN):{1} +authentication.step.ldap.authentication=ID de usuario aut\u00e9ntico:{0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=Autenticador no encontrado + + diff --git a/config/alfresco/messages/authentication_fr.properties b/config/alfresco/messages/authentication_fr.properties new file mode 100644 index 0000000000..111088941d --- /dev/null +++ b/config/alfresco/messages/authentication_fr.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=Echec d''authentification, authentificateur nomm\u00e9, {0}, introuvable +authentication.err.validation.authenticator.notactive=L'authentificateur n'est pas actif + +authentication.err.authentication=Echec d''authentification, nom d''utilisateur ou mot de passe incorrect. Nom d''utilisateur :{0} Raison {1} +authentication.err.connection=Echec de la connexion \u00e0 {0}. Raison {1} +authentication.err.communication=Echec de la communication avec {0}. Raison {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=Echec de la connexion, h\u00f4te ldap {0} inconnu +authentication.err.connection.ldap.user.notfound=Utilisateur LDAP {0} introuvable +authentication.err.connection.ldap.manager.notfound=Utilisateur gestionnaire LDAP {0} introuvable +authentication.err.connection.ldap.search=Impossible de rechercher dans LDAP. Raison {0} + +# PASSTHRU +authentication.err.connection.passthru.server=Impossible d'ouvrir une session sur le serveur interm\u00e9diaire +authentication.err.passthru.token.unsupported=Type de jeton d'authentification non pris en charge +authentication.err.passthru.guest.notenabled=Connexions invit\u00e9s d\u00e9sactiv\u00e9es +authentication.err.passthru.user.disabled=Compte d\u00e9sactiv\u00e9 +authentication.err.passthru.user.notfound=Utilisateur interm\u00e9diaire {0} introuvable + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=Validation de requ\u00eate +authentication.step.ldap.connecting=Connexion au serveur LDAP {0} +authentication.step.ldap.connected=Connect\u00e9 au serveur LDAP {0} avec le principal : {1} +authentication.step.ldap.lookup=Recherche utilisateur test userId : {0} avec la requ\u00eate : {1} +authentication.step.ldap.lookedup=Recherch\u00e9 utilisateur test userId : {0}, trouv\u00e9 nom unique :{1} +authentication.step.ldap.format.user=Format nom utilisateur sp\u00e9cifi\u00e9, userNameFormat : {2} Format utilisateur userId : {0} pour cr\u00e9er un nom unique : {1} +authentication.step.ldap.authentication=Authentifier userId : {0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=Authentificateur introuvable + + diff --git a/config/alfresco/messages/authentication_it.properties b/config/alfresco/messages/authentication_it.properties new file mode 100644 index 0000000000..ab50eebbeb --- /dev/null +++ b/config/alfresco/messages/authentication_it.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=Autenticazione non riuscita. Autenticatore con il nome {0} non trovato. +authentication.err.validation.authenticator.notactive=L'autenticatore non \u00e8 attivo + +authentication.err.authentication=Autenticazione non riuscita. Nome utente o password non corretti. Nome utente:{0} Motivo {1} +authentication.err.connection=Impossibile effettuare la connessione a {0}. Motivo {1} +authentication.err.communication=Impossibile comunicare con {0}. Motivo {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=Impossibile effettuare la connessione. Host ldap {0} sconosciuto +authentication.err.connection.ldap.user.notfound=Utente LDAP {0} non trovato +authentication.err.connection.ldap.manager.notfound=Utente Gestione LDAP {0} non trovato +authentication.err.connection.ldap.search=Impossibile cercare LDAP. Motivo {0} + +# PASSTHRU +authentication.err.connection.passthru.server=Impossibile aprire una sessione con il server passthru +authentication.err.passthru.token.unsupported=Tipo di token di autenticazione non supportato +authentication.err.passthru.guest.notenabled=Accessi guest disabilitati +authentication.err.passthru.user.disabled=Account disabilitato +authentication.err.passthru.user.notfound=Utente Passthru {0} non trovato + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=Convalida della richiesta +authentication.step.ldap.connecting=Connessione al server LDAP {0} in corso +authentication.step.ldap.connected=Connesso al server LDAP {0} con il server principale: {1} +authentication.step.ldap.lookup=Ricercare l''userId:{0} dell''utente test con la query: {1} +authentication.step.ldap.lookedup=UserId:{0} dell''utente test cercato. Nome distinto (DN) trovato:{1} +authentication.step.ldap.format.user=Formato del nome utente specificato. UserId :{0} dell''utente con il formato userNameFormat:{2} per creare un nome distino (DN):{1} +authentication.step.ldap.authentication=Autentica userId:{0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=Autenticatore non trovato + + diff --git a/config/alfresco/messages/authentication_ja.properties b/config/alfresco/messages/authentication_ja.properties new file mode 100644 index 0000000000..1156070821 --- /dev/null +++ b/config/alfresco/messages/authentication_ja.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=\u540d\u524d\u304c{0}\u306e\u8a8d\u8a3c\u5b50\u304c\u898b\u3064\u304b\u3089\u306a\u3044\u305f\u3081\u3001\u8a8d\u8a3c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +authentication.err.validation.authenticator.notactive=\u3053\u306e\u8a8d\u8a3c\u5b50\u306f\u30a2\u30af\u30c6\u30a3\u30d6\u3067\u306f\u3042\u308a\u307e\u305b\u3093 + +authentication.err.authentication=\u30e6\u30fc\u30b6\u30fc\u540d\u307e\u305f\u306f\u30d1\u30b9\u30ef\u30fc\u30c9\u304c\u9593\u9055\u3063\u3066\u3044\u308b\u305f\u3081\u3001\u8a8d\u8a3c\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u30e6\u30fc\u30b6\u30fc\u540d: {0} \u7406\u7531: {1} +authentication.err.connection={0}\u306b\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u7406\u7531: {1} +authentication.err.communication={0}\u3068\u901a\u4fe1\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 \u7406\u7531: {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=LDAP\u306e\u30db\u30b9\u30c8"{0}"\u304c\u4e0d\u660e\u306a\u305f\u3081\u3001\u63a5\u7d9a\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002 +authentication.err.connection.ldap.user.notfound=LDAP\u30e6\u30fc\u30b6\u30fc"{0}"\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 +authentication.err.connection.ldap.manager.notfound=LDAP\u30de\u30cd\u30fc\u30b8\u30e3"{0}"\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 +authentication.err.connection.ldap.search=LDAP\u3092\u691c\u7d22\u3067\u304d\u307e\u305b\u3093\u3002 \u7406\u7531: {0} + +# PASSTHRU +authentication.err.connection.passthru.server=\u30d1\u30b9\u30b9\u30eb\u30fc\u30b5\u30fc\u30d0\u30fc\u3078\u306e\u30bb\u30c3\u30b7\u30e7\u30f3\u3092\u958b\u3051\u307e\u305b\u3093\u3067\u3057\u305f +authentication.err.passthru.token.unsupported=\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u306a\u3044\u8a8d\u8a3c\u30c8\u30fc\u30af\u30f3\u30bf\u30a4\u30d7\u3067\u3059 +authentication.err.passthru.guest.notenabled=\u30b2\u30b9\u30c8\u30ed\u30b0\u30a4\u30f3\u304c\u7121\u52b9\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059 +authentication.err.passthru.user.disabled=\u30a2\u30ab\u30a6\u30f3\u30c8\u304c\u7121\u52b9\u306b\u8a2d\u5b9a\u3055\u308c\u3066\u3044\u307e\u3059 +authentication.err.passthru.user.notfound=\u30d1\u30b9\u30b9\u30eb\u30fc\u30e6\u30fc\u30b6\u30fc"{0}"\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=\u30ea\u30af\u30a8\u30b9\u30c8\u306e\u691c\u8a3c +authentication.step.ldap.connecting=LDAP\u30b5\u30fc\u30d0\u30fc"{0}"\u306b\u63a5\u7d9a\u3057\u3066\u3044\u307e\u3059 +authentication.step.ldap.connected=\u30d7\u30ea\u30f3\u30b7\u30d1\u30eb"{1}"\u306eLDAP\u30b5\u30fc\u30d0\u30fc"{0}"\u306b\u63a5\u7d9a\u3057\u307e\u3057\u305f +authentication.step.ldap.lookup=\u30e6\u30fc\u30b6\u30fcID"{0}"\u3001\u30af\u30a8\u30ea"{1}"\u306e\u30c6\u30b9\u30c8\u30e6\u30fc\u30b6\u30fc\u3092\u30eb\u30c3\u30af\u30a2\u30c3\u30d7\u3057\u307e\u3059 +authentication.step.ldap.lookedup=\u30e6\u30fc\u30b6\u30fcID"{0}"\u306e\u30c6\u30b9\u30c8\u30e6\u30fc\u30b6\u30fc\u3092\u30eb\u30c3\u30af\u30a2\u30c3\u30d7\u3057\u307e\u3057\u305f\u3002\u8b58\u5225\u540d(DN)"{1}"\u304c\u898b\u3064\u304b\u308a\u307e\u3057\u305f +authentication.step.ldap.format.user=\u30e6\u30fc\u30b6\u30fc\u540d\u5f62\u5f0f\u3092\u6307\u5b9a\u3057\u307e\u3057\u305f\u3002\u30e6\u30fc\u30b6\u30fc\u540d\u5f62\u5f0f: {2} \u3053\u306e\u5f62\u5f0f\u306b\u5f93\u3063\u3066\u30e6\u30fc\u30b6\u30fcID"{0}"\u304b\u3089\u8b58\u5225\u540d(DN)"{1}"\u3092\u4f5c\u6210\u3057\u307e\u3059 +authentication.step.ldap.authentication=\u8a8d\u8a3c\u30e6\u30fc\u30b6\u30fcID: {0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=\u8a8d\u8a3c\u5b50\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093 + + diff --git a/config/alfresco/messages/authentication_nl.properties b/config/alfresco/messages/authentication_nl.properties new file mode 100644 index 0000000000..bef3b15a30 --- /dev/null +++ b/config/alfresco/messages/authentication_nl.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=Kan verificatie niet uitvoeren, verificator met naam, {0}, niet gevonden +authentication.err.validation.authenticator.notactive=De verificator is niet actief + +authentication.err.authentication=Kan verificatie niet uitvoeren, gebruikersnaam of wachtwoord is onjuist. Gebruikersnaam:{0} Reden {1} +authentication.err.connection=Kan geen verbinding maken met {0}. Reden {1} +authentication.err.communication=Kan niet communiceren met {0}. Reden {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=Kan geen verbinding maken, de LDAP-host {0} is onbekend +authentication.err.connection.ldap.user.notfound=LDAP-gebruiker {0} niet gevonden +authentication.err.connection.ldap.manager.notfound=LDAP-beheerdergebruiker {0} niet gevonden +authentication.err.connection.ldap.search=Kan niet zoeken naar LDAP. Reden {0} + +# PASSTHRU +authentication.err.connection.passthru.server=Kan sessie met Passthru-server niet openen +authentication.err.passthru.token.unsupported=Niet-ondersteund type verificatietoken +authentication.err.passthru.guest.notenabled=Gastaanmeldingen uitgeschakeld +authentication.err.passthru.user.disabled=Account uitgeschakeld +authentication.err.passthru.user.notfound=Passthru-gebruiker {0} niet gevonden + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=Validatie van aanvraag +authentication.step.ldap.connecting=Verbinding maken LDAP-server {0} +authentication.step.ldap.connected=Verbonden met LDAP-server {0} met principal: {1} +authentication.step.ldap.lookup=Zoeken naar gebruikers-id van testgebruiker:{0} met query: {1} +authentication.step.ldap.lookedup=Gezocht naar gebruikers-id van testgebruiker:{0}, Distinguished Name (DN) gevonden:{1} +authentication.step.ldap.format.user=Notatie voor gebruikersnaam opgegeven, gebruikersnaamnotatie:{2} Notatie gebruiker gebruikers-id:{0} voor maken van Distinguished Name (DN):{1} +authentication.step.ldap.authentication=Gebruikers-id verifi\u00ebren:{0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=Verificator niet gevonden + + diff --git a/config/alfresco/messages/authentication_ru.properties b/config/alfresco/messages/authentication_ru.properties new file mode 100644 index 0000000000..f9f61c8d26 --- /dev/null +++ b/config/alfresco/messages/authentication_ru.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u0441 \u0438\u043c\u0435\u043d\u0435\u043c, {0}, \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d +authentication.err.validation.authenticator.notactive=\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043d\u0435\u0430\u043a\u0442\u0438\u0432\u0435\u043d + +authentication.err.authentication=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0432\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e, \u043d\u0435\u0432\u0435\u0440\u043d\u043e\u0435 \u0438\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0438\u043b\u0438 \u043f\u0430\u0440\u043e\u043b\u044c. \u0418\u043c\u044f \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f: {0} \u041f\u0440\u0438\u0447\u0438\u043d\u0430: {1} +authentication.err.connection=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f \u043a {0}. \u041f\u0440\u0438\u0447\u0438\u043d\u0430: {1} +authentication.err.communication=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u0441\u0432\u044f\u0437\u0430\u0442\u044c\u0441\u044f \u0441 {0}. \u041f\u0440\u0438\u0447\u0438\u043d\u0430: {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043f\u043e\u0434\u043a\u043b\u044e\u0447\u0438\u0442\u044c\u0441\u044f, \u0443\u0437\u0435\u043b ldap {0} \u043d\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u0435\u043d +authentication.err.connection.ldap.user.notfound=\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c LDAP {0} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d +authentication.err.connection.ldap.manager.notfound=\u041f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c \u0434\u0438\u0441\u043f\u0435\u0442\u0447\u0435\u0440\u0430 LDAP {0} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d +authentication.err.connection.ldap.search=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043d\u0430\u0439\u0442\u0438 LDAP. \u041f\u0440\u0438\u0447\u0438\u043d\u0430: {0} + +# PASSTHRU +authentication.err.connection.passthru.server=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043a\u0440\u044b\u0442\u044c \u0441\u0435\u0430\u043d\u0441 \u0441\u0432\u044f\u0437\u0438 \u0441 \u043f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u043c \u0441\u0435\u0440\u0432\u0435\u0440\u043e\u043c +authentication.err.passthru.token.unsupported=\u041d\u0435\u043f\u043e\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043c\u044b\u0439 \u0442\u0438\u043f \u043c\u0430\u0440\u043a\u0435\u0440\u0430 \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u0438 +authentication.err.passthru.guest.notenabled=\u0412\u0445\u043e\u0434\u044b \u0433\u043e\u0441\u0442\u044f \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u044b +authentication.err.passthru.user.disabled=\u0423\u0447\u0435\u0442\u043d\u0430\u044f \u0437\u0430\u043f\u0438\u0441\u044c \u043e\u0442\u043a\u043b\u044e\u0447\u0435\u043d\u0430 +authentication.err.passthru.user.notfound=\u041f\u0440\u043e\u043c\u0435\u0436\u0443\u0442\u043e\u0447\u043d\u044b\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044c {0} \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0437\u0430\u043f\u0440\u043e\u0441\u0430 +authentication.step.ldap.connecting=\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u0438\u0435 \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 LDAP {0} +authentication.step.ldap.connected=\u041f\u043e\u0434\u043a\u043b\u044e\u0447\u0435\u043d\u043e \u043a \u0441\u0435\u0440\u0432\u0435\u0440\u0443 LDAP {0} \u0441 \u0441\u0443\u0431\u044a\u0435\u043a\u0442\u043e\u043c: {1} +authentication.step.ldap.lookup=\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u043f\u043e\u0438\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f userId: {0} \u0441 \u0437\u0430\u043f\u0440\u043e\u0441\u043e\u043c: {1} +authentication.step.ldap.lookedup=\u0412\u044b\u043f\u043e\u043b\u043d\u0435\u043d \u043f\u043e\u0438\u0441\u043a \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0433\u043e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f userId: {0}, \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0449\u0435\u0435\u0441\u044f \u0438\u043c\u044f: {1} +authentication.step.ldap.format.user=\u0424\u043e\u0440\u043c\u0430\u0442 \u0438\u043c\u0435\u043d\u0438 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f \u0437\u0430\u0434\u0430\u043d, userNameFormat: {2} \u041e\u0442\u0444\u043e\u0440\u043c\u0430\u0442\u0438\u0440\u043e\u0432\u0430\u0442\u044c \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f userId: {0}, \u0447\u0442\u043e\u0431\u044b \u043f\u043e\u043b\u0443\u0447\u0438\u0442\u044c \u043e\u0442\u043b\u0438\u0447\u0430\u044e\u0449\u0435\u0435\u0441\u044f \u0438\u043c\u044f: {1} +authentication.step.ldap.authentication=\u0412\u044b\u043f\u043e\u043b\u043d\u0438\u0442\u044c \u0430\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0446\u0438\u044e \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u044f userId: {0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=\u0410\u0443\u0442\u0435\u043d\u0442\u0438\u0444\u0438\u043a\u0430\u0442\u043e\u0440 \u043d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d + + diff --git a/config/alfresco/messages/authentication_zh_CN.properties b/config/alfresco/messages/authentication_zh_CN.properties new file mode 100644 index 0000000000..4957fbe9ea --- /dev/null +++ b/config/alfresco/messages/authentication_zh_CN.properties @@ -0,0 +1,34 @@ +# authentication error messages +authentication.err.validation.authenticator.notfound=\u8eab\u4efd\u9a8c\u8bc1\u5931\u8d25\uff0c\u672a\u627e\u5230\u540d\u4e3a {0} \u7684\u8eab\u4efd\u9a8c\u8bc1\u5668 +authentication.err.validation.authenticator.notactive=\u8eab\u4efd\u9a8c\u8bc1\u5668\u672a\u6fc0\u6d3b + +authentication.err.authentication=\u8eab\u4efd\u9a8c\u8bc1\u5931\u8d25\uff0c\u7528\u6237\u540d\u6216\u5bc6\u7801\u9519\u8bef\u3002 \u7528\u6237\u540d\uff1a{0}\u3002\u539f\u56e0 {1} +authentication.err.connection=\u8fde\u63a5\u5230 {0} \u5931\u8d25\u3002 \u539f\u56e0 {1} +authentication.err.communication=\u4e0e {0} \u901a\u4fe1\u5931\u8d25\u3002 \u539f\u56e0 {1} + +# LDAP error messages +authentication.err.connection.ldap.authenticator.unknownhost=\u8fde\u63a5\u5931\u8d25\uff0cLDAP \u4e3b\u673a {0} \u672a\u77e5 +authentication.err.connection.ldap.user.notfound=\u672a\u627e\u5230 LDAP \u7528\u6237 {0} +authentication.err.connection.ldap.manager.notfound=\u672a\u627e\u5230 LDAP \u7ba1\u7406\u5668\u7528\u6237 {0} +authentication.err.connection.ldap.search=\u65e0\u6cd5\u641c\u7d22 LDAP\u3002 \u539f\u56e0 {0} + +# PASSTHRU +authentication.err.connection.passthru.server=\u6253\u5f00 passthru \u670d\u52a1\u5668\u4f1a\u8bdd\u5931\u8d25 +authentication.err.passthru.token.unsupported=\u8eab\u4efd\u9a8c\u8bc1\u4ee4\u724c\u7c7b\u578b\u4e0d\u53d7\u652f\u6301 +authentication.err.passthru.guest.notenabled=\u5df2\u7981\u7528\u8bbf\u5ba2\u767b\u5f55 +authentication.err.passthru.user.disabled=\u5df2\u7981\u7528\u5e10\u6237 +authentication.err.passthru.user.notfound=\u672a\u627e\u5230 Passthru \u7528\u6237 {0} + +# Authentication Diagnostic Steps +authentication.step.ldap.validation=\u8bf7\u6c42\u9a8c\u8bc1 +authentication.step.ldap.connecting=\u6b63\u5728\u8fde\u63a5\u5230 LDAP \u670d\u52a1\u5668 {0} +authentication.step.ldap.connected=\u5df2\u4f7f\u7528\u4e3b\u4f53 {1} \u8fde\u63a5\u5230 LDAP \u670d\u52a1\u5668 {0} +authentication.step.ldap.lookup=\u4f7f\u7528\u67e5\u8be2 {1} \u67e5\u627e\u6d4b\u8bd5\u7528\u6237\u7684 userId\uff1a{0} +authentication.step.ldap.lookedup=\u5df2\u67e5\u627e\u6d4b\u8bd5\u7528\u6237\u7684 userId\uff1a{0}\uff0c\u627e\u5230\u53ef\u5206\u8fa8\u540d\u79f0 (DN)\uff1a{1} +authentication.step.ldap.format.user=\u5df2\u6307\u5b9a\u7528\u6237\u540d\u683c\u5f0f\uff0cuserNameFormat\uff1a{2}\u3002\u683c\u5f0f\u5316\u7528\u6237\u7684 userId {0} \u4ee5\u751f\u6210\u53ef\u5206\u8fa8\u540d\u79f0 (DN)\uff1a{1} +authentication.step.ldap.authentication=\u8eab\u4efd\u9a8c\u8bc1 userId\uff1a{0} + +# Error messages +authentication.ldap.validation.authenticator.notfound=\u672a\u627e\u5230\u8eab\u4efd\u9a8c\u8bc1\u5668 + + diff --git a/config/alfresco/messages/avm-messages_ru.properties b/config/alfresco/messages/avm-messages_ru.properties index 8628a9e556..70ef3ef4d0 100755 --- a/config/alfresco/messages/avm-messages_ru.properties +++ b/config/alfresco/messages/avm-messages_ru.properties @@ -2,7 +2,7 @@ expiredcontent.workflow.title=\u041A\u043E\u043D\u0442\u0435\u043D\u0442 \u0441 \u0438\u0441\u0442\u0435\u043A\u0448\u0438\u043C \u0441\u0440\u043E\u043A\u043E\u043C \u0432 ''{0}'' avmlockservice.locked=\u041D\u0435\u0442 \u0434\u043E\u0441\u0442\u0443\u043F\u0430 \u043A ''{0}'', \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u043D\u043D\u043E\u043C\u0443 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u043C ''{1}''. -avmlockservice.accessdenied=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C '{0}' \u043F\u044B\u0442\u0430\u043B\u0441\u044F \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0443\u0437\u0435\u043B \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430, \u043D\u0435 \u0438\u043C\u0435\u044F \u043F\u0440\u0430\u0432\u0430 \u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u044C. +avmlockservice.accessdenied=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C ''{0}'' \u043F\u044B\u0442\u0430\u043B\u0441\u044F \u0437\u0430\u0431\u043B\u043E\u043A\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0443\u0437\u0435\u043B \u0432 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435 \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430, \u043D\u0435 \u0438\u043C\u0435\u044F \u043F\u0440\u0430\u0432\u0430 \u043D\u0430 \u0437\u0430\u043F\u0438\u0441\u044C. testserver.taken=\u0412\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0439 \u0442\u0435\u0441\u0442\u043E\u0432\u044B\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 ''{0}'' \u0432\u044B\u0434\u0435\u043B\u0435\u043D \u0434\u0440\u0443\u0433\u043E\u043C\u0443 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044E. \u0415\u0441\u043B\u0438 \u0432\u043E\u0437\u043C\u043E\u0436\u043D\u043E, \u0432\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0434\u0440\u0443\u0433\u043E\u0439 \u0441\u0435\u0440\u0432\u0435\u0440 \u0438 \u043F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u0435 \u043F\u043E\u043F\u044B\u0442\u043A\u0443. avm.cycle.create=\u0411\u044B\u043B \u0431\u044B \u0441\u043E\u0437\u0434\u0430\u043D \u0446\u0438\u043A\u043B. diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties index f3903a0ba1..04ce15ab41 100644 --- a/config/alfresco/messages/bootstrap-spaces.properties +++ b/config/alfresco/messages/bootstrap-spaces.properties @@ -129,6 +129,8 @@ version.italian=Italian version version.japanese=Japanese version version.spanish=Spanish version version.dutch=Dutch version +version.russian=Russian version +version.chinese=Simplified Chinese version spaces.web.client.extension.name=Web Client Extension spaces.web.client.extension.title=Customized Web Client diff --git a/config/alfresco/messages/bootstrap-spaces_de.properties b/config/alfresco/messages/bootstrap-spaces_de.properties index e09367c32c..78220589fd 100755 --- a/config/alfresco/messages/bootstrap-spaces_de.properties +++ b/config/alfresco/messages/bootstrap-spaces_de.properties @@ -129,6 +129,8 @@ version.italian=Italienische Version version.japanese=Japanische Version version.spanish=Spanische Version version.dutch=Niederl\u00e4ndische Version +version.russian=Russische Version +version.chinese=Version in vereinfachtem Chinesisch spaces.web.client.extension.name=Web Client Erweiterung spaces.web.client.extension.title=Parametrierter Web Client diff --git a/config/alfresco/messages/bootstrap-spaces_es.properties b/config/alfresco/messages/bootstrap-spaces_es.properties index 3144a22598..cfbf3d6b00 100755 --- a/config/alfresco/messages/bootstrap-spaces_es.properties +++ b/config/alfresco/messages/bootstrap-spaces_es.properties @@ -129,6 +129,8 @@ version.italian=Versi\u00f3n en italiano version.japanese=Versi\u00f3n en japon\u00e9s version.spanish=Versi\u00f3n en espa\u00f1ol version.dutch=Versi\u00f3n en holand\u00e9s +version.russian=Versi\u00f3n rusa +version.chinese=Versi\u00f3n en chino simplificado spaces.web.client.extension.name=Extensi\u00f3n de cliente Web spaces.web.client.extension.title=Cliente Web personalizado diff --git a/config/alfresco/messages/bootstrap-spaces_fr.properties b/config/alfresco/messages/bootstrap-spaces_fr.properties index 9ada948d95..379e7bb243 100755 --- a/config/alfresco/messages/bootstrap-spaces_fr.properties +++ b/config/alfresco/messages/bootstrap-spaces_fr.properties @@ -129,6 +129,8 @@ version.italian=Version italienne version.japanese=Version japonaise version.spanish=Version espagnole version.dutch=Version n\u00e9erlandaise +version.russian=Version en russe +version.chinese=Version en chinois simplifi\u00e9 spaces.web.client.extension.name=Extension client Web spaces.web.client.extension.title=Client Web personnalis\u00e9 diff --git a/config/alfresco/messages/bootstrap-spaces_it.properties b/config/alfresco/messages/bootstrap-spaces_it.properties index c228735019..15b82f1bb9 100755 --- a/config/alfresco/messages/bootstrap-spaces_it.properties +++ b/config/alfresco/messages/bootstrap-spaces_it.properties @@ -129,6 +129,8 @@ version.italian=Versione in lingua italiana version.japanese=Versione in lingua giapponese version.spanish=Versione in lingua spagnola version.dutch=Versione in lingua olandese +version.russian=Versione in lingua russa +version.chinese=Versione in lingua cinese semplificato spaces.web.client.extension.name=Estensione del client web spaces.web.client.extension.title=Client web personalizzato diff --git a/config/alfresco/messages/bootstrap-spaces_ja.properties b/config/alfresco/messages/bootstrap-spaces_ja.properties index 4e36e9c258..e57978d6e2 100755 --- a/config/alfresco/messages/bootstrap-spaces_ja.properties +++ b/config/alfresco/messages/bootstrap-spaces_ja.properties @@ -129,6 +129,8 @@ version.italian=\u30a4\u30bf\u30ea\u30a2\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 version.japanese=\u65e5\u672c\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 version.spanish=\u30b9\u30da\u30a4\u30f3\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 version.dutch=\u30aa\u30e9\u30f3\u30c0\u8a9e\u30d0\u30fc\u30b8\u30e7\u30f3 +version.russian=\u30ed\u30b7\u30a2\u8a9e\u7248 +version.chinese=\u4e2d\u56fd\u8a9e(\u7c21\u4f53)\u7248 spaces.web.client.extension.name=Web\u30af\u30e9\u30a4\u30a2\u30f3\u30c8\u306e\u62e1\u5f35 spaces.web.client.extension.title=\u30ab\u30b9\u30bf\u30de\u30a4\u30ba\u3055\u308c\u305fWeb\u30af\u30e9\u30a4\u30a2\u30f3\u30c8 diff --git a/config/alfresco/messages/bootstrap-spaces_nl.properties b/config/alfresco/messages/bootstrap-spaces_nl.properties index 6396ba3ee9..3b94ef4d1f 100755 --- a/config/alfresco/messages/bootstrap-spaces_nl.properties +++ b/config/alfresco/messages/bootstrap-spaces_nl.properties @@ -129,6 +129,8 @@ version.italian=Italiaanse versie version.japanese=Japanse versie version.spanish=Spaanse versie version.dutch=Nederlandse versie +version.russian=Russische versie +version.chinese=Versie in vereenvoudigd Chinees spaces.web.client.extension.name=Webclientuitbreiding spaces.web.client.extension.title=Aangepaste webclient diff --git a/config/alfresco/messages/bootstrap-spaces_ru.properties b/config/alfresco/messages/bootstrap-spaces_ru.properties index 59cd6a328c..6e09e0af9b 100755 --- a/config/alfresco/messages/bootstrap-spaces_ru.properties +++ b/config/alfresco/messages/bootstrap-spaces_ru.properties @@ -129,6 +129,8 @@ version.italian=\u0418\u0442\u0430\u043B\u044C\u044F\u043D\u0441\u043A\u0430\u04 version.japanese=\u042F\u043F\u043E\u043D\u0441\u043A\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F version.spanish=\u0418\u0441\u043F\u0430\u043D\u0441\u043A\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F version.dutch=\u0413\u043E\u043B\u043B\u0430\u043D\u0434\u0441\u043A\u0430\u044F \u0432\u0435\u0440\u0441\u0438\u044F +version.russian=\u0412\u0435\u0440\u0441\u0438\u044f \u0434\u043b\u044f \u0440\u0443\u0441\u0441\u043a\u043e\u0433\u043e +version.chinese=\u0412\u0435\u0440\u0441\u0438\u044f \u0434\u043b\u044f \u0443\u043f\u0440\u043e\u0449\u0435\u043d\u043d\u043e\u0433\u043e \u043a\u0438\u0442\u0430\u0439\u0441\u043a\u043e\u0433\u043e spaces.web.client.extension.name=\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u0435 \u0432\u0435\u0431-\u043A\u043B\u0438\u0435\u043D\u0442\u0430 spaces.web.client.extension.title=\u041D\u0430\u0441\u0442\u0440\u043E\u0435\u043D\u043D\u044B\u0439 \u0432\u0435\u0431-\u043A\u043B\u0438\u0435\u043D\u0442 diff --git a/config/alfresco/messages/bootstrap-spaces_zh_CN.properties b/config/alfresco/messages/bootstrap-spaces_zh_CN.properties index 4e628483c0..15f47108db 100755 --- a/config/alfresco/messages/bootstrap-spaces_zh_CN.properties +++ b/config/alfresco/messages/bootstrap-spaces_zh_CN.properties @@ -129,6 +129,8 @@ version.italian=\u610f\u5927\u5229\u8bed\u7248\u672c version.japanese=\u65e5\u8bed\u7248\u672c version.spanish=\u897f\u73ed\u7259\u8bed\u7248\u672c version.dutch=\u8377\u5170\u8bed\u7248\u672c +version.russian=\u4fc4\u6587\u7248 +version.chinese=\u7b80\u4f53\u4e2d\u6587\u7248 spaces.web.client.extension.name=Web \u5ba2\u6237\u7aef\u6269\u5c55 spaces.web.client.extension.title=\u5b9a\u5236\u7684 Web \u5ba2\u6237\u7aef diff --git a/config/alfresco/messages/bootstrap-templates_zh_CN.properties b/config/alfresco/messages/bootstrap-templates_zh_CN.properties index da4abbbe86..31fe3e662b 100755 --- a/config/alfresco/messages/bootstrap-templates_zh_CN.properties +++ b/config/alfresco/messages/bootstrap-templates_zh_CN.properties @@ -3,7 +3,7 @@ templates.space.project=\u8F6F\u4EF6\u5DE5\u7A0B\u9879\u76EE templates.space.documentation=\u6587\u6863 templates.space.drafts=\u8349\u7A3F -templates.space.pending_approval=\u5F85\u51B3\u6279\u51C6 +templates.space.pending_approval=\u5f85\u6279\u51c6 templates.space.published=\u5DF2\u53D1\u5E03 templates.space.samples=\u6837\u672C templates.document.system_overview.title=\u7CFB\u7EDF\u6982\u8FF0 diff --git a/config/alfresco/messages/bpm-messages_de.properties b/config/alfresco/messages/bpm-messages_de.properties index 3f480292e8..b0f0734f45 100755 --- a/config/alfresco/messages/bpm-messages_de.properties +++ b/config/alfresco/messages/bpm-messages_de.properties @@ -85,7 +85,7 @@ listconstraint.bpm_allowedPriority.1=Hoch listconstraint.bpm_allowedPriority.2=Mittel listconstraint.bpm_allowedPriority.3=Niedrig listconstraint.bpm_allowedStatus.Not\ Yet\ Started=noch nicht gestartet -listconstraint.bpm_allowedStatus.In\ Progress=l\u00e4uft +listconstraint.bpm_allowedStatus.In\ Progress=in Bearbeitung listconstraint.bpm_allowedStatus.On\ Hold= in Wartestellung listconstraint.bpm_allowedStatus.Cancelled=abgebrochen listconstraint.bpm_allowedStatus.Completed=abgeschlossen diff --git a/config/alfresco/messages/data-list-model_de.properties b/config/alfresco/messages/data-list-model_de.properties index da0605a9bb..f4ae62cd22 100755 --- a/config/alfresco/messages/data-list-model_de.properties +++ b/config/alfresco/messages/data-list-model_de.properties @@ -31,7 +31,7 @@ dl_datalistmodel.type.dl_task.description=Erweiterte Aufgabenliste einschlie\u00 dl_datalistmodel.property.dl_taskPriority.title=Priorit\u00e4t dl_datalistmodel.property.dl_taskStatus.title=Status dl_datalistmodel.property.dl_taskComments.title=Kommentare -dl_datalistmodel.association.dl_taskAssignee.title=Bevollm\u00e4chtigter +dl_datalistmodel.association.dl_taskAssignee.title=Zugewiesener #Task List (Simple) dl_datalistmodel.type.dl_simpletask.title=Aufgabenliste (Einfach) @@ -105,7 +105,7 @@ dl_datalistmodel.property.dl_eventAgendaNotes.title=Notizen #List constraint display labels listconstraint.dl_task_status.Not\ Started=nicht gestartet -listconstraint.dl_task_status.In\ Progress=l\u00e4uft +listconstraint.dl_task_status.In\ Progress=in Bearbeitung listconstraint.dl_task_status.On\ Hold= in Wartestellung listconstraint.dl_task_status.Complete=Abgeschlossen listconstraint.dl_priority_value.High=Hoch diff --git a/config/alfresco/messages/email-service_de.properties b/config/alfresco/messages/email-service_de.properties index e4d2ab4c8f..73d4742a26 100755 --- a/config/alfresco/messages/email-service_de.properties +++ b/config/alfresco/messages/email-service_de.properties @@ -23,3 +23,11 @@ email.server.err.parse_message=Die E-Mail Nachricht konnte nicht geparst werden: email.server.err.usupported_encoding=Zeichencodierung ''{0}'' wird nicht unterst\u00fctzt email.server.err.failed_to_read_content_stream=Teilinhalt der Nachricht konnte nicht gelesen werden: {0} email.server.err.incorrect_message_part=Fehlerhafter Nachrichtenteil: {0} + +email.outbound.err.send.failed=E-Mail an {0} konnte nicht gesendet werden. Grund: {1} + +email.outbound.test.send.success=Testnachricht gesendet an: {0} + +email.outbound.err.test.no.to=Testnachricht kann nicht gesendet werden, Feld 'Testnachricht an' ist leer +email.outbound.err.test.no.subject=Testnachricht kann nicht gesendet werden, Feld 'Betreff' ist leer +email.outbound.err.test.no.text=Testnachricht kann nicht gesendet werden, Feld 'Text' ist leer \ No newline at end of file diff --git a/config/alfresco/messages/email-service_es.properties b/config/alfresco/messages/email-service_es.properties index 5ba16a0223..ef6307a8a3 100755 --- a/config/alfresco/messages/email-service_es.properties +++ b/config/alfresco/messages/email-service_es.properties @@ -15,11 +15,19 @@ email.server.err.mail_read_error=Se produjo un error al leer el mensaje de corre email.server.err.failed_to_create_mime_message=No se pudo crear el mensaje MIME desde la corriente de entrada: {0} email.server.err.extracting_from_address=No se pudo extraer la direcci\u00f3n ''desde'': {0} email.server.err.no_from_address=El mensaje no tiene direcci\u00f3n 'desde'. -email.server.err.extracting_to_address=No se pudo extraer la direcci\u00f3n ''a'': {0} -email.server.err.no_to_address=El mensaje no tiene direcci\u00f3n 'a'. +email.server.err.extracting_to_address=No se pudo extraer la direcci\u00f3n ''Para'': {0} +email.server.err.no_to_address=El mensaje no tiene direcci\u00f3n 'Para'. email.server.err.extracting_subject=No se pudo extraer el asunto del mensaje: {0} email.server.err.extracting_sent_date=No se pudo extraer la fecha ''enviado el'': {0} email.server.err.parse_message=No se pudo analizar sint\u00e1cticamente el mensaje de correo: {0} email.server.err.usupported_encoding=Codificaci\u00f3n ''{0}'' no soportada email.server.err.failed_to_read_content_stream=No se pudo leer el contenido de la parte de mensaje: {0} email.server.err.incorrect_message_part=Parte incorrecta de mensaje: {0} + +email.outbound.err.send.failed=Fallo al enviar mensaje de correo electr\u00f3nico a: {0} causa {1} + +email.outbound.test.send.success=Enviar mensaje de prueba a: {0} + +email.outbound.err.test.no.to=No se pudo enviar el mensaje de prueba 'pruebaMensajepara', est\u00e1 vac\u00edo +email.outbound.err.test.no.subject=No se pudo enviar el mensaje de prueba 'asunto', est\u00e1 vac\u00edo +email.outbound.err.test.no.text=No se pudo enviar el mensaje de prueba 'texto', est\u00e1 vac\u00edo diff --git a/config/alfresco/messages/email-service_fr.properties b/config/alfresco/messages/email-service_fr.properties index e5fc7c6b06..54301b6414 100755 --- a/config/alfresco/messages/email-service_fr.properties +++ b/config/alfresco/messages/email-service_fr.properties @@ -18,8 +18,16 @@ email.server.err.no_from_address=Aucune adresse 'de' n'est sp\u00e9cifi\u00e9e p email.server.err.extracting_to_address=Impossible d''extraire l''adresse ''vers'' : {0} email.server.err.no_to_address=Aucune adresse 'vers' n'est sp\u00e9cifi\u00e9e pour le message. email.server.err.extracting_subject=Impossible d''extraire l''objet du message : {0} -email.server.err.extracting_sent_date=Impossible d'extraire la date ''envoy\u00e9 le'' : {0} +email.server.err.extracting_sent_date=Impossible d''extraire la date ''envoy\u00e9 le'' : {0} email.server.err.parse_message=Impossible d''analyser l''E-mail : {0} email.server.err.usupported_encoding=L''encodage ''{0}'' n''est pas pris en charge email.server.err.failed_to_read_content_stream=Impossible de lire le contenu partiel du message : {0} email.server.err.incorrect_message_part=Partie incorrecte du message : {0} + +email.outbound.err.send.failed=Echec d''envoi d''e-mail \u00e0 : {0} cause {1} + +email.outbound.test.send.success=Envoy\u00e9 message test \u00e0 : {0} + +email.outbound.err.test.no.to=Impossible d'envoyer le message test 'testMessageTo' est vide +email.outbound.err.test.no.subject=Impossible d'envoyer le message test 'subject' est vide +email.outbound.err.test.no.text=Impossible d'envoyer le message test 'text' est vide diff --git a/config/alfresco/messages/email-service_it.properties b/config/alfresco/messages/email-service_it.properties index daa05f77a3..8c74b09716 100755 --- a/config/alfresco/messages/email-service_it.properties +++ b/config/alfresco/messages/email-service_it.properties @@ -23,3 +23,11 @@ email.server.err.parse_message=Impossibile analizzare il messaggio e-mail: {0} email.server.err.usupported_encoding=La codifica ''{0}'' non \u00e8 supportata email.server.err.failed_to_read_content_stream=Impossibile leggere il contenuto della parte del messaggio: {0} email.server.err.incorrect_message_part=Parte del messaggio non corretta: {0} + +email.outbound.err.send.failed=Impossibile inviare un''e-mail a: {0} causa {1} + +email.outbound.test.send.success=Invia messaggio di prova a: {0} + +email.outbound.err.test.no.to=Impossibile inviare un messaggio di prova. "testMessageTo" vuoto. +email.outbound.err.test.no.subject=Impossibile inviare un messaggio di prova. "Oggetto" vuoto. +email.outbound.err.test.no.text=Impossibile inviare un messaggio di prova. "Testo" vuoto. diff --git a/config/alfresco/messages/email-service_ja.properties b/config/alfresco/messages/email-service_ja.properties index af50e67202..c4cce5e99e 100755 --- a/config/alfresco/messages/email-service_ja.properties +++ b/config/alfresco/messages/email-service_ja.properties @@ -13,7 +13,7 @@ email.server.err.invalid_node_address=E\u30e1\u30fc\u30eb\u30a2\u30c9\u30ec\u30b email.server.err.handler_not_found=\u30ce\u30fc\u30c9\u30bf\u30a4\u30d7''{0}''\u306eE\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8\u30cf\u30f3\u30c9\u30e9\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3067\u3057\u305f\u3002 email.server.err.mail_read_error=\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8\u306e\u8aad\u53d6\u308a\u4e2d\u306b\u30a8\u30e9\u30fc\u304c\u767a\u751f\u3057\u307e\u3057\u305f: {0} email.server.err.failed_to_create_mime_message=\u5165\u529b\u30b9\u30c8\u30ea\u30fc\u30e0\u304b\u3089MIME\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u4f5c\u6210\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f: {0} -email.server.err.extracting_from_address='\u5dee\u51fa\u4eba''\u30a2\u30c9\u30ec\u30b9\u3092\u62bd\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f: {0} +email.server.err.extracting_from_address=''\u5dee\u51fa\u4eba''\u30a2\u30c9\u30ec\u30b9\u3092\u62bd\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f: {0} email.server.err.no_from_address=\u30e1\u30c3\u30bb\u30fc\u30b8\u306b'\u5dee\u51fa\u4eba'\u30a2\u30c9\u30ec\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 email.server.err.extracting_to_address=''\u5b9b\u5148''\u30a2\u30c9\u30ec\u30b9\u3092\u62bd\u51fa\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f: {0} email.server.err.no_to_address=\u30e1\u30c3\u30bb\u30fc\u30b8\u306b'\u5b9b\u5148'\u30a2\u30c9\u30ec\u30b9\u304c\u3042\u308a\u307e\u305b\u3093\u3002 @@ -23,3 +23,11 @@ email.server.err.parse_message=E\u30e1\u30fc\u30eb\u30e1\u30c3\u30bb\u30fc\u30b8 email.server.err.usupported_encoding=\u30a8\u30f3\u30b3\u30fc\u30c7\u30a3\u30f3\u30b0''{0}''\u306f\u30b5\u30dd\u30fc\u30c8\u3055\u308c\u3066\u3044\u307e\u305b\u3093 email.server.err.failed_to_read_content_stream=\u30e1\u30c3\u30bb\u30fc\u30b8\u90e8\u5206\u306e\u30b3\u30f3\u30c6\u30f3\u30c4\u3092\u8aad\u53d6\u308c\u307e\u305b\u3093\u3067\u3057\u305f: {0} email.server.err.incorrect_message_part=\u30e1\u30c3\u30bb\u30fc\u30b8\u90e8\u5206\u304c\u6b63\u3057\u304f\u3042\u308a\u307e\u305b\u3093: {0} + +email.outbound.err.send.failed=E\u30e1\u30fc\u30eb\u3092\u9001\u4fe1\u3067\u304d\u307e\u305b\u3093\u3067\u3057\u305f\u3002\u5b9b\u5148: {0} \u7406\u7531: {1} + +email.outbound.test.send.success=\u30c6\u30b9\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1\u3057\u307e\u3057\u305f\u3002\u5b9b\u5148: {0} + +email.outbound.err.test.no.to='testMessageTo'\u304c\u7a7a\u306e\u305f\u3081\u3001\u30c6\u30b9\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1\u3067\u304d\u307e\u305b\u3093 +email.outbound.err.test.no.subject='subject'\u304c\u7a7a\u306e\u305f\u3081\u3001\u30c6\u30b9\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1\u3067\u304d\u307e\u305b\u3093 +email.outbound.err.test.no.text='text'\u304c\u7a7a\u306e\u305f\u3081\u3001\u30c6\u30b9\u30c8\u30e1\u30c3\u30bb\u30fc\u30b8\u3092\u9001\u4fe1\u3067\u304d\u307e\u305b\u3093 diff --git a/config/alfresco/messages/email-service_nl.properties b/config/alfresco/messages/email-service_nl.properties index 1f9aa33481..cc81ba9934 100755 --- a/config/alfresco/messages/email-service_nl.properties +++ b/config/alfresco/messages/email-service_nl.properties @@ -6,20 +6,29 @@ email.server.err.sender_blocked=''{0}'' krijgt geen toegang. email.server.err.inbound_mail_disabled=De Alfresco-server is niet geconfigureerd voor de ontvangst van inkomende e-mails. email.server.err.access_denied=''{0}'' krijgt geen toegang tot ''{1}''. email.server.err.invalid_subject=De onderwerpregel moet een geldige bestandsnaam bevatten. -email.server.err.unknown_source_address=Het 'van'-e-mailadres is niet herkend: {0}. +email.server.err.unknown_source_address=Het ''van''-e-mailadres is niet herkend: {0}. email.server.err.user_not_email_contributor=Gebruiker ''{0}'' bevindt zich niet in de groep e-mailbijdragers. email.server.err.no_email_contributor_group=De groep e-mailbijdragers bestaat niet. email.server.err.invalid_node_address=Het e-mailadres ''{0}'' verwijst niet naar een geldige, toegankelijke node. email.server.err.handler_not_found=Kan e-mailberichthandler niet vinden voor nodetype ''{0}''. email.server.err.mail_read_error=Er is een fout opgetreden bij het lezen van het e-mailbericht: {0} email.server.err.failed_to_create_mime_message=Kan geen MIME-bericht maken van invoerstroom: {0} -email.server.err.extracting_from_address=Kan het 'van'-adres niet ophalen: {0} +email.server.err.extracting_from_address=Kan het ''van''-adres niet ophalen: {0} email.server.err.no_from_address=Het bericht heeft geen 'van'-adres. -email.server.err.extracting_to_address=Kan het 'aan'-adres niet ophalen: {0} +email.server.err.extracting_to_address=Kan het ''aan''-adres niet ophalen: {0} email.server.err.no_to_address=Het bericht heeft geen 'aan'-adres. email.server.err.extracting_subject=Kan het berichtonderwerp niet ophalen: {0} -email.server.err.extracting_sent_date=Kan de 'verzonden op'-datum niet ophalen: {0} +email.server.err.extracting_sent_date=Kan de ''verzonden op''-datum niet ophalen: {0} email.server.err.parse_message=Kan het e-mailbericht niet parseren: {0} email.server.err.usupported_encoding=''{0}'' coderen wordt niet ondersteund email.server.err.failed_to_read_content_stream=Kan content van berichtgedeelte niet lezen: {0} email.server.err.incorrect_message_part=Onjuist berichtgedeelte: {0} + + +email.outbound.err.send.failed=Kan geen e-mail verzenden naar: {0} oorzaak {1} + +email.outbound.test.send.success=Testbericht verzenden naar: {0} + +email.outbound.err.test.no.to=Kan geen testbericht verzenden, veld 'Testbericht naar' is leeg +email.outbound.err.test.no.subject=Kan geen testbericht verzenden, veld 'Onderwerp' is leeg +email.outbound.err.test.no.text=Kan geen testbericht verzenden, veld 'Tekst' is leeg diff --git a/config/alfresco/messages/email-service_ru.properties b/config/alfresco/messages/email-service_ru.properties index 0554c8186e..24cf1b4968 100755 --- a/config/alfresco/messages/email-service_ru.properties +++ b/config/alfresco/messages/email-service_ru.properties @@ -1,7 +1,7 @@ email.server.msg.received_by_smtp=\u041F\u043E\u043B\u0443\u0447\u0435\u043D\u043E \u043F\u043E SMTP \u0438\u0437 ''{0}'' email.server.msg.default_subject=\u042D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u0430\u044F \u043F\u043E\u0447\u0442\u0430-{0} -email.server.err.duplicate_alias=Node with email alias ''{0}'' already exists. Duplicate isn't allowed. +email.server.err.duplicate_alias=Node with email alias ''{0}'' already exists. Duplicate isn''t allowed. email.server.err.sender_blocked=''{0}'' \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0432 \u0434\u043E\u0441\u0442\u0443\u043F\u0435. email.server.err.inbound_mail_disabled=\u0421\u0435\u0440\u0432\u0435\u0440 Alfresco \u043D\u0435 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043D \u043D\u0430 \u043F\u0440\u0438\u043D\u044F\u0442\u0438\u0435 \u0432\u0445\u043E\u0434\u044F\u0449\u0438\u0445 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u0439 \u044D\u043B\u0435\u043A\u0442\u0440\u043E\u043D\u043D\u044B\u0445 \u043F\u043E\u0447\u0442\u044B. email.server.err.access_denied=''{0}'' \u043E\u0442\u043A\u0430\u0437\u0430\u043B \u0432 \u0434\u043E\u0441\u0442\u0443\u043F\u0435 \u043A ''{1}''. @@ -23,3 +23,11 @@ email.server.err.parse_message=\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441 email.server.err.usupported_encoding=\u041A\u043E\u0434\u0438\u0440\u043E\u0432\u043A\u0430 ''{0}'' \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F email.server.err.failed_to_read_content_stream=\u041D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C \u043A\u043E\u043D\u0442\u0435\u043D\u0442 \u0447\u0430\u0441\u0442\u0438 \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F: {0} email.server.err.incorrect_message_part=\u041D\u0435\u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u0430\u044F \u0447\u0430\u0441\u0442\u044C \u0441\u043E\u043E\u0431\u0449\u0435\u043D\u0438\u044F: {0} + +email.outbound.err.send.failed=\u041d\u0435 \u0443\u0434\u0430\u043b\u043e\u0441\u044c \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043f\u043e \u044d\u043b. \u043f\u043e\u0447\u0442\u0435: {0} \u0438\u0437-\u0437\u0430 {1} + +email.outbound.test.send.success=\u0422\u0435\u0441\u0442\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435 \u043e\u0442\u043f\u0440\u0430\u0432\u043b\u0435\u043d\u043e: {0} + +email.outbound.err.test.no.to=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u0435 '\u043a\u043e\u043c\u0443' \u043f\u0443\u0441\u0442\u043e\u0435 +email.outbound.err.test.no.subject=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u0435 '\u0442\u0435\u043c\u0430' \u043f\u0443\u0441\u0442\u043e\u0435 +email.outbound.err.test.no.text=\u041d\u0435 \u0443\u0434\u0430\u0435\u0442\u0441\u044f \u043e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0442\u0435\u0441\u0442\u043e\u0432\u043e\u0435 \u0441\u043e\u043e\u0431\u0449\u0435\u043d\u0438\u0435, \u043f\u043e\u043b\u0435 '\u0442\u0435\u043a\u0441\u0442' \u043f\u0443\u0441\u0442\u043e\u0435 diff --git a/config/alfresco/messages/email-service_zh_CN.properties b/config/alfresco/messages/email-service_zh_CN.properties index ee7ee23eac..676968e254 100755 --- a/config/alfresco/messages/email-service_zh_CN.properties +++ b/config/alfresco/messages/email-service_zh_CN.properties @@ -23,3 +23,11 @@ email.server.err.parse_message=\u89E3\u6790\u7535\u5B50\u90AE\u4EF6\u6D88\u606F\ email.server.err.usupported_encoding=\u4E0D\u652F\u6301\u5BF9 ''{0}'' \u7F16\u7801 email.server.err.failed_to_read_content_stream=\u8BFB\u6D88\u606F\u90E8\u5206\u5185\u5BB9\u5931\u8D25\uFF1A{0} email.server.err.incorrect_message_part=\u6D88\u606F\u90E8\u5206\u4E0D\u6B63\u786E\uFF1A{0} + +email.outbound.err.send.failed=\u56e0\u4e3a {1}\uff0c\u53d1\u9001\u7535\u5b50\u90ae\u4ef6\u5230 {0} \u5931\u8d25 + +email.outbound.test.send.success=\u5df2\u5c06\u6d4b\u8bd5\u6d88\u606f\u53d1\u9001\u5230\uff1a{0} + +email.outbound.err.test.no.to=\u65e0\u6cd5\u53d1\u9001\u6d4b\u8bd5\u6d88\u606f\uff0c\u201ctestMessageTo\u201d\u4e3a\u7a7a +email.outbound.err.test.no.subject=\u65e0\u6cd5\u53d1\u9001\u6d4b\u8bd5\u6d88\u606f\uff0c\u201csubject\u201d\u4e3a\u7a7a +email.outbound.err.test.no.text=\u65e0\u6cd5\u53d1\u9001\u6d4b\u8bd5\u6d88\u606f\uff0c\u201ctext\u201d\u4e3a\u7a7a \ No newline at end of file diff --git a/config/alfresco/messages/imap-service_it.properties b/config/alfresco/messages/imap-service_it.properties index 52298ea100..ad3443c0f9 100755 --- a/config/alfresco/messages/imap-service_it.properties +++ b/config/alfresco/messages/imap-service_it.properties @@ -11,4 +11,4 @@ imap.server.error.permission_denied = "Impossibile creare la cartella - Permesso imap.server.error.folder_already_exist = "La cartella esiste gi\u00e0." imap.server.error.mailbox_name_is_mandatory = "Il nome della cassetta postale \u00e8 un parametro obbligatorio." imap.server.error.cannot_get_a_folder = "Impossibile recuperare una cartella con il nome ''{0}''." -imap.server.error.cannot_parse_default_email = "Impossibile analizzare l'indirizzo e-mail predefinito ''{0}''." +imap.server.error.cannot_parse_default_email = "Impossibile analizzare l''indirizzo e-mail predefinito ''{0}''." diff --git a/config/alfresco/messages/invitation-service.properties b/config/alfresco/messages/invitation-service.properties index ee03a61bb5..621f21d724 100644 --- a/config/alfresco/messages/invitation-service.properties +++ b/config/alfresco/messages/invitation-service.properties @@ -4,6 +4,7 @@ invitation.error.noworkflow="Invitation workflow not found, workflow name : {0}" invitation.error.not_found="Invitation not found, invitationId: {0}" +invitation.error.not_found_by_invitee="Invitation not found for site: {0}, invitee: {1}" invitation.error.invalid_inviteId_format="Invitation Id not valid format, valid formats are $ : {0}" invitation.invite.already_member="The user, {0} is already a member of {1} and cannot be invited again" invitation.cancel.not_site_manager="Current user, {0}, cannot cancel invitation: {1} because they are not a Site Manager for site: {2}" diff --git a/config/alfresco/messages/invitation-service_de.properties b/config/alfresco/messages/invitation-service_de.properties index 9dc7d9cf2f..0434bfa34d 100755 --- a/config/alfresco/messages/invitation-service_de.properties +++ b/config/alfresco/messages/invitation-service_de.properties @@ -2,15 +2,16 @@ #Invitation service messages # -invitation.error.noworkflow="Einladungsworkflow wurde nicht gefunden, Name des Workflows: {0}" -invitation.error.not_found="Einladung nicht gefunden, Einladungs-ID: {0}" -invitation.error.invalid_inviteId_format="Einladungs-ID hat ung\u00fcltiges Format, g\u00fcltige Formate sind $: {0}" -invitation.invite.already_member="Der Benutzer {0} ist bereits Mitglied von {1} und kann nicht noch einmal eingeladen werden" -invitation.cancel.not_site_manager="Aktueller Benutzer {0} kann Einladung: {1} nicht abbrechen, da sie nicht Site Manager f\u00fcr Site: {2} sind" -invitation.invite.not_site_manager="Aktueller Benutzer {0} ist kein Site Manager f\u00fcr Site: {1}" -invitation.invite.unable_generate_id="Kann keinen Benutzernamen f\u00fcr Eingeladenen generieren, der nicht schon jemand anderem geh\u00f6rt Vorname: {0} Nachname: {1} E-Mail: {2}" -invitation.invite.already_finished="Einladung {0} wurde bereits angenommen, abgebrochen oder abgelehnt" -invitation.invite.authentication_chain="Authentifizierungskette l\u00e4sst das Erstellen neuer Konten nicht zu" +invitation.error.noworkflow=''Einladungsworkflow wurde nicht gefunden, Name des Workflows: {0}'' +invitation.error.not_found=''Einladung nicht gefunden, Einladungs-ID: {0}'' +invitation.error.not_found_by_invitee=''Einladung von {1} zu Site {0} nicht gefunden'' +invitation.error.invalid_inviteId_format=''Einladungs-ID hat ung\u00fcltiges Format, g\u00fcltige Formate sind $: {0}'' +invitation.invite.already_member=''Der Benutzer {0} ist bereits Mitglied von {1} und kann nicht noch einmal eingeladen werden'' +invitation.cancel.not_site_manager=''Aktueller Benutzer {0} kann Einladung: {1} nicht abbrechen, da sie nicht Site Manager f\u00fcr Site: {2} sind'' +invitation.invite.not_site_manager=''Aktueller Benutzer {0} ist kein Site Manager f\u00fcr Site: {1}'' +invitation.invite.unable_generate_id=''Kann keinen Benutzernamen f\u00fcr Eingeladenen generieren, der nicht schon jemand anderem geh\u00f6rt Vorname: {0} Nachname: {1} E-Mail: {2}'' +invitation.invite.already_finished=''Einladung {0} wurde bereits angenommen, abgebrochen oder abgelehnt'' +invitation.invite.authentication_chain=''Authentifizierungskette l\u00e4sst das Erstellen neuer Konten nicht zu'' #InviteSender messages invitation.invitesender.email.subject=Alfresco {0}: Sie wurden eingeladen, der {1} Site beizutreten. diff --git a/config/alfresco/messages/invitation-service_es.properties b/config/alfresco/messages/invitation-service_es.properties index b8b953366f..f608465d4a 100755 --- a/config/alfresco/messages/invitation-service_es.properties +++ b/config/alfresco/messages/invitation-service_es.properties @@ -2,15 +2,16 @@ #Invitation service messages # -invitation.error.noworkflow="No se encuentra el flujo de trabajo de invitaci\u00f3n, nombre de flujo de trabajo: {0}" -invitation.error.not_found="Invitaci\u00f3n no encontrada, invitationId: {0}" -invitation.error.invalid_inviteId_format="Formato del Id de invitaci\u00f3n inv\u00e1lido, los formatos v\u00e1lidos son $: {0}" -invitation.invite.already_member="El usuario {0} ya es un miembro de {1} y no puede ser invitado de nuevo" -invitation.cancel.not_site_manager="El usuario actual, {0}, no puede cancelar la invitaci\u00f3n: {1} porque no es administrador del sitio en el siguiente sitio: {2}" -invitation.invite.not_site_manager="El usuario actual, {0}, no es administrador del sitio para el siguiente sitio: {1}" -invitation.invite.unable_generate_id="No se puede generar un nombre de usuario para el invitado, que contenga Nombre: {0} Apellidos: {1} correo electr\u00f3nico: {2} de otra persona" -invitation.invite.already_finished="La invitaci\u00f3n, {0} ya ha sido aceptada, cancelada o rechazada" -invitation.invite.authentication_chain="El m\u00e9todo de autenticaci\u00f3n establecido no permite crear nuevas cuentas" +invitation.error.noworkflow=''No se encuentra el flujo de trabajo de invitaci\u00f3n, nombre de flujo de trabajo: {0}'' +invitation.error.not_found=''Invitaci\u00f3n no encontrada, invitationId: {0}'' +invitation.error.not_found_by_invitee=''No se encontr\u00f3 la invitaci\u00f3n para el sitio: {0}, invitado: {1}'' +invitation.error.invalid_inviteId_format=''Formato del Id de invitaci\u00f3n inv\u00e1lido, los formatos v\u00e1lidos son $: {0}'' +invitation.invite.already_member=''El usuario {0} ya es un miembro de {1} y no puede ser invitado de nuevo'' +invitation.cancel.not_site_manager=''El usuario actual, {0}, no puede cancelar la invitaci\u00f3n: {1} porque no es administrador del sitio en el siguiente sitio: {2}'' +invitation.invite.not_site_manager=''El usuario actual, {0}, no es administrador del sitio para el siguiente sitio: {1}'' +invitation.invite.unable_generate_id=''No se puede generar un nombre de usuario para el invitado, que contenga Nombre: {0} Apellidos: {1} correo electr\u00f3nico: {2} de otra persona'' +invitation.invite.already_finished=''La invitaci\u00f3n, {0} ya ha sido aceptada, cancelada o rechazada'' +invitation.invite.authentication_chain=''El m\u00e9todo de autenticaci\u00f3n establecido no permite crear nuevas cuentas'' #InviteSender messages invitation.invitesender.email.subject={0} Alfresco: Ha sido invitado a unirse al sitio {1} diff --git a/config/alfresco/messages/invitation-service_fr.properties b/config/alfresco/messages/invitation-service_fr.properties index d98aba3308..5949265156 100755 --- a/config/alfresco/messages/invitation-service_fr.properties +++ b/config/alfresco/messages/invitation-service_fr.properties @@ -2,14 +2,15 @@ #Invitation service messages # -invitation.error.noworkflow="Workflow d''invitation introuvable, nom du workflow : {0}" -invitation.error.not_found="Invitation introuvable, identifiant de l''invitation : {0}" -invitation.error.invalid_inviteId_format="Format de l''identifiant de l''invitation non valide, les formats valides sont $ : {0}" -invitation.invite.already_member="L''utilisateur {0} est d\u00e9j\u00e0 membre de {1} et ne peut pas \u00eatre \u00e0 nouveau invit\u00e9" -invitation.cancel.not_site_manager="L''utilisateur actuel {0} ne peut pas annuler l''invitation suivante : {1}, car il n''est pas un gestionnaire du site suivant : {2}" -invitation.invite.not_site_manager="L''utilisateur actuel {0} n''est pas un gestionnaire du site suivant : {1}" -invitation.invite.unable_generate_id="Impossible de g\u00e9n\u00e9rer un nom d''utilisateur pour l''invit\u00e9, qui n''appartient pas d\u00e9j\u00e0 \u00e0 une autre personne Pr\u00e9nom : {0} Nom : {1} E-mail : {2}" -invitation.invite.already_finished="L''invitation {0} a d\u00e9j\u00e0 \u00e9t\u00e9 accept\u00e9e, annul\u00e9e ou rejet\u00e9e" +invitation.error.noworkflow=''Workflow d''invitation introuvable, nom du workflow : {0}'' +invitation.error.not_found=''Invitation introuvable, identifiant de l''invitation : {0}'' +invitation.error.not_found_by_invitee=''Invitation introuvable pour le site : {0}, invit\u00e9 : {1}'' +invitation.error.invalid_inviteId_format=''Format de l''identifiant de l''invitation non valide, les formats valides sont $ : {0}'' +invitation.invite.already_member=''L''utilisateur {0} est d\u00e9j\u00e0 membre de {1} et ne peut pas \u00eatre \u00e0 nouveau invit\u00e9'' +invitation.cancel.not_site_manager=''L''utilisateur actuel {0} ne peut pas annuler l''invitation suivante : {1}, car il n''est pas un gestionnaire du site suivant : {2}'' +invitation.invite.not_site_manager=''L''utilisateur actuel {0} n''est pas un gestionnaire du site suivant : {1}'' +invitation.invite.unable_generate_id=''Impossible de g\u00e9n\u00e9rer un nom d''utilisateur pour l''invit\u00e9, qui n''appartient pas d\u00e9j\u00e0 \u00e0 une autre personne Pr\u00e9nom : {0} Nom : {1} E-mail : {2}'' +invitation.invite.already_finished=''L''invitation {0} a d\u00e9j\u00e0 \u00e9t\u00e9 accept\u00e9e, annul\u00e9e ou rejet\u00e9e'' invitation.invite.authentication_chain="La cha\u00eene d'authentification ne permet pas la cr\u00e9ation de nouveaux comptes" #InviteSender messages diff --git a/config/alfresco/messages/invitation-service_it.properties b/config/alfresco/messages/invitation-service_it.properties index 07a04fe3cf..e7d38aceca 100755 --- a/config/alfresco/messages/invitation-service_it.properties +++ b/config/alfresco/messages/invitation-service_it.properties @@ -2,15 +2,16 @@ #Invitation service messages # -invitation.error.noworkflow="Impossibile trovare il workflow di invito con nome: {0}" -invitation.error.not_found="Impossibile trovare l''invito con ID: {0}" -invitation.error.invalid_inviteId_format="Il formato dell''ID di invito non \u00e8 valido, i formati validi sono $ : {0}" -invitation.invite.already_member="L''utente, {0} \u00e8 gi\u00e0 un membro di {1} e non pu\u00f2 essere invitato di nuovo" -invitation.cancel.not_site_manager="L''utente attuale, {0}, non pu\u00f2 annullare l''invito: {1} perch\u00e9 non \u00e8 un manager del sito: {2}" -invitation.invite.not_site_manager="L''utente attuale, {0}, non \u00e8 un manager del sito: {1}" -invitation.invite.unable_generate_id="Impossibile generare per l''invitato un nome utente che non appartiene gi\u00e0 a un altro utente Nome: {0} Cognome: {1} Email: {2}" -invitation.invite.already_finished="L''invito, {0} \u00e8 gi\u00e0 stato accettato, annullato o respinto" -invitation.invite.authentication_chain="La catena di autenticazione non consente la creazione di nuovi account" +invitation.error.noworkflow=''Impossibile trovare il workflow di invito con nome: {0}'' +invitation.error.not_found=''Impossibile trovare l''invito con ID: {0}'' +invitation.error.not_found_by_invitee=''Invito non trovato per il sito: {0}, invitato: {1}'' +invitation.error.invalid_inviteId_format=''Il formato dell''ID di invito non \u00e8 valido, i formati validi sono $ : {0}'' +invitation.invite.already_member=''L''utente, {0} \u00e8 gi\u00e0 un membro di {1} e non pu\u00f2 essere invitato di nuovo'' +invitation.cancel.not_site_manager=''L''utente attuale, {0}, non pu\u00f2 annullare l''invito: {1} perch\u00e9 non \u00e8 un manager del sito: {2}'' +invitation.invite.not_site_manager=''L''utente attuale, {0}, non \u00e8 un manager del sito: {1}'' +invitation.invite.unable_generate_id=''Impossibile generare per l''invitato un nome utente che non appartiene gi\u00e0 a un altro utente Nome: {0} Cognome: {1} Email: {2}'' +invitation.invite.already_finished=''L''invito, {0} \u00e8 gi\u00e0 stato accettato, annullato o respinto'' +invitation.invite.authentication_chain=''La catena di autenticazione non consente la creazione di nuovi account'' #InviteSender messages invitation.invitesender.email.subject=Alfresco {0}: \u00e8 stato ricevuto un invito a partecipare al sito {1} diff --git a/config/alfresco/messages/invitation-service_ja.properties b/config/alfresco/messages/invitation-service_ja.properties index 192484219d..0708585261 100755 --- a/config/alfresco/messages/invitation-service_ja.properties +++ b/config/alfresco/messages/invitation-service_ja.properties @@ -4,6 +4,7 @@ invitation.error.noworkflow="\u62db\u5f85\u306e\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u540d: {0}" invitation.error.not_found="\u62db\u5f85\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093\u3002\u62db\u5f85ID: {0}" +invitation.error.not_found_by_invitee="\u30b5\u30a4\u30c8: {0}\u3001\u88ab\u62db\u5f85\u8005: {1}\u306e\u62db\u5f85\u304c\u898b\u3064\u304b\u308a\u307e\u305b\u3093" invitation.error.invalid_inviteId_format="\u62db\u5f85ID\u304c\u7121\u52b9\u306a\u5f62\u5f0f\u3067\u3059\u3002\u6709\u52b9\u306a\u5f62\u5f0f\u306f$ : {0}\u3067\u3059" invitation.invite.already_member="\u30e6\u30fc\u30b6\u30fc{0}\u306f\u3059\u3067\u306b{1}\u306e\u30e1\u30f3\u30d0\u30fc\u3067\u3059\u3002\u3082\u3046\u4e00\u5ea6\u62db\u5f85\u3059\u308b\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093" invitation.cancel.not_site_manager="\u73fe\u5728\u306e\u30e6\u30fc\u30b6\u30fc{0}\u306f\u3001\u62db\u5f85{1}\u3092\u30ad\u30e3\u30f3\u30bb\u30eb\u3067\u304d\u307e\u305b\u3093\u3002\u30b5\u30a4\u30c8{2}\u306e\u30b5\u30a4\u30c8\u30de\u30cd\u30fc\u30b8\u30e3\u3067\u306f\u3042\u308a\u307e\u305b\u3093" diff --git a/config/alfresco/messages/invitation-service_nl.properties b/config/alfresco/messages/invitation-service_nl.properties index 41ee32a91d..2271f7d3c8 100755 --- a/config/alfresco/messages/invitation-service_nl.properties +++ b/config/alfresco/messages/invitation-service_nl.properties @@ -2,15 +2,16 @@ # Invitation service messages # -invitation.error.noworkflow="Kan uitnodigingswerkstroom niet vinden, naam van werkstroom: {0}" -invitation.error.not_found="Kan uitnodiging niet vinden, uitnodigings-id: {0}" -invitation.error.invalid_inviteId_format="Notatie van uitnodigings-id is ongeldig, geldige notatie: $ : {0}" -invitation.invite.already_member="Gebruiker {0} is al lid van {1} en kan niet opnieuw worden uitgenodigd" -invitation.cancel.not_site_manager="Huidige gebruiker, {0}, kan uitnodiging niet annuleren: {1} omdat deze geen sitemanager is voor site: {2}" -invitation.invite.not_site_manager="Huidige gebruiker, {0}, is geen sitemanager voor site: {1}" -invitation.invite.unable_generate_id="Kan geen gebruikersnaam genereren voor genodigde die nog niet bij iemand anders hoort firstName:{0} lastName:{1} email:{2}" -invitation.invite.already_finished="Uitnodiging {0} is al geaccepteerd, geannuleerd of afgewezen" -invitation.invite.authentication_chain="Verificatieketen staat het maken van nieuwe accounts niet toe" +invitation.error.noworkflow=''Kan uitnodigingswerkstroom niet vinden, naam van werkstroom: {0}'' +invitation.error.not_found=''Kan uitnodiging niet vinden, uitnodigings-id: {0}'' +invitation.error.not_found_by_invitee=''Uitnodiging niet gevonden voor site: {0}, genodigde: {1}'' +invitation.error.invalid_inviteId_format=''Notatie van uitnodigings-id is ongeldig, geldige notatie: $ : {0}'' +invitation.invite.already_member=''Gebruiker {0} is al lid van {1} en kan niet opnieuw worden uitgenodigd'' +invitation.cancel.not_site_manager=''Huidige gebruiker, {0}, kan uitnodiging niet annuleren: {1} omdat deze geen sitemanager is voor site: {2}'' +invitation.invite.not_site_manager=''Huidige gebruiker, {0}, is geen sitemanager voor site: {1}'' +invitation.invite.unable_generate_id=''Kan geen gebruikersnaam genereren voor genodigde die nog niet bij iemand anders hoort firstName:{0} lastName:{1} email:{2}'' +invitation.invite.already_finished=''Uitnodiging {0} is al geaccepteerd, geannuleerd of afgewezen'' +invitation.invite.authentication_chain=''Verificatieketen staat het maken van nieuwe accounts niet toe'' # InviteSender messages invitation.invitesender.email.subject=Alfresco {0}: u bent uitgenodigd voor de site {1} diff --git a/config/alfresco/messages/invitation-service_ru.properties b/config/alfresco/messages/invitation-service_ru.properties index cf07594bb7..ab4f10f5cd 100755 --- a/config/alfresco/messages/invitation-service_ru.properties +++ b/config/alfresco/messages/invitation-service_ru.properties @@ -4,6 +4,7 @@ invitation.error.noworkflow="\u0411\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441 \u043F\u0440\u0438\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u044F \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D, \u0438\u043C\u044F \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u0430: {0}" invitation.error.not_found="\u041F\u0440\u0438\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u0435 \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E, invitationId: {0}" +invitation.error.not_found_by_invitee="\u041d\u0435 \u043d\u0430\u0439\u0434\u0435\u043d\u043e \u043f\u0440\u0438\u0433\u043b\u0430\u0448\u0435\u043d\u0438\u0435 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u0430: {0}, \u043f\u0440\u0438\u0433\u043b\u0430\u0448\u0435\u043d\u043d\u044b\u0439: {1}" invitation.error.invalid_inviteId_format=\u0423 \u0438\u0434\u0435\u043D\u0442\u0438\u0444\u0438\u043A\u0430\u0442\u043E\u0440\u0430 \u043F\u0440\u0438\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u044F \u043D\u0435\u0432\u0435\u0440\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442, \u043F\u0440\u0430\u0432\u0438\u043B\u044C\u043D\u044B\u0439 \u0444\u043E\u0440\u043C\u0430\u0442: $ : {0} invitation.invite.already_member=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C {0} \u0443\u0436\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u0443\u0447\u0430\u0441\u0442\u043D\u0438\u043A\u043E\u043C {1} \u0438 \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u0431\u044B\u0442\u044C \u043F\u043E\u0432\u0442\u043E\u0440\u043D\u043E \u043F\u0440\u0438\u0433\u043B\u0430\u0448\u0435\u043D invitation.cancel.not_site_manager="\u0422\u0435\u043A\u0443\u0449\u0438\u0439 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C {0} \u043D\u0435 \u043C\u043E\u0436\u0435\u0442 \u043E\u0442\u043C\u0435\u043D\u0438\u0442\u044C \u043F\u0440\u0438\u0433\u043B\u0430\u0448\u0435\u043D\u0438\u0435 {1}, \u0442\u0430\u043A \u043A\u0430\u043A \u043E\u043D \u043D\u0435 \u044F\u0432\u043B\u044F\u0435\u0442\u0441\u044F \u043C\u0435\u043D\u0435\u0434\u0436\u0435\u0440\u043E\u043C \u0441\u0430\u0439\u0442\u0430 {2}" diff --git a/config/alfresco/messages/invitation-service_zh_CN.properties b/config/alfresco/messages/invitation-service_zh_CN.properties index 005a44a145..4971f0a5a4 100755 --- a/config/alfresco/messages/invitation-service_zh_CN.properties +++ b/config/alfresco/messages/invitation-service_zh_CN.properties @@ -4,6 +4,7 @@ invitation.error.noworkflow="\u672A\u627E\u5230\u9080\u8BF7\u5DE5\u4F5C\u6D41\u7A0B\uFF1B\u5DE5\u4F5C\u6D41\u7A0B\u540D\u79F0\uFF1A{0}" invitation.error.not_found="\u672A\u627E\u5230\u9080\u8BF7\uFF1B\u9080\u8BF7 ID\uFF1A{0}" +invitation.error.not_found_by_invitee="\u672a\u627e\u5230\u7ad9\u70b9 {0}\uff0c\u88ab\u9080\u8bf7\u8005 {1} \u7684\u9080\u8bf7" invitation.error.invalid_inviteId_format="\u9080\u8BF7 ID \u7684\u683C\u5F0F\u65E0\u6548\uFF0C\u6709\u6548\u683C\u5F0F\u4E3A $\uFF1A{0}" invitation.invite.already_member="\u7528\u6237 {0} \u5DF2\u7ECF\u662F {1} \u7684\u6210\u5458\uFF0C\u56E0\u6B64\u4E0D\u80FD\u88AB\u518D\u6B21\u9080\u8BF7" invitation.cancel.not_site_manager="\u5F53\u524D\u7528\u6237 {0} \u4E0D\u80FD\u53D6\u6D88\u9080\u8BF7 {1}\uFF0C\u539F\u56E0\u662F\u8BE5\u7528\u6237\u4E0D\u662F\u7AD9\u70B9 {2} \u7684\u7AD9\u70B9\u7BA1\u7406\u5458" diff --git a/config/alfresco/messages/lock-service_it.properties b/config/alfresco/messages/lock-service_it.properties index 623d7c296c..e5bd2036a2 100755 --- a/config/alfresco/messages/lock-service_it.properties +++ b/config/alfresco/messages/lock-service_it.properties @@ -5,3 +5,4 @@ lock_service.node_locked=Impossibile bloccare il nodo (id: {0}) perch\u00e9 \u00 lock_service.no_op=Impossibile eseguire l''operazione perch\u00e9 il nodo (id: {0}) \u00e8 bloccato. lock_service.no_op2=Impossibile eseguire l''operazione {0} perch\u00e9 il nodo (id: {1}) \u00e8 bloccato. lock_service.unlock_checkedout=Impossibile sbloccare il nodo (ID: {0}) poich\u00e9 \u00e8 stato sottoposto a Check Out. + diff --git a/config/alfresco/messages/lock-service_ja.properties b/config/alfresco/messages/lock-service_ja.properties index 80a40acc97..c36a28f2c0 100755 --- a/config/alfresco/messages/lock-service_ja.properties +++ b/config/alfresco/messages/lock-service_ja.properties @@ -4,4 +4,5 @@ lock_service.insufficent_privileges=\u3053\u306e\u30ce\u30fc\u30c9(id: {0})\u306 lock_service.node_locked=\u3053\u306e\u30ce\u30fc\u30c9(id: {0}) \u306f\u4ed6\u306e\u30e6\u30fc\u30b6\u30fc\u306b\u3088\u3063\u3066\u3059\u3067\u306b\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30ed\u30c3\u30af\u3067\u304d\u307e\u305b\u3093\u3002 lock_service.no_op=\u30ce\u30fc\u30c9(id: {0})\u304c\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3\u3092\u5b9f\u884c\u3059\u308b\u3053\u3068\u304c\u3067\u304d\u307e\u305b\u3093\u3002 lock_service.no_op2=\u30ce\u30fc\u30c9(id: {1})\u304c\u30ed\u30c3\u30af\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30aa\u30da\u30ec\u30fc\u30b7\u30e7\u30f3 {0} \u3092\u5b9f\u884c\u3067\u304d\u307e\u305b\u3093\u3002 -lock_service.unlock_checkedout=\u3053\u306e\u30ce\u30fc\u30c9(id: {0})\u306f\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30ed\u30c3\u30af\u3092\u89e3\u9664\u3067\u304d\u307e\u305b\u3093\u3002 \ No newline at end of file +lock_service.unlock_checkedout=\u3053\u306e\u30ce\u30fc\u30c9(id: {0})\u306f\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30ed\u30c3\u30af\u3092\u89e3\u9664\u3067\u304d\u307e\u305b\u3093\u3002 +lock_service.unlock_checkedout=\u3053\u306e\u30ce\u30fc\u30c9(id: {0})\u306f\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3055\u308c\u3066\u3044\u308b\u305f\u3081\u3001\u30ed\u30c3\u30af\u3092\u89e3\u9664\u3067\u304d\u307e\u305b\u3093\u3002 diff --git a/config/alfresco/messages/notification-service_fr.properties b/config/alfresco/messages/notification-service_fr.properties index 9020a81279..7b31569d22 100755 --- a/config/alfresco/messages/notification-service_fr.properties +++ b/config/alfresco/messages/notification-service_fr.properties @@ -1,7 +1,7 @@ # Notification Service externalised display strings np-does-not-exist=Impossible d''envoyer la notification, car le fournisseur de notification {0} n''existe pas. -default-sender-used=Aucune adresse \u00e9lectronique d\u00e9finie pour l'exp\u00e9diteur de l'e-mail de notification. L'exp\u00e9diteur par d\u00e9faut sera utilis\u00e9. (utilisateur={0}) -no-recipients=Impossible d'envoyer l'e-mail de notification, car aucun destinataire n'a \u00e9t\u00e9 sp\u00e9cifi\u00e9. (document={0}) -no-body-or-template=Impossible d'envoyer la notification, car aucun corps ou mod\u00e8le de corps n'a \u00e9t\u00e9 sp\u00e9cifi\u00e9. (n\u0153ud={0}) +default-sender-used=Aucune adresse \u00e9lectronique d\u00e9finie pour l''exp\u00e9diteur de l''e-mail de notification. L''exp\u00e9diteur par d\u00e9faut sera utilis\u00e9. (utilisateur={0}) +no-recipients=Impossible d''envoyer l''e-mail de notification, car aucun destinataire n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9. (document={0}) +no-body-or-template=Impossible d''envoyer la notification, car aucun corps ou mod\u00e8le de corps n''a \u00e9t\u00e9 sp\u00e9cifi\u00e9. (n\u0153ud={0}) assigned-task=Une t\u00e2che vous a \u00e9t\u00e9 assign\u00e9e new-pooled-task=Nouvelle t\u00e2che partag\u00e9e diff --git a/config/alfresco/messages/notification-service_it.properties b/config/alfresco/messages/notification-service_it.properties index 9638c43937..f86f008934 100755 --- a/config/alfresco/messages/notification-service_it.properties +++ b/config/alfresco/messages/notification-service_it.properties @@ -1,6 +1,6 @@ # Notification Service externalised display strings np-does-not-exist=Impossibile inviare la notifica. Il provider di notifiche {0} non esiste. -default-sender-used=Per il mittente della notifica e-mail non \u00e8 impostato alcun indirizzo e-mail. Verr\u00e0 usato l'indirizzo del mittente predefinito. (utente={0}) +default-sender-used=Per il mittente della notifica e-mail non \u00e8 impostato alcun indirizzo e-mail. Verr\u00e0 usato l''indirizzo del mittente predefinito. (utente={0}) no-recipients=Impossibile inviare la notifica tramite e-mail. Nessun destinatario fornito. (documento={0}) no-body-or-template=Impossibile inviare la notifica. Nessun corpo o modello di corpo specificato. (nodo={0}) assigned-task=All'utente \u00e8 stato assegnato un compito diff --git a/config/alfresco/messages/rule-config_es.properties b/config/alfresco/messages/rule-config_es.properties index 82fdd2135a..22282bcd30 100755 --- a/config/alfresco/messages/rule-config_es.properties +++ b/config/alfresco/messages/rule-config_es.properties @@ -6,4 +6,4 @@ update.display-label=Se actualizan elementos inboundAndUpdate.display-label=Los elementos est\u00e1n creados, entran en esta carpeta o est\u00e1n actualizados # Rule error messages -cannot.create.rule.checkout.outbound=No se ha podido ejecutar el bloqueo para el tipo de regla saliente. +cannot.create.rule.checkout.outbound=No se ha podido ejecutar el bloqueo para el tipo de regla saliente. \ No newline at end of file diff --git a/config/alfresco/messages/rule-config_fr.properties b/config/alfresco/messages/rule-config_fr.properties index b6ce9cecfb..2179863ba7 100755 --- a/config/alfresco/messages/rule-config_fr.properties +++ b/config/alfresco/messages/rule-config_fr.properties @@ -6,4 +6,4 @@ update.display-label=Des \u00e9l\u00e9ments sont mis \u00e0 jour inboundAndUpdate.display-label=Des \u00e9l\u00e9ments sont cr\u00e9\u00e9s, entrent dans ce dossier ou sont mis \u00e0 jour # Rule error messages -cannot.create.rule.checkout.outbound=L'action R\u00e9server ne peut pas \u00eatre ex\u00e9cut\u00e9e pour le type de r\u00e8gle sortant\u00a0! \ No newline at end of file +cannot.create.rule.checkout.outbound=L'action R\u00e9server ne peut pas \u00eatre ex\u00e9cut\u00e9e pour le type de r\u00e8gle sortant\u00a0! diff --git a/config/alfresco/messages/rule-config_it.properties b/config/alfresco/messages/rule-config_it.properties index a4981f6475..e3771b8f19 100755 --- a/config/alfresco/messages/rule-config_it.properties +++ b/config/alfresco/messages/rule-config_it.properties @@ -6,4 +6,4 @@ update.display-label=Gli elementi vengono aggiornati inboundAndUpdate.display-label=Gli elementi vengono creati, inseriti in questa cartella o aggiornati # Rule error messages -cannot.create.rule.checkout.outbound=Impossibile effettuare l'azione di Check Out per il tipo di regola in uscita. \ No newline at end of file +cannot.create.rule.checkout.outbound=Impossibile effettuare l'azione di Check Out per il tipo di regola in uscita. diff --git a/config/alfresco/messages/rule-config_ja.properties b/config/alfresco/messages/rule-config_ja.properties index 1bdc7e3796..94306f3b82 100755 --- a/config/alfresco/messages/rule-config_ja.properties +++ b/config/alfresco/messages/rule-config_ja.properties @@ -6,4 +6,4 @@ update.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u66f4\u65b0\u3055\u308c\u305 inboundAndUpdate.display-label=\u30a2\u30a4\u30c6\u30e0\u304c\u4f5c\u6210\u3055\u308c\u3001\u3053\u306e\u30d5\u30a9\u30eb\u30c0\u306b\u5165\u308b\u304b\u66f4\u65b0\u3055\u308c\u307e\u3059\u3002 # Rule error messages -cannot.create.rule.checkout.outbound=\u30eb\u30fc\u30eb\u30bf\u30a4\u30d7\u306e\u9001\u4fe1\u3067\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3092\u884c\u3046\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 \ No newline at end of file +cannot.create.rule.checkout.outbound=\u30eb\u30fc\u30eb\u30bf\u30a4\u30d7\u306e\u9001\u4fe1\u3067\u30c1\u30a7\u30c3\u30af\u30a2\u30a6\u30c8\u3092\u884c\u3046\u3053\u3068\u306f\u3067\u304d\u307e\u305b\u3093\u3002 diff --git a/config/alfresco/messages/subscription-service_de.properties b/config/alfresco/messages/subscription-service_de.properties index 67208293b3..444bd12191 100755 --- a/config/alfresco/messages/subscription-service_de.properties +++ b/config/alfresco/messages/subscription-service_de.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject={0} folgt Ihnen jetzt +subscription.notification.email.subject={0} folgt Ihnen jetzt auf Alfresco subscription_service.err.disabled=Das Abonnement ist deaktiviert subscription_service.err.write-denied=Keine Berechtigungen zum Aktualisieren diff --git a/config/alfresco/messages/subscription-service_es.properties b/config/alfresco/messages/subscription-service_es.properties index ce7033be6a..9c9ab2ec92 100755 --- a/config/alfresco/messages/subscription-service_es.properties +++ b/config/alfresco/messages/subscription-service_es.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject={0} est\u00e1 sigui\u00e9ndole +subscription.notification.email.subject={0} est\u00e1 sigui\u00e9ndole en Alfresco subscription_service.err.disabled=La suscripci\u00f3n est\u00e1 desactivada subscription_service.err.write-denied=No hay permisos para actualizar diff --git a/config/alfresco/messages/subscription-service_fr.properties b/config/alfresco/messages/subscription-service_fr.properties index eb60f82e76..9ade360c20 100755 --- a/config/alfresco/messages/subscription-service_fr.properties +++ b/config/alfresco/messages/subscription-service_fr.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject={0} vous suit +subscription.notification.email.subject={0} vous suit \u00e0 pr\u00e9sent sur Alfresco subscription_service.err.disabled=L'abonnement est d\u00e9sactiv\u00e9 subscription_service.err.write-denied=Aucune permission de mise \u00e0 jour diff --git a/config/alfresco/messages/subscription-service_it.properties b/config/alfresco/messages/subscription-service_it.properties index 52284c56b9..9f3cc13c58 100755 --- a/config/alfresco/messages/subscription-service_it.properties +++ b/config/alfresco/messages/subscription-service_it.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject={0} ti sta seguendo ora +subscription.notification.email.subject={0} ti sta seguendo su Alfresco subscription_service.err.disabled=La sottoscrizione \u00e8 disabilitata subscription_service.err.write-denied=Nessun permesso da aggiornare diff --git a/config/alfresco/messages/subscription-service_ja.properties b/config/alfresco/messages/subscription-service_ja.properties index c3610054cf..8268a5f629 100755 --- a/config/alfresco/messages/subscription-service_ja.properties +++ b/config/alfresco/messages/subscription-service_ja.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject=\u73fe\u5728\u3001{0}\u304c\u3042\u306a\u305f\u3092\u30d5\u30a9\u30ed\u30fc\u3057\u3066\u3044\u307e\u3059 +subscription.notification.email.subject={0}\u304cAlfresco\u3067\u30d5\u30a9\u30ed\u30fc\u3057\u3066\u3044\u307e\u3059 subscription_service.err.disabled=\u30b5\u30d6\u30b9\u30af\u30ea\u30d7\u30b7\u30e7\u30f3\u304c\u7121\u52b9\u3067\u3059 subscription_service.err.write-denied=\u66f4\u65b0\u3059\u308b\u6a29\u9650\u304c\u3042\u308a\u307e\u305b\u3093 diff --git a/config/alfresco/messages/subscription-service_nl.properties b/config/alfresco/messages/subscription-service_nl.properties index 72593cb55a..f87d8e8fea 100755 --- a/config/alfresco/messages/subscription-service_nl.properties +++ b/config/alfresco/messages/subscription-service_nl.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject={0} volgt u nu +subscription.notification.email.subject={0} volgt u nu op Alfresco subscription_service.err.disabled=Het abonnement is uitgeschakeld subscription_service.err.write-denied=Geen rechten om bij te werken diff --git a/config/alfresco/messages/subscription-service_ru.properties b/config/alfresco/messages/subscription-service_ru.properties index e1df517636..f78bdf8adb 100755 --- a/config/alfresco/messages/subscription-service_ru.properties +++ b/config/alfresco/messages/subscription-service_ru.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject=\u041F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u044C {0} \u043F\u043E\u0434\u043F\u0438\u0441\u0430\u043B\u0441\u044F \u043D\u0430 \u0432\u0430\u0448\u0438 \u043D\u043E\u0432\u043E\u0441\u0442\u0438 +subscription.notification.email.subject={0} \u043f\u043e\u0434\u043f\u0438\u0441\u0430\u043b\u0441\u044f \u043d\u0430 \u0432\u0430\u0448\u0438 \u043d\u043e\u0432\u043e\u0441\u0442\u0438 \u043d\u0430 Alfresco subscription_service.err.disabled=\u041F\u043E\u0434\u043F\u0438\u0441\u043A\u0430 \u043E\u0442\u043A\u043B\u044E\u0447\u0435\u043D\u0430 subscription_service.err.write-denied=\u041D\u0435\u0442 \u043F\u0440\u0430\u0432 \u043D\u0430 \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 diff --git a/config/alfresco/messages/subscription-service_zh_CN.properties b/config/alfresco/messages/subscription-service_zh_CN.properties index ff1f599627..6422e7efb5 100755 --- a/config/alfresco/messages/subscription-service_zh_CN.properties +++ b/config/alfresco/messages/subscription-service_zh_CN.properties @@ -1,6 +1,6 @@ # Subscription service messages -subscription.notification.email.subject={0} \u73B0\u5728\u6B63\u5728\u5173\u6CE8\u60A8 +subscription.notification.email.subject={0} \u73b0\u5728\u6b63\u5728 Alfresco \u4e0a\u5173\u6ce8\u60a8 subscription_service.err.disabled=\u6B64\u8BA2\u9605\u5DF2\u7981\u7528 subscription_service.err.write-denied=\u65E0\u6743\u66F4\u65B0 diff --git a/config/alfresco/model-specific-services-context.xml b/config/alfresco/model-specific-services-context.xml index bb99ebd352..695826bc53 100644 --- a/config/alfresco/model-specific-services-context.xml +++ b/config/alfresco/model-specific-services-context.xml @@ -14,6 +14,7 @@ + diff --git a/config/alfresco/model/systemModel.xml b/config/alfresco/model/systemModel.xml index 1b81f3c11a..1008142e6a 100644 --- a/config/alfresco/model/systemModel.xml +++ b/config/alfresco/model/systemModel.xml @@ -195,7 +195,23 @@ d:boolean false - + + + + d:boolean + false + false + + + + d:boolean + false + false + + + d:boolean + false + diff --git a/config/alfresco/module-context.xml b/config/alfresco/module-context.xml index 21af2cea75..de13910d32 100644 --- a/config/alfresco/module-context.xml +++ b/config/alfresco/module-context.xml @@ -24,7 +24,7 @@
- + diff --git a/config/alfresco/mt/mt-base-context.xml b/config/alfresco/mt/mt-base-context.xml index 2983ce4ccc..a65a2f0336 100644 --- a/config/alfresco/mt/mt-base-context.xml +++ b/config/alfresco/mt/mt-base-context.xml @@ -33,7 +33,9 @@ - + + + - - - + + + + + @@ -980,6 +982,7 @@ org.alfresco.service.cmr.site.SiteService.getSiteRoot=ACL_ALLOW,AFTER_ACL_NODE.sys:base.ReadProperties org.alfresco.service.cmr.site.SiteService.hasContainer=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.hasCreateSitePermissions=ACL_ALLOW + org.alfresco.service.cmr.site.SiteService.hasSite=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.isMember=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listMembers=ACL_ALLOW org.alfresco.service.cmr.site.SiteService.listMembersInfo=ACL_ALLOW diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 20232aca93..e139e85e8e 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -1,79 +1,79 @@ -# Repository configuration - -repository.name=Main Repository - -# Directory configuration - -dir.root=./alf_data - -dir.contentstore=${dir.root}/contentstore -dir.contentstore.deleted=${dir.root}/contentstore.deleted - -# The location of cached content -dir.cachedcontent=${dir.root}/cachedcontent - -dir.auditcontentstore=${dir.root}/audit.contentstore - -# The value for the maximum permitted size in bytes of all content. -# No value (or a negative long) will be taken to mean that no limit should be applied. -# See content-services-context.xml -system.content.maximumFileSizeLimit= - -# The location for lucene index files -dir.indexes=${dir.root}/lucene-indexes - -# The location for index backups -dir.indexes.backup=${dir.root}/backup-lucene-indexes - -# The location for lucene index locks -dir.indexes.lock=${dir.indexes}/locks - -#Directory to find external license -dir.license.external=. -# Spring resource location of external license files -location.license.external=file://${dir.license.external}/*.lic -# Spring resource location of embedded license files -location.license.embedded=/WEB-INF/alfresco/license/*.lic -# Spring resource location of license files on shared classpath -location.license.shared=classpath*:/alfresco/extension/license/*.lic - -# WebDAV initialization properties -system.webdav.servlet.enabled=true -system.webdav.url.path.prefix= -system.webdav.storeName=${protocols.storeName} -system.webdav.rootPath=${protocols.rootPath} -system.webdav.activities.enabled=true -# File name patterns that trigger rename shuffle detection -# pattern is used by move - tested against full path after it has been lower cased. -system.webdav.renameShufflePattern=(.*/\\..*)|(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)|(.*backup.*\\.do[ct]{1}[x]?[m]?$) -system.webdav.activities.enabled=false - - -# Is the JBPM Deploy Process Servlet enabled? -# Default is false. Should not be enabled in production environments as the -# servlet allows unauthenticated deployment of new workflows. -system.workflow.deployservlet.enabled=false - -# Sets the location for the JBPM Configuration File -system.workflow.jbpm.config.location=classpath:org/alfresco/repo/workflow/jbpm/jbpm.cfg.xml - -# Determines if JBPM workflow definitions are shown. -# Default is false. This controls the visibility of JBPM -# workflow definitions from the getDefinitions and -# getAllDefinitions WorkflowService API but still allows -# any in-flight JBPM workflows to be completed. -system.workflow.engine.jbpm.definitions.visible=false - -#Determines if Activiti definitions are visible -system.workflow.engine.activiti.definitions.visible=true - -# Determines if the JBPM engine is enabled -system.workflow.engine.jbpm.enabled=true - -# Determines if the Activiti engine is enabled -system.workflow.engine.activiti.enabled=true +# Repository configuration + +repository.name=Main Repository + +# Directory configuration + +dir.root=./alf_data + +dir.contentstore=${dir.root}/contentstore +dir.contentstore.deleted=${dir.root}/contentstore.deleted + +# The location of cached content +dir.cachedcontent=${dir.root}/cachedcontent + +dir.auditcontentstore=${dir.root}/audit.contentstore + +# The value for the maximum permitted size in bytes of all content. +# No value (or a negative long) will be taken to mean that no limit should be applied. +# See content-services-context.xml +system.content.maximumFileSizeLimit= + +# The location for lucene index files +dir.indexes=${dir.root}/lucene-indexes + +# The location for index backups +dir.indexes.backup=${dir.root}/backup-lucene-indexes + +# The location for lucene index locks +dir.indexes.lock=${dir.indexes}/locks + +#Directory to find external license +dir.license.external=. +# Spring resource location of external license files +location.license.external=file://${dir.license.external}/*.lic +# Spring resource location of embedded license files +location.license.embedded=/WEB-INF/alfresco/license/*.lic +# Spring resource location of license files on shared classpath +location.license.shared=classpath*:/alfresco/extension/license/*.lic + +# WebDAV initialization properties +system.webdav.servlet.enabled=true +system.webdav.url.path.prefix= +system.webdav.storeName=${protocols.storeName} +system.webdav.rootPath=${protocols.rootPath} +system.webdav.activities.enabled=true +# File name patterns that trigger rename shuffle detection +# pattern is used by move - tested against full path after it has been lower cased. +system.webdav.renameShufflePattern=(.*/\\..*)|(.*[a-f0-9]{8}+$)|(.*\\.tmp$)|(.*\\.wbk$)|(.*\\.bak$)|(.*\\~$)|(.*backup.*\\.do[ct]{1}[x]?[m]?$) +system.webdav.activities.enabled=false + + +# Is the JBPM Deploy Process Servlet enabled? +# Default is false. Should not be enabled in production environments as the +# servlet allows unauthenticated deployment of new workflows. +system.workflow.deployservlet.enabled=false + +# Sets the location for the JBPM Configuration File +system.workflow.jbpm.config.location=classpath:org/alfresco/repo/workflow/jbpm/jbpm.cfg.xml + +# Determines if JBPM workflow definitions are shown. +# Default is false. This controls the visibility of JBPM +# workflow definitions from the getDefinitions and +# getAllDefinitions WorkflowService API but still allows +# any in-flight JBPM workflows to be completed. +system.workflow.engine.jbpm.definitions.visible=false + +#Determines if Activiti definitions are visible +system.workflow.engine.activiti.definitions.visible=true + +# Determines if the JBPM engine is enabled +system.workflow.engine.jbpm.enabled=true + +# Determines if the Activiti engine is enabled +system.workflow.engine.activiti.enabled=true system.workflow.engine.activiti.idblocksize=100 - + # Determines if the workflows that are deployed to the activiti engine should # be deployed in the tenant-context of the thread IF the tenant-service is enabled # If set to false, all workflows deployed will be shared among tenants. Recommended @@ -81,282 +81,282 @@ system.workflow.engine.activiti.idblocksize=100 # worklfows when a MT-environment is set up. system.workflow.deployWorkflowsInTenant=true -# The maximum number of groups to check for pooled tasks. For performance -# reasons, this is limited to 100 by default. -system.workflow.maxAuthoritiesForPooledTasks=100 - -# The maximum number of pooled tasks to return in a query. It may be necessary -# to limit this depending on UI limitations. -system.workflow.maxPooledTasks=-1 - -index.subsystem.name=lucene - -# ######################################### # -# Index Recovery and Tracking Configuration # -# ######################################### # -# -# Recovery types are: -# NONE: Ignore -# VALIDATE: Checks that the first and last transaction for each store is represented in the indexes -# AUTO: Validates and auto-recovers if validation fails -# FULL: Full index rebuild, processing all transactions in order. The server is temporarily suspended. -index.recovery.mode=VALIDATE -# FULL recovery continues when encountering errors -index.recovery.stopOnError=false -index.recovery.maximumPoolSize=5 -# Set the frequency with which the index tracking is triggered. -# For more information on index tracking in a cluster: -# http://wiki.alfresco.com/wiki/High_Availability_Configuration_V1.4_to_V2.1#Version_1.4.5.2C_2.1.1_and_later -# By default, this is effectively never, but can be modified as required. -# Examples: -# Never: * * * * * ? 2099 -# Once every five seconds: 0/5 * * * * ? -# Once every two seconds : 0/2 * * * * ? -# See http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html -index.tracking.cronExpression=0/5 * * * * ? -index.tracking.adm.cronExpression=${index.tracking.cronExpression} -index.tracking.avm.cronExpression=${index.tracking.cronExpression} -# Other properties. -index.tracking.maxTxnDurationMinutes=10 -index.tracking.reindexLagMs=1000 -index.tracking.maxRecordSetSize=1000 -index.tracking.maxTransactionsPerLuceneCommit=100 -index.tracking.disableInTransactionIndexing=false -# Index tracking information of a certain age is cleaned out by a scheduled job. -# Any clustered system that has been offline for longer than this period will need to be seeded -# with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. -# Use -1 to disable purging. This can be switched on at any stage. -index.tracking.minRecordPurgeAgeDays=30 -# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size -# of the chunk (in ms). Default is a couple of hours. -index.tracking.purgeSize=7200000 - -# Reindexing of missing content is by default 'never' carried out. -# The cron expression below can be changed to control the timing of this reindexing. -# Users of Enterprise Alfresco can configure this cron expression via JMX without a server restart. -# Note that if alfresco.cluster.name is not set, then reindexing will not occur. -index.reindexMissingContent.cronExpression=* * * * * ? 2099 - -# Change the failure behaviour of the configuration checker -system.bootstrap.config_check.strict=true - - -# Time to live for concurrent user credentials -alfresco.conccurrentusers.timeToLive=300 - -# -# How long should shutdown wait to complete normally before -# taking stronger action and calling System.exit() -# in ms, 10,000 is 10 seconds -# -shutdown.backstop.timeout=10000 -shutdown.backstop.enabled=false - -# Server Single User Mode -# note: -# only allow named user (note: if blank or not set then will allow all users) -# assuming maxusers is not set to 0 -#server.singleuseronly.name=admin - -# Server Max Users - limit number of users with non-expired tickets -# note: -# -1 allows any number of users, assuming not in single-user mode -# 0 prevents further logins, including the ability to enter single-user mode -server.maxusers=-1 - -# The Cron expression controlling the frequency with which the OpenOffice connection is tested -openOffice.test.cronExpression=0 * * * * ? - -# -# Disable all shared caches (mutable and immutable) -# These properties are used for diagnostic purposes -system.cache.disableMutableSharedCaches=false -system.cache.disableImmutableSharedCaches=false - -# The maximum capacity of the parent assocs cache (the number of nodes whose parents can be cached) -system.cache.parentAssocs.maxSize=130000 - -# The average number of parents expected per cache entry. This parameter is multiplied by the above -# value to compute a limit on the total number of cached parents, which will be proportional to the -# cache's memory usage. The cache will be pruned when this limit is exceeded to avoid excessive -# memory usage. -system.cache.parentAssocs.limitFactor=8 - -# -# Properties to limit resources spent on individual searches -# -# The maximum time spent pruning results -system.acl.maxPermissionCheckTimeMillis=10000 -# The maximum number of search results to perform permission checks against -system.acl.maxPermissionChecks=1000 - -# The maximum number of filefolder list results -system.filefolderservice.defaultListMaxResults=5000 - -# Properties to control read permission evaluation for acegi -system.readpermissions.optimise=true -system.readpermissions.bulkfetchsize=1000 - -# -# Manually control how the system handles maximum string lengths. -# Any zero or negative value is ignored. -# Only change this after consulting support or reading the appropriate Javadocs for -# org.alfresco.repo.domain.schema.SchemaBootstrap for V2.1.2 -system.maximumStringLength=-1 - -# -# Limit hibernate session size by trying to amalgamate events for the L2 session invalidation -# - hibernate works as is up to this size -# - after the limit is hit events that can be grouped invalidate the L2 cache by type and not instance -# events may not group if there are post action listener registered (this is not the case with the default distribution) -system.hibernateMaxExecutions=20000 - -# -# Determine if modification timestamp propagation from child to parent nodes is respected or not. -# Even if 'true', the functionality is only supported for child associations that declare the -# 'propagateTimestamps' element in the dictionary definition. -system.enableTimestampPropagation=true - -# -# Decide if content should be removed from the system immediately after being orphaned. -# Do not change this unless you have examined the impact it has on your backup procedures. -system.content.eagerOrphanCleanup=false -# The number of days to keep orphaned content in the content stores. -# This has no effect on the 'deleted' content stores, which are not automatically emptied. -system.content.orphanProtectDays=14 -# The action to take when a store or stores fails to delete orphaned content -# IGNORE: Just log a warning. The binary remains and the record is expunged -# KEEP_URL: Log a warning and create a URL entry with orphan time 0. It won't be processed or removed. -system.content.deletionFailureAction=IGNORE -# The CRON expression to trigger the deletion of resources associated with orphaned content. -system.content.orphanCleanup.cronExpression=0 0 4 * * ? -# The CRON expression to trigger content URL conversion. This process is not intesive and can -# be triggered on a live system. Similarly, it can be triggered using JMX on a dedicated machine. -system.content.contentUrlConverter.cronExpression=* * * * * ? 2099 -system.content.contentUrlConverter.threadCount=2 -system.content.contentUrlConverter.batchSize=500 -system.content.contentUrlConverter.runAsScheduledJob=false - -# #################### # -# Lucene configuration # -# #################### # -# -# Millisecond threshold for text transformations -# Slower transformers will force the text extraction to be asynchronous -# -lucene.maxAtomicTransformationTime=100 -# -# The maximum number of clauses that are allowed in a lucene query -# -lucene.query.maxClauses=10000 -# -# The size of the queue of nodes waiting for index -# Events are generated as nodes are changed, this is the maximum size of the queue used to coalesce event -# When this size is reached the lists of nodes will be indexed -# -# http://issues.alfresco.com/browse/AR-1280: Setting this high is the workaround as of 1.4.3. -# -lucene.indexer.batchSize=1000000 -fts.indexer.batchSize=1000 -# -# Index cache sizes -# -lucene.indexer.cacheEnabled=true -lucene.indexer.maxDocIdCacheSize=100000 -lucene.indexer.maxDocumentCacheSize=100 -lucene.indexer.maxIsCategoryCacheSize=-1 -lucene.indexer.maxLinkAspectCacheSize=10000 -lucene.indexer.maxParentCacheSize=100000 -lucene.indexer.maxPathCacheSize=100000 -lucene.indexer.maxTypeCacheSize=10000 -# -# Properties for merge (not this does not affect the final index segment which will be optimised) -# Max merge docs only applies to the merge process not the resulting index which will be optimised. -# -lucene.indexer.mergerMaxMergeDocs=1000000 -lucene.indexer.mergerMergeFactor=5 -lucene.indexer.mergerMaxBufferedDocs=-1 -lucene.indexer.mergerRamBufferSizeMb=16 -# -# Properties for delta indexes (not this does not affect the final index segment which will be optimised) -# Max merge docs only applies to the index building process not the resulting index which will be optimised. -# -lucene.indexer.writerMaxMergeDocs=1000000 -lucene.indexer.writerMergeFactor=5 -lucene.indexer.writerMaxBufferedDocs=-1 -lucene.indexer.writerRamBufferSizeMb=16 -# -# Target number of indexes and deltas in the overall index and what index size to merge in memory -# -lucene.indexer.mergerTargetIndexCount=8 -lucene.indexer.mergerTargetOverlayCount=5 -lucene.indexer.mergerTargetOverlaysBlockingFactor=2 -lucene.indexer.maxDocsForInMemoryMerge=60000 -lucene.indexer.maxRamInMbForInMemoryMerge=16 -lucene.indexer.maxDocsForInMemoryIndex=60000 -lucene.indexer.maxRamInMbForInMemoryIndex=16 -# -# Other lucene properties -# -lucene.indexer.termIndexInterval=128 -lucene.indexer.useNioMemoryMapping=true -# over-ride to false for pre 3.0 behaviour -lucene.indexer.postSortDateTime=true -lucene.indexer.defaultMLIndexAnalysisMode=EXACT_LANGUAGE_AND_ALL -lucene.indexer.defaultMLSearchAnalysisMode=EXACT_LANGUAGE_AND_ALL -# -# The number of terms from a document that will be indexed -# -lucene.indexer.maxFieldLength=10000 - -# Should we use a 'fair' locking policy, giving queue-like access behaviour to -# the indexes and avoiding starvation of waiting writers? Set to false on old -# JVMs where this appears to cause deadlock -lucene.indexer.fairLocking=true - -# -# Index locks (mostly deprecated and will be tidied up with the next lucene upgrade) -# -lucene.write.lock.timeout=10000 -lucene.commit.lock.timeout=100000 -lucene.lock.poll.interval=100 - -lucene.indexer.useInMemorySort=true -lucene.indexer.maxRawResultSetSizeForInMemorySort=1000 -lucene.indexer.contentIndexingEnabled=true - -index.backup.cronExpression=0 0 3 * * ? - -lucene.defaultAnalyserResourceBundleName=alfresco/model/dataTypeAnalyzers - - -# When transforming archive files (.zip etc) into text representations (such as -# for full text indexing), should the files within the archive be processed too? -# If enabled, transformation takes longer, but searches of the files find more. -transformer.Archive.includeContents=false - -# Database configuration -db.schema.stopAfterSchemaBootstrap=false -db.schema.update=true -db.schema.update.lockRetryCount=24 -db.schema.update.lockRetryWaitSeconds=5 -db.driver=org.gjt.mm.mysql.Driver -db.name=alfresco -db.url=jdbc:mysql:///${db.name} -db.username=alfresco -db.password=alfresco -db.pool.initial=10 -db.pool.max=40 -db.txn.isolation=-1 -db.pool.statements.enable=true -db.pool.statements.max=40 -db.pool.min=0 -db.pool.idle=-1 -db.pool.wait.max=-1 +# The maximum number of groups to check for pooled tasks. For performance +# reasons, this is limited to 100 by default. +system.workflow.maxAuthoritiesForPooledTasks=100 -db.pool.validate.query= -db.pool.evict.interval=-1 -db.pool.evict.idle.min=1800000 +# The maximum number of pooled tasks to return in a query. It may be necessary +# to limit this depending on UI limitations. +system.workflow.maxPooledTasks=-1 + +index.subsystem.name=lucene + +# ######################################### # +# Index Recovery and Tracking Configuration # +# ######################################### # +# +# Recovery types are: +# NONE: Ignore +# VALIDATE: Checks that the first and last transaction for each store is represented in the indexes +# AUTO: Validates and auto-recovers if validation fails +# FULL: Full index rebuild, processing all transactions in order. The server is temporarily suspended. +index.recovery.mode=VALIDATE +# FULL recovery continues when encountering errors +index.recovery.stopOnError=false +index.recovery.maximumPoolSize=5 +# Set the frequency with which the index tracking is triggered. +# For more information on index tracking in a cluster: +# http://wiki.alfresco.com/wiki/High_Availability_Configuration_V1.4_to_V2.1#Version_1.4.5.2C_2.1.1_and_later +# By default, this is effectively never, but can be modified as required. +# Examples: +# Never: * * * * * ? 2099 +# Once every five seconds: 0/5 * * * * ? +# Once every two seconds : 0/2 * * * * ? +# See http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html +index.tracking.cronExpression=0/5 * * * * ? +index.tracking.adm.cronExpression=${index.tracking.cronExpression} +index.tracking.avm.cronExpression=${index.tracking.cronExpression} +# Other properties. +index.tracking.maxTxnDurationMinutes=10 +index.tracking.reindexLagMs=1000 +index.tracking.maxRecordSetSize=1000 +index.tracking.maxTransactionsPerLuceneCommit=100 +index.tracking.disableInTransactionIndexing=false +# Index tracking information of a certain age is cleaned out by a scheduled job. +# Any clustered system that has been offline for longer than this period will need to be seeded +# with a more recent backup of the Lucene indexes or the indexes will have to be fully rebuilt. +# Use -1 to disable purging. This can be switched on at any stage. +index.tracking.minRecordPurgeAgeDays=30 +# Unused transactions will be purged in chunks determined by commit time boundaries. 'index.tracking.purgeSize' specifies the size +# of the chunk (in ms). Default is a couple of hours. +index.tracking.purgeSize=7200000 + +# Reindexing of missing content is by default 'never' carried out. +# The cron expression below can be changed to control the timing of this reindexing. +# Users of Enterprise Alfresco can configure this cron expression via JMX without a server restart. +# Note that if alfresco.cluster.name is not set, then reindexing will not occur. +index.reindexMissingContent.cronExpression=* * * * * ? 2099 + +# Change the failure behaviour of the configuration checker +system.bootstrap.config_check.strict=true + + +# Time to live for concurrent user credentials +alfresco.conccurrentusers.timeToLive=300 + +# +# How long should shutdown wait to complete normally before +# taking stronger action and calling System.exit() +# in ms, 10,000 is 10 seconds +# +shutdown.backstop.timeout=10000 +shutdown.backstop.enabled=false + +# Server Single User Mode +# note: +# only allow named user (note: if blank or not set then will allow all users) +# assuming maxusers is not set to 0 +#server.singleuseronly.name=admin + +# Server Max Users - limit number of users with non-expired tickets +# note: +# -1 allows any number of users, assuming not in single-user mode +# 0 prevents further logins, including the ability to enter single-user mode +server.maxusers=-1 + +# The Cron expression controlling the frequency with which the OpenOffice connection is tested +openOffice.test.cronExpression=0 * * * * ? + +# +# Disable all shared caches (mutable and immutable) +# These properties are used for diagnostic purposes +system.cache.disableMutableSharedCaches=false +system.cache.disableImmutableSharedCaches=false + +# The maximum capacity of the parent assocs cache (the number of nodes whose parents can be cached) +system.cache.parentAssocs.maxSize=130000 + +# The average number of parents expected per cache entry. This parameter is multiplied by the above +# value to compute a limit on the total number of cached parents, which will be proportional to the +# cache's memory usage. The cache will be pruned when this limit is exceeded to avoid excessive +# memory usage. +system.cache.parentAssocs.limitFactor=8 + +# +# Properties to limit resources spent on individual searches +# +# The maximum time spent pruning results +system.acl.maxPermissionCheckTimeMillis=10000 +# The maximum number of search results to perform permission checks against +system.acl.maxPermissionChecks=1000 + +# The maximum number of filefolder list results +system.filefolderservice.defaultListMaxResults=5000 + +# Properties to control read permission evaluation for acegi +system.readpermissions.optimise=true +system.readpermissions.bulkfetchsize=1000 + +# +# Manually control how the system handles maximum string lengths. +# Any zero or negative value is ignored. +# Only change this after consulting support or reading the appropriate Javadocs for +# org.alfresco.repo.domain.schema.SchemaBootstrap for V2.1.2 +system.maximumStringLength=-1 + +# +# Limit hibernate session size by trying to amalgamate events for the L2 session invalidation +# - hibernate works as is up to this size +# - after the limit is hit events that can be grouped invalidate the L2 cache by type and not instance +# events may not group if there are post action listener registered (this is not the case with the default distribution) +system.hibernateMaxExecutions=20000 + +# +# Determine if modification timestamp propagation from child to parent nodes is respected or not. +# Even if 'true', the functionality is only supported for child associations that declare the +# 'propagateTimestamps' element in the dictionary definition. +system.enableTimestampPropagation=true + +# +# Decide if content should be removed from the system immediately after being orphaned. +# Do not change this unless you have examined the impact it has on your backup procedures. +system.content.eagerOrphanCleanup=false +# The number of days to keep orphaned content in the content stores. +# This has no effect on the 'deleted' content stores, which are not automatically emptied. +system.content.orphanProtectDays=14 +# The action to take when a store or stores fails to delete orphaned content +# IGNORE: Just log a warning. The binary remains and the record is expunged +# KEEP_URL: Log a warning and create a URL entry with orphan time 0. It won't be processed or removed. +system.content.deletionFailureAction=IGNORE +# The CRON expression to trigger the deletion of resources associated with orphaned content. +system.content.orphanCleanup.cronExpression=0 0 4 * * ? +# The CRON expression to trigger content URL conversion. This process is not intesive and can +# be triggered on a live system. Similarly, it can be triggered using JMX on a dedicated machine. +system.content.contentUrlConverter.cronExpression=* * * * * ? 2099 +system.content.contentUrlConverter.threadCount=2 +system.content.contentUrlConverter.batchSize=500 +system.content.contentUrlConverter.runAsScheduledJob=false + +# #################### # +# Lucene configuration # +# #################### # +# +# Millisecond threshold for text transformations +# Slower transformers will force the text extraction to be asynchronous +# +lucene.maxAtomicTransformationTime=100 +# +# The maximum number of clauses that are allowed in a lucene query +# +lucene.query.maxClauses=10000 +# +# The size of the queue of nodes waiting for index +# Events are generated as nodes are changed, this is the maximum size of the queue used to coalesce event +# When this size is reached the lists of nodes will be indexed +# +# http://issues.alfresco.com/browse/AR-1280: Setting this high is the workaround as of 1.4.3. +# +lucene.indexer.batchSize=1000000 +fts.indexer.batchSize=1000 +# +# Index cache sizes +# +lucene.indexer.cacheEnabled=true +lucene.indexer.maxDocIdCacheSize=100000 +lucene.indexer.maxDocumentCacheSize=100 +lucene.indexer.maxIsCategoryCacheSize=-1 +lucene.indexer.maxLinkAspectCacheSize=10000 +lucene.indexer.maxParentCacheSize=100000 +lucene.indexer.maxPathCacheSize=100000 +lucene.indexer.maxTypeCacheSize=10000 +# +# Properties for merge (not this does not affect the final index segment which will be optimised) +# Max merge docs only applies to the merge process not the resulting index which will be optimised. +# +lucene.indexer.mergerMaxMergeDocs=1000000 +lucene.indexer.mergerMergeFactor=5 +lucene.indexer.mergerMaxBufferedDocs=-1 +lucene.indexer.mergerRamBufferSizeMb=16 +# +# Properties for delta indexes (not this does not affect the final index segment which will be optimised) +# Max merge docs only applies to the index building process not the resulting index which will be optimised. +# +lucene.indexer.writerMaxMergeDocs=1000000 +lucene.indexer.writerMergeFactor=5 +lucene.indexer.writerMaxBufferedDocs=-1 +lucene.indexer.writerRamBufferSizeMb=16 +# +# Target number of indexes and deltas in the overall index and what index size to merge in memory +# +lucene.indexer.mergerTargetIndexCount=8 +lucene.indexer.mergerTargetOverlayCount=5 +lucene.indexer.mergerTargetOverlaysBlockingFactor=2 +lucene.indexer.maxDocsForInMemoryMerge=60000 +lucene.indexer.maxRamInMbForInMemoryMerge=16 +lucene.indexer.maxDocsForInMemoryIndex=60000 +lucene.indexer.maxRamInMbForInMemoryIndex=16 +# +# Other lucene properties +# +lucene.indexer.termIndexInterval=128 +lucene.indexer.useNioMemoryMapping=true +# over-ride to false for pre 3.0 behaviour +lucene.indexer.postSortDateTime=true +lucene.indexer.defaultMLIndexAnalysisMode=EXACT_LANGUAGE_AND_ALL +lucene.indexer.defaultMLSearchAnalysisMode=EXACT_LANGUAGE_AND_ALL +# +# The number of terms from a document that will be indexed +# +lucene.indexer.maxFieldLength=10000 + +# Should we use a 'fair' locking policy, giving queue-like access behaviour to +# the indexes and avoiding starvation of waiting writers? Set to false on old +# JVMs where this appears to cause deadlock +lucene.indexer.fairLocking=true + +# +# Index locks (mostly deprecated and will be tidied up with the next lucene upgrade) +# +lucene.write.lock.timeout=10000 +lucene.commit.lock.timeout=100000 +lucene.lock.poll.interval=100 + +lucene.indexer.useInMemorySort=true +lucene.indexer.maxRawResultSetSizeForInMemorySort=1000 +lucene.indexer.contentIndexingEnabled=true + +index.backup.cronExpression=0 0 3 * * ? + +lucene.defaultAnalyserResourceBundleName=alfresco/model/dataTypeAnalyzers + + +# When transforming archive files (.zip etc) into text representations (such as +# for full text indexing), should the files within the archive be processed too? +# If enabled, transformation takes longer, but searches of the files find more. +transformer.Archive.includeContents=false + +# Database configuration +db.schema.stopAfterSchemaBootstrap=false +db.schema.update=true +db.schema.update.lockRetryCount=24 +db.schema.update.lockRetryWaitSeconds=5 +db.driver=org.gjt.mm.mysql.Driver +db.name=alfresco +db.url=jdbc:mysql:///${db.name} +db.username=alfresco +db.password=alfresco +db.pool.initial=10 +db.pool.max=40 +db.txn.isolation=-1 +db.pool.statements.enable=true +db.pool.statements.max=40 +db.pool.min=0 +db.pool.idle=-1 +db.pool.wait.max=-1 + +db.pool.validate.query= +db.pool.evict.interval=-1 +db.pool.evict.idle.min=1800000 # # note: for 'db.pool.evict.num.tests' see http://commons.apache.org/dbcp/configuration.html (numTestsPerEvictionRun) # and also following extract from "org.apache.commons.pool.impl.GenericKeyedObjectPool" (1.5.5) @@ -369,677 +369,675 @@ db.pool.evict.idle.min=1800000 db.pool.evict.num.tests=-1 db.pool.evict.validate=false -db.pool.validate.borrow=true -db.pool.validate.return=false +db.pool.validate.borrow=true +db.pool.validate.return=false -db.pool.abandoned.detect=false -db.pool.abandoned.time=300 -# -# db.pool.abandoned.log=true (logAbandoned) adds overhead (http://commons.apache.org/dbcp/configuration.html) -# and also requires db.pool.abandoned.detect=true (removeAbandoned) -# -db.pool.abandoned.log=false - - -# Audit configuration -audit.enabled=true -audit.tagging.enabled=true -audit.alfresco-access.enabled=false -audit.alfresco-access.sub-actions.enabled=false -audit.cmischangelog.enabled=false -audit.dod5015.enabled=false -# Setting this flag to true will force startup failure when invalid audit configurations are detected -audit.config.strict=false -# Audit map filter for AccessAuditor - restricts recorded events to user driven events -audit.filter.alfresco-access.default.enabled=true -audit.filter.alfresco-access.transaction.user=~System;~null;.* -audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site -audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.* - - -# System Configuration -system.store=system://system -system.descriptor.childname=sys:descriptor -system.descriptor.current.childname=sys:descriptor-current - -# User config -alfresco_user_store.store=user://alfrescoUserStore -alfresco_user_store.system_container.childname=sys:system -alfresco_user_store.user_container.childname=sys:people - -# note: default admin username - should not be changed after installation -alfresco_user_store.adminusername=admin - -# Initial password - editing this will not have any effect once the repository is installed -alfresco_user_store.adminpassword=209c6174da490caeb422f3fa5a7ae634 +db.pool.abandoned.detect=false +db.pool.abandoned.time=300 +# +# db.pool.abandoned.log=true (logAbandoned) adds overhead (http://commons.apache.org/dbcp/configuration.html) +# and also requires db.pool.abandoned.detect=true (removeAbandoned) +# +db.pool.abandoned.log=false + + +# Audit configuration +audit.enabled=true +audit.tagging.enabled=true +audit.alfresco-access.enabled=false +audit.alfresco-access.sub-actions.enabled=false +audit.cmischangelog.enabled=false +audit.dod5015.enabled=false +# Setting this flag to true will force startup failure when invalid audit configurations are detected +audit.config.strict=false +# Audit map filter for AccessAuditor - restricts recorded events to user driven events +audit.filter.alfresco-access.default.enabled=true +audit.filter.alfresco-access.transaction.user=~System;~null;.* +audit.filter.alfresco-access.transaction.type=cm:folder;cm:content;st:site +audit.filter.alfresco-access.transaction.path=~/sys:archivedItem;~/ver:;.* + + +# System Configuration +system.store=system://system +system.descriptor.childname=sys:descriptor +system.descriptor.current.childname=sys:descriptor-current + +# User config +alfresco_user_store.store=user://alfrescoUserStore +alfresco_user_store.system_container.childname=sys:system +alfresco_user_store.user_container.childname=sys:people + +# note: default admin username - should not be changed after installation +alfresco_user_store.adminusername=admin + +# Initial password - editing this will not have any effect once the repository is installed +alfresco_user_store.adminpassword=209c6174da490caeb422f3fa5a7ae634 alfresco_user_store.adminsalt=ad3b938f-c1ad-4f2b-828b-6f3afd30ffdd alfresco_user_store.adminpassword2=f378d5d7b947d5c26f478e21819e7ec3a6668c8149b050d086c64447bc40173b - -# note: default guest username - should not be changed after installation -alfresco_user_store.guestusername=guest - -# Used to move home folders to a new location -home_folder_provider_synchronizer.enabled=false -home_folder_provider_synchronizer.override_provider= -home_folder_provider_synchronizer.keep_empty_parents=false - -# Spaces Archive Configuration -spaces.archive.store=archive://SpacesStore - -# Spaces Configuration -spaces.store=workspace://SpacesStore -spaces.company_home.childname=app:company_home -spaces.guest_home.childname=app:guest_home -spaces.dictionary.childname=app:dictionary -spaces.templates.childname=app:space_templates -spaces.imap_attachments.childname=cm:Imap Attachments -spaces.imap_home.childname=cm:Imap Home -spaces.imapConfig.childname=app:imap_configs -spaces.imap_templates.childname=app:imap_templates -spaces.scheduled_actions.childname=cm:Scheduled Actions -spaces.emailActions.childname=app:email_actions -spaces.searchAction.childname=cm:search -spaces.templates.content.childname=app:content_templates -spaces.templates.email.childname=app:email_templates -spaces.templates.email.invite1.childname=app:invite_email_templates -spaces.templates.email.notify.childname=app:notify_email_templates -spaces.templates.email.following.childname=app:following -spaces.templates.rss.childname=app:rss_templates -spaces.savedsearches.childname=app:saved_searches -spaces.scripts.childname=app:scripts -spaces.wcm.childname=app:wcm -spaces.wcm_content_forms.childname=app:wcm_forms -spaces.content_forms.childname=app:forms -spaces.user_homes.childname=app:user_homes -spaces.user_homes.regex.key=userName -spaces.user_homes.regex.pattern= -spaces.user_homes.regex.group_order= -spaces.sites.childname=st:sites -spaces.templates.email.invite.childname=cm:invite -spaces.templates.email.activities.childname=cm:activities -spaces.rendition.rendering_actions.childname=app:rendering_actions -spaces.replication.replication_actions.childname=app:replication_actions -spaces.wcm_deployed.childname=cm:wcm_deployed -spaces.transfers.childname=app:transfers -spaces.transfer_groups.childname=app:transfer_groups -spaces.transfer_temp.childname=app:temp -spaces.inbound_transfer_records.childname=app:inbound_transfer_records -spaces.webscripts.childname=cm:webscripts -spaces.extension_webscripts.childname=cm:extensionwebscripts -spaces.models.childname=app:models -spaces.workflow.definitions.childname=app:workflow_defs -spaces.publishing.root.childname=app:publishing_root -spaces.templates.email.workflowemailnotification.childname=cm:workflownotification -spaces.nodetemplates.childname=app:node_templates - -# ADM VersionStore Configuration -version.store.enableAutoVersioning=true -version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore -version.store.version2Store=workspace://version2Store - -version.store.migrateVersionStore.threadCount=3 -version.store.migrateVersionStore.batchSize=1 - -version.store.migrateCleanupJob.threadCount=3 -version.store.migrateCleanupJob.batchSize=1 - - -# WARNING: For non-production testing only !!! Do not change (to avoid version store issues, including possible mismatch). Should be false since lightWeightVersionStore is deprecated. -version.store.onlyUseDeprecatedV1=false - -# The CRON expression to trigger migration of the version store from V1 (2.x) to V2 (3.x) -# By default, this is effectively 'never' but can be modified as required. -# Examples: -# Never: * * * * * ? 2099 -# Once every thirty minutes: 0 0/30 * * * ? -# See http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html -version.store.migrateVersionStore.cronExpression=* * * * * ? 2099 -# Limit number of version histories to migrate per job cycle, where -1 = unlimited. Note: if limit > 0 then need to schedule job to run regularly in order to complete the migration. -version.store.migrateVersionStore.limitPerJobCycle=-1 -version.store.migrateVersionStore.runAsScheduledJob=false - -# Optional Comparator class name to sort versions. -# Set to: org.alfresco.repo.version.common.VersionLabelComparator -# if upgrading from a version that used unordered sequences in a cluster. -version.store.versionComparatorClass= - -# Folders for storing people -system.system_container.childname=sys:system -system.people_container.childname=sys:people -system.authorities_container.childname=sys:authorities -system.zones_container.childname=sys:zones - -# Folders for storing workflow related info -system.workflow_container.childname=sys:workflow - -# Folder for storing shared remote credentials -system.remote_credentials_container.childname=sys:remote_credentials - -# Folder for storing syncset definitions -system.syncset_definition_container.childname=sys:syncset_definitions - -# Folder for storing download archives -system.downloads_container.childname=sys:downloads - + +# note: default guest username - should not be changed after installation +alfresco_user_store.guestusername=guest + +# Used to move home folders to a new location +home_folder_provider_synchronizer.enabled=false +home_folder_provider_synchronizer.override_provider= +home_folder_provider_synchronizer.keep_empty_parents=false + +# Spaces Archive Configuration +spaces.archive.store=archive://SpacesStore + +# Spaces Configuration +spaces.store=workspace://SpacesStore +spaces.company_home.childname=app:company_home +spaces.guest_home.childname=app:guest_home +spaces.dictionary.childname=app:dictionary +spaces.templates.childname=app:space_templates +spaces.imap_attachments.childname=cm:Imap Attachments +spaces.imap_home.childname=cm:Imap Home +spaces.imapConfig.childname=app:imap_configs +spaces.imap_templates.childname=app:imap_templates +spaces.scheduled_actions.childname=cm:Scheduled Actions +spaces.emailActions.childname=app:email_actions +spaces.searchAction.childname=cm:search +spaces.templates.content.childname=app:content_templates +spaces.templates.email.childname=app:email_templates +spaces.templates.email.invite1.childname=app:invite_email_templates +spaces.templates.email.notify.childname=app:notify_email_templates +spaces.templates.email.following.childname=app:following +spaces.templates.rss.childname=app:rss_templates +spaces.savedsearches.childname=app:saved_searches +spaces.scripts.childname=app:scripts +spaces.wcm.childname=app:wcm +spaces.wcm_content_forms.childname=app:wcm_forms +spaces.content_forms.childname=app:forms +spaces.user_homes.childname=app:user_homes +spaces.user_homes.regex.key=userName +spaces.user_homes.regex.pattern= +spaces.user_homes.regex.group_order= +spaces.sites.childname=st:sites +spaces.templates.email.invite.childname=cm:invite +spaces.templates.email.activities.childname=cm:activities +spaces.rendition.rendering_actions.childname=app:rendering_actions +spaces.replication.replication_actions.childname=app:replication_actions +spaces.wcm_deployed.childname=cm:wcm_deployed +spaces.transfers.childname=app:transfers +spaces.transfer_groups.childname=app:transfer_groups +spaces.transfer_temp.childname=app:temp +spaces.inbound_transfer_records.childname=app:inbound_transfer_records +spaces.webscripts.childname=cm:webscripts +spaces.extension_webscripts.childname=cm:extensionwebscripts +spaces.models.childname=app:models +spaces.workflow.definitions.childname=app:workflow_defs +spaces.publishing.root.childname=app:publishing_root +spaces.templates.email.workflowemailnotification.childname=cm:workflownotification +spaces.nodetemplates.childname=app:node_templates + +# ADM VersionStore Configuration +version.store.enableAutoVersioning=true +version.store.deprecated.lightWeightVersionStore=workspace://lightWeightVersionStore +version.store.version2Store=workspace://version2Store + +version.store.migrateVersionStore.threadCount=3 +version.store.migrateVersionStore.batchSize=1 + +version.store.migrateCleanupJob.threadCount=3 +version.store.migrateCleanupJob.batchSize=1 + + +# WARNING: For non-production testing only !!! Do not change (to avoid version store issues, including possible mismatch). Should be false since lightWeightVersionStore is deprecated. +version.store.onlyUseDeprecatedV1=false + +# The CRON expression to trigger migration of the version store from V1 (2.x) to V2 (3.x) +# By default, this is effectively 'never' but can be modified as required. +# Examples: +# Never: * * * * * ? 2099 +# Once every thirty minutes: 0 0/30 * * * ? +# See http://www.quartz-scheduler.org/docs/tutorials/crontrigger.html +version.store.migrateVersionStore.cronExpression=* * * * * ? 2099 +# Limit number of version histories to migrate per job cycle, where -1 = unlimited. Note: if limit > 0 then need to schedule job to run regularly in order to complete the migration. +version.store.migrateVersionStore.limitPerJobCycle=-1 +version.store.migrateVersionStore.runAsScheduledJob=false + +# Optional Comparator class name to sort versions. +# Set to: org.alfresco.repo.version.common.VersionLabelComparator +# if upgrading from a version that used unordered sequences in a cluster. +version.store.versionComparatorClass= + +# Folders for storing people +system.system_container.childname=sys:system +system.people_container.childname=sys:people +system.authorities_container.childname=sys:authorities +system.zones_container.childname=sys:zones + +# Folders for storing workflow related info +system.workflow_container.childname=sys:workflow + +# Folder for storing shared remote credentials +system.remote_credentials_container.childname=sys:remote_credentials + +# Folder for storing syncset definitions +system.syncset_definition_container.childname=sys:syncset_definitions + +# Folder for storing download archives +system.downloads_container.childname=sys:downloads + # Folder for storing IdP's certificate definitions system.certificate_container.childname=sys:samlcertificate -# Are user names case sensitive? -user.name.caseSensitive=false -domain.name.caseSensitive=false -domain.separator= - -# AVM Specific properties. -avm.remote.idlestream.timeout=30000 - -#Format caption extracted from the XML Schema. -xforms.formatCaption=true - -# ECM content usages/quotas -system.usages.enabled=false -system.usages.clearBatchSize=50 -system.usages.updateBatchSize=50 - -# Repository endpoint - used by Activity Service -repo.remote.endpoint=/service - -# Some authentication mechanisms may need to create people -# in the repository on demand. This enables that feature. -# If disabled an error will be generated for missing -# people. If enabled then a person will be created and -# persisted. -create.missing.people=${server.transaction.allow-writes} - +# Are user names case sensitive? +user.name.caseSensitive=false +domain.name.caseSensitive=false +domain.separator= + +# AVM Specific properties. +avm.remote.idlestream.timeout=30000 + +#Format caption extracted from the XML Schema. +xforms.formatCaption=true + +# ECM content usages/quotas +system.usages.enabled=false +system.usages.clearBatchSize=50 +system.usages.updateBatchSize=50 + +# Repository endpoint - used by Activity Service +repo.remote.endpoint=/service + +# Some authentication mechanisms may need to create people +# in the repository on demand. This enables that feature. +# If disabled an error will be generated for missing +# people. If enabled then a person will be created and +# persisted. +create.missing.people=${server.transaction.allow-writes} + # Create home folders (unless disabled, see next property) as people are created (true) or create them lazily (false) -home.folder.creation.eager=true +home.folder.creation.eager=true # Disable home folder creation - if true then home folders are not created (neither eagerly nor lazily) home.folder.creation.disabled=false - -# Should we consider zero byte content to be the same as no content when firing -# content update policies? Prevents 'premature' firing of inbound content rules -# for some clients such as Mac OS X Finder -policy.content.update.ignoreEmpty=true - -# The well known RMI registry port and external host name published in the stubs -# is defined in the alfresco-shared.properties file -# -# alfresco.rmi.services.port=50500 - -# Default value of alfresco.rmi.services.host is 0.0.0.0 which means 'listen on all adapters'. -# This allows connections to JMX both remotely and locally. -# -alfresco.rmi.services.host=0.0.0.0 - -# If the RMI address is in-use, how many retries should be done before aborting -# Default value of alfresco.rmi.services.retries is 0 which means 'Don't retry if the address is in-use' -alfresco.rmi.services.retries=4 - -# RMI service ports for the individual services. -# These eight services are available remotely. -# -# Assign individual ports for each service for best performance -# or run several services on the same port, you can even run everything on 50500 if -# running through a firewall. -# -# Specify 0 to use a random unused port. -# -avm.rmi.service.port=50501 -avmsync.rmi.service.port=50502 -authentication.rmi.service.port=50504 -repo.rmi.service.port=50505 -action.rmi.service.port=50506 -deployment.rmi.service.port=50507 -monitor.rmi.service.port=50508 - -# -# enable or disable individual RMI services -# -avm.rmi.service.enabled=true -avmsync.rmi.service.enabled=true -authentication.rmi.service.enabled=true -repo.rmi.service.enabled=true -action.rmi.service.enabled=true -deployment.rmi.service.enabled=true -monitor.rmi.service.enabled=true - - -# Should the Mbean server bind to an existing server. Set to true for most application servers. -# false for WebSphere clusters. -mbean.server.locateExistingServerIfPossible=true - -# External executable locations -ooo.exe=soffice -ooo.user=${dir.root}/oouser -img.root=./ImageMagick -img.dyn=${img.root}/lib -img.exe=${img.root}/bin/convert -swf.exe=./bin/pdf2swf -swf.languagedir=. - -# Thumbnail Service -system.thumbnail.generate=true - -# Default thumbnail limits -# When creating thumbnails, only use the first pageLimit pages + +# Should we consider zero byte content to be the same as no content when firing +# content update policies? Prevents 'premature' firing of inbound content rules +# for some clients such as Mac OS X Finder +policy.content.update.ignoreEmpty=true + +# The well known RMI registry port and external host name published in the stubs +# is defined in the alfresco-shared.properties file +# +# alfresco.rmi.services.port=50500 + +# Default value of alfresco.rmi.services.host is 0.0.0.0 which means 'listen on all adapters'. +# This allows connections to JMX both remotely and locally. +# +alfresco.rmi.services.host=0.0.0.0 + +# If the RMI address is in-use, how many retries should be done before aborting +# Default value of alfresco.rmi.services.retries is 0 which means 'Don't retry if the address is in-use' +alfresco.rmi.services.retries=4 + +# RMI service ports for the individual services. +# These eight services are available remotely. +# +# Assign individual ports for each service for best performance +# or run several services on the same port, you can even run everything on 50500 if +# running through a firewall. +# +# Specify 0 to use a random unused port. +# +avm.rmi.service.port=50501 +avmsync.rmi.service.port=50502 +authentication.rmi.service.port=50504 +repo.rmi.service.port=50505 +action.rmi.service.port=50506 +deployment.rmi.service.port=50507 +monitor.rmi.service.port=50508 + +# +# enable or disable individual RMI services +# +avm.rmi.service.enabled=true +avmsync.rmi.service.enabled=true +authentication.rmi.service.enabled=true +repo.rmi.service.enabled=true +action.rmi.service.enabled=true +deployment.rmi.service.enabled=true +monitor.rmi.service.enabled=true + + +# Should the Mbean server bind to an existing server. Set to true for most application servers. +# false for WebSphere clusters. +mbean.server.locateExistingServerIfPossible=true + +# External executable locations +ooo.exe=soffice +ooo.user=${dir.root}/oouser +img.root=./ImageMagick +img.dyn=${img.root}/lib +img.exe=${img.root}/bin/convert +swf.exe=./bin/pdf2swf +swf.languagedir=. + +# Thumbnail Service +system.thumbnail.generate=true + +# Default thumbnail limits +# When creating thumbnails, only use the first pageLimit pages system.thumbnail.definition.default.timeoutMs=-1 system.thumbnail.definition.default.readLimitTimeMs=-1 system.thumbnail.definition.default.maxSourceSizeKBytes=-1 system.thumbnail.definition.default.readLimitKBytes=-1 system.thumbnail.definition.default.pageLimit=1 system.thumbnail.definition.default.maxPages=-1 - -# Max mimetype sizes to create thumbnail icons -system.thumbnail.mimetype.maxSourceSizeKBytes.pdf=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.txt=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.docx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.xlsx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.pptx=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.odt=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.ods=-1 -system.thumbnail.mimetype.maxSourceSizeKBytes.odp=-1 - -# Configuration for handling of failing thumbnails. -# See NodeEligibleForRethumbnailingEvaluator's javadoc for details. -# -# Retry periods limit the frequency with which the repository will attempt to create Share thumbnails -# for content nodes which have previously failed in their thumbnail attempts. -# These periods are in seconds. -# -# 604800s = 60s * 60m * 24h * 7d = 1 week -system.thumbnail.retryPeriod=60 -system.thumbnail.retryCount=2 -system.thumbnail.quietPeriod=604800 -system.thumbnail.quietPeriodRetriesEnabled=true -system.thumbnail.redeployStaticDefsOnStartup=true - -# Content Transformers -content.transformer.failover=true - -# Property to enable upgrade from 2.1-A -V2.1-A.fixes.to.schema=0 -#V2.1-A.fixes.to.schema=82 - -# The default authentication chain -authentication.chain=alfrescoNtlm1:alfrescoNtlm - -# Do authentication tickets expire or live for ever? -authentication.ticket.ticketsExpire=true - -# If ticketsEpire is true then how they should expire? -# Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE -# The default is AFTER_FIXED_TIME -authentication.ticket.expiryMode=AFTER_INACTIVITY - -# If authentication.ticket.ticketsExpire is true and -# authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, -# this controls the minimum period for which tickets are valid. -# The default is PT1H for one hour. -authentication.ticket.validDuration=PT1H - -# Default NFS user mappings (empty). Note these users will be able to -# authenticate through NFS without password so ensure NFS port is secure before -# enabling and adding mappings -nfs.user.mappings= -nfs.user.mappings.default.uid=0 -nfs.user.mappings.default.gid=0 - -#Example NFS user mappings -#nfs.user.mappings=admin,user1 -#nfs.user.mappings.value.admin.uid=0 -#nfs.user.mappings.value.admin.gid=0 -#nfs.user.mappings.value.user1.uid=500 -#nfs.user.mappings.value.user1.gid=500 - -# Default root path for protocols -protocols.storeName=${spaces.store} -protocols.rootPath=/${spaces.company_home.childname} - -# OpenCMIS -opencmis.connector.default.store=${spaces.store} -opencmis.connector.default.rootPath=/${spaces.company_home.childname} -opencmis.connector.default.typesDefaultMaxItems=500 -opencmis.connector.default.typesDefaultDepth=-1 -opencmis.connector.default.objectsDefaultMaxItems=10000 -opencmis.connector.default.objectsDefaultDepth=100 -opencmis.connector.default.openHttpSession=false -opencmis.activities.enabled=true - -# IMAP -imap.server.enabled=false -imap.server.port=143 -imap.server.attachments.extraction.enabled=true - -# Default IMAP mount points -imap.config.home.store=${spaces.store} -imap.config.home.rootPath=/${spaces.company_home.childname} -imap.config.home.folderPath=${spaces.imap_home.childname} -imap.config.server.mountPoints=AlfrescoIMAP -imap.config.server.mountPoints.default.mountPointName=IMAP -imap.config.server.mountPoints.default.modeName=ARCHIVE -imap.config.server.mountPoints.default.store=${spaces.store} -imap.config.server.mountPoints.default.rootPath=${protocols.rootPath} -imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP -imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED - -#Imap extraction settings -#imap.attachments.mode: -# SEPARATE -- All attachments for each email will be extracted to separate folder. -# COMMON -- All attachments for all emails will be extracted to one folder. -# SAME -- Attachments will be extracted to the same folder where email lies. -imap.server.attachments.extraction.enabled=true -imap.attachments.mode=SEPARATE -imap.attachments.folder.store=${spaces.store} -imap.attachments.folder.rootPath=/${spaces.company_home.childname} -imap.attachments.folder.folderPath=${spaces.imap_attachments.childname} - -# Activities Feed - refer to subsystem - -# Feed max ID range to limit maximum number of entries -activities.feed.max.idRange=1000000 -# Feed max size (number of entries) -activities.feed.max.size=100 -# Feed max age (eg. 44640 mins => 31 days) -activities.feed.max.ageMins=44640 - -activities.feed.generator.jsonFormatOnly=true - -activities.feedNotifier.batchSize=200 -activities.feedNotifier.numThreads=2 - -# Subsystem unit test values. Will not have any effect on production servers -subsystems.test.beanProp.default.longProperty=123456789123456789 -subsystems.test.beanProp.default.anotherStringProperty=Global Default -subsystems.test.beanProp=inst1,inst2,inst3 -subsystems.test.beanProp.value.inst2.boolProperty=true -subsystems.test.beanProp.value.inst3.anotherStringProperty=Global Instance Default -subsystems.test.simpleProp2=true -subsystems.test.simpleProp3=Global Default3 - -# Default Async Action Thread Pool -default.async.action.threadPriority=1 -default.async.action.corePoolSize=8 -default.async.action.maximumPoolSize=20 - -# Deployment Service -deployment.service.numberOfSendingThreads=5 -deployment.service.corePoolSize=2 -deployment.service.maximumPoolSize=3 -deployment.service.threadPriority=5 -# How long to wait in mS before refreshing a target lock - detects shutdown servers -deployment.service.targetLockRefreshTime=60000 -# How long to wait in mS from the last communication before deciding that deployment has failed, possibly -# the destination is no longer available? -deployment.service.targetLockTimeout=3600000 - -#Invitation Service -# Should send emails as part of invitation process. -notification.email.siteinvite=true - -# Transfer Service -transferservice.receiver.enabled=true -transferservice.receiver.stagingDir=${java.io.tmpdir}/alfresco-transfer-staging -# -# How long to wait in mS before refreshing a transfer lock - detects shutdown servers -# Default 1 minute. -transferservice.receiver.lockRefreshTime=60000 -# -# How many times to attempt retry the transfer lock -transferservice.receiver.lockRetryCount=3 -# How long to wait, in mS, before retrying the transfer lock -transferservice.receiver.lockRetryWait=100 -# -# How long to wait, in mS, since the last contact with from the client before -# timing out a transfer. Needs to be long enough to cope with network delays and "thinking -# time" for both source and destination. Default 5 minutes. -transferservice.receiver.lockTimeOut=300000 - -# Max time allowed for WCM folder rename operation issued by external clients (CIFS, FTP) -wcm.rename.max.time.milliseconds=2000 - -; DM Receiever Properties -; -; The name of the DM Receiver target - you deploy to this target name -deployment.dmr.name=alfresco - -; consolidate staging, author and workflow sandboxes to one -deployment.dmr.consolidate=true - -; The name of the Alfresco receiver targer -deployment.avm.name=avm - -;Where should the root of the web project be stored, by default /www/avm_webapps -deployment.avm.rootPath=/www/avm_webapps - -; Pattern for live stores deployment by the alfresco receiver -deployment.avm.storeNamePattern=%storeName%-live - -; Built in deployment receiver properties for the default -; filesystem receiver - -; filesystem receiver configuration -deployment.filesystem.rootdir=./wcm -deployment.filesystem.datadir=${deployment.filesystem.rootdir}/depdata -deployment.filesystem.logdir=${deployment.filesystem.rootdir}/deplog -deployment.filesystem.metadatadir=${deployment.filesystem.rootdir}/depmetadata - -deployment.filesystem.autofix=true -deployment.filesystem.errorOnOverwrite=false - -; default filesystem target configuration -deployment.filesystem.default.rootdir=./www -deployment.filesystem.default.name=filesystem -deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/default - -# OrphanReaper -orphanReaper.lockRefreshTime=60000 -orphanReaper.lockTimeOut=3600000 - - -# security -security.anyDenyDenies=true - -# -# Encryption properties -# -# default keystores location -dir.keystore=classpath:alfresco/keystore - -# general encryption parameters -encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator -encryption.keyAlgorithm=DESede -encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding - -# secret key keystore configuration -encryption.keystore.location=${dir.keystore}/keystore -encryption.keystore.keyMetaData.location=${dir.keystore}/keystore-passwords.properties -encryption.keystore.provider= -encryption.keystore.type=JCEKS - -# backup secret key keystore configuration -encryption.keystore.backup.location=${dir.keystore}/backup-keystore -encryption.keystore.backup.keyMetaData.location=${dir.keystore}/backup-keystore-passwords.properties -encryption.keystore.backup.provider= -encryption.keystore.backup.type=JCEKS - -# Should encryptable properties be re-encrypted with new encryption keys on botstrap? -encryption.bootstrap.reencrypt=false - -# mac/md5 encryption -encryption.mac.messageTimeout=30000 -encryption.mac.algorithm=HmacSHA1 - -# ssl encryption -encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore -encryption.ssl.keystore.provider= -encryption.ssl.keystore.type=JCEKS -encryption.ssl.keystore.keyMetaData.location=${dir.keystore}/ssl-keystore-passwords.properties -encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore -encryption.ssl.truststore.provider= -encryption.ssl.truststore.type=JCEKS -encryption.ssl.truststore.keyMetaData.location=${dir.keystore}/ssl-truststore-passwords.properties - -# Re-encryptor properties -encryption.reencryptor.chunkSize=100 -encryption.reencryptor.numThreads=2 - -# SOLR connection details (e.g. for JMX) -solr.host=localhost -solr.port=8080 -solr.port.ssl=8443 -solr.solrUser=solr -solr.solrPassword=solr -# none, https -solr.secureComms=https + +# Max mimetype sizes to create thumbnail icons +system.thumbnail.mimetype.maxSourceSizeKBytes.pdf=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.txt=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.docx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.xlsx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.pptx=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.odt=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.ods=-1 +system.thumbnail.mimetype.maxSourceSizeKBytes.odp=-1 + +# Configuration for handling of failing thumbnails. +# See NodeEligibleForRethumbnailingEvaluator's javadoc for details. +# +# Retry periods limit the frequency with which the repository will attempt to create Share thumbnails +# for content nodes which have previously failed in their thumbnail attempts. +# These periods are in seconds. +# +# 604800s = 60s * 60m * 24h * 7d = 1 week +system.thumbnail.retryPeriod=60 +system.thumbnail.retryCount=2 +system.thumbnail.quietPeriod=604800 +system.thumbnail.quietPeriodRetriesEnabled=true +system.thumbnail.redeployStaticDefsOnStartup=true + +# Property to enable upgrade from 2.1-A +V2.1-A.fixes.to.schema=0 +#V2.1-A.fixes.to.schema=82 + +# The default authentication chain +authentication.chain=alfrescoNtlm1:alfrescoNtlm + +# Do authentication tickets expire or live for ever? +authentication.ticket.ticketsExpire=true + +# If ticketsEpire is true then how they should expire? +# Valid values are: AFTER_INACTIVITY, AFTER_FIXED_TIME, DO_NOT_EXPIRE +# The default is AFTER_FIXED_TIME +authentication.ticket.expiryMode=AFTER_INACTIVITY + +# If authentication.ticket.ticketsExpire is true and +# authentication.ticket.expiryMode is AFTER_FIXED_TIME or AFTER_INACTIVITY, +# this controls the minimum period for which tickets are valid. +# The default is PT1H for one hour. +authentication.ticket.validDuration=PT1H + +# Default NFS user mappings (empty). Note these users will be able to +# authenticate through NFS without password so ensure NFS port is secure before +# enabling and adding mappings +nfs.user.mappings= +nfs.user.mappings.default.uid=0 +nfs.user.mappings.default.gid=0 + +#Example NFS user mappings +#nfs.user.mappings=admin,user1 +#nfs.user.mappings.value.admin.uid=0 +#nfs.user.mappings.value.admin.gid=0 +#nfs.user.mappings.value.user1.uid=500 +#nfs.user.mappings.value.user1.gid=500 + +# Default root path for protocols +protocols.storeName=${spaces.store} +protocols.rootPath=/${spaces.company_home.childname} + +# OpenCMIS +opencmis.connector.default.store=${spaces.store} +opencmis.connector.default.rootPath=/${spaces.company_home.childname} +opencmis.connector.default.typesDefaultMaxItems=500 +opencmis.connector.default.typesDefaultDepth=-1 +opencmis.connector.default.objectsDefaultMaxItems=10000 +opencmis.connector.default.objectsDefaultDepth=100 +opencmis.connector.default.openHttpSession=false +opencmis.activities.enabled=true + +# IMAP +imap.server.enabled=false +imap.server.port=143 +imap.server.attachments.extraction.enabled=true + +# Default IMAP mount points +imap.config.home.store=${spaces.store} +imap.config.home.rootPath=/${spaces.company_home.childname} +imap.config.home.folderPath=${spaces.imap_home.childname} +imap.config.server.mountPoints=AlfrescoIMAP +imap.config.server.mountPoints.default.mountPointName=IMAP +imap.config.server.mountPoints.default.modeName=ARCHIVE +imap.config.server.mountPoints.default.store=${spaces.store} +imap.config.server.mountPoints.default.rootPath=${protocols.rootPath} +imap.config.server.mountPoints.value.AlfrescoIMAP.mountPointName=Alfresco IMAP +imap.config.server.mountPoints.value.AlfrescoIMAP.modeName=MIXED + +#Imap extraction settings +#imap.attachments.mode: +# SEPARATE -- All attachments for each email will be extracted to separate folder. +# COMMON -- All attachments for all emails will be extracted to one folder. +# SAME -- Attachments will be extracted to the same folder where email lies. +imap.server.attachments.extraction.enabled=true +imap.attachments.mode=SEPARATE +imap.attachments.folder.store=${spaces.store} +imap.attachments.folder.rootPath=/${spaces.company_home.childname} +imap.attachments.folder.folderPath=${spaces.imap_attachments.childname} + +# Activities Feed - refer to subsystem + +# Feed max ID range to limit maximum number of entries +activities.feed.max.idRange=1000000 +# Feed max size (number of entries) +activities.feed.max.size=100 +# Feed max age (eg. 44640 mins => 31 days) +activities.feed.max.ageMins=44640 + +activities.feed.generator.jsonFormatOnly=true +activities.feed.fetchBatchSize=150 + +activities.feedNotifier.batchSize=200 +activities.feedNotifier.numThreads=2 + +# Subsystem unit test values. Will not have any effect on production servers +subsystems.test.beanProp.default.longProperty=123456789123456789 +subsystems.test.beanProp.default.anotherStringProperty=Global Default +subsystems.test.beanProp=inst1,inst2,inst3 +subsystems.test.beanProp.value.inst2.boolProperty=true +subsystems.test.beanProp.value.inst3.anotherStringProperty=Global Instance Default +subsystems.test.simpleProp2=true +subsystems.test.simpleProp3=Global Default3 + +# Default Async Action Thread Pool +default.async.action.threadPriority=1 +default.async.action.corePoolSize=8 +default.async.action.maximumPoolSize=20 + +# Deployment Service +deployment.service.numberOfSendingThreads=5 +deployment.service.corePoolSize=2 +deployment.service.maximumPoolSize=3 +deployment.service.threadPriority=5 +# How long to wait in mS before refreshing a target lock - detects shutdown servers +deployment.service.targetLockRefreshTime=60000 +# How long to wait in mS from the last communication before deciding that deployment has failed, possibly +# the destination is no longer available? +deployment.service.targetLockTimeout=3600000 + +#Invitation Service +# Should send emails as part of invitation process. +notification.email.siteinvite=true + +# Transfer Service +transferservice.receiver.enabled=true +transferservice.receiver.stagingDir=${java.io.tmpdir}/alfresco-transfer-staging +# +# How long to wait in mS before refreshing a transfer lock - detects shutdown servers +# Default 1 minute. +transferservice.receiver.lockRefreshTime=60000 +# +# How many times to attempt retry the transfer lock +transferservice.receiver.lockRetryCount=3 +# How long to wait, in mS, before retrying the transfer lock +transferservice.receiver.lockRetryWait=100 +# +# How long to wait, in mS, since the last contact with from the client before +# timing out a transfer. Needs to be long enough to cope with network delays and "thinking +# time" for both source and destination. Default 5 minutes. +transferservice.receiver.lockTimeOut=300000 + +# Max time allowed for WCM folder rename operation issued by external clients (CIFS, FTP) +wcm.rename.max.time.milliseconds=2000 + +; DM Receiever Properties +; +; The name of the DM Receiver target - you deploy to this target name +deployment.dmr.name=alfresco + +; consolidate staging, author and workflow sandboxes to one +deployment.dmr.consolidate=true + +; The name of the Alfresco receiver targer +deployment.avm.name=avm + +;Where should the root of the web project be stored, by default /www/avm_webapps +deployment.avm.rootPath=/www/avm_webapps + +; Pattern for live stores deployment by the alfresco receiver +deployment.avm.storeNamePattern=%storeName%-live + +; Built in deployment receiver properties for the default +; filesystem receiver + +; filesystem receiver configuration +deployment.filesystem.rootdir=./wcm +deployment.filesystem.datadir=${deployment.filesystem.rootdir}/depdata +deployment.filesystem.logdir=${deployment.filesystem.rootdir}/deplog +deployment.filesystem.metadatadir=${deployment.filesystem.rootdir}/depmetadata + +deployment.filesystem.autofix=true +deployment.filesystem.errorOnOverwrite=false + +; default filesystem target configuration +deployment.filesystem.default.rootdir=./www +deployment.filesystem.default.name=filesystem +deployment.filesystem.default.metadatadir=${deployment.filesystem.metadatadir}/default + +# OrphanReaper +orphanReaper.lockRefreshTime=60000 +orphanReaper.lockTimeOut=3600000 + + +# security +security.anyDenyDenies=true + +# +# Encryption properties +# +# default keystores location +dir.keystore=classpath:alfresco/keystore + +# general encryption parameters +encryption.keySpec.class=org.alfresco.encryption.DESEDEKeyGenerator +encryption.keyAlgorithm=DESede +encryption.cipherAlgorithm=DESede/CBC/PKCS5Padding + +# secret key keystore configuration +encryption.keystore.location=${dir.keystore}/keystore +encryption.keystore.keyMetaData.location=${dir.keystore}/keystore-passwords.properties +encryption.keystore.provider= +encryption.keystore.type=JCEKS + +# backup secret key keystore configuration +encryption.keystore.backup.location=${dir.keystore}/backup-keystore +encryption.keystore.backup.keyMetaData.location=${dir.keystore}/backup-keystore-passwords.properties +encryption.keystore.backup.provider= +encryption.keystore.backup.type=JCEKS + +# Should encryptable properties be re-encrypted with new encryption keys on botstrap? +encryption.bootstrap.reencrypt=false + +# mac/md5 encryption +encryption.mac.messageTimeout=30000 +encryption.mac.algorithm=HmacSHA1 + +# ssl encryption +encryption.ssl.keystore.location=${dir.keystore}/ssl.keystore +encryption.ssl.keystore.provider= +encryption.ssl.keystore.type=JCEKS +encryption.ssl.keystore.keyMetaData.location=${dir.keystore}/ssl-keystore-passwords.properties +encryption.ssl.truststore.location=${dir.keystore}/ssl.truststore +encryption.ssl.truststore.provider= +encryption.ssl.truststore.type=JCEKS +encryption.ssl.truststore.keyMetaData.location=${dir.keystore}/ssl-truststore-passwords.properties + +# Re-encryptor properties +encryption.reencryptor.chunkSize=100 +encryption.reencryptor.numThreads=2 + +# SOLR connection details (e.g. for JMX) +solr.host=localhost +solr.port=8080 +solr.port.ssl=8443 +solr.solrUser=solr +solr.solrPassword=solr +# none, https +solr.secureComms=https solr.cmis.alternativeDictionary=DEFAULT_DICTIONARY - -solr.max.total.connections=40 -solr.max.host.connections=40 - -# Solr connection timeouts -# solr connect timeout in ms -solr.solrConnectTimeout=5000 - -# cron expression defining how often the Solr Admin client (used by JMX) pings Solr if it goes away -solr.solrPingCronExpression=0 0/5 * * * ? * - - -#Default SOLR store mappings mappings -solr.store.mappings=solrMappingAlfresco,solrMappingArchive -solr.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory -solr.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco -solr.store.mappings.value.solrMappingAlfresco.protocol=workspace -solr.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore -solr.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory -solr.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive -solr.store.mappings.value.solrMappingArchive.protocol=archive -solr.store.mappings.value.solrMappingArchive.identifier=SpacesStore - -# -# Web Publishing Properties -# -publishing.root.path=/app:company_home/app:dictionary -publishing.root=${publishing.root.path}/${spaces.publishing.root.childname} - -# -# URL Shortening Properties -# -urlshortening.bitly.username=brianalfresco -urlshortening.bitly.api.key=R_ca15c6c89e9b25ccd170bafd209a0d4f -urlshortening.bitly.url.length=20 - -# -# Bulk Filesystem Importer -# - -# The number of threads to employ in a batch import -bulkImport.batch.numThreads=4 - -# The size of a batch in a batch import i.e. the number of files to import in a -# transaction/thread -bulkImport.batch.batchSize=20 - - -# -# Caching Content Store -# -system.content.caching.cacheOnInbound=true -system.content.caching.maxDeleteWatchCount=1 -# Clean up every day at 3 am -system.content.caching.contentCleanup.cronExpression=0 0 3 * * ? -system.content.caching.minFileAgeMillis=60000 -system.content.caching.maxUsageMB=4096 -# maxFileSizeMB - 0 means no max file size. -system.content.caching.maxFileSizeMB=0 - -mybatis.useLocalCaches=false - -fileFolderService.checkHidden.enabled=true - - -ticket.cleanup.cronExpression=0 0 * * * ? - -# -# Disable load of sample site -# -sample.site.disabled=false - -# -# Download Service Cleanup -# -download.cleaner.startDelayMins=60 -download.cleaner.repeatIntervalMins=60 -download.cleaner.maxAgeMins=60 - -# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden -system.quickshare.enabled=true - -# -# Cache configuration -# -cache.propertyValueCache.maxItems=10000 -cache.contentDataSharedCache.maxItems=130000 -cache.immutableEntitySharedCache.maxItems=50000 -cache.node.rootNodesSharedCache.maxItems=1000 -cache.node.allRootNodesSharedCache.maxItems=1000 -cache.node.nodesSharedCache.maxItems=250000 -cache.node.aspectsSharedCache.maxItems=130000 -cache.node.propertiesSharedCache.maxItems=130000 -cache.node.parentAssocsSharedCache.maxItems=130000 -cache.node.childByNameSharedCache.maxItems=130000 -cache.userToAuthoritySharedCache.maxItems=5000 -cache.authenticationSharedCache.maxItems=5000 -cache.authoritySharedCache.maxItems=10000 -cache.authorityToChildAuthoritySharedCache.maxItems=40000 -cache.zoneToAuthoritySharedCache.maxItems=500 -cache.permissionsAccessSharedCache.maxItems=50000 -cache.readersSharedCache.maxItems=10000 -cache.readersDeniedSharedCache.maxItems=10000 -cache.nodeOwnerSharedCache.maxItems=40000 -cache.personSharedCache.maxItems=1000 -cache.ticketsCache.maxItems=1000 -cache.avmEntitySharedCache.maxItems=5000 -cache.avmVersionRootEntitySharedCache.maxItems=1000 -cache.avmNodeSharedCache.maxItems=5000 -cache.avmNodeAspectsSharedCache.maxItems=5000 -cache.webServicesQuerySessionSharedCache.maxItems=1000 -cache.aclSharedCache.maxItems=50000 -cache.aclEntitySharedCache.maxItems=50000 -cache.resourceBundleBaseNamesSharedCache.maxItems=1000 -cache.loadedResourceBundlesSharedCache.maxItems=1000 -cache.messagesSharedCache.maxItems=1000 -cache.compiledModelsSharedCache.maxItems=1000 -cache.prefixesSharedCache.maxItems=1000 -cache.webScriptsRegistrySharedCache.maxItems=1000 -cache.routingContentStoreSharedCache.maxItems=10000 -cache.executingActionsCache.maxItems=1000 -cache.tagscopeSummarySharedCache.maxItems=1000 -cache.imapMessageSharedCache.maxItems=2000 -cache.tenantEntitySharedCache.maxItems=1000 -cache.immutableSingletonSharedCache.maxItems=12000 -cache.remoteAlfrescoTicketService.ticketsCache.maxItems=1000 -cache.contentDiskDriver.fileInfoCache.maxItems=1000 -cache.globalConfigSharedCache.maxItems=1000 -cache.propertyUniqueContextSharedCache.maxItems=10000 -cache.siteNodeRefSharedCache.maxItems=5000 + +solr.max.total.connections=40 +solr.max.host.connections=40 + +# Solr connection timeouts +# solr connect timeout in ms +solr.solrConnectTimeout=5000 + +# cron expression defining how often the Solr Admin client (used by JMX) pings Solr if it goes away +solr.solrPingCronExpression=0 0/5 * * * ? * + + +#Default SOLR store mappings mappings +solr.store.mappings=solrMappingAlfresco,solrMappingArchive +solr.store.mappings.value.solrMappingAlfresco.httpClientFactory=solrHttpClientFactory +solr.store.mappings.value.solrMappingAlfresco.baseUrl=/solr/alfresco +solr.store.mappings.value.solrMappingAlfresco.protocol=workspace +solr.store.mappings.value.solrMappingAlfresco.identifier=SpacesStore +solr.store.mappings.value.solrMappingArchive.httpClientFactory=solrHttpClientFactory +solr.store.mappings.value.solrMappingArchive.baseUrl=/solr/archive +solr.store.mappings.value.solrMappingArchive.protocol=archive +solr.store.mappings.value.solrMappingArchive.identifier=SpacesStore + +# +# Web Publishing Properties +# +publishing.root.path=/app:company_home/app:dictionary +publishing.root=${publishing.root.path}/${spaces.publishing.root.childname} + +# +# URL Shortening Properties +# +urlshortening.bitly.username=brianalfresco +urlshortening.bitly.api.key=R_ca15c6c89e9b25ccd170bafd209a0d4f +urlshortening.bitly.url.length=20 + +# +# Bulk Filesystem Importer +# + +# The number of threads to employ in a batch import +bulkImport.batch.numThreads=4 + +# The size of a batch in a batch import i.e. the number of files to import in a +# transaction/thread +bulkImport.batch.batchSize=20 + + +# +# Caching Content Store +# +system.content.caching.cacheOnInbound=true +system.content.caching.maxDeleteWatchCount=1 +# Clean up every day at 3 am +system.content.caching.contentCleanup.cronExpression=0 0 3 * * ? +system.content.caching.minFileAgeMillis=60000 +system.content.caching.maxUsageMB=4096 +# maxFileSizeMB - 0 means no max file size. +system.content.caching.maxFileSizeMB=0 + +mybatis.useLocalCaches=false + +fileFolderService.checkHidden.enabled=true + + +ticket.cleanup.cronExpression=0 0 * * * ? + +# +# Disable load of sample site +# +sample.site.disabled=false + +# +# Download Service Cleanup +# +download.cleaner.startDelayMins=60 +download.cleaner.repeatIntervalMins=60 +download.cleaner.maxAgeMins=60 + +# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden +system.quickshare.enabled=true + +# +# Cache configuration +# +cache.propertyValueCache.maxItems=10000 +cache.contentDataSharedCache.maxItems=130000 +cache.immutableEntitySharedCache.maxItems=50000 +cache.node.rootNodesSharedCache.maxItems=1000 +cache.node.allRootNodesSharedCache.maxItems=1000 +cache.node.nodesSharedCache.maxItems=250000 +cache.node.aspectsSharedCache.maxItems=130000 +cache.node.propertiesSharedCache.maxItems=130000 +cache.node.parentAssocsSharedCache.maxItems=130000 +cache.node.childByNameSharedCache.maxItems=130000 +cache.userToAuthoritySharedCache.maxItems=5000 +cache.authenticationSharedCache.maxItems=5000 +cache.authoritySharedCache.maxItems=10000 +cache.authorityToChildAuthoritySharedCache.maxItems=40000 +cache.zoneToAuthoritySharedCache.maxItems=500 +cache.permissionsAccessSharedCache.maxItems=50000 +cache.readersSharedCache.maxItems=10000 +cache.readersDeniedSharedCache.maxItems=10000 +cache.nodeOwnerSharedCache.maxItems=40000 +cache.personSharedCache.maxItems=1000 +cache.ticketsCache.maxItems=1000 +cache.avmEntitySharedCache.maxItems=5000 +cache.avmVersionRootEntitySharedCache.maxItems=1000 +cache.avmNodeSharedCache.maxItems=5000 +cache.avmNodeAspectsSharedCache.maxItems=5000 +cache.webServicesQuerySessionSharedCache.maxItems=1000 +cache.aclSharedCache.maxItems=50000 +cache.aclEntitySharedCache.maxItems=50000 +cache.resourceBundleBaseNamesSharedCache.maxItems=1000 +cache.loadedResourceBundlesSharedCache.maxItems=1000 +cache.messagesSharedCache.maxItems=1000 +cache.compiledModelsSharedCache.maxItems=1000 +cache.prefixesSharedCache.maxItems=1000 +cache.webScriptsRegistrySharedCache.maxItems=1000 +cache.routingContentStoreSharedCache.maxItems=10000 +cache.executingActionsCache.maxItems=1000 +cache.tagscopeSummarySharedCache.maxItems=1000 +cache.imapMessageSharedCache.maxItems=2000 +cache.tenantEntitySharedCache.maxItems=1000 +cache.immutableSingletonSharedCache.maxItems=12000 +cache.remoteAlfrescoTicketService.ticketsCache.maxItems=1000 +cache.contentDiskDriver.fileInfoCache.maxItems=1000 +cache.globalConfigSharedCache.maxItems=1000 +cache.propertyUniqueContextSharedCache.maxItems=10000 +cache.siteNodeRefSharedCache.maxItems=5000 cache.samlTrustEngineSharedCache.maxItems=5000 - -# -# Download Service Limits, in bytes -# -download.maxContentSize=2152852358 - -# Max size of view trashcan files -# -trashcan.MaxSize=1000 - -# -# Use bridge tables for caching authority evaluation. -# -authority.useBridgeTable=true - -# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden -system.quickshare.enabled=true - + +# +# Download Service Limits, in bytes +# +download.maxContentSize=2152852358 + +# Max size of view trashcan files +# +trashcan.MaxSize=1000 + +# +# Use bridge tables for caching authority evaluation. +# +authority.useBridgeTable=true + +# enable QuickShare - if false then the QuickShare-specific REST APIs will return 403 Forbidden +system.quickshare.enabled=true + # Oubound Mail mail.service.corePoolSize=8 mail.service.maximumPoolSize=20 @@ -1055,4 +1053,14 @@ opencmis.context.value= opencmis.servletpath.override=false opencmis.servletpath.value= opencmis.server.override=false -opencmis.server.value= +opencmis.server.value= + +nodes.bulkLoad.cachingThreshold=10 + +# Multi-Tenancy + +# if "dir.contentstore.tenants" is set then +# tenants are not co-mingled and all content roots will appear below this container (in sub-folder) +# and when creating a tenant the "contentRootPath" (root content store directory for a given tenant) will be ignored +dir.contentstore.tenants= + diff --git a/config/alfresco/subsystems/Transformers/default/transformers-context.xml b/config/alfresco/subsystems/Transformers/default/transformers-context.xml index 2cd109474e..39883ce95b 100644 --- a/config/alfresco/subsystems/Transformers/default/transformers-context.xml +++ b/config/alfresco/subsystems/Transformers/default/transformers-context.xml @@ -7,6 +7,18 @@ + + + + + + + + + + + + @@ -19,4 +31,46 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/config/alfresco/subsystems/Transformers/default/transformers.properties b/config/alfresco/subsystems/Transformers/default/transformers.properties index 26d3113f9c..8efa442619 100644 --- a/config/alfresco/subsystems/Transformers/default/transformers.properties +++ b/config/alfresco/subsystems/Transformers/default/transformers.properties @@ -1,6 +1,12 @@ +# Debug and Log buffer sizes +# ========================== +transformer.debug.entries=0 +transformer.log.entries=50 + + # Base setting for all transformers # ================================= -content.transformer.default.priority=10 +content.transformer.default.priority=100 content.transformer.default.thresholdCount=3 content.transformer.default.time=0 content.transformer.default.count=100000 @@ -12,22 +18,108 @@ content.transformer.default.readLimitKBytes=-1 content.transformer.default.pageLimit=-1 content.transformer.default.maxPages=-1 - # Transformer specific settings # ============================= -content.transformer.ImageMagick.extensions.png.png.priority=5 -content.transformer.ImageMagick.extensions.pdf.png.priority=5 -content.transformer.ImageMagick.extensions.ai.png.priority=5 -content.transformer.iWorksQuicklooks.extensions.key.jpg.priority=5 -content.transformer.iWorksQuicklooks.extensions.pages.jpg.priority=5 -content.transformer.iWorksQuicklooks.extensions.numbers.jpg.priority=5 -content.transformer.iWorksQuicklooks.extensions.key.pdf.priority=5 -content.transformer.iWorksQuicklooks.extensions.pages.pdf.priority=5 -content.transformer.iWorksQuicklooks.extensions.numbers.pdf.priority=5 +content.transformer.Archive.extensions.*.txt.priority=50 -content.transformer.PdfBox.extensions.pdf.txt.priority=5 +content.transformer.BinaryPassThrough.priority=20 +# Remote transformations (remote alfresco node followed by transformation server) +content.transformer.remote.alfresco.priority=30 +content.transformer.remoteServer.priority=40 + + +# Text +# ---- +content.transformer.TikaAuto.priority=120 + +content.transformer.Office.priority=130 + +content.transformer.OutlookMsg.priority=125 + +content.transformer.OOXML.priority=130 + +content.transformer.Poi.priority=130 + +content.transformer.TextMining.priority=130 +content.transformer.TextMining.extensions.doc.txt.priority=50 + +content.transformer.HtmlParser.extensions.html.txt.priority=50 + + +# Image +# ----- +content.transformer.ImageMagick.extensions.png.png.priority=50 +content.transformer.ImageMagick.extensions.pdf.png.priority=50 +content.transformer.ImageMagick.extensions.ai.png.priority=50 + +content.transformer.complex.Any.Image.priority=400 + +content.transformer.complex.Text.Image.priority=350 + +content.transformer.OOXMLThumbnail.extensions.dotx.jpg.priority=50 +content.transformer.OOXMLThumbnail.extensions.potx.jpg.priority=50 + +content.transformer.complex.Text.Image.extensions.csv.*.supported=true +content.transformer.complex.Text.Image.extensions.xml.*.supported=true +content.transformer.complex.Text.Image.extensions.txt.*.supported=true + +content.transformer.iWorksQuicklooks.extensions.key.jpg.priority=50 +content.transformer.iWorksQuicklooks.extensions.pages.jpg.priority=50 +content.transformer.iWorksQuicklooks.extensions.numbers.jpg.priority=50 +content.transformer.iWorksQuicklooks.extensions.key.pdf.priority=50 +content.transformer.iWorksQuicklooks.extensions.pages.pdf.priority=50 +content.transformer.iWorksQuicklooks.extensions.numbers.pdf.priority=50 + +content.transformer.complex.iWorks.Image.priority=400 +content.transformer.complex.iWorks.Image.extensions.key.png.priority=50 +content.transformer.complex.iWorks.Image.extensions.numbers.png.priority=50 +content.transformer.complex.iWorks.Image.extensions.pages.png.priority=50 + +content.transformer.complex.OutlookMsg2Image.priority=450 + + +# PDF +# --- +content.transformer.PdfBox.priority=110 +content.transformer.PdfBox.extensions.pdf.txt.priority=50 + +content.transformer.PdfBox.TextToPdf.extensions.csv.pdf.supported=true +content.transformer.PdfBox.TextToPdf.extensions.xml.pdf.supported=true +content.transformer.PdfBox.TextToPdf.maxSourceSizeKBytes=10240 + +content.transformer.Pdf2swf.maxSourceSizeKBytes=5120 +content.transformer.Pdf2swf.extensions.pdf.swf.supported=true +content.transformer.Pdf2swf.extensions.ai.swf.supported=true + +content.transformer.complex.Text.Pdf2swf.extensions.csv.swf.supported=true +content.transformer.complex.Text.Pdf2swf.extensions.xml.swf.supported=true +content.transformer.complex.Text.Pdf2swf.maxSourceSizeKBytes=5120 + +content.transformer.complex.image.Pdf2swf.extensions.tiff.swf.supported=true + +content.transformer.complex.PDF.Image.extensions.ai.png.supported=true +content.transformer.complex.PDF.Image.extensions.ai.eps.supported=true +content.transformer.complex.PDF.Image.extensions.ai.jp2.supported=true +content.transformer.complex.PDF.Image.extensions.ai.psd.supported=true +content.transformer.complex.PDF.Image.extensions.ai.ppj.supported=true +content.transformer.complex.PDF.Image.extensions.ai.cgm.supported=true +content.transformer.complex.PDF.Image.extensions.ai.gif.supported=true +content.transformer.complex.PDF.Image.extensions.ai.ief.supported=true +content.transformer.complex.PDF.Image.extensions.ai.bmp.supported=true +content.transformer.complex.PDF.Image.extensions.ai.jpg.supported=true +content.transformer.complex.PDF.Image.extensions.ai.pbm.supported=true +content.transformer.complex.PDF.Image.extensions.ai.pgm.supported=true +content.transformer.complex.PDF.Image.extensions.ai.pnm.supported=true +content.transformer.complex.PDF.Image.extensions.ai.ppm.supported=true +content.transformer.complex.PDF.Image.extensions.ai.ras.supported=true +content.transformer.complex.PDF.Image.extensions.ai.tiff.supported=true +content.transformer.complex.PDF.Image.extensions.ai.xbm.supported=true +content.transformer.complex.PDF.Image.extensions.ai.xpm.supported=true +content.transformer.complex.PDF.Image.extensions.ai.xwd.supported=true +content.transformer.complex.PDF.Image.extensions.ai.bin.supported=true +content.transformer.complex.PDF.Image.extensions.ai.dwt.supported=true content.transformer.complex.PDF.Image.extensions.pdf.png.supported=true content.transformer.complex.PDF.Image.extensions.pdf.eps.supported=true content.transformer.complex.PDF.Image.extensions.pdf.jp2.supported=true @@ -49,164 +141,15 @@ content.transformer.complex.PDF.Image.extensions.pdf.xpm.supported=true content.transformer.complex.PDF.Image.extensions.pdf.xwd.supported=true content.transformer.complex.PDF.Image.extensions.pdf.bin.supported=true content.transformer.complex.PDF.Image.extensions.pdf.dwt.supported=true -content.transformer.complex.PDF.Image.extensions.pdf.jpg.priority=5 -content.transformer.complex.PDF.Image.extensions.pdf.gif.priority=5 +content.transformer.complex.PDF.Image.extensions.ai.jpg.priority=50 +content.transformer.complex.PDF.Image.extensions.ai.gif.priority=50 +content.transformer.complex.PDF.Image.extensions.pdf.jpg.priority=50 +content.transformer.complex.PDF.Image.extensions.pdf.gif.priority=50 -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.png.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.eps.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.jp2.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.psd.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.ppj.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.cgm.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.gif.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.ief.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.bmp.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.jpg.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.pbm.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.pgm.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.pnm.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.ppm.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.ras.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.tiff.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.xbm.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.xpm.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.xwd.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.bin.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.dwt.supported=true -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.jpg.priority=5 -content.transformer.complex.AdobeIllustrator.Image.extensions.ai.gif.priority=5 - -content.transformer.complex.iWorks.Image.extensions.key.png.priority=5 -content.transformer.complex.iWorks.Image.extensions.numbers.png.priority=5 -content.transformer.complex.iWorks.Image.extensions.pages.png.priority=5 - -content.transformer.OOXMLThumbnail.extensions.dotx.jpg.priority=5 -content.transformer.OOXMLThumbnail.extensions.potx.jpg.priority=5 - -content.transformer.OpenOffice.extensions.*.xlsm.supported=false -content.transformer.OpenOffice.extensions.*.pptm.supported=false -content.transformer.OpenOffice.extensions.*.sldm.supported=false -content.transformer.OpenOffice.extensions.*.xltx.supported=false -content.transformer.OpenOffice.extensions.*.docx.supported=false -content.transformer.OpenOffice.extensions.*.potx.supported=false -content.transformer.OpenOffice.extensions.*.xlsx.supported=false -content.transformer.OpenOffice.extensions.*.pptx.supported=false -content.transformer.OpenOffice.extensions.*.xlam.supported=false -content.transformer.OpenOffice.extensions.*.docm.supported=false -content.transformer.OpenOffice.extensions.*.xltm.supported=false -content.transformer.OpenOffice.extensions.*.dotx.supported=false -content.transformer.OpenOffice.extensions.*.xlsb.supported=false -content.transformer.OpenOffice.extensions.*.sldx.supported=false -content.transformer.OpenOffice.extensions.*.ppsm.supported=false -content.transformer.OpenOffice.extensions.*.txt.supported=false -content.transformer.OpenOffice.extensions.*.potm.supported=false -content.transformer.OpenOffice.extensions.*.ppam.supported=false -content.transformer.OpenOffice.extensions.*.dotm.supported=false -content.transformer.OpenOffice.extensions.*.ppsx.supported=false -content.transformer.OpenOffice.extensions.html.pdf.supported=false -content.transformer.OpenOffice.extensions.xlsm.pdf.maxSourceSizeKBytes=1536 -content.transformer.OpenOffice.extensions.pptm.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.xls.pdf.maxSourceSizeKBytes=10240 -content.transformer.OpenOffice.extensions.sldm.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.xltx.pdf.maxSourceSizeKBytes=1536 -content.transformer.OpenOffice.extensions.potx.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.docx.pdf.maxSourceSizeKBytes=768 -content.transformer.OpenOffice.extensions.xlsx.pdf.maxSourceSizeKBytes=1536 -content.transformer.OpenOffice.extensions.pptx.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.xlam.pdf.maxSourceSizeKBytes=1536 -content.transformer.OpenOffice.extensions.ppt.pdf.maxSourceSizeKBytes=6144 -content.transformer.OpenOffice.extensions.docm.pdf.maxSourceSizeKBytes=768 -content.transformer.OpenOffice.extensions.xltm.pdf.maxSourceSizeKBytes=1536 -content.transformer.OpenOffice.extensions.dotx.pdf.maxSourceSizeKBytes=768 -content.transformer.OpenOffice.extensions.xlsb.pdf.maxSourceSizeKBytes=1536 -content.transformer.OpenOffice.extensions.sldx.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.ppsm.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.potm.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.txt.pdf.maxSourceSizeKBytes=5120 -content.transformer.OpenOffice.extensions.ppam.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.dotm.pdf.maxSourceSizeKBytes=768 -content.transformer.OpenOffice.extensions.doc.pdf.maxSourceSizeKBytes=10240 -content.transformer.OpenOffice.extensions.vsd.pdf.maxSourceSizeKBytes=4096 -content.transformer.OpenOffice.extensions.ppsx.pdf.maxSourceSizeKBytes=4096 - -content.transformer.OpenOffice.Html2Pdf.extensions.html.pdf.supported=true -content.transformer.OpenOffice.Html2Pdf.extensions.html.pdf.priority=5 - -content.transformer.OpenOffice.2Pdf.extensions.*.pdf.supported=true - -content.transformer.PdfBox.TextToPdf.extensions.csv.pdf.supported=true -content.transformer.PdfBox.TextToPdf.extensions.xml.pdf.supported=true -content.transformer.PdfBox.TextToPdf.maxSourceSizeKBytes=10240 - -content.transformer.complex.Text.Image.extensions.csv.*.supported=true -content.transformer.complex.Text.Image.extensions.xml.*.supported=true -content.transformer.complex.Text.Image.extensions.txt.*.supported=true - -content.transformer.TextMining.extensions.doc.txt.priority=5 - -content.transformer.HtmlParser.extensions.html.txt.priority=5 - -content.transformer.complex.OpenOffice.PdfBox.extensions.xlsm.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.pptm.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.xls.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.sldm.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.xltx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.docx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.potx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.xlsx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.pptx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.xlam.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.ppt.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.docm.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.xltm.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.dotx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.sldx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.ppsm.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.txt.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.ppam.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.dotm.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.doc.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.ppsx.txt.supported=false -content.transformer.complex.OpenOffice.PdfBox.extensions.xlsb.txt.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.PdfBox.extensions.potm.txt.maxSourceSizeKBytes=1024 - -content.transformer.Archive.extensions.zip.txt.priority=5 - -content.transformer.Pdf2swf.maxSourceSizeKBytes=5120 -content.transformer.Pdf2swf.extensions.pdf.swf.supported=true -content.transformer.Pdf2swf.extensions.ai.swf.supported=true - -content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlsm.swf.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.pptm.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.xls.swf.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.sldm.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.xltx.swf.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.docx.swf.maxSourceSizeKBytes=256 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.potx.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlsx.swf.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.pptx.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlam.swf.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppt.swf.maxSourceSizeKBytes=6144 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.docm.swf.maxSourceSizeKBytes=256 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.xltm.swf.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.dotx.swf.maxSourceSizeKBytes=256 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlsb.swf.maxSourceSizeKBytes=1024 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.sldx.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppsm.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.potm.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.txt.swf.maxSourceSizeKBytes=5120 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppam.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.dotm.swf.maxSourceSizeKBytes=256 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.doc.swf.maxSourceSizeKBytes=1536 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.vsd.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppsx.swf.maxSourceSizeKBytes=4096 - -content.transformer.complex.Text.Pdf2swf.extensions.csv.swf.supported=true -content.transformer.complex.Text.Pdf2swf.extensions.xml.swf.supported=true -content.transformer.complex.Text.Pdf2swf.maxSourceSizeKBytes=5120 - -content.transformer.complex.image.Pdf2swf.extensions.tiff.swf.supported=true +# JodConverter (OpenOffice should be the same) +# -------------------------------------------- +content.transformer.JodConverter.priority=110 content.transformer.JodConverter.extensions.*.xlsm.supported=false content.transformer.JodConverter.extensions.*.pptm.supported=false content.transformer.JodConverter.extensions.*.sldm.supported=false @@ -253,11 +196,16 @@ content.transformer.JodConverter.extensions.doc.pdf.maxSourceSizeKBytes=10240 content.transformer.JodConverter.extensions.vsd.pdf.maxSourceSizeKBytes=4096 content.transformer.JodConverter.extensions.ppsx.pdf.maxSourceSizeKBytes=4096 +content.transformer.JodConverter.Html2Pdf.priority=200 content.transformer.JodConverter.Html2Pdf.extensions.html.pdf.supported=true -content.transformer.JodConverter.Html2Pdf.extensions.html.pdf.priority=5 +content.transformer.JodConverter.Html2Pdf.extensions.html.pdf.priority=50 +content.transformer.JodConverter.2Pdf.priority=150 content.transformer.JodConverter.2Pdf.extensions.*.pdf.supported=true +content.transformer.complex.JodConverter.Image.priority=250 + +content.transformer.complex.JodConverter.PdfBox.priority=150 content.transformer.complex.JodConverter.PdfBox.extensions.xlsm.txt.supported=false content.transformer.complex.JodConverter.PdfBox.extensions.pptm.txt.supported=false content.transformer.complex.JodConverter.PdfBox.extensions.xls.txt.supported=false @@ -282,6 +230,7 @@ content.transformer.complex.JodConverter.PdfBox.extensions.ppsx.txt.supported=fa content.transformer.complex.JodConverter.PdfBox.extensions.xlsb.txt.maxSourceSizeKBytes=1024 content.transformer.complex.JodConverter.PdfBox.extensions.potm.txt.maxSourceSizeKBytes=1024 +content.transformer.complex.JodConverter.Pdf2swf.priority=150 content.transformer.complex.JodConverter.Pdf2swf.extensions.xlsm.swf.maxSourceSizeKBytes=1024 content.transformer.complex.JodConverter.Pdf2swf.extensions.pptm.swf.maxSourceSizeKBytes=4096 content.transformer.complex.JodConverter.Pdf2swf.extensions.xls.swf.maxSourceSizeKBytes=1024 @@ -305,4 +254,114 @@ content.transformer.complex.JodConverter.Pdf2swf.extensions.ppam.swf.maxSourceSi content.transformer.complex.JodConverter.Pdf2swf.extensions.dotm.swf.maxSourceSizeKBytes=256 content.transformer.complex.JodConverter.Pdf2swf.extensions.doc.swf.maxSourceSizeKBytes=1536 content.transformer.complex.JodConverter.Pdf2swf.extensions.vsd.swf.maxSourceSizeKBytes=4096 -content.transformer.complex.JodConverter.Pdf2swf.extensions.ppsx.swf.maxSourceSizeKBytes=4096 \ No newline at end of file +content.transformer.complex.JodConverter.Pdf2swf.extensions.ppsx.swf.maxSourceSizeKBytes=4096 + + +# OpenOffice (JodConverter should be the same) +# -------------------------------------------- +content.transformer.OpenOffice.priority=110 +content.transformer.OpenOffice.extensions.*.xlsm.supported=false +content.transformer.OpenOffice.extensions.*.pptm.supported=false +content.transformer.OpenOffice.extensions.*.sldm.supported=false +content.transformer.OpenOffice.extensions.*.xltx.supported=false +content.transformer.OpenOffice.extensions.*.docx.supported=false +content.transformer.OpenOffice.extensions.*.potx.supported=false +content.transformer.OpenOffice.extensions.*.xlsx.supported=false +content.transformer.OpenOffice.extensions.*.pptx.supported=false +content.transformer.OpenOffice.extensions.*.xlam.supported=false +content.transformer.OpenOffice.extensions.*.docm.supported=false +content.transformer.OpenOffice.extensions.*.xltm.supported=false +content.transformer.OpenOffice.extensions.*.dotx.supported=false +content.transformer.OpenOffice.extensions.*.xlsb.supported=false +content.transformer.OpenOffice.extensions.*.sldx.supported=false +content.transformer.OpenOffice.extensions.*.ppsm.supported=false +content.transformer.OpenOffice.extensions.*.txt.supported=false +content.transformer.OpenOffice.extensions.*.potm.supported=false +content.transformer.OpenOffice.extensions.*.ppam.supported=false +content.transformer.OpenOffice.extensions.*.dotm.supported=false +content.transformer.OpenOffice.extensions.*.ppsx.supported=false +content.transformer.OpenOffice.extensions.html.pdf.supported=false +content.transformer.OpenOffice.extensions.xlsm.pdf.maxSourceSizeKBytes=1536 +content.transformer.OpenOffice.extensions.pptm.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.xls.pdf.maxSourceSizeKBytes=10240 +content.transformer.OpenOffice.extensions.sldm.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.xltx.pdf.maxSourceSizeKBytes=1536 +content.transformer.OpenOffice.extensions.potx.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.docx.pdf.maxSourceSizeKBytes=768 +content.transformer.OpenOffice.extensions.xlsx.pdf.maxSourceSizeKBytes=1536 +content.transformer.OpenOffice.extensions.pptx.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.xlam.pdf.maxSourceSizeKBytes=1536 +content.transformer.OpenOffice.extensions.ppt.pdf.maxSourceSizeKBytes=6144 +content.transformer.OpenOffice.extensions.docm.pdf.maxSourceSizeKBytes=768 +content.transformer.OpenOffice.extensions.xltm.pdf.maxSourceSizeKBytes=1536 +content.transformer.OpenOffice.extensions.dotx.pdf.maxSourceSizeKBytes=768 +content.transformer.OpenOffice.extensions.xlsb.pdf.maxSourceSizeKBytes=1536 +content.transformer.OpenOffice.extensions.sldx.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.ppsm.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.potm.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.txt.pdf.maxSourceSizeKBytes=5120 +content.transformer.OpenOffice.extensions.ppam.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.dotm.pdf.maxSourceSizeKBytes=768 +content.transformer.OpenOffice.extensions.doc.pdf.maxSourceSizeKBytes=10240 +content.transformer.OpenOffice.extensions.vsd.pdf.maxSourceSizeKBytes=4096 +content.transformer.OpenOffice.extensions.ppsx.pdf.maxSourceSizeKBytes=4096 + +content.transformer.OpenOffice.Html2Pdf.priority=200 +content.transformer.OpenOffice.Html2Pdf.extensions.html.pdf.supported=true +content.transformer.OpenOffice.Html2Pdf.extensions.html.pdf.priority=50 + +content.transformer.OpenOffice.2Pdf.priority=150 +content.transformer.OpenOffice.2Pdf.extensions.*.pdf.supported=true + +content.transformer.complex.OpenOffice.Image.priority=250 + +content.transformer.complex.OpenOffice.PdfBox.priority=150 +content.transformer.complex.OpenOffice.PdfBox.extensions.xlsm.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.pptm.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.xls.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.sldm.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.xltx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.docx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.potx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.xlsx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.pptx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.xlam.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.ppt.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.docm.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.xltm.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.dotx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.sldx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.ppsm.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.txt.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.ppam.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.dotm.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.doc.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.ppsx.txt.supported=false +content.transformer.complex.OpenOffice.PdfBox.extensions.xlsb.txt.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.PdfBox.extensions.potm.txt.maxSourceSizeKBytes=1024 + +content.transformer.complex.OpenOffice.Pdf2swf.priority=150 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlsm.swf.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.pptm.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.xls.swf.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.sldm.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.xltx.swf.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.docx.swf.maxSourceSizeKBytes=256 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.potx.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlsx.swf.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.pptx.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlam.swf.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppt.swf.maxSourceSizeKBytes=6144 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.docm.swf.maxSourceSizeKBytes=256 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.xltm.swf.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.dotx.swf.maxSourceSizeKBytes=256 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.xlsb.swf.maxSourceSizeKBytes=1024 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.sldx.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppsm.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.potm.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.txt.swf.maxSourceSizeKBytes=5120 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppam.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.dotm.swf.maxSourceSizeKBytes=256 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.doc.swf.maxSourceSizeKBytes=1536 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.vsd.swf.maxSourceSizeKBytes=4096 +content.transformer.complex.OpenOffice.Pdf2swf.extensions.ppsx.swf.maxSourceSizeKBytes=4096 \ No newline at end of file diff --git a/config/alfresco/subsystems/googledocs/default/googledocs.properties b/config/alfresco/subsystems/googledocs/default/googledocs.properties index 6af9cba3cd..fcafc00d4d 100755 --- a/config/alfresco/subsystems/googledocs/default/googledocs.properties +++ b/config/alfresco/subsystems/googledocs/default/googledocs.properties @@ -1,5 +1,5 @@ -# Enables google editable functionality +# Enables GoogleDocsV1 functionality googledocs.googleeditable.enabled=false # Google docs application name diff --git a/config/alfresco/tx-cache-context.xml b/config/alfresco/tx-cache-context.xml index af580a7a95..fe3319dda4 100644 --- a/config/alfresco/tx-cache-context.xml +++ b/config/alfresco/tx-cache-context.xml @@ -306,7 +306,7 @@ org.alfresco.nodeOwnerTransactionalCache - + diff --git a/config/alfresco/workflow/invitation-moderated-workflow-model.xml b/config/alfresco/workflow/invitation-moderated-workflow-model.xml index e4a32b3a94..c080ecd9f7 100644 --- a/config/alfresco/workflow/invitation-moderated-workflow-model.xml +++ b/config/alfresco/workflow/invitation-moderated-workflow-model.xml @@ -88,6 +88,9 @@ d:text + + d:date + diff --git a/config/alfresco/workflow/workflow-messages_de.properties b/config/alfresco/workflow/workflow-messages_de.properties index a539950bb9..35b66defb0 100755 --- a/config/alfresco/workflow/workflow-messages_de.properties +++ b/config/alfresco/workflow/workflow-messages_de.properties @@ -90,8 +90,8 @@ wf_parallelgroupreview.node.review.transition.approve.description=Genehmigen # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=Adhoc Workflow -activitiAdhoc.workflow.description=Kollegen mithilfe der Activiti Workflow Engine beliebige Aufgabe zuweisen +activitiAdhoc.workflow.title=Neue Aufgabe +activitiAdhoc.workflow.description=Eine neue Aufgabe sich selbst oder einem Kollegen zuweisen # # Activiti Review And Approve Workflow @@ -105,8 +105,8 @@ activitiReview.task.rejected.description=Das Dokument wurde gepr\u00fcft und abg # Parallel Review Workflow # -activitiParallelReview.workflow.title=Paralleles \u00dcberpr\u00fcfen und Genehmigen -activitiParallelReview.workflow.description=Parallele \u00dcberpr\u00fcfung und Genehmigung von Inhalten mit der Activiti Workflow Engine +activitiParallelReview.workflow.title=Dokument(e) zur \u00dcberpr\u00fcfung senden +activitiParallelReview.workflow.description=Dokumentengenehmigung von einem oder mehreren Kollegen anfordern activitiParallelReview.task.approved.description=Das Dokument wurde gepr\u00fcft und genehmigt. activitiParallelReview.task.rejected.description=Das Dokument wurde gepr\u00fcft und abgelehnt. @@ -141,14 +141,23 @@ publishWebContent.workflow.description=Ver\u00f6ffentlichen von Web Content mit # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=Adhoc Aufgabe starten +wf_workflowmodel.type.wf_submitAdhocTask.title=Aufgabe wf_workflowmodel.type.wf_submitAdhocTask.description=Kollegen Aufgabe zuweisen wf_workflowmodel.property.wf_notifyMe.title=Mich benachrichtigen wf_workflowmodel.property.wf_notifyMe.description=Mich bei Abschluss der Aufgabe benachrichtigen -wf_workflowmodel.type.wf_adhocTask.title=Adhoc Aufgabe -wf_workflowmodel.type.wf_adhocTask.description=Von Kollege zugewiesene adhoc Aufgabe -wf_workflowmodel.type.wf_completedAdhocTask.title=Adhoc Aufgabe abgeschlossen -wf_workflowmodel.type.wf_completedAdhocTask.description=Adhoc Aufgabe abgeschlossen +wf_workflowmodel.type.wf_adhocTask.title=Aufgabe +wf_workflowmodel.type.wf_adhocTask.description=Von Kollege zugewiesene Aufgabe +wf_workflowmodel.type.wf_completedAdhocTask.title=Aufgabe abgeschlossen +wf_workflowmodel.type.wf_completedAdhocTask.description=Aufgabe abgeschlossen + +activitiAdhoc.task.wf_submitAdhocTask.title=Aufgabe +activitiAdhoc.task.wf_submitAdhocTask.description=Kollegen Aufgabe zuweisen +activitiAdhoc.property.wf_notifyMe.title=Mich benachrichtigen +activitiAdhoc.property.wf_notifyMe.description=Mich bei Abschluss der Aufgabe benachrichtigen +activitiAdhoc.task.wf_adhocTask.title=Aufgabe +activitiAdhoc.task.wf_adhocTask.description=Von Kollege zugewiesene Aufgabe +activitiAdhoc.task.wf_completedAdhocTask.title=Aufgabe abgeschlossen +activitiAdhoc.task.wf_completedAdhocTask.description=Aufgabe abgeschlossen # Review And Approve Task Definitions @@ -169,8 +178,8 @@ wf_workflowmodel.property.wf_reviewOutcome.description=Dem Inhalt genehmigen ode # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=Parallele \u00dcberpr\u00fcfung starten -wf_workflowmodel.type.wf_submitParallelReviewTask.description=Dokumente zur \u00dcberpr\u00fcfung und Genehmigung einer Liste von Mitarbeitern vorlegen +wf_workflowmodel.type.wf_submitParallelReviewTask.title=Dokument(e) zur \u00dcberpr\u00fcfung senden +wf_workflowmodel.type.wf_submitParallelReviewTask.description=Dokumentengenehmigung von einem oder mehreren Kollegen anfordern wf_workflowmodel.property.wf_requiredApprovePercent.title=Erforderlicher Prozentsatz Genehmigung wf_workflowmodel.property.wf_requiredApprovePercent.description=Prozentsatz Pr\u00fcfer, die zur Genehmigung genehmigen m\u00fcssen wf_workflowmodel.type.wf_rejectedParallelTask.title=Abgelehnt @@ -188,6 +197,27 @@ wf_workflowmodel.property.wf_actualPercent.description=Derzeitiger Prozentsatz G wf_workflowmodel.property.wf_reviewOutcome.title=\u00dcberpr\u00fcfungsergebnis wf_workflowmodel.property.wf_reviewOutcome.description=\u00dcberpr\u00fcfungsergebnis +activitiParallelReview.task.wf_submitParallelReviewTask.title=Dokument(e) zur \u00dcberpr\u00fcfung senden +activitiParallelReview.task.wf_submitParallelReviewTask.description=Dokumentengenehmigung von einem oder mehreren Kollegen anfordern +activitiParallelReview.property.wf_requiredApprovePercent.title=Erforderlicher Prozentsatz Genehmigung +activitiParallelReview.property.wf_requiredApprovePercent.description=Prozentsatz Pr\u00fcfer, die zur Genehmigung genehmigen m\u00fcssen +activitiParallelReview.task.wf_activitiReviewTask.title=\u00dcberpr\u00fcfen +activitiParallelReview.task.wf_activitiReviewTask.description=Dokumente zwecks Genehmigung oder Ablehnung pr\u00fcfen +activitiParallelReview.task.wf_rejectedParallelTask.title=Dokument abgelehnt +activitiParallelReview.task.wf_rejectedParallelTask.description=Dokument(e) wurde(n) abgelehnt +activitiParallelReview.task.wf_approvedParallelTask.title=Dokument genehmigt +activitiParallelReview.task.wf_approvedParallelTask.description=Dokument(e) wurde(n) genehmigt +activitiParallelReview.property.wf_reviewerCount.title=Anzahl Pr\u00fcfer +activitiParallelReview.property.wf_reviewerCount.description=Anzahl Pr\u00fcfer +activitiParallelReview.property.wf_requiredPercent.title=Erforderlicher Prozentsatz Genehmigung +activitiParallelReview.property.wf_requiredPercent.description=Erforderlicher Prozentsatz Genehmigung +activitiParallelReview.property.wf_approveCount.title=Pr\u00fcfer, die genehmigt haben +activitiParallelReview.property.wf_approveCount.description=Pr\u00fcfer, die genehmigt haben +activitiParallelReview.property.wf_actualPercent.title=Derzeitiger Prozentsatz Genehmigung +activitiParallelReview.property.wf_actualPercent.description=Derzeitiger Prozentsatz Genehmigung +activitiParallelReview.property.wf_reviewOutcome.title=\u00dcberpr\u00fcfungsergebnis +activitiParallelReview.property.wf_reviewOutcome.description=\u00dcberpr\u00fcfungsergebnis + # Pooled Review Task Definitions wf_workflowmodel.type.wf_submitGroupReviewTask.title=Gruppen\u00fcberpr\u00fcfung starten @@ -200,4 +230,5 @@ listconstraint.wf_reviewOutcomeOptions.Reject=Ablehnen # The result of a workflow task seen on Workflow Details - History # This is formed by prefixing workflowtask.outcome to the value of the wf:outcome property workflowtask.outcome.Approve=Genehmigt -workflowtask.outcome.Reject=Abgelehnt \ No newline at end of file +workflowtask.outcome.Reject=Abgelehnt + diff --git a/config/alfresco/workflow/workflow-messages_es.properties b/config/alfresco/workflow/workflow-messages_es.properties index ade45be89f..065a5c1e67 100755 --- a/config/alfresco/workflow/workflow-messages_es.properties +++ b/config/alfresco/workflow/workflow-messages_es.properties @@ -1,23 +1,23 @@ -#Display labels for out-of-the-box Content-oriented Workflows +# Display labels for out-of-the-box Content-oriented Workflows #################### # JBPM WORKFLOWS # #################### # -#Adhoc Task Workflow +# Adhoc Task Workflow # wf_adhoc.workflow.title=Flujo de trabajo ad hoc (JBPM) wf_adhoc.workflow.description=Asignaci\u00f3n de tarea arbitraria a un compa\u00f1ero mediante el motor de flujo de trabajo JBPM # -#Review And Approve Workflow +# Review And Approve Workflow # wf_review.workflow.title=Revisi\u00f3n y aprobaci\u00f3n (JBPM) wf_review.workflow.description=Revisi\u00f3n y aprobaci\u00f3n de contenido mediante el motor de flujo de trabajo JBPM -#Review And Approve Process Definitions +# Review And Approve Process Definitions wf_review.node.start.title=Iniciar wf_review.node.start.description=Iniciar @@ -31,23 +31,23 @@ wf_review.node.review.transition.approve.title=Aprobar wf_review.node.review.transition.approve.description=Aprobar wf_review.node.rejected.title=Rechazados wf_review.node.rejected.description=Rechazados -wf_review.task.wf_rejectedTask.title=Rechazados -wf_review.task.wf_rejectedTask.description=Rechazados -wf_review.node.approved.title=Aprobados -wf_review.node.approved.description=Aprobados -wf_review.task.wf_approvedTask.title=Aprobados -wf_review.task.wf_approvedTask.description=Aprobados +wf_review.task.wf_rejectedTask.title=Rechazada +wf_review.task.wf_rejectedTask.description=Rechazada +wf_review.node.approved.title=Aprobadas +wf_review.node.approved.description=Aprobadas +wf_review.task.wf_approvedTask.title=Aprobada +wf_review.task.wf_approvedTask.description=Aprobada wf_review.node.end.title=Fin wf_review.node.end.description=Fin # -#Parallel Review Workflow +# Parallel Review Workflow # wf_parallelreview.workflow.title=Revisi\u00f3n y aprobaci\u00f3n paralela (JBPM) wf_parallelreview.workflow.description=Revisi\u00f3n y aprobaci\u00f3n paralela de contenido mediante el motor de flujo de trabajo JBPM -#Parallel Review Process Definitions +# Parallel Review Process Definitions wf_parallelreview.node.review.transition.reject.title=Rechazar wf_parallelreview.node.review.transition.reject.description=Rechazar @@ -55,13 +55,13 @@ wf_parallelreview.node.review.transition.approve.title=Aprobar wf_parallelreview.node.review.transition.approve.description=Aprobar # -#Pooled Review Workflow +# Pooled Review Workflow # wf_reviewpooled.workflow.title=Revisi\u00f3n y aprobaci\u00f3n en conjunto (JBPM) wf_reviewpooled.workflow.description=Revisi\u00f3n y aprobaci\u00f3n en conjunto de contenido mediante el motor de flujo de trabajo JBPM -#Pooled Review Process Definitions +# Pooled Review Process Definitions wf_reviewpooled.node.review.transition.reject.title=Rechazar wf_reviewpooled.node.review.transition.reject.description=Rechazar @@ -69,13 +69,13 @@ wf_reviewpooled.node.review.transition.approve.title=Aprobar wf_reviewpooled.node.review.transition.approve.description=Aprobar # -#Parallel Group Review Workflow +# Parallel Group Review Workflow # wf_parallelgroupreview.workflow.title=Revisi\u00f3n y aprobaci\u00f3n en grupo (JBPM) wf_parallelgroupreview.workflow.description=Revisi\u00f3n y aprobaci\u00f3n en grupo de contenido mediante el motor de flujo de trabajo JBPM -#Parallel Group Review Process Definitions +# Parallel Group Review Process Definitions wf_parallelgroupreview.node.review.transition.reject.title=Rechazar wf_parallelgroupreview.node.review.transition.reject.description=Rechazar @@ -90,11 +90,11 @@ wf_parallelgroupreview.node.review.transition.approve.description=Aprobar # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=Flujo de trabajo ad hoc -activitiAdhoc.workflow.description=Asignar tarea arbitraria a un compa\u00f1ero mediante el motor de flujo de trabajo Activiti +activitiAdhoc.workflow.title=Nueva tarea +activitiAdhoc.workflow.description=Asignar una tarea nueva a usted o a un colega # -#Activiti Review And Approve Workflow +# Activiti Review And Approve Workflow # activitiReview.workflow.title=Revisi\u00f3n y aprobaci\u00f3n activitiReview.workflow.description=Revisi\u00f3n y aprobaci\u00f3n de contenido mediante el motor de flujo de trabajo Activiti @@ -102,11 +102,11 @@ activitiReview.task.approved.description=Se ha revisado y aprobado el documento. activitiReview.task.rejected.description=Se ha revisado y rechazado el documento. # -#Parallel Review Workflow +# Parallel Review Workflow # -activitiParallelReview.workflow.title=Revisi\u00f3n y aprobaci\u00f3n paralela -activitiParallelReview.workflow.description=Revisi\u00f3n y aprobaci\u00f3n paralela de contenido mediante el motor de flujo de trabajo Activiti +activitiParallelReview.workflow.title=Enviar documentos para revisar +activitiParallelReview.workflow.description=Solicitar aprobaci\u00f3n del documento de uno o m\u00e1s colegas activitiParallelReview.task.approved.description=Se ha revisado y aprobado el documento. activitiParallelReview.task.rejected.description=Se ha revisado y rechazado el documento. @@ -132,51 +132,60 @@ activitiParallelGroupReview.task.rejected.description=Se ha revisado y rechazado # Activiti Publish Web Content Workflow # -publishWebContent.workflow.title=Publicar contenido Web con Activiti +publishWebContent.workflow.title=Publicar contenido Web con Activiti publishWebContent.workflow.description=Publicaci\u00f3n de contenido Web utilizando el motor de flujo de trabajo Activiti ############################ # WORKFLOW MODEL LABELS # ############################ -#Adhoc Task Definitions +# Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=Iniciar tarea ad hoc +wf_workflowmodel.type.wf_submitAdhocTask.title=Tarea wf_workflowmodel.type.wf_submitAdhocTask.description=Asignar tarea a un colega wf_workflowmodel.property.wf_notifyMe.title=Notificarme wf_workflowmodel.property.wf_notifyMe.description=Notificarme cuando la tarea se haya completado -wf_workflowmodel.type.wf_adhocTask.title=Tarea ad hoc -wf_workflowmodel.type.wf_adhocTask.description=Tarea ad hoc asignada por un colega -wf_workflowmodel.type.wf_completedAdhocTask.title=Tarea ad hoc completada -wf_workflowmodel.type.wf_completedAdhocTask.description=Tarea ad hoc completada +wf_workflowmodel.type.wf_adhocTask.title=Tarea +wf_workflowmodel.type.wf_adhocTask.description=Tarea asignada por un colega +wf_workflowmodel.type.wf_completedAdhocTask.title=Tarea completada +wf_workflowmodel.type.wf_completedAdhocTask.description=Tarea completada -#Review And Approve Task Definitions +activitiAdhoc.task.wf_submitAdhocTask.title=Tarea +activitiAdhoc.task.wf_submitAdhocTask.description=Asignar tarea a un colega +activitiAdhoc.property.wf_notifyMe.title=Notificarme +activitiAdhoc.property.wf_notifyMe.description=Notificarme cuando la tarea se haya completado +activitiAdhoc.task.wf_adhocTask.title=Tarea +activitiAdhoc.task.wf_adhocTask.description=Tarea asignada por un colega +activitiAdhoc.task.wf_completedAdhocTask.title=Tarea completada +activitiAdhoc.task.wf_completedAdhocTask.description=Tarea completada + +# Review And Approve Task Definitions wf_workflowmodel.type.wf_submitReviewTask.title=Iniciar revisi\u00f3n wf_workflowmodel.type.wf_submitReviewTask.description=Enviar documentos para su revisi\u00f3n y aprobaci\u00f3n wf_workflowmodel.type.wf_reviewTask.title=Revisar wf_workflowmodel.type.wf_reviewTask.description=Revisar documentos para aprobarlos o rechazarlos -wf_workflowmodel.type.wf_rejectedTask.title=Rechazados -wf_workflowmodel.type.wf_rejectedTask.description=Rechazados -wf_workflowmodel.type.wf_approvedTask.title=Aprobados -wf_workflowmodel.type.wf_approvedTask.description=Aprobados +wf_workflowmodel.type.wf_rejectedTask.title=Rechazada +wf_workflowmodel.type.wf_rejectedTask.description=Rechazada +wf_workflowmodel.type.wf_approvedTask.title=Aprobada +wf_workflowmodel.type.wf_approvedTask.description=Aprobada -#Activiti Review And Approve Task Definitions +# Activiti Review And Approve Task Definitions wf_workflowmodel.type.wf_activitiReviewTask.title=Revisar wf_workflowmodel.type.wf_activitiReviewTask.description=Revisar documentos para aprobarlos o rechazarlos wf_workflowmodel.property.wf_reviewOutcome.title=Revisar resultado wf_workflowmodel.property.wf_reviewOutcome.description=Aprobar y rechazar el contenido -#Parallel Review And Approve Task Definitions +# Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=Iniciar revisi\u00f3n paralela -wf_workflowmodel.type.wf_submitParallelReviewTask.description=Enviar documentos para su revisi\u00f3n y aprobaci\u00f3n a una lista de personas +wf_workflowmodel.type.wf_submitParallelReviewTask.title=Enviar documentos para revisar +wf_workflowmodel.type.wf_submitParallelReviewTask.description=Solicitar aprobaci\u00f3n del documento de uno o m\u00e1s colegas wf_workflowmodel.property.wf_requiredApprovePercent.title=Porcentaje de aprobaci\u00f3n requerido wf_workflowmodel.property.wf_requiredApprovePercent.description=Porcentaje de revisores que deben confirmar su aprobaci\u00f3n -wf_workflowmodel.type.wf_rejectedParallelTask.title=Rechazados -wf_workflowmodel.type.wf_rejectedParallelTask.description=Rechazados -wf_workflowmodel.type.wf_approvedParallelTask.title=Aprobados -wf_workflowmodel.type.wf_approvedParallelTask.description=Aprobados +wf_workflowmodel.type.wf_rejectedParallelTask.title=Rechazada +wf_workflowmodel.type.wf_rejectedParallelTask.description=Rechazada +wf_workflowmodel.type.wf_approvedParallelTask.title=Aprobada +wf_workflowmodel.type.wf_approvedParallelTask.description=Aprobada wf_workflowmodel.property.wf_reviewerCount.title=N\u00famero de revisores wf_workflowmodel.property.wf_reviewerCount.description=N\u00famero de revisores wf_workflowmodel.property.wf_requiredPercent.title=Porcentaje de aprobaci\u00f3n requerido @@ -188,12 +197,33 @@ wf_workflowmodel.property.wf_actualPercent.description=Porcentaje de aprobaci\u0 wf_workflowmodel.property.wf_reviewOutcome.title=Revisar resultado wf_workflowmodel.property.wf_reviewOutcome.description=Revisar resultado -#Pooled Review Task Definitions +activitiParallelReview.task.wf_submitParallelReviewTask.title=Enviar documentos para revisar +activitiParallelReview.task.wf_submitParallelReviewTask.description=Solicitar aprobaci\u00f3n del documento de uno o m\u00e1s colegas +activitiParallelReview.property.wf_requiredApprovePercent.title=Porcentaje de aprobaci\u00f3n requerido +activitiParallelReview.property.wf_requiredApprovePercent.description=Porcentaje de revisores que deben confirmar su aprobaci\u00f3n +activitiParallelReview.task.wf_activitiReviewTask.title=Revisar +activitiParallelReview.task.wf_activitiReviewTask.description=Revisar documentos para aprobarlos o rechazarlos +activitiParallelReview.task.wf_rejectedParallelTask.title=Documento rechazado +activitiParallelReview.task.wf_rejectedParallelTask.description=Se han rechazado los documentos +activitiParallelReview.task.wf_approvedParallelTask.title=Documento aprobado +activitiParallelReview.task.wf_approvedParallelTask.description=Se han aprobado los documentos +activitiParallelReview.property.wf_reviewerCount.title=N\u00famero de revisores +activitiParallelReview.property.wf_reviewerCount.description=N\u00famero de revisores +activitiParallelReview.property.wf_requiredPercent.title=Porcentaje de aprobaci\u00f3n requerido +activitiParallelReview.property.wf_requiredPercent.description=Porcentaje de aprobaci\u00f3n requerido +activitiParallelReview.property.wf_approveCount.title=Revisores que han dado su aprobaci\u00f3n +activitiParallelReview.property.wf_approveCount.description=Revisores que han dado su aprobaci\u00f3n +activitiParallelReview.property.wf_actualPercent.title=Porcentaje de aprobaci\u00f3n real +activitiParallelReview.property.wf_actualPercent.description=Porcentaje de aprobaci\u00f3n real +activitiParallelReview.property.wf_reviewOutcome.title=Revisar resultado +activitiParallelReview.property.wf_reviewOutcome.description=Revisar resultado + +# Pooled Review Task Definitions wf_workflowmodel.type.wf_submitGroupReviewTask.title=Iniciar revisi\u00f3n en grupo wf_workflowmodel.type.wf_submitGroupReviewTask.description=Enviar documentos para su revisi\u00f3n y aprobaci\u00f3n a un grupo de personas -#List constraint display labels +# List constraint display labels listconstraint.wf_reviewOutcomeOptions.Approve=Aprobar listconstraint.wf_reviewOutcomeOptions.Reject=Rechazar diff --git a/config/alfresco/workflow/workflow-messages_fr.properties b/config/alfresco/workflow/workflow-messages_fr.properties index b734fe987d..b6bee70291 100755 --- a/config/alfresco/workflow/workflow-messages_fr.properties +++ b/config/alfresco/workflow/workflow-messages_fr.properties @@ -90,8 +90,8 @@ wf_parallelgroupreview.node.review.transition.approve.description=Approuver # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=Workflow adhoc -activitiAdhoc.workflow.description=Assignez une t\u00e2che arbitraire \u00e0 un coll\u00e8gue via le moteur de workflow Activiti +activitiAdhoc.workflow.title=Nouvelle t\u00e2che +activitiAdhoc.workflow.description=Assigner une nouvelle t\u00e2che \u00e0 vous-m\u00eame ou \u00e0 un coll\u00e8gue # # Activiti Review And Approve Workflow @@ -105,8 +105,8 @@ activitiReview.task.rejected.description=Le document a \u00e9t\u00e9 r\u00e9vis\ # Parallel Review Workflow # -activitiParallelReview.workflow.title=R\u00e9viser et approuver en parall\u00e8le -activitiParallelReview.workflow.description=R\u00e9viser et approuver en parall\u00e8le le contenu via le moteur de workflow Activiti +activitiParallelReview.workflow.title=Envoyer un (des) document(s) pour r\u00e9vision +activitiParallelReview.workflow.description=Demander l'approbation d'un document \u00e0 un ou plusieurs coll\u00e8gues activitiParallelReview.task.approved.description=Le document a \u00e9t\u00e9 r\u00e9vis\u00e9 et approuv\u00e9. activitiParallelReview.task.rejected.description=Le document a \u00e9t\u00e9 r\u00e9vis\u00e9 et rejet\u00e9. @@ -141,14 +141,23 @@ publishWebContent.workflow.description=Publication de contenu Web \u00e0 l'aide # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=D\u00e9marrer une t\u00e2che adhoc +wf_workflowmodel.type.wf_submitAdhocTask.title=T\u00e2che wf_workflowmodel.type.wf_submitAdhocTask.description=Allouer une t\u00e2che \u00e0 un coll\u00e8gue wf_workflowmodel.property.wf_notifyMe.title=M'avertir wf_workflowmodel.property.wf_notifyMe.description=M'avertir quand la t\u00e2che est achev\u00e9e -wf_workflowmodel.type.wf_adhocTask.title=T\u00e2che Adhoc -wf_workflowmodel.type.wf_adhocTask.description=T\u00e2che adhoc allou\u00e9e par un coll\u00e8gue -wf_workflowmodel.type.wf_completedAdhocTask.title=T\u00e2che adhoc termin\u00e9e -wf_workflowmodel.type.wf_completedAdhocTask.description=T\u00e2che adhoc termin\u00e9e +wf_workflowmodel.type.wf_adhocTask.title=T\u00e2che +wf_workflowmodel.type.wf_adhocTask.description=T\u00e2che attribu\u00e9e par un coll\u00e8gue +wf_workflowmodel.type.wf_completedAdhocTask.title=T\u00e2che termin\u00e9e +wf_workflowmodel.type.wf_completedAdhocTask.description=T\u00e2che termin\u00e9e + +activitiAdhoc.task.wf_submitAdhocTask.title=T\u00e2che +activitiAdhoc.task.wf_submitAdhocTask.description=Allouer une t\u00e2che \u00e0 un coll\u00e8gue +activitiAdhoc.property.wf_notifyMe.title=M'avertir +activitiAdhoc.property.wf_notifyMe.description=M'avertir quand la t\u00e2che est achev\u00e9e +activitiAdhoc.task.wf_adhocTask.title=T\u00e2che +activitiAdhoc.task.wf_adhocTask.description=T\u00e2che attribu\u00e9e par un coll\u00e8gue +activitiAdhoc.task.wf_completedAdhocTask.title=T\u00e2che termin\u00e9e +activitiAdhoc.task.wf_completedAdhocTask.description=T\u00e2che termin\u00e9e # Review And Approve Task Definitions @@ -169,8 +178,8 @@ wf_workflowmodel.property.wf_reviewOutcome.description=Approuver ou rejeter le c # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=D\u00e9marrer la r\u00e9vision en parall\u00e8le -wf_workflowmodel.type.wf_submitParallelReviewTask.description=Soumettre des documents pour r\u00e9vision et approbation \u00e0 une liste de personnes +wf_workflowmodel.type.wf_submitParallelReviewTask.title=Envoyer un (des) document(s) pour r\u00e9vision +wf_workflowmodel.type.wf_submitParallelReviewTask.description=Demander l'approbation d'un document \u00e0 un ou plusieurs coll\u00e8gues wf_workflowmodel.property.wf_requiredApprovePercent.title=Pourcentage d'approbation requis wf_workflowmodel.property.wf_requiredApprovePercent.description=Pourcentage de r\u00e9viseurs qui doivent approuver le contenu pour qu'il soit approuv\u00e9 wf_workflowmodel.type.wf_rejectedParallelTask.title=Rejet\u00e9 @@ -188,16 +197,38 @@ wf_workflowmodel.property.wf_actualPercent.description=Pourcentage d'approbation wf_workflowmodel.property.wf_reviewOutcome.title=R\u00e9sultat de la r\u00e9vision wf_workflowmodel.property.wf_reviewOutcome.description=R\u00e9sultat de la r\u00e9vision +activitiParallelReview.task.wf_submitParallelReviewTask.title=Envoyer un (des) document(s) pour r\u00e9vision +activitiParallelReview.task.wf_submitParallelReviewTask.description=Demander l'approbation d'un document \u00e0 un ou plusieurs coll\u00e8gues +activitiParallelReview.property.wf_requiredApprovePercent.title=Pourcentage d'approbation requis +activitiParallelReview.property.wf_requiredApprovePercent.description=Pourcentage de r\u00e9viseurs qui doivent approuver le contenu pour qu'il soit approuv\u00e9 +activitiParallelReview.task.wf_activitiReviewTask.title=R\u00e9viser +activitiParallelReview.task.wf_activitiReviewTask.description=R\u00e9viser des documents pour les approuver ou les rejeter +activitiParallelReview.task.wf_rejectedParallelTask.title=Document rejet\u00e9 +activitiParallelReview.task.wf_rejectedParallelTask.description=Un ou plusieurs documents ont \u00e9t\u00e9 rejet\u00e9s +activitiParallelReview.task.wf_approvedParallelTask.title=Document approuv\u00e9 +activitiParallelReview.task.wf_approvedParallelTask.description=Un ou plusieurs documents ont \u00e9t\u00e9 approuv\u00e9s +activitiParallelReview.property.wf_reviewerCount.title=Nombre de r\u00e9viseurs +activitiParallelReview.property.wf_reviewerCount.description=Nombre de r\u00e9viseurs +activitiParallelReview.property.wf_requiredPercent.title=Pourcentage d'approbation requis +activitiParallelReview.property.wf_requiredPercent.description=Pourcentage d'approbation requis +activitiParallelReview.property.wf_approveCount.title=R\u00e9viseurs qui ont approuv\u00e9 +activitiParallelReview.property.wf_approveCount.description=R\u00e9viseurs qui ont approuv\u00e9 +activitiParallelReview.property.wf_actualPercent.title=Pourcentage d'approbation exact +activitiParallelReview.property.wf_actualPercent.description=Pourcentage d'approbation exact +activitiParallelReview.property.wf_reviewOutcome.title=R\u00e9sultat de la r\u00e9vision +activitiParallelReview.property.wf_reviewOutcome.description=R\u00e9sultat de la r\u00e9vision + # Pooled Review Task Definitions wf_workflowmodel.type.wf_submitGroupReviewTask.title=D\u00e9marrer la r\u00e9vision de groupe wf_workflowmodel.type.wf_submitGroupReviewTask.description=Soumettre des documents pour r\u00e9vision et approbation \u00e0 un groupe de personnes -#List constraint display labels +# List constraint display labels listconstraint.wf_reviewOutcomeOptions.Approve=Approuver listconstraint.wf_reviewOutcomeOptions.Reject=Rejeter # The result of a workflow task seen on Workflow Details - History # This is formed by prefixing workflowtask.outcome to the value of the wf:outcome property workflowtask.outcome.Approve=Approuv\u00e9 -workflowtask.outcome.Reject=Rejet\u00e9 \ No newline at end of file +workflowtask.outcome.Reject=Rejet\u00e9 + diff --git a/config/alfresco/workflow/workflow-messages_it.properties b/config/alfresco/workflow/workflow-messages_it.properties index d7e95823d2..801a550b1c 100755 --- a/config/alfresco/workflow/workflow-messages_it.properties +++ b/config/alfresco/workflow/workflow-messages_it.properties @@ -90,8 +90,8 @@ wf_parallelgroupreview.node.review.transition.approve.description=Approva # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=Workflow ad-hoc -activitiAdhoc.workflow.description=Assegna un compito arbitrario a un collega utilizzando il motore di workflow Activiti +activitiAdhoc.workflow.title=Nuovo compito +activitiAdhoc.workflow.description=Assegna un nuovo compito a te stesso o a un collega # # Activiti Review And Approve Workflow @@ -105,8 +105,8 @@ activitiReview.task.rejected.description=Il documento \u00e8 stato esaminato e r # Parallel Review Workflow # -activitiParallelReview.workflow.title=Esamina e approva in parallelo -activitiParallelReview.workflow.description=Esamina e approva in parallelo contenuti utilizzando il motore di workflow Activiti +activitiParallelReview.workflow.title=Invia documenti da esaminare +activitiParallelReview.workflow.description=Richiedi l'approvazione del documento da uno o pi\u00f9 colleghi activitiParallelReview.task.approved.description=Il documento \u00e8 stato esaminato e approvato. activitiParallelReview.task.rejected.description=Il documento \u00e8 stato esaminato e respinto. @@ -141,14 +141,23 @@ publishWebContent.workflow.description=Pubblicazione di contenuti web utilizzand # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=Avvia compito ad-hoc +wf_workflowmodel.type.wf_submitAdhocTask.title=Compito wf_workflowmodel.type.wf_submitAdhocTask.description=Assegna il compito a un collega wf_workflowmodel.property.wf_notifyMe.title=Notifica wf_workflowmodel.property.wf_notifyMe.description=Notifica l'utente quando il compito \u00e8 stato completato -wf_workflowmodel.type.wf_adhocTask.title=Compito ad-hoc -wf_workflowmodel.type.wf_adhocTask.description=Compito ad-hoc assegnato dal collega -wf_workflowmodel.type.wf_completedAdhocTask.title=Compito ad-hoc completato -wf_workflowmodel.type.wf_completedAdhocTask.description=Compito ad-hoc completato +wf_workflowmodel.type.wf_adhocTask.title=Compito +wf_workflowmodel.type.wf_adhocTask.description=Compito assegnato dal collega +wf_workflowmodel.type.wf_completedAdhocTask.title=Compito completato +wf_workflowmodel.type.wf_completedAdhocTask.description=Compito completato + +activitiAdhoc.task.wf_submitAdhocTask.title=Compito +activitiAdhoc.task.wf_submitAdhocTask.description=Assegna il compito a un collega +activitiAdhoc.property.wf_notifyMe.title=Notifica +activitiAdhoc.property.wf_notifyMe.description=Notifica l'utente quando il compito \u00e8 stato completato +activitiAdhoc.task.wf_adhocTask.title=Compito +activitiAdhoc.task.wf_adhocTask.description=Compito assegnato dal collega +activitiAdhoc.task.wf_completedAdhocTask.title=Compito completato +activitiAdhoc.task.wf_completedAdhocTask.description=Compito completato # Review And Approve Task Definitions @@ -169,8 +178,8 @@ wf_workflowmodel.property.wf_reviewOutcome.description=Approva o respingi il con # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=Avvia esame parallelo -wf_workflowmodel.type.wf_submitParallelReviewTask.description=Invia i documenti per l'esame e l'approvazione a un elenco di persone +wf_workflowmodel.type.wf_submitParallelReviewTask.title=Invia documenti da esaminare +wf_workflowmodel.type.wf_submitParallelReviewTask.description=Richiedi l'approvazione del documento da uno o pi\u00f9 colleghi wf_workflowmodel.property.wf_requiredApprovePercent.title=Percentuale di approvazioni richiesta wf_workflowmodel.property.wf_requiredApprovePercent.description=Percentuale di esaminatori che devono approvare il contenuto per l'approvazione wf_workflowmodel.type.wf_rejectedParallelTask.title=Respinto @@ -188,6 +197,27 @@ wf_workflowmodel.property.wf_actualPercent.description=Percentuale di approvazio wf_workflowmodel.property.wf_reviewOutcome.title=Esamina risultato wf_workflowmodel.property.wf_reviewOutcome.description=Esamina risultato +activitiParallelReview.task.wf_submitParallelReviewTask.title=Invia documenti da esaminare +activitiParallelReview.task.wf_submitParallelReviewTask.description=Richiedi l'approvazione del documento da uno o pi\u00f9 colleghi +activitiParallelReview.property.wf_requiredApprovePercent.title=Percentuale di approvazioni richiesta +activitiParallelReview.property.wf_requiredApprovePercent.description=Percentuale di esaminatori che devono approvare il contenuto per l'approvazione +activitiParallelReview.task.wf_activitiReviewTask.title=Esamina +activitiParallelReview.task.wf_activitiReviewTask.description=Esamina i documenti per approvarli o respingerli +activitiParallelReview.task.wf_rejectedParallelTask.title=Documento respinto +activitiParallelReview.task.wf_rejectedParallelTask.description=I documenti sono stati respinti +activitiParallelReview.task.wf_approvedParallelTask.title=Documento approvato +activitiParallelReview.task.wf_approvedParallelTask.description=I documenti sono stati approvati +activitiParallelReview.property.wf_reviewerCount.title=Numero di esaminatori +activitiParallelReview.property.wf_reviewerCount.description=Numero di esaminatori +activitiParallelReview.property.wf_requiredPercent.title=Percentuale di approvazioni richiesta +activitiParallelReview.property.wf_requiredPercent.description=Percentuale di approvazioni richiesta +activitiParallelReview.property.wf_approveCount.title=Esaminatori che hanno approvato il contenuto +activitiParallelReview.property.wf_approveCount.description=Esaminatori che hanno approvato il contenuto +activitiParallelReview.property.wf_actualPercent.title=Percentuale di approvazioni effettiva +activitiParallelReview.property.wf_actualPercent.description=Percentuale di approvazioni effettiva +activitiParallelReview.property.wf_reviewOutcome.title=Esamina risultato +activitiParallelReview.property.wf_reviewOutcome.description=Esamina risultato + # Pooled Review Task Definitions wf_workflowmodel.type.wf_submitGroupReviewTask.title=Avvia esame di gruppo @@ -200,4 +230,5 @@ listconstraint.wf_reviewOutcomeOptions.Reject=Respingi # The result of a workflow task seen on Workflow Details - History # This is formed by prefixing workflowtask.outcome to the value of the wf:outcome property workflowtask.outcome.Approve=Approvato -workflowtask.outcome.Reject=Respinto \ No newline at end of file +workflowtask.outcome.Reject=Respinto + diff --git a/config/alfresco/workflow/workflow-messages_ja.properties b/config/alfresco/workflow/workflow-messages_ja.properties index 781b1b1f22..969cd8eaee 100755 --- a/config/alfresco/workflow/workflow-messages_ja.properties +++ b/config/alfresco/workflow/workflow-messages_ja.properties @@ -1,12 +1,12 @@ # Display labels for out-of-the-box Content-oriented Workflows #################### -# JBPM WORKFLOWS # +# JBPM WORKFLOWS # #################### -# +# # Adhoc Task Workflow -# +# wf_adhoc.workflow.title=\u30a2\u30c9\u30db\u30c3\u30af\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\uff08JBPM\uff09 wf_adhoc.workflow.description=JBPM\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30a8\u30f3\u30b8\u30f3\u3092\u4f7f\u7528\u3057\u3066\u4efb\u610f\u306e\u30bf\u30b9\u30af\u3092\u540c\u50da\u306b\u5272\u308a\u5f53\u3066\u308b @@ -83,45 +83,45 @@ wf_parallelgroupreview.node.review.transition.approve.title=\u627f\u8a8d wf_parallelgroupreview.node.review.transition.approve.description=\u627f\u8a8d ####################### -# ACTIVITI WORKFLOWS # +# ACTIVITI WORKFLOWS # ####################### -# +# # Activiti Adhoc Task Workflow -# +# -activitiAdhoc.workflow.title=\u30a2\u30c9\u30db\u30c3\u30af\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\uff08Activiti\uff09 -activitiAdhoc.workflow.description=Activiti\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30a8\u30f3\u30b8\u30f3\u3092\u4f7f\u7528\u3057\u3066\u4efb\u610f\u306e\u30bf\u30b9\u30af\u3092\u540c\u50da\u306b\u5272\u308a\u5f53\u3066\u308b +activitiAdhoc.workflow.title=\u65b0\u3057\u3044\u30bf\u30b9\u30af +activitiAdhoc.workflow.description=\u65b0\u3057\u3044\u30bf\u30b9\u30af\u3092\u81ea\u5206\u307e\u305f\u306f\u540c\u50da\u306b\u5272\u308a\u5f53\u3066\u308b -# +# # Activiti Review And Approve Workflow -# +# activitiReview.workflow.title=\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\uff08Activiti\uff09 activitiReview.workflow.description=Activiti\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30a8\u30f3\u30b8\u30f3\u3092\u4f7f\u7528\u3057\u3066\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u3092\u884c\u3046 activitiReview.task.approved.description=\u3053\u306e\u6587\u66f8\u306f\u30ec\u30d3\u30e5\u30fc\u3055\u308c\u3001\u627f\u8a8d\u3055\u308c\u307e\u3057\u305f\u3002 activitiReview.task.rejected.description=\u3053\u306e\u6587\u66f8\u306f\u30ec\u30d3\u30e5\u30fc\u3055\u308c\u3001\u5374\u4e0b\u3055\u308c\u307e\u3057\u305f\u3002 -# +# # Parallel Review Workflow -# +# -activitiParallelReview.workflow.title=\u4e26\u5217\u306e\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\uff08Activiti\uff09 -activitiParallelReview.workflow.description=Activiti\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30a8\u30f3\u30b8\u30f3\u3092\u4f7f\u7528\u3057\u3066\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u4e26\u5217\u306e\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u3092\u884c\u3046 +activitiParallelReview.workflow.title=\u6587\u66f8\u3092\u30ec\u30d3\u30e5\u30fc\u7528\u306b\u9001\u4fe1 +activitiParallelReview.workflow.description=1\u540d\u4ee5\u4e0a\u306e\u540c\u50da\u304b\u3089\u306e\u6587\u66f8\u627f\u8a8d\u3092\u8981\u6c42\u3059\u308b activitiParallelReview.task.approved.description=\u3053\u306e\u6587\u66f8\u306f\u30ec\u30d3\u30e5\u30fc\u3055\u308c\u3001\u627f\u8a8d\u3055\u308c\u307e\u3057\u305f\u3002 activitiParallelReview.task.rejected.description=\u3053\u306e\u6587\u66f8\u306f\u30ec\u30d3\u30e5\u30fc\u3055\u308c\u3001\u5374\u4e0b\u3055\u308c\u307e\u3057\u305f\u3002 -# +# # Activiti Pooled Review Workflow -# +# activitiReviewPooled.workflow.title=\u30d7\u30fc\u30eb\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\uff08Activiti\uff09 activitiReviewPooled.workflow.description=Activiti\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30a8\u30f3\u30b8\u30f3\u3092\u4f7f\u7528\u3057\u3066\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30d7\u30fc\u30eb\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u3092\u884c\u3046 activitiReviewPooled.task.approved.description=\u3053\u306e\u6587\u66f8\u306f\u30ec\u30d3\u30e5\u30fc\u3055\u308c\u3001\u627f\u8a8d\u3055\u308c\u307e\u3057\u305f\u3002 activitiReviewPooled.task.rejected.description=\u3053\u306e\u6587\u66f8\u306f\u30ec\u30d3\u30e5\u30fc\u3055\u308c\u3001\u5374\u4e0b\u3055\u308c\u307e\u3057\u305f\u3002 -# +# # Activiti Parallel Group Review Workflow -# +# activitiParallelGroupReview.workflow.title=\u30b0\u30eb\u30fc\u30d7\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\uff08Activiti\uff09 activitiParallelGroupReview.workflow.description=Activiti\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30a8\u30f3\u30b8\u30f3\u3092\u4f7f\u7528\u3057\u3066\u30b3\u30f3\u30c6\u30f3\u30c4\u306e\u30b0\u30eb\u30fc\u30d7\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u3092\u884c\u3046 @@ -135,20 +135,29 @@ activitiParallelGroupReview.task.rejected.description=\u3053\u306e\u6587\u66f8\u publishWebContent.workflow.title=\u30a6\u30a7\u30d6\u30b3\u30f3\u30c6\u30f3\u30c4\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3\u30d7\u30ed\u30bb\u30b9\u3092\u516c\u958b\u3059\u308b publishWebContent.workflow.description=\u30a2\u30af\u30c6\u30a3\u30d3\u30c6\u30a3\u30ef\u30fc\u30af\u30d5\u30ed\u30fc\u30a8\u30f3\u30b8\u30f3\u3092\u4f7f\u7528\u3057\u3066\u30a6\u30a7\u30d6\u30b3\u30f3\u30c6\u30f3\u30c4\u3092\u516c\u958b\u3059\u308b -#################### -# WORKFLOW MODEL LABELS # -#################### +############################ +# WORKFLOW MODEL LABELS # +############################ # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=\u30a2\u30c9\u30db\u30c3\u30af\u30bf\u30b9\u30af\u306e\u958b\u59cb -wf_workflowmodel.type.wf_submitAdhocTask.description=\u540c\u50da\u306b\u30bf\u30b9\u30af\u3092\u5272\u308a\u5f53\u3066\u308b +wf_workflowmodel.type.wf_submitAdhocTask.title=\u30bf\u30b9\u30af +wf_workflowmodel.type.wf_submitAdhocTask.description=\u540c\u50da\u306b\u30bf\u30b9\u30af\u3092\u5272\u308a\u5f53\u3066\u307e\u3059\u3002 wf_workflowmodel.property.wf_notifyMe.title=\u81ea\u5206\u306b\u901a\u77e5\u3059\u308b -wf_workflowmodel.property.wf_notifyMe.description=\u30bf\u30b9\u30af\u304c\u5b8c\u4e86\u3057\u305f\u3089\u81ea\u5206\u306b\u901a\u77e5\u3059\u308b -wf_workflowmodel.type.wf_adhocTask.title=\u30a2\u30c9\u30db\u30c3\u30af\u30bf\u30b9\u30af -wf_workflowmodel.type.wf_adhocTask.description=\u540c\u50da\u306b\u5272\u308a\u5f53\u3066\u3089\u308c\u305f\u30a2\u30c9\u30db\u30c3\u30af\u30bf\u30b9\u30af -wf_workflowmodel.type.wf_completedAdhocTask.title=\u5b8c\u4e86\u6e08\u307f\u30a2\u30c9\u30db\u30c3\u30af\u30bf\u30b9\u30af -wf_workflowmodel.type.wf_completedAdhocTask.description=\u5b8c\u4e86\u6e08\u307f\u30a2\u30c9\u30db\u30c3\u30af\u30bf\u30b9\u30af +wf_workflowmodel.property.wf_notifyMe.description=\u30bf\u30b9\u30af\u306e\u5b8c\u4e86\u6642\u306b\u81ea\u5206\u306b\u901a\u77e5\u3057\u307e\u3059\u3002 +wf_workflowmodel.type.wf_adhocTask.title=\u30bf\u30b9\u30af +wf_workflowmodel.type.wf_adhocTask.description=\u540c\u50da\u306b\u5272\u308a\u5f53\u3066\u3089\u308c\u305f\u30bf\u30b9\u30af +wf_workflowmodel.type.wf_completedAdhocTask.title=\u5b8c\u4e86\u6e08\u307f\u30bf\u30b9\u30af +wf_workflowmodel.type.wf_completedAdhocTask.description=\u5b8c\u4e86\u6e08\u307f\u30bf\u30b9\u30af + +activitiAdhoc.task.wf_submitAdhocTask.title=\u30bf\u30b9\u30af +activitiAdhoc.task.wf_submitAdhocTask.description=\u540c\u50da\u306b\u30bf\u30b9\u30af\u3092\u5272\u308a\u5f53\u3066\u307e\u3059\u3002 +activitiAdhoc.property.wf_notifyMe.title=\u81ea\u5206\u306b\u901a\u77e5\u3059\u308b +activitiAdhoc.property.wf_notifyMe.description=\u30bf\u30b9\u30af\u306e\u5b8c\u4e86\u6642\u306b\u81ea\u5206\u306b\u901a\u77e5\u3057\u307e\u3059\u3002 +activitiAdhoc.task.wf_adhocTask.title=\u30bf\u30b9\u30af +activitiAdhoc.task.wf_adhocTask.description=\u540c\u50da\u306b\u5272\u308a\u5f53\u3066\u3089\u308c\u305f\u30bf\u30b9\u30af +activitiAdhoc.task.wf_completedAdhocTask.title=\u5b8c\u4e86\u6e08\u307f\u30bf\u30b9\u30af +activitiAdhoc.task.wf_completedAdhocTask.description=\u5b8c\u4e86\u6e08\u307f\u30bf\u30b9\u30af # Review And Approve Task Definitions @@ -169,8 +178,8 @@ wf_workflowmodel.property.wf_reviewOutcome.description=\u30b3\u30f3\u30c6\u30f3\ # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=\u4e26\u5217\u306e\u30ec\u30d3\u30e5\u30fc\u306e\u958b\u59cb -wf_workflowmodel.type.wf_submitParallelReviewTask.description=\u30ec\u30d3\u30e5\u30fc\u3068\u627f\u8a8d\u7528\u306b\u6587\u66f8\u3092\u30e6\u30fc\u30b6\u30fc\u306e\u30ea\u30b9\u30c8\u3078\u9001\u4fe1 +wf_workflowmodel.type.wf_submitParallelReviewTask.title=\u6587\u66f8\u3092\u30ec\u30d3\u30e5\u30fc\u7528\u306b\u9001\u4fe1 +wf_workflowmodel.type.wf_submitParallelReviewTask.description=1\u540d\u4ee5\u4e0a\u306e\u540c\u50da\u304b\u3089\u306e\u6587\u66f8\u627f\u8a8d\u3092\u8981\u6c42\u3059\u308b wf_workflowmodel.property.wf_requiredApprovePercent.title=\u8981\u6c42\u3055\u308c\u305f\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 wf_workflowmodel.property.wf_requiredApprovePercent.description=\u627f\u8a8d\u3092\u53d7\u3051\u308b\u305f\u3081\u306b\u627f\u8a8d\u3059\u308b\u5fc5\u8981\u306e\u3042\u308b\u30ec\u30d3\u30e5\u30a2\u306e\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 wf_workflowmodel.type.wf_rejectedParallelTask.title=\u5374\u4e0b\u6e08\u307f @@ -188,6 +197,27 @@ wf_workflowmodel.property.wf_actualPercent.description=\u5b9f\u969b\u306e\u627f\ wf_workflowmodel.property.wf_reviewOutcome.title=\u30ec\u30d3\u30e5\u30fc\u7d50\u679c wf_workflowmodel.property.wf_reviewOutcome.description=\u30ec\u30d3\u30e5\u30fc\u7d50\u679c +activitiParallelReview.task.wf_submitParallelReviewTask.title=\u6587\u66f8\u3092\u30ec\u30d3\u30e5\u30fc\u7528\u306b\u9001\u4fe1 +activitiParallelReview.task.wf_submitParallelReviewTask.description=1\u540d\u4ee5\u4e0a\u306e\u540c\u50da\u304b\u3089\u306e\u6587\u66f8\u627f\u8a8d\u3092\u8981\u6c42\u3059\u308b +activitiParallelReview.property.wf_requiredApprovePercent.title=\u8981\u6c42\u3055\u308c\u305f\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +activitiParallelReview.property.wf_requiredApprovePercent.description=\u627f\u8a8d\u3092\u53d7\u3051\u308b\u305f\u3081\u306b\u627f\u8a8d\u3059\u308b\u5fc5\u8981\u306e\u3042\u308b\u30ec\u30d3\u30e5\u30a2\u306e\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +activitiParallelReview.task.wf_activitiReviewTask.title=\u30ec\u30d3\u30e5\u30fc +activitiParallelReview.task.wf_activitiReviewTask.description=\u6587\u66f8\u3092\u30ec\u30d3\u30e5\u30fc\u3057\u305d\u308c\u3092\u627f\u8a8d\u307e\u305f\u306f\u5374\u4e0b\u3059\u308b +activitiParallelReview.task.wf_rejectedParallelTask.title=\u6587\u66f8\u5374\u4e0b +activitiParallelReview.task.wf_rejectedParallelTask.description=\u6587\u66f8\u306f\u5374\u4e0b\u3055\u308c\u307e\u3057\u305f +activitiParallelReview.task.wf_approvedParallelTask.title=\u6587\u66f8\u627f\u8a8d +activitiParallelReview.task.wf_approvedParallelTask.description=\u6587\u66f8\u306f\u627f\u8a8d\u3055\u308c\u307e\u3057\u305f +activitiParallelReview.property.wf_reviewerCount.title=\u30ec\u30d3\u30e5\u30a2\u306e\u6570 +activitiParallelReview.property.wf_reviewerCount.description=\u30ec\u30d3\u30e5\u30a2\u306e\u6570 +activitiParallelReview.property.wf_requiredPercent.title=\u8981\u6c42\u3055\u308c\u305f\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +activitiParallelReview.property.wf_requiredPercent.description=\u8981\u6c42\u3055\u308c\u305f\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +activitiParallelReview.property.wf_approveCount.title=\u627f\u8a8d\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30a2 +activitiParallelReview.property.wf_approveCount.description=\u627f\u8a8d\u3055\u308c\u305f\u30ec\u30d3\u30e5\u30a2 +activitiParallelReview.property.wf_actualPercent.title=\u5b9f\u969b\u306e\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +activitiParallelReview.property.wf_actualPercent.description=\u5b9f\u969b\u306e\u627f\u8a8d\u30d1\u30fc\u30bb\u30f3\u30c6\u30fc\u30b8 +activitiParallelReview.property.wf_reviewOutcome.title=\u30ec\u30d3\u30e5\u30fc\u7d50\u679c +activitiParallelReview.property.wf_reviewOutcome.description=\u30ec\u30d3\u30e5\u30fc\u7d50\u679c + # Pooled Review Task Definitions wf_workflowmodel.type.wf_submitGroupReviewTask.title=\u30b0\u30eb\u30fc\u30d7\u30ec\u30d3\u30e5\u30fc\u306e\u958b\u59cb @@ -201,3 +231,4 @@ listconstraint.wf_reviewOutcomeOptions.Reject=\u5374\u4e0b # This is formed by prefixing workflowtask.outcome to the value of the wf:outcome property workflowtask.outcome.Approve=\u627f\u8a8d\u6e08\u307f workflowtask.outcome.Reject=\u5374\u4e0b\u6e08\u307f + diff --git a/config/alfresco/workflow/workflow-messages_nl.properties b/config/alfresco/workflow/workflow-messages_nl.properties index c5a8eae5e0..0a4e24f5bf 100755 --- a/config/alfresco/workflow/workflow-messages_nl.properties +++ b/config/alfresco/workflow/workflow-messages_nl.properties @@ -90,8 +90,8 @@ wf_parallelgroupreview.node.review.transition.approve.description=Goedkeuren # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=Ad-hocwerkstroom -activitiAdhoc.workflow.description=Willekeurige taak aan collega toewijzen met Activiti-werkstroomengine +activitiAdhoc.workflow.title=Nieuwe taak +activitiAdhoc.workflow.description=Een nieuwe taak toewijzen aan uzelf of een collega # # Activiti Review And Approve Workflow @@ -105,8 +105,8 @@ activitiReview.task.rejected.description=Het document is gereviseerd en afgeweze # Parallel Review Workflow # -activitiParallelReview.workflow.title=Parallel reviseren en goedkeuren -activitiParallelReview.workflow.description=Parallelle revisie en goedkeuring van content met Activiti-werkstroomengine +activitiParallelReview.workflow.title=Document(en) ter revisie verzenden +activitiParallelReview.workflow.description=Een of meer collega's vragen een document goed te keuren activitiParallelReview.task.approved.description=Het document is gereviseerd en goedgekeurd. activitiParallelReview.task.rejected.description=Het document is gereviseerd en afgewezen. @@ -123,7 +123,7 @@ activitiReviewPooled.task.rejected.description=Het document is gereviseerd en af # Activiti Parallel Group Review Workflow # -activitiParallelGroupReview.workflow.title=Groepsreviserie en -goedkeuring +activitiParallelGroupReview.workflow.title=Groepsrevisie en -goedkeuring activitiParallelGroupReview.workflow.description=Groepsrevisie en -goedkeuring van content met Activiti-werkstroomengine activitiParallelGroupReview.task.approved.description=Het document is gereviseerd en goedgekeurd. activitiParallelGroupReview.task.rejected.description=Het document is gereviseerd en afgewezen. @@ -141,14 +141,23 @@ publishWebContent.workflow.description=Webcontent publiceren met de Activiti-wer # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=Ad-hoctaak starten +wf_workflowmodel.type.wf_submitAdhocTask.title=Taak wf_workflowmodel.type.wf_submitAdhocTask.description=Taak toewijzen aan collega wf_workflowmodel.property.wf_notifyMe.title=Mij op de hoogte stellen wf_workflowmodel.property.wf_notifyMe.description=Mij op de hoogte stellen wanneer taak is voltooid -wf_workflowmodel.type.wf_adhocTask.title=Ad-hoctaak -wf_workflowmodel.type.wf_adhocTask.description=Ad-hoctaak die is toegewezen door collega -wf_workflowmodel.type.wf_completedAdhocTask.title=Ad-hoctaak voltooid -wf_workflowmodel.type.wf_completedAdhocTask.description=Ad-hoctaak voltooid +wf_workflowmodel.type.wf_adhocTask.title=Taak +wf_workflowmodel.type.wf_adhocTask.description=Taak die is toegewezen door collega +wf_workflowmodel.type.wf_completedAdhocTask.title=Taak voltooid +wf_workflowmodel.type.wf_completedAdhocTask.description=Taak voltooid + +activitiAdhoc.task.wf_submitAdhocTask.title=Taak +activitiAdhoc.task.wf_submitAdhocTask.description=Taak toewijzen aan collega +activitiAdhoc.property.wf_notifyMe.title=Mij op de hoogte stellen +activitiAdhoc.property.wf_notifyMe.description=Mij op de hoogte stellen wanneer taak is voltooid +activitiAdhoc.task.wf_adhocTask.title=Taak +activitiAdhoc.task.wf_adhocTask.description=Taak die is toegewezen door collega +activitiAdhoc.task.wf_completedAdhocTask.title=Taak voltooid +activitiAdhoc.task.wf_completedAdhocTask.description=Taak voltooid # Review And Approve Task Definitions @@ -169,8 +178,8 @@ wf_workflowmodel.property.wf_reviewOutcome.description=De content goedkeuren of # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=Parallelle revisie starten -wf_workflowmodel.type.wf_submitParallelReviewTask.description=Documenten ter revisie en goedkeuring verzenden naar een lijst met mensen +wf_workflowmodel.type.wf_submitParallelReviewTask.title=Document(en) ter revisie verzenden +wf_workflowmodel.type.wf_submitParallelReviewTask.description=Een of meer collega's vragen een document goed te keuren wf_workflowmodel.property.wf_requiredApprovePercent.title=Vereist goedkeuringspercentage wf_workflowmodel.property.wf_requiredApprovePercent.description=Percentage van revisoren die hun goedkeuring moeten geven wf_workflowmodel.type.wf_rejectedParallelTask.title=Afgewezen @@ -188,6 +197,27 @@ wf_workflowmodel.property.wf_actualPercent.description=Goedkeuringspercentage wf_workflowmodel.property.wf_reviewOutcome.title=Resultaat van revisie wf_workflowmodel.property.wf_reviewOutcome.description=Resultaat van revisie +activitiParallelReview.task.wf_submitParallelReviewTask.title=Document(en) ter revisie verzenden +activitiParallelReview.task.wf_submitParallelReviewTask.description=Een of meer collega's vragen een document goed te keuren +activitiParallelReview.property.wf_requiredApprovePercent.title=Vereist goedkeuringspercentage +activitiParallelReview.property.wf_requiredApprovePercent.description=Percentage van revisoren die hun goedkeuring moeten geven +activitiParallelReview.task.wf_activitiReviewTask.title=Reviseren +activitiParallelReview.task.wf_activitiReviewTask.description=Documenten reviseren om ze goed te keuren of af te wijzen +activitiParallelReview.task.wf_rejectedParallelTask.title=Document afgewezen +activitiParallelReview.task.wf_rejectedParallelTask.description=Een of meer documenten zijn afgewezen +activitiParallelReview.task.wf_approvedParallelTask.title=Document goedgekeurd +activitiParallelReview.task.wf_approvedParallelTask.description=Een of meer documenten zijn goedgekeurd +activitiParallelReview.property.wf_reviewerCount.title=Aantal revisoren +activitiParallelReview.property.wf_reviewerCount.description=Aantal revisoren +activitiParallelReview.property.wf_requiredPercent.title=Vereist goedkeuringspercentage +activitiParallelReview.property.wf_requiredPercent.description=Vereist goedkeuringspercentage +activitiParallelReview.property.wf_approveCount.title=Revisoren die hun goedkeuring hebben gegeven +activitiParallelReview.property.wf_approveCount.description=Revisoren die hun goedkeuring hebben gegeven +activitiParallelReview.property.wf_actualPercent.title=Goedkeuringspercentage +activitiParallelReview.property.wf_actualPercent.description=Goedkeuringspercentage +activitiParallelReview.property.wf_reviewOutcome.title=Resultaat van revisie +activitiParallelReview.property.wf_reviewOutcome.description=Resultaat van revisie + # Pooled Review Task Definitions wf_workflowmodel.type.wf_submitGroupReviewTask.title=Groepsrevisie starten @@ -200,4 +230,5 @@ listconstraint.wf_reviewOutcomeOptions.Reject=Afwijzen # The result of a workflow task seen on Workflow Details - History # This is formed by prefixing workflowtask.outcome to the value of the wf:outcome property workflowtask.outcome.Approve=Goedgekeurd -workflowtask.outcome.Reject=Afgewezen \ No newline at end of file +workflowtask.outcome.Reject=Afgewezen + diff --git a/config/alfresco/workflow/workflow-messages_ru.properties b/config/alfresco/workflow/workflow-messages_ru.properties index 01057301e7..eaca5106b1 100755 --- a/config/alfresco/workflow/workflow-messages_ru.properties +++ b/config/alfresco/workflow/workflow-messages_ru.properties @@ -7,80 +7,80 @@ # # Adhoc Task Workflow # -wf_adhoc.workflow.title=\u0421\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u044B\u0439 \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441 (JBPM) -wf_adhoc.workflow.description=\u041D\u0430\u0437\u043D\u0430\u0447\u0438\u0442\u044C \u043A\u043E\u043B\u043B\u0435\u0433\u0435 \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 JBPM +wf_adhoc.workflow.title=\u0421\u043f\u0435\u0446\u0438\u0430\u043b\u044c\u043d\u044b\u0439 \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441 (JBPM) +wf_adhoc.workflow.description=\u041d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u043a\u043e\u043b\u043b\u0435\u0433\u0435 \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u043b\u044c\u043d\u0443\u044e \u0437\u0430\u0434\u0430\u0447\u0443 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 JBPM # # Review And Approve Workflow # -wf_review.workflow.title=\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 (JBPM) -wf_review.workflow.description=\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 JBPM +wf_review.workflow.title=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 (JBPM) +wf_review.workflow.description=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 JBPM # Review And Approve Process Definitions -wf_review.node.start.title=\u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C -wf_review.node.start.description=\u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C -wf_review.node.review.title=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C -wf_review.node.review.description=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C -wf_review.task.wf_reviewTask.title=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C -wf_review.task.wf_reviewTask.description=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C -wf_review.node.review.transition.reject.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_review.node.review.transition.reject.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_review.node.review.transition.approve.title=\u041F\u0440\u0438\u043D\u044F\u0442\u044C -wf_review.node.review.transition.approve.description=\u041F\u0440\u0438\u043D\u044F\u0442\u044C -wf_review.node.rejected.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_review.node.rejected.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_review.task.wf_rejectedTask.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_review.task.wf_rejectedTask.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_review.node.approved.title=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E -wf_review.node.approved.description=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E -wf_review.task.wf_approvedTask.title=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E -wf_review.task.wf_approvedTask.description=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E -wf_review.node.end.title=\u041A\u043E\u043D\u0435\u0446 -wf_review.node.end.description=\u041A\u043E\u043D\u0435\u0446 +wf_review.node.start.title=\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c +wf_review.node.start.description=\u0417\u0430\u043f\u0443\u0441\u0442\u0438\u0442\u044c +wf_review.node.review.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c +wf_review.node.review.description=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c +wf_review.task.wf_reviewTask.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c +wf_review.task.wf_reviewTask.description=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c +wf_review.node.review.transition.reject.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_review.node.review.transition.reject.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_review.node.review.transition.approve.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c +wf_review.node.review.transition.approve.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c +wf_review.node.rejected.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_review.node.rejected.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_review.task.wf_rejectedTask.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_review.task.wf_rejectedTask.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_review.node.approved.title=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +wf_review.node.approved.description=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +wf_review.task.wf_approvedTask.title=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +wf_review.task.wf_approvedTask.description=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +wf_review.node.end.title=\u041a\u043e\u043d\u0435\u0446 +wf_review.node.end.description=\u041a\u043e\u043d\u0435\u0446 # # Parallel Review Workflow # -wf_parallelreview.workflow.title=\u041F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 (JBPM) -wf_parallelreview.workflow.description=\u041F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u043F\u043E\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 JBPM +wf_parallelreview.workflow.title=\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 (JBPM) +wf_parallelreview.workflow.description=\u041f\u0430\u0440\u0430\u043b\u043b\u0435\u043b\u044c\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 JBPM # Parallel Review Process Definitions -wf_parallelreview.node.review.transition.reject.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_parallelreview.node.review.transition.reject.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_parallelreview.node.review.transition.approve.title=\u041F\u0440\u0438\u043D\u044F\u0442\u044C -wf_parallelreview.node.review.transition.approve.description=\u041F\u0440\u0438\u043D\u044F\u0442\u044C +wf_parallelreview.node.review.transition.reject.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_parallelreview.node.review.transition.reject.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_parallelreview.node.review.transition.approve.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c +wf_parallelreview.node.review.transition.approve.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c # # Pooled Review Workflow # -wf_reviewpooled.workflow.title=\u0421\u043E\u0432\u043C\u0435\u0441\u0442\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 (JBPM) -wf_reviewpooled.workflow.description=\u0421\u043E\u0432\u043C\u0435\u0441\u0442\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 JBPM +wf_reviewpooled.workflow.title=\u0421\u043e\u0432\u043c\u0435\u0441\u0442\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 (JBPM) +wf_reviewpooled.workflow.description=\u0421\u043e\u0432\u043c\u0435\u0441\u0442\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 JBPM # Pooled Review Process Definitions -wf_reviewpooled.node.review.transition.reject.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_reviewpooled.node.review.transition.reject.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_reviewpooled.node.review.transition.approve.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C -wf_reviewpooled.node.review.transition.approve.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C +wf_reviewpooled.node.review.transition.reject.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_reviewpooled.node.review.transition.reject.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_reviewpooled.node.review.transition.approve.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c +wf_reviewpooled.node.review.transition.approve.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c # # Parallel Group Review Workflow # -wf_parallelgroupreview.workflow.title=\u0413\u0440\u0443\u043F\u043F\u043E\u0432\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 (JBPM) -wf_parallelgroupreview.workflow.description=\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0433\u0440\u0443\u043F\u043F\u043E\u0439 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u0439 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 JBPM +wf_parallelgroupreview.workflow.title=\u0413\u0440\u0443\u043f\u043f\u043e\u0432\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 (JBPM) +wf_parallelgroupreview.workflow.description=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0433\u0440\u0443\u043f\u043f\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 JBPM # Parallel Group Review Process Definitions -wf_parallelgroupreview.node.review.transition.reject.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_parallelgroupreview.node.review.transition.reject.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C -wf_parallelgroupreview.node.review.transition.approve.title=\u041F\u0440\u0438\u043D\u044F\u0442\u044C -wf_parallelgroupreview.node.review.transition.approve.description=\u041F\u0440\u0438\u043D\u044F\u0442\u044C +wf_parallelgroupreview.node.review.transition.reject.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_parallelgroupreview.node.review.transition.reject.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c +wf_parallelgroupreview.node.review.transition.approve.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c +wf_parallelgroupreview.node.review.transition.approve.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c ####################### # ACTIVITI WORKFLOWS # @@ -90,50 +90,50 @@ wf_parallelgroupreview.node.review.transition.approve.description=\u041F\u0440\u # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=\u0421\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u044B\u0439 \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441 -activitiAdhoc.workflow.description=\u041D\u0430\u0437\u043D\u0430\u0447\u0438\u0442\u044C \u043A\u043E\u043B\u043B\u0435\u0433\u0435 \u043F\u0440\u043E\u0438\u0437\u0432\u043E\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 Activiti +activitiAdhoc.workflow.title=\u041d\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 +activitiAdhoc.workflow.description=\u041d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u0438\u0435 \u043d\u043e\u0432\u043e\u0439 \u0437\u0430\u0434\u0430\u0447\u0438 \u0441\u0435\u0431\u0435 \u0438\u043b\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0435 # # Activiti Review And Approve Workflow # -activitiReview.workflow.title=\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 -activitiReview.workflow.description=\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 Activiti -activitiReview.task.approved.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D. -activitiReview.task.rejected.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D. +activitiReview.workflow.title=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 +activitiReview.workflow.description=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 Activiti +activitiReview.task.approved.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d. +activitiReview.task.rejected.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d. # # Parallel Review Workflow # -activitiParallelReview.workflow.title=\u041F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 -activitiParallelReview.workflow.description=\u041F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 Activiti -activitiParallelReview.task.approved.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D. -activitiParallelReview.task.rejected.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D. +activitiParallelReview.workflow.title=\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 +activitiParallelReview.workflow.description=\u0417\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u043e\u0434\u043d\u0438\u043c \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0430\u043c\u0438 +activitiParallelReview.task.approved.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d. +activitiParallelReview.task.rejected.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d. # # Activiti Pooled Review Workflow # -activitiReviewPooled.workflow.title=\u0421\u043E\u0432\u043C\u0435\u0441\u0442\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 -activitiReviewPooled.workflow.description=\u0421\u043E\u0432\u043C\u0435\u0441\u0442\u043D\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 Activiti -activitiReviewPooled.task.approved.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D. -activitiReviewPooled.task.rejected.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D. +activitiReviewPooled.workflow.title=\u0421\u043e\u0432\u043c\u0435\u0441\u0442\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 +activitiReviewPooled.workflow.description=\u0421\u043e\u0432\u043c\u0435\u0441\u0442\u043d\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 Activiti +activitiReviewPooled.task.approved.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d. +activitiReviewPooled.task.rejected.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d. # # Activiti Parallel Group Review Workflow # -activitiParallelGroupReview.workflow.title=\u0413\u0440\u0443\u043F\u043F\u043E\u0432\u0430\u044F \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 -activitiParallelGroupReview.workflow.description=\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0433\u0440\u0443\u043F\u043F\u043E\u0439 \u043F\u043E\u043B\u044C\u0437\u043E\u0432\u0430\u0442\u0435\u043B\u0435\u0439 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 Activiti -activitiParallelGroupReview.task.approved.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D. -activitiParallelGroupReview.task.rejected.description=\u0414\u0430\u043D\u043D\u044B\u0439 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0431\u044B\u043B \u043F\u0440\u043E\u0432\u0435\u0440\u0435\u043D \u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D. +activitiParallelGroupReview.workflow.title=\u0413\u0440\u0443\u043f\u043f\u043e\u0432\u0430\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 +activitiParallelGroupReview.workflow.description=\u041f\u0440\u043e\u0432\u0435\u0440\u043a\u0430 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u0433\u0440\u0443\u043f\u043f\u043e\u0439 \u043f\u043e\u043b\u044c\u0437\u043e\u0432\u0430\u0442\u0435\u043b\u0435\u0439 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 Activiti +activitiParallelGroupReview.task.approved.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d. +activitiParallelGroupReview.task.rejected.description=\u0414\u0430\u043d\u043d\u044b\u0439 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0431\u044b\u043b \u043f\u0440\u043e\u0432\u0435\u0440\u0435\u043d \u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d. # # Activiti Publish Web Content Workflow # -publishWebContent.workflow.title=\u041F\u0440\u043E\u0446\u0435\u0441\u0441 Activiti \u0434\u043B\u044F \u043F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u0438 \u0432\u0435\u0431-\u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 -publishWebContent.workflow.description=\u041F\u0443\u0431\u043B\u0438\u043A\u0430\u0446\u0438\u044F \u0432\u0435\u0431-\u043A\u043E\u043D\u0442\u0435\u043D\u0442\u0430 \u0441 \u043F\u043E\u043C\u043E\u0449\u044C\u044E \u043F\u043E\u0434\u0441\u0438\u0441\u0442\u0435\u043C\u044B \u0431\u0438\u0437\u043D\u0435\u0441-\u043F\u0440\u043E\u0446\u0435\u0441\u0441\u043E\u0432 Activiti +publishWebContent.workflow.title=\u041f\u0440\u043e\u0446\u0435\u0441\u0441 Activiti \u0434\u043b\u044f \u043f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u0438 \u0432\u0435\u0431-\u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 +publishWebContent.workflow.description=\u041f\u0443\u0431\u043b\u0438\u043a\u0430\u0446\u0438\u044f \u0432\u0435\u0431-\u043a\u043e\u043d\u0442\u0435\u043d\u0442\u0430 \u0441 \u043f\u043e\u043c\u043e\u0449\u044c\u044e \u043f\u043e\u0434\u0441\u0438\u0441\u0442\u0435\u043c\u044b \u0431\u0438\u0437\u043d\u0435\u0441-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u043e\u0432 Activiti ############################ # WORKFLOW MODEL LABELS # @@ -141,64 +141,94 @@ publishWebContent.workflow.description=\u041F\u0443\u0431\u043B\u0438\u043A\u043 # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=\u0417\u0430\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0441\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u0443\u044E \u0437\u0430\u0434\u0430\u0447\u0443 -wf_workflowmodel.type.wf_submitAdhocTask.description=\u041D\u0430\u0437\u043D\u0430\u0447\u0438\u0442\u044C \u0437\u0430\u0434\u0430\u0447\u0443 \u043A\u043E\u043B\u043B\u0435\u0433\u0435 -wf_workflowmodel.property.wf_notifyMe.title=\u0423\u0432\u0435\u0434\u043E\u043C\u0438\u0442\u044C \u043C\u0435\u043D\u044F -wf_workflowmodel.property.wf_notifyMe.description=\u0423\u0432\u0435\u0434\u043E\u043C\u0438\u0442\u044C \u043C\u0435\u043D\u044F \u043F\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0438\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 -wf_workflowmodel.type.wf_adhocTask.title=\u0421\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 -wf_workflowmodel.type.wf_adhocTask.description=\u0421\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430, \u043D\u0430\u0437\u043D\u0430\u0447\u0435\u043D\u043D\u0430\u044F \u043A\u043E\u043B\u043B\u0435\u0433\u043E\u0439 -wf_workflowmodel.type.wf_completedAdhocTask.title=\u0421\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430 -wf_workflowmodel.type.wf_completedAdhocTask.description=\u0421\u043F\u0435\u0446\u0438\u0430\u043B\u044C\u043D\u0430\u044F \u0437\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043D\u0430 +wf_workflowmodel.type.wf_submitAdhocTask.title=\u0417\u0430\u0434\u0430\u0447\u0430 +wf_workflowmodel.type.wf_submitAdhocTask.description=\u041d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0443 \u043a\u043e\u043b\u043b\u0435\u0433\u0435 +wf_workflowmodel.property.wf_notifyMe.title=\u0423\u0432\u0435\u0434\u043e\u043c\u0438\u0442\u044c \u043c\u0435\u043d\u044f +wf_workflowmodel.property.wf_notifyMe.description=\u0423\u0432\u0435\u0434\u043e\u043c\u0438\u0442\u044c \u043c\u0435\u043d\u044f \u043f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 +wf_workflowmodel.type.wf_adhocTask.title=\u0417\u0430\u0434\u0430\u0447\u0430 +wf_workflowmodel.type.wf_adhocTask.description=\u0417\u0430\u0434\u0430\u0447\u0430, \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043b\u043b\u0435\u0433\u043e\u0439 +wf_workflowmodel.type.wf_completedAdhocTask.title=\u0417\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430 +wf_workflowmodel.type.wf_completedAdhocTask.description=\u0417\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430 + +activitiAdhoc.task.wf_submitAdhocTask.title=\u0417\u0430\u0434\u0430\u0447\u0430 +activitiAdhoc.task.wf_submitAdhocTask.description=\u041d\u0430\u0437\u043d\u0430\u0447\u0438\u0442\u044c \u0437\u0430\u0434\u0430\u0447\u0443 \u043a\u043e\u043b\u043b\u0435\u0433\u0435 +activitiAdhoc.property.wf_notifyMe.title=\u0423\u0432\u0435\u0434\u043e\u043c\u0438\u0442\u044c \u043c\u0435\u043d\u044f +activitiAdhoc.property.wf_notifyMe.description=\u0423\u0432\u0435\u0434\u043e\u043c\u0438\u0442\u044c \u043c\u0435\u043d\u044f \u043f\u0440\u0438 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0438\u0438 \u0437\u0430\u0434\u0430\u0447\u0438 +activitiAdhoc.task.wf_adhocTask.title=\u0417\u0430\u0434\u0430\u0447\u0430 +activitiAdhoc.task.wf_adhocTask.description=\u0417\u0430\u0434\u0430\u0447\u0430, \u043d\u0430\u0437\u043d\u0430\u0447\u0435\u043d\u043d\u0430\u044f \u043a\u043e\u043b\u043b\u0435\u0433\u043e\u0439 +activitiAdhoc.task.wf_completedAdhocTask.title=\u0417\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430 +activitiAdhoc.task.wf_completedAdhocTask.description=\u0417\u0430\u0434\u0430\u0447\u0430 \u0437\u0430\u0432\u0435\u0440\u0448\u0435\u043d\u0430 # Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitReviewTask.title=\u041D\u0430\u0447\u0430\u0442\u044C \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 -wf_workflowmodel.type.wf_submitReviewTask.description=\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 -wf_workflowmodel.type.wf_reviewTask.title=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C -wf_workflowmodel.type.wf_reviewTask.description=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u0441 \u0446\u0435\u043B\u044C\u044E \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0438\u043B\u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0438\u044F -wf_workflowmodel.type.wf_rejectedTask.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_workflowmodel.type.wf_rejectedTask.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_workflowmodel.type.wf_approvedTask.title=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E -wf_workflowmodel.type.wf_approvedTask.description=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E +wf_workflowmodel.type.wf_submitReviewTask.title=\u041d\u0430\u0447\u0430\u0442\u044c \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 +wf_workflowmodel.type.wf_submitReviewTask.description=\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u043d\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 +wf_workflowmodel.type.wf_reviewTask.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c +wf_workflowmodel.type.wf_reviewTask.description=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0441 \u0446\u0435\u043b\u044c\u044e \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f +wf_workflowmodel.type.wf_rejectedTask.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_workflowmodel.type.wf_rejectedTask.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_workflowmodel.type.wf_approvedTask.title=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +wf_workflowmodel.type.wf_approvedTask.description=\u041f\u0440\u0438\u043d\u044f\u0442\u043e # Activiti Review And Approve Task Definitions -wf_workflowmodel.type.wf_activitiReviewTask.title=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C -wf_workflowmodel.type.wf_activitiReviewTask.description=\u041F\u0440\u043E\u0432\u0435\u0440\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u0441 \u0446\u0435\u043B\u044C\u044E \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F \u0438\u043B\u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u0438\u044F -wf_workflowmodel.property.wf_reviewOutcome.title=\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 -wf_workflowmodel.property.wf_reviewOutcome.description=\u041F\u0440\u0438\u043D\u044F\u0442\u044C \u0438\u043B\u0438 \u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C \u043A\u043E\u043D\u0442\u0435\u043D\u0442 +wf_workflowmodel.type.wf_activitiReviewTask.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c +wf_workflowmodel.type.wf_activitiReviewTask.description=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0441 \u0446\u0435\u043b\u044c\u044e \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f +wf_workflowmodel.property.wf_reviewOutcome.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 +wf_workflowmodel.property.wf_reviewOutcome.description=\u041f\u0440\u0438\u043d\u044f\u0442\u044c \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c \u043a\u043e\u043d\u0442\u0435\u043d\u0442 # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=\u041D\u0430\u0447\u0430\u0442\u044C \u043F\u0430\u0440\u0430\u043B\u043B\u0435\u043B\u044C\u043D\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 -wf_workflowmodel.type.wf_submitParallelReviewTask.description=\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u043B\u044E\u0434\u044F\u043C \u0438\u0437 \u0441\u043F\u0438\u0441\u043A\u0430 -wf_workflowmodel.property.wf_requiredApprovePercent.title=\u0422\u0440\u0435\u0431\u0443\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0446\u0435\u043D\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F -wf_workflowmodel.property.wf_requiredApprovePercent.description=\u041F\u0440\u043E\u0446\u0435\u043D\u0442 \u0440\u0435\u0446\u0435\u043D\u0437\u0435\u043D\u0442\u043E\u0432, \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u0434\u043E\u043B\u0436\u043D\u044B \u0443\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u0434\u043B\u044F \u0435\u0433\u043E \u043F\u0440\u0438\u043D\u044F\u0442\u0438\u044F -wf_workflowmodel.type.wf_rejectedParallelTask.title=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_workflowmodel.type.wf_rejectedParallelTask.description=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E -wf_workflowmodel.type.wf_approvedParallelTask.title=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E -wf_workflowmodel.type.wf_approvedParallelTask.description=\u0423\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u043E -wf_workflowmodel.property.wf_reviewerCount.title=\u0427\u0438\u0441\u043B\u043E \u0440\u0435\u0446\u0435\u043D\u0437\u0435\u043D\u0442\u043E\u0432 -wf_workflowmodel.property.wf_reviewerCount.description=\u0427\u0438\u0441\u043B\u043E \u0440\u0435\u0446\u0435\u043D\u0437\u0435\u043D\u0442\u043E\u0432 -wf_workflowmodel.property.wf_requiredPercent.title=\u0422\u0440\u0435\u0431\u0443\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0446\u0435\u043D\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F -wf_workflowmodel.property.wf_requiredPercent.description=\u0422\u0440\u0435\u0431\u0443\u0435\u043C\u044B\u0439 \u043F\u0440\u043E\u0446\u0435\u043D\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F -wf_workflowmodel.property.wf_approveCount.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0435 \u0440\u0435\u0446\u0435\u043D\u0437\u0435\u043D\u0442\u044B -wf_workflowmodel.property.wf_approveCount.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0435 \u0440\u0435\u0446\u0435\u043D\u0437\u0435\u043D\u0442\u044B -wf_workflowmodel.property.wf_actualPercent.title=\u0424\u0430\u043A\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u043F\u0440\u043E\u0446\u0435\u043D\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F -wf_workflowmodel.property.wf_actualPercent.description=\u0424\u0430\u043A\u0442\u0438\u0447\u0435\u0441\u043A\u0438\u0439 \u043F\u0440\u043E\u0446\u0435\u043D\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u044F -wf_workflowmodel.property.wf_reviewOutcome.title=\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 -wf_workflowmodel.property.wf_reviewOutcome.description=\u0420\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0438 +wf_workflowmodel.type.wf_submitParallelReviewTask.title=\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 +wf_workflowmodel.type.wf_submitParallelReviewTask.description=\u0417\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u043e\u0434\u043d\u0438\u043c \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0430\u043c\u0438 +wf_workflowmodel.property.wf_requiredApprovePercent.title=\u0422\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +wf_workflowmodel.property.wf_requiredApprovePercent.description=\u041f\u0440\u043e\u0446\u0435\u043d\u0442 \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0435\u0433\u043e \u043f\u0440\u0438\u043d\u044f\u0442\u0438\u044f +wf_workflowmodel.type.wf_rejectedParallelTask.title=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_workflowmodel.type.wf_rejectedParallelTask.description=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e +wf_workflowmodel.type.wf_approvedParallelTask.title=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +wf_workflowmodel.type.wf_approvedParallelTask.description=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +wf_workflowmodel.property.wf_reviewerCount.title=\u0427\u0438\u0441\u043b\u043e \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u043e\u0432 +wf_workflowmodel.property.wf_reviewerCount.description=\u0427\u0438\u0441\u043b\u043e \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u043e\u0432 +wf_workflowmodel.property.wf_requiredPercent.title=\u0422\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +wf_workflowmodel.property.wf_requiredPercent.description=\u0422\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +wf_workflowmodel.property.wf_approveCount.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0435 \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u044b +wf_workflowmodel.property.wf_approveCount.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0435 \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u044b +wf_workflowmodel.property.wf_actualPercent.title=\u0424\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +wf_workflowmodel.property.wf_actualPercent.description=\u0424\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +wf_workflowmodel.property.wf_reviewOutcome.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 +wf_workflowmodel.property.wf_reviewOutcome.description=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 + +activitiParallelReview.task.wf_submitParallelReviewTask.title=\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0434\u043b\u044f \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0438 +activitiParallelReview.task.wf_submitParallelReviewTask.description=\u0417\u0430\u043f\u0440\u043e\u0441\u0438\u0442\u044c \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u0430 \u043e\u0434\u043d\u0438\u043c \u0438\u043b\u0438 \u043d\u0435\u0441\u043a\u043e\u043b\u044c\u043a\u0438\u043c\u0438 \u043a\u043e\u043b\u043b\u0435\u0433\u0430\u043c\u0438 +activitiParallelReview.property.wf_requiredApprovePercent.title=\u0422\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +activitiParallelReview.property.wf_requiredApprovePercent.description=\u041f\u0440\u043e\u0446\u0435\u043d\u0442 \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u043e\u0432, \u043a\u043e\u0442\u043e\u0440\u044b\u0435 \u0434\u043e\u043b\u0436\u043d\u044b \u0443\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0434\u043b\u044f \u0435\u0433\u043e \u043f\u0440\u0438\u043d\u044f\u0442\u0438\u044f +activitiParallelReview.task.wf_activitiReviewTask.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c +activitiParallelReview.task.wf_activitiReviewTask.description=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0441 \u0446\u0435\u043b\u044c\u044e \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f \u0438\u043b\u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u0438\u044f +activitiParallelReview.task.wf_rejectedParallelTask.title=\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d +activitiParallelReview.task.wf_rejectedParallelTask.description=\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0431\u044b\u043b\u0438 \u043e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u044b +activitiParallelReview.task.wf_approvedParallelTask.title=\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d +activitiParallelReview.task.wf_approvedParallelTask.description=\u0414\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u0431\u044b\u043b\u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u044b +activitiParallelReview.property.wf_reviewerCount.title=\u0427\u0438\u0441\u043b\u043e \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u043e\u0432 +activitiParallelReview.property.wf_reviewerCount.description=\u0427\u0438\u0441\u043b\u043e \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u043e\u0432 +activitiParallelReview.property.wf_requiredPercent.title=\u0422\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +activitiParallelReview.property.wf_requiredPercent.description=\u0422\u0440\u0435\u0431\u0443\u0435\u043c\u044b\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +activitiParallelReview.property.wf_approveCount.title=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0435 \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u044b +activitiParallelReview.property.wf_approveCount.description=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0432\u0448\u0438\u0435 \u0440\u0435\u0446\u0435\u043d\u0437\u0435\u043d\u0442\u044b +activitiParallelReview.property.wf_actualPercent.title=\u0424\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +activitiParallelReview.property.wf_actualPercent.description=\u0424\u0430\u043a\u0442\u0438\u0447\u0435\u0441\u043a\u0438\u0439 \u043f\u0440\u043e\u0446\u0435\u043d\u0442 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u044f +activitiParallelReview.property.wf_reviewOutcome.title=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 +activitiParallelReview.property.wf_reviewOutcome.description=\u041f\u0440\u043e\u0432\u0435\u0440\u0438\u0442\u044c \u0440\u0435\u0437\u0443\u043b\u044c\u0442\u0430\u0442 # Pooled Review Task Definitions -wf_workflowmodel.type.wf_submitGroupReviewTask.title=\u041D\u0430\u0447\u0430\u0442\u044C \u0433\u0440\u0443\u043F\u043F\u043E\u0432\u0443\u044E \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 -wf_workflowmodel.type.wf_submitGroupReviewTask.description=\u041E\u0442\u043F\u0440\u0430\u0432\u0438\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u043D\u0430 \u043F\u0440\u043E\u0432\u0435\u0440\u043A\u0443 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043D\u0438\u0435 \u0433\u0440\u0443\u043F\u043F\u0435 \u043B\u044E\u0434\u0435\u0439 +wf_workflowmodel.type.wf_submitGroupReviewTask.title=\u041d\u0430\u0447\u0430\u0442\u044c \u0433\u0440\u0443\u043f\u043f\u043e\u0432\u0443\u044e \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 +wf_workflowmodel.type.wf_submitGroupReviewTask.description=\u041e\u0442\u043f\u0440\u0430\u0432\u0438\u0442\u044c \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b \u043d\u0430 \u043f\u0440\u043e\u0432\u0435\u0440\u043a\u0443 \u0438 \u0443\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u0438\u0435 \u0433\u0440\u0443\u043f\u043f\u0435 \u043b\u044e\u0434\u0435\u0439 # List constraint display labels -listconstraint.wf_reviewOutcomeOptions.Approve=\u041F\u0440\u0438\u043D\u044F\u0442\u044C -listconstraint.wf_reviewOutcomeOptions.Reject=\u041E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C +listconstraint.wf_reviewOutcomeOptions.Approve=\u0423\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c +listconstraint.wf_reviewOutcomeOptions.Reject=\u041e\u0442\u043a\u043b\u043e\u043d\u0438\u0442\u044c # The result of a workflow task seen on Workflow Details - History # This is formed by prefixing workflowtask.outcome to the value of the wf:outcome property -workflowtask.outcome.Approve=\u041F\u0440\u0438\u043D\u044F\u0442\u043E -workflowtask.outcome.Reject=\u041E\u0442\u043A\u043B\u043E\u043D\u0435\u043D\u043E +workflowtask.outcome.Approve=\u041f\u0440\u0438\u043d\u044f\u0442\u043e +workflowtask.outcome.Reject=\u041e\u0442\u043a\u043b\u043e\u043d\u0435\u043d\u043e diff --git a/config/alfresco/workflow/workflow-messages_zh_CN.properties b/config/alfresco/workflow/workflow-messages_zh_CN.properties index cd332e479b..3cf1aafc19 100755 --- a/config/alfresco/workflow/workflow-messages_zh_CN.properties +++ b/config/alfresco/workflow/workflow-messages_zh_CN.properties @@ -7,80 +7,80 @@ # # Adhoc Task Workflow # -wf_adhoc.workflow.title=\u7279\u6B8A\u5DE5\u4F5C\u6D41\u7A0B (JBPM) -wf_adhoc.workflow.description=\u4F7F\u7528 JBPM \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u4E3A\u540C\u4E8B\u6307\u6D3E\u4EFB\u610F\u4EFB\u52A1 +wf_adhoc.workflow.title=\u7279\u6b8a\u5de5\u4f5c\u6d41\u7a0b (JBPM) +wf_adhoc.workflow.description=\u4f7f\u7528 JBPM \u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u4e3a\u540c\u4e8b\u6307\u6d3e\u4efb\u610f\u4efb\u52a1 # # Review And Approve Workflow # -wf_review.workflow.title=\u590D\u67E5\u548C\u6279\u51C6 (JBPM) -wf_review.workflow.description=\u4F7F\u7528 JBPM \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 +wf_review.workflow.title=\u590d\u67e5\u548c\u6279\u51c6 (JBPM) +wf_review.workflow.description=\u4f7f\u7528 JBPM \u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u590d\u67e5\u548c\u6279\u51c6\u5185\u5bb9 # Review And Approve Process Definitions -wf_review.node.start.title=\u5F00\u59CB -wf_review.node.start.description=\u5F00\u59CB -wf_review.node.review.title=\u590D\u67E5 -wf_review.node.review.description=\u590D\u67E5 -wf_review.task.wf_reviewTask.title=\u590D\u67E5 -wf_review.task.wf_reviewTask.description=\u590D\u67E5 -wf_review.node.review.transition.reject.title=\u62D2\u7EDD -wf_review.node.review.transition.reject.description=\u62D2\u7EDD -wf_review.node.review.transition.approve.title=\u6279\u51C6 -wf_review.node.review.transition.approve.description=\u6279\u51C6 -wf_review.node.rejected.title=\u5DF2\u62D2\u7EDD -wf_review.node.rejected.description=\u5DF2\u62D2\u7EDD -wf_review.task.wf_rejectedTask.title=\u5DF2\u62D2\u7EDD -wf_review.task.wf_rejectedTask.description=\u5DF2\u62D2\u7EDD -wf_review.node.approved.title=\u5DF2\u6279\u51C6 -wf_review.node.approved.description=\u5DF2\u6279\u51C6 -wf_review.task.wf_approvedTask.title=\u5DF2\u6279\u51C6 -wf_review.task.wf_approvedTask.description=\u5DF2\u6279\u51C6 -wf_review.node.end.title=\u7ED3\u675F -wf_review.node.end.description=\u7ED3\u675F +wf_review.node.start.title=\u542f\u52a8 +wf_review.node.start.description=\u542f\u52a8 +wf_review.node.review.title=\u590d\u67e5 +wf_review.node.review.description=\u590d\u67e5 +wf_review.task.wf_reviewTask.title=\u590d\u67e5 +wf_review.task.wf_reviewTask.description=\u590d\u67e5 +wf_review.node.review.transition.reject.title=\u62d2\u7edd +wf_review.node.review.transition.reject.description=\u62d2\u7edd +wf_review.node.review.transition.approve.title=\u6279\u51c6 +wf_review.node.review.transition.approve.description=\u6279\u51c6 +wf_review.node.rejected.title=\u5df2\u62d2\u7edd +wf_review.node.rejected.description=\u5df2\u62d2\u7edd +wf_review.task.wf_rejectedTask.title=\u5df2\u62d2\u7edd +wf_review.task.wf_rejectedTask.description=\u5df2\u62d2\u7edd +wf_review.node.approved.title=\u5df2\u6279\u51c6 +wf_review.node.approved.description=\u5df2\u6279\u51c6 +wf_review.task.wf_approvedTask.title=\u5df2\u6279\u51c6 +wf_review.task.wf_approvedTask.description=\u5df2\u6279\u51c6 +wf_review.node.end.title=\u7ed3\u675f +wf_review.node.end.description=\u7ed3\u675f # # Parallel Review Workflow # -wf_parallelreview.workflow.title=\u5E76\u884C\u590D\u67E5\u548C\u6279\u51C6 (JBPM) -wf_parallelreview.workflow.description=\u4F7F\u7528 JBPM \u7684\u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u6267\u884C\u5E76\u884C\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 +wf_parallelreview.workflow.title=\u5e76\u884c\u590d\u67e5\u548c\u6279\u51c6 (JBPM) +wf_parallelreview.workflow.description=\u4f7f\u7528 JBPM \u7684\u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u6267\u884c\u5e76\u884c\u590d\u67e5\u548c\u6279\u51c6\u5185\u5bb9 # Parallel Review Process Definitions -wf_parallelreview.node.review.transition.reject.title=\u62D2\u7EDD -wf_parallelreview.node.review.transition.reject.description=\u62D2\u7EDD -wf_parallelreview.node.review.transition.approve.title=\u6279\u51C6 -wf_parallelreview.node.review.transition.approve.description=\u6279\u51C6 +wf_parallelreview.node.review.transition.reject.title=\u62d2\u7edd +wf_parallelreview.node.review.transition.reject.description=\u62d2\u7edd +wf_parallelreview.node.review.transition.approve.title=\u6279\u51c6 +wf_parallelreview.node.review.transition.approve.description=\u6279\u51c6 # # Pooled Review Workflow # -wf_reviewpooled.workflow.title=\u5165\u6C60\u590D\u67E5\u548C\u6279\u51C6 (JBPM) -wf_reviewpooled.workflow.description=\u4F7F\u7528 JBPM \u7684\u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u8FDB\u884C\u5165\u6C60\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 +wf_reviewpooled.workflow.title=\u5165\u6c60\u590d\u67e5\u548c\u6279\u51c6 (JBPM) +wf_reviewpooled.workflow.description=\u4f7f\u7528 JBPM \u7684\u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u8fdb\u884c\u5165\u6c60\u590d\u67e5\u548c\u6279\u51c6\u5185\u5bb9 # Pooled Review Process Definitions -wf_reviewpooled.node.review.transition.reject.title=\u62D2\u7EDD -wf_reviewpooled.node.review.transition.reject.description=\u62D2\u7EDD -wf_reviewpooled.node.review.transition.approve.title=\u6279\u51C6 -wf_reviewpooled.node.review.transition.approve.description=\u6279\u51C6 +wf_reviewpooled.node.review.transition.reject.title=\u62d2\u7edd +wf_reviewpooled.node.review.transition.reject.description=\u62d2\u7edd +wf_reviewpooled.node.review.transition.approve.title=\u6279\u51c6 +wf_reviewpooled.node.review.transition.approve.description=\u6279\u51c6 # # Parallel Group Review Workflow # -wf_parallelgroupreview.workflow.title=\u7EC4\u590D\u67E5\u548C\u6279\u51C6 (JBPM) -wf_parallelgroupreview.workflow.description=\u4F7F\u7528 JBPM \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u7EC4\u8FDB\u884C\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 +wf_parallelgroupreview.workflow.title=\u7ec4\u590d\u67e5\u548c\u6279\u51c6 (JBPM) +wf_parallelgroupreview.workflow.description=\u4f7f\u7528 JBPM \u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u7ec4\u8fdb\u884c\u590d\u67e5\u548c\u6279\u51c6\u5185\u5bb9 # Parallel Group Review Process Definitions -wf_parallelgroupreview.node.review.transition.reject.title=\u62D2\u7EDD -wf_parallelgroupreview.node.review.transition.reject.description=\u62D2\u7EDD -wf_parallelgroupreview.node.review.transition.approve.title=\u6279\u51C6 -wf_parallelgroupreview.node.review.transition.approve.description=\u6279\u51C6 +wf_parallelgroupreview.node.review.transition.reject.title=\u62d2\u7edd +wf_parallelgroupreview.node.review.transition.reject.description=\u62d2\u7edd +wf_parallelgroupreview.node.review.transition.approve.title=\u6279\u51c6 +wf_parallelgroupreview.node.review.transition.approve.description=\u6279\u51c6 ####################### # ACTIVITI WORKFLOWS # @@ -90,50 +90,50 @@ wf_parallelgroupreview.node.review.transition.approve.description=\u6279\u51C6 # Activiti Adhoc Task Workflow # -activitiAdhoc.workflow.title=\u7279\u6B8A\u5DE5\u4F5C\u6D41\u7A0B -activitiAdhoc.workflow.description=\u4F7F\u7528 Activiti \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u5C06\u4EFB\u610F\u4EFB\u52A1\u6307\u6D3E\u7ED9\u540C\u4E8B +activitiAdhoc.workflow.title=\u65b0\u4efb\u52a1 +activitiAdhoc.workflow.description=\u4e3a\u81ea\u5df1\u6216\u540c\u4e8b\u6307\u6d3e\u65b0\u4efb\u52a1 # # Activiti Review And Approve Workflow # -activitiReview.workflow.title=\u590D\u67E5\u548C\u6279\u51C6 -activitiReview.workflow.description=\u4F7F\u7528 Activiti \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u8FDB\u884C\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 -activitiReview.task.approved.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u6279\u51C6\u3002 -activitiReview.task.rejected.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u62D2\u7EDD\u3002 +activitiReview.workflow.title=\u590d\u67e5\u548c\u6279\u51c6 +activitiReview.workflow.description=\u4f7f\u7528 Activiti \u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u8fdb\u884c\u590d\u67e5\u548c\u6279\u51c6\u5185\u5bb9 +activitiReview.task.approved.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u6279\u51c6\u3002 +activitiReview.task.rejected.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u62d2\u7edd\u3002 # # Parallel Review Workflow # -activitiParallelReview.workflow.title=\u5E76\u884C\u590D\u67E5\u548C\u6279\u51C6 -activitiParallelReview.workflow.description=\u4F7F\u7528 Activiti \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u8FDB\u884C\u5E76\u884C\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 -activitiParallelReview.task.approved.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u6279\u51C6\u3002 -activitiParallelReview.task.rejected.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u62D2\u7EDD\u3002 +activitiParallelReview.workflow.title=\u53d1\u9001\u6587\u6863\u4f9b\u590d\u67e5 +activitiParallelReview.workflow.description=\u4e00\u4f4d\u6216\u591a\u4f4d\u540c\u4e8b\u6279\u51c6\u8bf7\u6c42\u6587\u6863 +activitiParallelReview.task.approved.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u6279\u51c6\u3002 +activitiParallelReview.task.rejected.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u62d2\u7edd\u3002 # # Activiti Pooled Review Workflow # -activitiReviewPooled.workflow.title=\u5165\u6C60\u590D\u67E5\u548C\u6279\u51C6 -activitiReviewPooled.workflow.description=\u4F7F\u7528 Activiti \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u8FDB\u884C\u5165\u6C60\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 -activitiReviewPooled.task.approved.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u6279\u51C6\u3002 -activitiReviewPooled.task.rejected.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u62D2\u7EDD\u3002 +activitiReviewPooled.workflow.title=\u5165\u6c60\u590d\u67e5\u548c\u6279\u51c6 +activitiReviewPooled.workflow.description=\u4f7f\u7528 Activiti \u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u8fdb\u884c\u5165\u6c60\u590d\u67e5\u548c\u6279\u51c6\u5185\u5bb9 +activitiReviewPooled.task.approved.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u6279\u51c6\u3002 +activitiReviewPooled.task.rejected.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u62d2\u7edd\u3002 # # Activiti Parallel Group Review Workflow # -activitiParallelGroupReview.workflow.title=\u7EC4\u590D\u67E5\u548C\u6279\u51C6 -activitiParallelGroupReview.workflow.description=\u4F7F\u7528 Activiti \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u8FDB\u884C\u7EC4\u590D\u67E5\u548C\u6279\u51C6\u5185\u5BB9 -activitiParallelGroupReview.task.approved.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u6279\u51C6\u3002 -activitiParallelGroupReview.task.rejected.description=\u6B64\u6587\u6863\u5DF2\u88AB\u590D\u67E5\u5E76\u62D2\u7EDD\u3002 +activitiParallelGroupReview.workflow.title=\u7ec4\u590d\u67e5\u548c\u6279\u51c6 +activitiParallelGroupReview.workflow.description=\u4f7f\u7528 Activiti \u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u8fdb\u884c\u7ec4\u590d\u67e5\u548c\u6279\u51c6\u5185\u5bb9 +activitiParallelGroupReview.task.approved.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u6279\u51c6\u3002 +activitiParallelGroupReview.task.rejected.description=\u6b64\u6587\u6863\u5df2\u88ab\u590d\u67e5\u5e76\u62d2\u7edd\u3002 # # Activiti Publish Web Content Workflow # -publishWebContent.workflow.title=\u53D1\u5E03 Web \u5185\u5BB9\u7684 Activiti \u6D41\u7A0B -publishWebContent.workflow.description=\u4F7F\u7528 Activiti \u5DE5\u4F5C\u6D41\u7A0B\u5F15\u64CE\u53D1\u5E03 Web \u5185\u5BB9 +publishWebContent.workflow.title=\u53d1\u5e03 Web \u5185\u5bb9\u7684 Activiti \u6d41\u7a0b +publishWebContent.workflow.description=\u4f7f\u7528 Activiti \u5de5\u4f5c\u6d41\u7a0b\u5f15\u64ce\u53d1\u5e03 Web \u5185\u5bb9 ############################ # WORKFLOW MODEL LABELS # @@ -141,64 +141,94 @@ publishWebContent.workflow.description=\u4F7F\u7528 Activiti \u5DE5\u4F5C\u6D41\ # Adhoc Task Definitions -wf_workflowmodel.type.wf_submitAdhocTask.title=\u5F00\u59CB\u7279\u6B8A\u4EFB\u52A1 -wf_workflowmodel.type.wf_submitAdhocTask.description=\u5C06\u4EFB\u52A1\u5206\u914D\u7ED9\u540C\u4E8B -wf_workflowmodel.property.wf_notifyMe.title=\u901A\u77E5\u6211 -wf_workflowmodel.property.wf_notifyMe.description=\u4EFB\u52A1\u5B8C\u6210\u65F6\u901A\u77E5\u6211 -wf_workflowmodel.type.wf_adhocTask.title=\u7279\u6B8A\u4EFB\u52A1 -wf_workflowmodel.type.wf_adhocTask.description=\u7531\u540C\u4E8B\u5206\u914D\u7684\u7279\u6B8A\u4EFB\u52A1 -wf_workflowmodel.type.wf_completedAdhocTask.title=\u7279\u6B8A\u4EFB\u52A1\u5DF2\u5B8C\u6210 -wf_workflowmodel.type.wf_completedAdhocTask.description=\u7279\u6B8A\u4EFB\u52A1\u5DF2\u5B8C\u6210 +wf_workflowmodel.type.wf_submitAdhocTask.title=\u4efb\u52a1 +wf_workflowmodel.type.wf_submitAdhocTask.description=\u5c06\u4efb\u52a1\u5206\u914d\u7ed9\u540c\u4e8b +wf_workflowmodel.property.wf_notifyMe.title=\u901a\u77e5\u6211 +wf_workflowmodel.property.wf_notifyMe.description=\u4efb\u52a1\u5b8c\u6210\u65f6\u901a\u77e5\u6211 +wf_workflowmodel.type.wf_adhocTask.title=\u4efb\u52a1 +wf_workflowmodel.type.wf_adhocTask.description=\u7531\u540c\u4e8b\u5206\u914d\u7684\u4efb\u52a1 +wf_workflowmodel.type.wf_completedAdhocTask.title=\u4efb\u52a1\u5df2\u5b8c\u6210 +wf_workflowmodel.type.wf_completedAdhocTask.description=\u4efb\u52a1\u5df2\u5b8c\u6210 + +activitiAdhoc.task.wf_submitAdhocTask.title=\u4efb\u52a1 +activitiAdhoc.task.wf_submitAdhocTask.description=\u5c06\u4efb\u52a1\u5206\u914d\u7ed9\u540c\u4e8b +activitiAdhoc.property.wf_notifyMe.title=\u901a\u77e5\u6211 +activitiAdhoc.property.wf_notifyMe.description=\u4efb\u52a1\u5b8c\u6210\u65f6\u901a\u77e5\u6211 +activitiAdhoc.task.wf_adhocTask.title=\u4efb\u52a1 +activitiAdhoc.task.wf_adhocTask.description=\u7531\u540c\u4e8b\u5206\u914d\u7684\u4efb\u52a1 +activitiAdhoc.task.wf_completedAdhocTask.title=\u4efb\u52a1\u5df2\u5b8c\u6210 +activitiAdhoc.task.wf_completedAdhocTask.description=\u4efb\u52a1\u5df2\u5b8c\u6210 # Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitReviewTask.title=\u5F00\u59CB\u590D\u67E5 -wf_workflowmodel.type.wf_submitReviewTask.description=\u63D0\u4EA4\u6587\u6863\u4F9B\u590D\u67E5\u548C\u6279\u51C6 -wf_workflowmodel.type.wf_reviewTask.title=\u590D\u67E5 -wf_workflowmodel.type.wf_reviewTask.description=\u590D\u67E5\u6587\u6863\u4EE5\u6279\u51C6\u6216\u62D2\u7EDD -wf_workflowmodel.type.wf_rejectedTask.title=\u5DF2\u62D2\u7EDD -wf_workflowmodel.type.wf_rejectedTask.description=\u5DF2\u62D2\u7EDD -wf_workflowmodel.type.wf_approvedTask.title=\u5DF2\u6279\u51C6 -wf_workflowmodel.type.wf_approvedTask.description=\u5DF2\u6279\u51C6 +wf_workflowmodel.type.wf_submitReviewTask.title=\u5f00\u59cb\u590d\u67e5 +wf_workflowmodel.type.wf_submitReviewTask.description=\u63d0\u4ea4\u6587\u6863\u4f9b\u590d\u67e5\u548c\u6279\u51c6 +wf_workflowmodel.type.wf_reviewTask.title=\u590d\u67e5 +wf_workflowmodel.type.wf_reviewTask.description=\u590d\u67e5\u6587\u6863\u4ee5\u6279\u51c6\u6216\u62d2\u7edd +wf_workflowmodel.type.wf_rejectedTask.title=\u5df2\u62d2\u7edd +wf_workflowmodel.type.wf_rejectedTask.description=\u5df2\u62d2\u7edd +wf_workflowmodel.type.wf_approvedTask.title=\u5df2\u6279\u51c6 +wf_workflowmodel.type.wf_approvedTask.description=\u5df2\u6279\u51c6 # Activiti Review And Approve Task Definitions -wf_workflowmodel.type.wf_activitiReviewTask.title=\u590D\u67E5 -wf_workflowmodel.type.wf_activitiReviewTask.description=\u590D\u67E5\u6587\u6863\u4EE5\u6279\u51C6\u6216\u62D2\u7EDD -wf_workflowmodel.property.wf_reviewOutcome.title=\u590D\u67E5\u7ED3\u679C -wf_workflowmodel.property.wf_reviewOutcome.description=\u6279\u51C6\u6216\u62D2\u7EDD\u5185\u5BB9 +wf_workflowmodel.type.wf_activitiReviewTask.title=\u590d\u67e5 +wf_workflowmodel.type.wf_activitiReviewTask.description=\u590d\u67e5\u6587\u6863\u4ee5\u6279\u51c6\u6216\u62d2\u7edd +wf_workflowmodel.property.wf_reviewOutcome.title=\u590d\u67e5\u7ed3\u679c +wf_workflowmodel.property.wf_reviewOutcome.description=\u6279\u51c6\u6216\u62d2\u7edd\u5185\u5bb9 # Parallel Review And Approve Task Definitions -wf_workflowmodel.type.wf_submitParallelReviewTask.title=\u5F00\u59CB\u5E76\u884C\u590D\u67E5 -wf_workflowmodel.type.wf_submitParallelReviewTask.description=\u5411\u4E00\u5217\u4EBA\u5458\u63D0\u4EA4\u6587\u6863\u4F9B\u590D\u67E5\u548C\u6279\u51C6 -wf_workflowmodel.property.wf_requiredApprovePercent.title=\u6240\u9700\u6279\u51C6\u767E\u5206\u6BD4 -wf_workflowmodel.property.wf_requiredApprovePercent.description=\u8FBE\u5230\u6279\u51C6\u6761\u4EF6\u6240\u9700\u6279\u51C6\u590D\u67E5\u8005\u6240\u5360\u767E\u5206\u6BD4 -wf_workflowmodel.type.wf_rejectedParallelTask.title=\u5DF2\u62D2\u7EDD -wf_workflowmodel.type.wf_rejectedParallelTask.description=\u5DF2\u62D2\u7EDD -wf_workflowmodel.type.wf_approvedParallelTask.title=\u5DF2\u6279\u51C6 -wf_workflowmodel.type.wf_approvedParallelTask.description=\u5DF2\u6279\u51C6 -wf_workflowmodel.property.wf_reviewerCount.title=\u590D\u67E5\u8005\u6570 -wf_workflowmodel.property.wf_reviewerCount.description=\u590D\u67E5\u8005\u6570 -wf_workflowmodel.property.wf_requiredPercent.title=\u6240\u9700\u6279\u51C6\u767E\u5206\u6BD4 -wf_workflowmodel.property.wf_requiredPercent.description=\u6240\u9700\u6279\u51C6\u767E\u5206\u6BD4 -wf_workflowmodel.property.wf_approveCount.title=\u5DF2\u6279\u51C6\u7684\u590D\u67E5\u8005 -wf_workflowmodel.property.wf_approveCount.description=\u5DF2\u6279\u51C6\u7684\u590D\u67E5\u8005 -wf_workflowmodel.property.wf_actualPercent.title=\u5B9E\u9645\u6279\u51C6\u767E\u5206\u6BD4 -wf_workflowmodel.property.wf_actualPercent.description=\u5B9E\u9645\u6279\u51C6\u767E\u5206\u6BD4 -wf_workflowmodel.property.wf_reviewOutcome.title=\u590D\u67E5\u7ED3\u679C -wf_workflowmodel.property.wf_reviewOutcome.description=\u590D\u67E5\u7ED3\u679C +wf_workflowmodel.type.wf_submitParallelReviewTask.title=\u53d1\u9001\u6587\u6863\u4f9b\u590d\u67e5 +wf_workflowmodel.type.wf_submitParallelReviewTask.description=\u4e00\u4f4d\u6216\u591a\u4f4d\u540c\u4e8b\u6279\u51c6\u8bf7\u6c42\u6587\u6863 +wf_workflowmodel.property.wf_requiredApprovePercent.title=\u6240\u9700\u6279\u51c6\u767e\u5206\u6bd4 +wf_workflowmodel.property.wf_requiredApprovePercent.description=\u8fbe\u5230\u6279\u51c6\u6761\u4ef6\u6240\u9700\u6279\u51c6\u590d\u67e5\u8005\u6240\u5360\u767e\u5206\u6bd4 +wf_workflowmodel.type.wf_rejectedParallelTask.title=\u5df2\u62d2\u7edd +wf_workflowmodel.type.wf_rejectedParallelTask.description=\u5df2\u62d2\u7edd +wf_workflowmodel.type.wf_approvedParallelTask.title=\u5df2\u6279\u51c6 +wf_workflowmodel.type.wf_approvedParallelTask.description=\u5df2\u6279\u51c6 +wf_workflowmodel.property.wf_reviewerCount.title=\u590d\u67e5\u8005\u6570 +wf_workflowmodel.property.wf_reviewerCount.description=\u590d\u67e5\u8005\u6570 +wf_workflowmodel.property.wf_requiredPercent.title=\u6240\u9700\u6279\u51c6\u767e\u5206\u6bd4 +wf_workflowmodel.property.wf_requiredPercent.description=\u6240\u9700\u6279\u51c6\u767e\u5206\u6bd4 +wf_workflowmodel.property.wf_approveCount.title=\u5df2\u6279\u51c6\u7684\u590d\u67e5\u8005 +wf_workflowmodel.property.wf_approveCount.description=\u5df2\u6279\u51c6\u7684\u590d\u67e5\u8005 +wf_workflowmodel.property.wf_actualPercent.title=\u5b9e\u9645\u6279\u51c6\u767e\u5206\u6bd4 +wf_workflowmodel.property.wf_actualPercent.description=\u5b9e\u9645\u6279\u51c6\u767e\u5206\u6bd4 +wf_workflowmodel.property.wf_reviewOutcome.title=\u590d\u67e5\u7ed3\u679c +wf_workflowmodel.property.wf_reviewOutcome.description=\u590d\u67e5\u7ed3\u679c + +activitiParallelReview.task.wf_submitParallelReviewTask.title=\u53d1\u9001\u6587\u6863\u4f9b\u590d\u67e5 +activitiParallelReview.task.wf_submitParallelReviewTask.description=\u4e00\u4f4d\u6216\u591a\u4f4d\u540c\u4e8b\u6279\u51c6\u8bf7\u6c42\u6587\u6863 +activitiParallelReview.property.wf_requiredApprovePercent.title=\u6240\u9700\u6279\u51c6\u767e\u5206\u6bd4 +activitiParallelReview.property.wf_requiredApprovePercent.description=\u8fbe\u5230\u6279\u51c6\u6761\u4ef6\u6240\u9700\u6279\u51c6\u590d\u67e5\u8005\u6240\u5360\u767e\u5206\u6bd4 +activitiParallelReview.task.wf_activitiReviewTask.title=\u590d\u67e5 +activitiParallelReview.task.wf_activitiReviewTask.description=\u590d\u67e5\u6587\u6863\u4ee5\u6279\u51c6\u6216\u62d2\u7edd +activitiParallelReview.task.wf_rejectedParallelTask.title=\u6587\u6863\u88ab\u62d2\u7edd +activitiParallelReview.task.wf_rejectedParallelTask.description=\u6587\u6863\u88ab\u62d2\u7edd +activitiParallelReview.task.wf_approvedParallelTask.title=\u5df2\u6279\u51c6\u6587\u6863 +activitiParallelReview.task.wf_approvedParallelTask.description=\u5df2\u6279\u51c6\u6587\u6863 +activitiParallelReview.property.wf_reviewerCount.title=\u590d\u67e5\u8005\u6570 +activitiParallelReview.property.wf_reviewerCount.description=\u590d\u67e5\u8005\u6570 +activitiParallelReview.property.wf_requiredPercent.title=\u6240\u9700\u6279\u51c6\u767e\u5206\u6bd4 +activitiParallelReview.property.wf_requiredPercent.description=\u6240\u9700\u6279\u51c6\u767e\u5206\u6bd4 +activitiParallelReview.property.wf_approveCount.title=\u5df2\u6279\u51c6\u7684\u590d\u67e5\u8005 +activitiParallelReview.property.wf_approveCount.description=\u5df2\u6279\u51c6\u7684\u590d\u67e5\u8005 +activitiParallelReview.property.wf_actualPercent.title=\u5b9e\u9645\u6279\u51c6\u767e\u5206\u6bd4 +activitiParallelReview.property.wf_actualPercent.description=\u5b9e\u9645\u6279\u51c6\u767e\u5206\u6bd4 +activitiParallelReview.property.wf_reviewOutcome.title=\u590d\u67e5\u7ed3\u679c +activitiParallelReview.property.wf_reviewOutcome.description=\u590d\u67e5\u7ed3\u679c # Pooled Review Task Definitions -wf_workflowmodel.type.wf_submitGroupReviewTask.title=\u5F00\u59CB\u7EC4\u590D\u67E5 -wf_workflowmodel.type.wf_submitGroupReviewTask.description=\u5411\u4E00\u7EC4\u4EBA\u5458\u63D0\u4EA4\u6587\u6863\u4F9B\u590D\u67E5\u548C\u6279\u51C6 +wf_workflowmodel.type.wf_submitGroupReviewTask.title=\u5f00\u59cb\u7ec4\u590d\u67e5 +wf_workflowmodel.type.wf_submitGroupReviewTask.description=\u5411\u4e00\u7ec4\u4eba\u5458\u63d0\u4ea4\u6587\u6863\u4f9b\u590d\u67e5\u548c\u6279\u51c6 # List constraint display labels -listconstraint.wf_reviewOutcomeOptions.Approve=\u6279\u51C6 -listconstraint.wf_reviewOutcomeOptions.Reject=\u62D2\u7EDD +listconstraint.wf_reviewOutcomeOptions.Approve=\u6279\u51c6 +listconstraint.wf_reviewOutcomeOptions.Reject=\u62d2\u7edd # The result of a workflow task seen on Workflow Details - History # This is formed by prefixing workflowtask.outcome to the value of the wf:outcome property -workflowtask.outcome.Approve=\u5DF2\u6279\u51C6 -workflowtask.outcome.Reject=\u5DF2\u62D2\u7EDD +workflowtask.outcome.Approve=\u5df2\u6279\u51c6 +workflowtask.outcome.Reject=\u5df2\u62d2\u7edd diff --git a/pom.xml b/pom.xml index 8f82230d9a..2fbfd77184 100644 --- a/pom.xml +++ b/pom.xml @@ -851,7 +851,6 @@ **/org/alfresco/util/schemacomp/model/AbstractDbObjectTest.* - **/org/alfresco/cmis/changelog/CMISChangeLogServiceTest.* **/org/alfresco/cmis/renditions/CMISRenditionServiceTest.* **/org/alfresco/filesys/FTPServerTest.* **/org/alfresco/repo/avm/AVMServiceTest.* diff --git a/source/java/org/alfresco/cmis/CMISQueryOptions.java b/source/java/org/alfresco/cmis/CMISQueryOptions.java index ce9593b550..8a22437ab8 100644 --- a/source/java/org/alfresco/cmis/CMISQueryOptions.java +++ b/source/java/org/alfresco/cmis/CMISQueryOptions.java @@ -73,6 +73,7 @@ public class CMISQueryOptions extends QueryOptions //options.setQueryMode(); Should set afterwards options.setQueryParameterDefinitions(searchParameters.getQueryParameterDefinitions()); options.setDefaultFieldName(searchParameters.getDefaultFieldName()); + options.setExcludeTenantFilter(searchParameters.getExcludeTenantFilter()); return options; } /** diff --git a/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceTest.java b/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceTest.java deleted file mode 100644 index 2f6e4563c1..0000000000 --- a/source/java/org/alfresco/cmis/changelog/CMISChangeLogServiceTest.java +++ /dev/null @@ -1,468 +0,0 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * 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 . - */ -package org.alfresco.cmis.changelog; - -import java.io.Serializable; -import java.security.SecureRandom; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -import javax.transaction.Status; -import javax.transaction.UserTransaction; - -import junit.framework.TestCase; - -import org.alfresco.cmis.CMISCapabilityChanges; -import org.alfresco.cmis.CMISChangeEvent; -import org.alfresco.cmis.CMISChangeLog; -import org.alfresco.cmis.CMISChangeLogService; -import org.alfresco.cmis.CMISChangeType; -import org.alfresco.cmis.CMISInvalidArgumentException; -import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.audit.model.AuditModelRegistryImpl; -import org.alfresco.repo.security.authentication.AuthenticationComponent; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.cmr.model.FileFolderService; -import org.alfresco.service.cmr.model.FileInfo; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.repository.StoreRef; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.cmr.security.PermissionService; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; -import org.springframework.context.ApplicationContext; -import org.alfresco.util.Pair; - - -/** - * Base tests for {@link CMISChangeLogServiceImpl} - * - * @author Dmitry Velichkevich - */ -public class CMISChangeLogServiceTest extends TestCase -{ - private static final String CMIS_AUTHORITY = "cmis"; - private static final String CHANGE_PREFIX = "Changed"; - private static final String INVALID_CHANGE_TOKEN = ""; - private static final String[] NAME_PARTS = new String[] { "TestDocument (", ").txt", "TestFolder (", ")" }; - - private static int TOTAL_AMOUNT = 31; - private static int CREATED_AMOUNT = 18; - private static final int THE_HALFT_OF_CREATED_AMOUNT = CREATED_AMOUNT / 2; - private static final Map EXPECTED_AMOUNTS = new HashMap(); - static - { - EXPECTED_AMOUNTS.put(CMISChangeType.CREATED, 5); - EXPECTED_AMOUNTS.put(CMISChangeType.DELETED, 3); - EXPECTED_AMOUNTS.put(CMISChangeType.SECURITY, 4); - EXPECTED_AMOUNTS.put(CMISChangeType.UPDATED, 6); - } - - private AuditModelRegistryImpl auditSubsystem; - private CMISChangeLogService changeLogService; - private NodeService nodeService; - private FileFolderService fileFolderService; - private PermissionService permissionService; - private TransactionService transactionService; - private AuthenticationComponent authenticationComponent; - private RetryingTransactionHelper retryingTransactionHelper; - private UserTransaction testTX; - - private int actualCount = 0; - private Map actualAmounts = new HashMap(); - private List created = null; - private List deleted = null; - - private void disableAudit() - { - auditSubsystem.stop(); - auditSubsystem.setProperty("audit.enabled", "true"); - auditSubsystem.setProperty("audit.cmischangelog.enabled", "false"); - } - - private void enableAudit() - { - auditSubsystem.stop(); - auditSubsystem.setProperty("audit.enabled", "true"); - auditSubsystem.setProperty("audit.cmischangelog.enabled", "true"); - } - - /** - * Tests {@link CMISChangeLogServiceImpl} with disabled Auditing feature - * - * @throws Exception - */ - public void testServiceWithDisabledAuditing() throws Exception - { - disableAudit(); - String lastChangeLogToken = changeLogService.getLastChangeLogToken(); - createTestData(EXPECTED_AMOUNTS, false); - assertEquals(CMISCapabilityChanges.NONE, changeLogService.getCapability()); - try - { - changeLogService.getChangeLogEvents(lastChangeLogToken, null); - fail("Changes Logging was not enabled but no one Change Log Service method thrown exception"); - } - catch (Exception e) - { - assertTrue("Invalid exception type from Change Log Service method call with desabled Changes Logging", e instanceof AlfrescoRuntimeException); - } - } - - /** - * Tests {@link CMISChangeLogServiceImpl} with enabled Auditing feature - * - * @throws Exception - */ - public void testEnabledAuditing() throws Exception - { - enableAudit(); - String logToken = changeLogService.getLastChangeLogToken(); - createTestData(EXPECTED_AMOUNTS, false); - assertEquals(CMISCapabilityChanges.OBJECTIDSONLY, changeLogService.getCapability()); - CMISChangeLog changeLog = changeLogService.getChangeLogEvents(logToken, null); - assertChangeLog(logToken, changeLog); - assertChangeEvents(logToken, changeLog, null, FoldersAppearing.NOT_EXPECTED); - } - - /** - * Validates Change Log descriptor that was returned for some Change Log Token - * - * @param logToken {@link String} value that represents last Change Log Token - * @param changeLog {@link CMISChangeLog} instance that represents Change Log descriptor - */ - private void assertChangeLog(String logToken, CMISChangeLog changeLog) - { - assertNotNull(("'" + logToken + "' Change Log Token has no descriptor"), changeLog); - assertNotNull(("Event Etries for '" + logToken + "' Change Log Token are undefined"), changeLog.getChangeEvents()); - assertFalse(("Descriptor for '" + logToken + "' Change Log Token has no any Event Entry"), changeLog.getChangeEvents().isEmpty()); - } - - /** - * Creates test data which will represent Change Events of all possible types - * - * @param requiredAmounts {@link Map}<{@link CMISChangeType}, {@link Integer}> container instance that determines amount of Change Event for each Change Type - * @return pair containing list of created node refs, and list of deleted node refs - * @see CMISChangeType - */ - private Pair, List> createTestData(Map requiredAmounts, boolean withFolders) - { - changeLogService.getLastChangeLogToken(); - created = new LinkedList(); - deleted = new LinkedList(); - Pair, List> result = new Pair, List>(created, deleted); - NodeRef parentNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); - SecureRandom randomizer = new SecureRandom(); - for (CMISChangeType key : requiredAmounts.keySet()) - { - Integer amount = requiredAmounts.get(key); - for (int i = 0; i < amount; i++) - { - boolean folder = withFolders && (0 == ((Math.abs(randomizer.nextInt()) % amount) % 2)); - QName objectType = (folder) ? (ContentModel.TYPE_FOLDER) : (ContentModel.TYPE_CONTENT); - FileInfo object = fileFolderService.create(parentNodeRef, generateName(randomizer, folder), objectType); - created.add(object.getNodeRef()); - addOneToAmount(actualAmounts, CMISChangeType.CREATED); - switch (key) - { - case DELETED: - { - nodeService.deleteNode(object.getNodeRef()); - deleted.add(object.getNodeRef()); - addOneToAmount(actualAmounts, CMISChangeType.DELETED); - break; - } - case SECURITY: - { - permissionService.setPermission(object.getNodeRef(), CMIS_AUTHORITY, PermissionService.EXECUTE_CONTENT, true); - addOneToAmount(actualAmounts, CMISChangeType.SECURITY); - break; - } - case UPDATED: - { - StringBuilder nameBuilder = new StringBuilder(CHANGE_PREFIX); - nameBuilder.append(nodeService.getProperty(object.getNodeRef(), ContentModel.PROP_NAME)); - nodeService.setProperty(object.getNodeRef(), ContentModel.PROP_NAME, nameBuilder.toString()); - addOneToAmount(actualAmounts, CMISChangeType.UPDATED); - } - } - actualCount++; - } - } - return result; - } - - /** - * Deletes each element of created test data if element exist and current user has appropriate rights - * - * @param testData {@link Map}<{@link NodeRef}, {@link Map}<{@link QName}, {@link Serializable}>> container instance that contains test data - */ - private void deleteTestData() - { - if (created != null) - { - for (NodeRef object : created) - { - if (nodeService.exists(object) && (AccessStatus.ALLOWED == permissionService.hasPermission(object, PermissionService.DELETE))) - { - nodeService.deleteNode(object); - } - } - } - } - - /** - * @param folder {@link Boolean} value that determines which name should be generated (for Folder or Document Object) - * @return {@link String} value that represents generated uniquely name for Document Object - */ - private String generateName(SecureRandom randomizer, boolean folder) - { - StringBuilder nameBuilder = new StringBuilder(); - int i = (folder) ? (2) : (0); - nameBuilder.append(NAME_PARTS[i++]).append(Math.abs(randomizer.nextInt())).append(NAME_PARTS[i++]); - return nameBuilder.toString(); - } - - /** - * This method validates Change Event entries according to created earlier Objects. According to assertProperties parameter this method may and may not check properties - * of Change Event entry according to appropriate expected Object against Change Type - * - * @param expectedObjects {@link Map}<{@link NodeRef}, {@link Map}<{@link QName}, {@link Serializable}>> container instance that contains Ids and properties of - * expected Objects - * @param logToken {@link String} value that represents last Change Log Token - * @param changeLog {@link CMISChangeLog} instance that represents Change Log descriptor for last Change Log Token - * @param maxItems {@link Integer} value that determines high bound of Change Events amount - * @see CMISChangeType - */ - private void assertChangeEvents(String logToken, CMISChangeLog changeLog, Integer maxItems, FoldersAppearing foldersAppearing) - { - Map logAmounts = new HashMap(); - boolean folderWasFound = false; - int idx = 0; - for (CMISChangeEvent event : changeLog.getChangeEvents()) - { - // skip first change log entry if a log token has been specified, as the CMIS spec expects - // the change entry to be returned for the specified log token - idx++; - if (logToken != null && idx == 1) - { - continue; - } - - assertNotNull(("One of the Change Log Event Enries is undefined for '" + logToken + "' Change Log Token"), event); - assertNotNull(("Change Event Entry Id of one of the Change Entries is undefined for '" + logToken + "' Change Log Token"), event.getChangedNode()); - assertNotNull(("Change Event Change Type of one of the Change Entries is undefined for '" + logToken + "' Change Log Token"), event.getChangeType()); - assertTrue("Unexpected Object Id='" + event.getChangedNode().toString() + "' from Change Log Token Entries list for '" + logToken + "' Change Log Token", created - .contains(event.getChangedNode())); - if (!deleted.contains(event.getChangedNode())) - { - folderWasFound = folderWasFound || fileFolderService.getFileInfo(event.getChangedNode()).isFolder(); - assertTrue( - ("Object from Change Event Entries list is marked as '" + event.getChangeType().toString() + "' but does not exist for '" + logToken + "' Change Log Token"), - nodeService.exists(event.getChangedNode())); - } - else - { - assertTrue("Object has been deleted", deleted.contains(event.getChangedNode())); - assertFalse(("Object from Change Event Entries list is marked as 'DELETED' but it still exist for '" + logToken + "' Change Log Token"), nodeService.exists(event - .getChangedNode())); - } - addOneToAmount(logAmounts, event.getChangeType()); - } - if (FoldersAppearing.MUST_APPEAR == foldersAppearing) - { - assertTrue("No one Folder Object was returned", folderWasFound); - } - else - { - if (FoldersAppearing.NOT_EXPECTED == foldersAppearing) - { - assertFalse("Some Folder Object was found", folderWasFound); - } - } - if ((null == maxItems) || (maxItems >= TOTAL_AMOUNT)) - { - for (CMISChangeType key : actualAmounts.keySet()) - { - Integer actualAmount = actualAmounts.get(key); - Integer logAmount = logAmounts.get(key); - assertTrue(("Invalid Entries amount for '" + key.toString() + "' Change Type. Actual amount: " + actualAmount + ", but log amount: " + logAmount), actualAmount - .equals(logAmount)); - } - } - } - - private enum FoldersAppearing - { - NOT_EXPECTED, MAY_APPEAR, MUST_APPEAR - } - - /** - * Determines which kind of Change was handled and increments appropriate amount to 1 - * - * @param mappedAmounts {@link Map}>{@link CMISChangeType}, {@link Integer}< container instance that contains all accumulated amounts for each kind of Change - * @param changeType {@link CMISChangeType} enum value that determines kind of Change - */ - private void addOneToAmount(Map mappedAmounts, CMISChangeType changeType) - { - Integer amount = mappedAmounts.get(changeType); - amount = (null == amount) ? (Integer.valueOf(1)) : (Integer.valueOf(amount.intValue() + 1)); - mappedAmounts.put(changeType, amount); - } - - /** - * Test {@link CMISChangeLogServiceImpl} with enabled Auditing feature for Max Items parameter - * - * @throws Exception - */ - public void testEnabledAuditingForMaxItems() throws Exception - { - enableAudit(); - String logToken = changeLogService.getLastChangeLogToken(); - createTestData(EXPECTED_AMOUNTS, false); - assertEquals(CMISCapabilityChanges.OBJECTIDSONLY, changeLogService.getCapability()); - CMISChangeLog changeLog = changeLogService.getChangeLogEvents(logToken, THE_HALFT_OF_CREATED_AMOUNT); - assertChangeLog(logToken, changeLog); - assertChangeEvents(logToken, changeLog, THE_HALFT_OF_CREATED_AMOUNT, FoldersAppearing.NOT_EXPECTED); - assertEquals(THE_HALFT_OF_CREATED_AMOUNT, changeLog.getChangeEvents().size()); - assertTrue("Not all Change Log Entries were requested but result set is indicating that no one more Entry is avilable", changeLog.hasMoreItems()); - changeLog = changeLogService.getChangeLogEvents(logToken, TOTAL_AMOUNT + (logToken == null ? 0 : 1)); - assertChangeEvents(logToken, changeLog, TOTAL_AMOUNT, FoldersAppearing.NOT_EXPECTED); - assertFalse("All Change Log Entries were requested but result set is indicating that some more Entry(s) are available", changeLog.hasMoreItems()); - } - - /** - * This method tests {@link CMISChangeLogServiceImpl} on receiving Change Event Entries for Invalid Change Log Token with enable and disabled Changes Logging - * - * @throws Exception - */ - public void testReceivingChangeEventsForInvalidChangeToken() throws Exception - { - enableAudit(); - try - { - changeLogService.getChangeLogEvents(INVALID_CHANGE_TOKEN, null); - fail("Change Events were received normally for Invalid Change Log Token"); - } - catch (Exception e) - { - assertTrue("Invalid exception type from Change Log Service method call with enabled Changes Logging", e instanceof CMISInvalidArgumentException); - } - disableAudit(); - try - { - changeLogService.getChangeLogEvents(INVALID_CHANGE_TOKEN, null); - fail("Changes Logging was not enabled but not one Change Log Service method thrown exception"); - } - catch (Exception e) - { - assertTrue("Invalid exception type from Change Log Service method call with desabled Changes Logging", e instanceof AlfrescoRuntimeException); - } - } - - /** - * This method tests {@link CMISChangeLogServiceImpl} on working with Change Event entries which could contain Folder Objects - * - * @throws Exception - */ - public void testReceivingOfChangeEventsExpectingFolders() throws Exception - { - enableAudit(); - String changeToken = changeLogService.getLastChangeLogToken(); - createTestData(EXPECTED_AMOUNTS, true); - CMISChangeLog changeLogEvents = changeLogService.getChangeLogEvents(changeToken, null); - assertChangeLog(changeToken, changeLogEvents); - assertChangeEvents(changeToken, changeLogEvents, null, FoldersAppearing.MUST_APPEAR); - } - - /** - * This method tests {@link CMISChangeLogServiceImpl} on working with Change Event entries which could contain Folder Objects. Also this method tests behavior of Max Items - * parameter for Folder Objects - * - * @throws Exception - */ - public void testReceivingOfChangeEventsExpectingFoldersAndBoundedByMaxItems() throws Exception - { - enableAudit(); - String changeToken = changeLogService.getLastChangeLogToken(); - createTestData(EXPECTED_AMOUNTS, true); - CMISChangeLog changeLogEvents = changeLogService.getChangeLogEvents(changeToken, 15); - assertTrue("Not all Change Event Entries were requested but result set indicates that no more Entry(s) available", changeLogEvents.hasMoreItems()); - assertChangeLog(changeToken, changeLogEvents); - assertChangeEvents(changeToken, changeLogEvents, 15, FoldersAppearing.MAY_APPEAR); - changeLogEvents = changeLogService.getChangeLogEvents(changeToken, TOTAL_AMOUNT + (changeToken == null ? 0 : 1)); - assertChangeLog(changeToken, changeLogEvents); - assertChangeEvents(changeToken, changeLogEvents, TOTAL_AMOUNT, FoldersAppearing.MUST_APPEAR); - assertFalse("All Change Event Entries were requested but results indicating that some more Entry(s) available", changeLogEvents.hasMoreItems()); - } - - @Override - public void setUp() throws Exception - { - ApplicationContext applicationContext = ApplicationContextHelper.getApplicationContext(); - changeLogService = (CMISChangeLogService) applicationContext.getBean("CMISChangeLogService"); - nodeService = (NodeService) applicationContext.getBean("NodeService"); - permissionService = (PermissionService) applicationContext.getBean("PermissionService"); - fileFolderService = (FileFolderService) applicationContext.getBean("FileFolderService"); - transactionService = (TransactionService) applicationContext.getBean("transactionComponent"); - authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); - retryingTransactionHelper = (RetryingTransactionHelper) applicationContext.getBean("retryingTransactionHelper"); - auditSubsystem = (AuditModelRegistryImpl) applicationContext.getBean("Audit"); - - // initialise audit subsystem - RetryingTransactionCallback initAudit = new RetryingTransactionCallback() - { - public Void execute() throws Exception - { - auditSubsystem.stop(); - auditSubsystem.setProperty("audit.enabled", "true"); - auditSubsystem.setProperty("audit.cmischangelog.enabled", "true"); - auditSubsystem.start(); - return null; - } - }; - retryingTransactionHelper.doInTransaction(initAudit, false, true); - - // start test transaction - testTX = transactionService.getUserTransaction(); - testTX.begin(); - this.authenticationComponent.setSystemUserAsCurrentUser(); - } - - @Override - protected void tearDown() throws Exception - { - deleteTestData(); - - if (testTX.getStatus() == Status.STATUS_ACTIVE) - { - testTX.rollback(); - } - AuthenticationUtil.clearCurrentSecurityContext(); - - auditSubsystem.destroy(); - } -} diff --git a/source/java/org/alfresco/email/server/EmailServiceImplTest.java b/source/java/org/alfresco/email/server/EmailServiceImplTest.java index f40cfad343..c5787046f0 100644 --- a/source/java/org/alfresco/email/server/EmailServiceImplTest.java +++ b/source/java/org/alfresco/email/server/EmailServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -55,9 +55,9 @@ import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.tools.ant.filters.StringInputStream; import org.springframework.context.ApplicationContext; import com.sun.mail.smtp.SMTPMessage; @@ -196,7 +196,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -237,7 +237,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -276,7 +276,7 @@ public class EmailServiceImplTest extends TestCase ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(System.out); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -314,7 +314,7 @@ public class EmailServiceImplTest extends TestCase ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(System.out); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -436,7 +436,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -460,7 +460,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -484,7 +484,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -509,7 +509,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -533,7 +533,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -557,7 +557,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -581,7 +581,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -666,7 +666,7 @@ public class EmailServiceImplTest extends TestCase ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -706,7 +706,7 @@ public class EmailServiceImplTest extends TestCase msg.setSubject(TEST_LONG_SUBJECT); ByteArrayOutputStream bos2 = new ByteArrayOutputStream(); msg.writeTo(bos2); - is = new StringInputStream(bos2.toString()); + is = IOUtils.toInputStream(bos2.toString()); assertNotNull("is is null", is); m = new SubethaEmailMessage(is); @@ -728,7 +728,7 @@ public class EmailServiceImplTest extends TestCase msg.setSubject(EXT_NAME); ByteArrayOutputStream bos3 = new ByteArrayOutputStream(); msg.writeTo(bos3); - is = new StringInputStream(bos3.toString()); + is = IOUtils.toInputStream(bos3.toString()); assertNotNull("is is null", is); m = new SubethaEmailMessage(is); folderEmailMessageHandler.setOverwriteDuplicates(false); @@ -836,7 +836,7 @@ public class EmailServiceImplTest extends TestCase ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -940,7 +940,7 @@ public class EmailServiceImplTest extends TestCase ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); @@ -1033,7 +1033,7 @@ public class EmailServiceImplTest extends TestCase StringBuffer sb = new StringBuffer(); ByteArrayOutputStream bos = new ByteArrayOutputStream(); msg.writeTo(bos); - InputStream is = new StringInputStream(bos.toString()); + InputStream is = IOUtils.toInputStream(bos.toString()); assertNotNull("is is null", is); SubethaEmailMessage m = new SubethaEmailMessage(is); diff --git a/source/java/org/alfresco/opencmis/ActivityPoster.java b/source/java/org/alfresco/opencmis/ActivityPoster.java index 55814ac363..783066b709 100644 --- a/source/java/org/alfresco/opencmis/ActivityPoster.java +++ b/source/java/org/alfresco/opencmis/ActivityPoster.java @@ -19,7 +19,6 @@ package org.alfresco.opencmis; import org.alfresco.opencmis.ActivityPosterImpl.ActivityInfo; -import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.NodeRef; /** @@ -30,7 +29,7 @@ import org.alfresco.service.cmr.repository.NodeRef; // TODO consolidate with ActivityPost for WebDAV public interface ActivityPoster { - void postFileFolderAdded(FileInfo fileInfo); + void postFileFolderAdded(NodeRef nodeRef); void postFileFolderUpdated(boolean isFolder, NodeRef nodeRef); diff --git a/source/java/org/alfresco/opencmis/ActivityPosterImpl.java b/source/java/org/alfresco/opencmis/ActivityPosterImpl.java index d6dfcf8647..6ca7fc6357 100644 --- a/source/java/org/alfresco/opencmis/ActivityPosterImpl.java +++ b/source/java/org/alfresco/opencmis/ActivityPosterImpl.java @@ -24,17 +24,17 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.activities.ActivityType; import org.alfresco.repo.model.filefolder.HiddenAspect; -import org.alfresco.repo.model.filefolder.HiddenAspect.Visibility; import org.alfresco.repo.tenant.TenantService; import org.alfresco.service.cmr.activities.ActivityService; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileFolderServiceType; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteService; -import org.alfresco.util.FileFilterMode.Client; +import org.alfresco.service.namespace.QName; import org.alfresco.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -162,15 +162,22 @@ public class ActivityPosterImpl implements ActivityPoster, InitializingBean return tenantDomain; } + private boolean isFolder(NodeRef nodeRef) + { + QName typeQName = nodeService.getType(nodeRef); + FileFolderServiceType type = fileFolderService.getType(typeQName); + boolean isFolder = type.equals(FileFolderServiceType.FOLDER); + return isFolder; + } + /** * {@inheritDoc} */ @Override - public void postFileFolderAdded(FileInfo fileInfo) + public void postFileFolderAdded(NodeRef nodeRef) { - if(activitiesEnabled && !fileInfo.isHidden()) + if(activitiesEnabled && !hiddenAspect.hasHiddenAspect(nodeRef)) { - NodeRef nodeRef = fileInfo.getNodeRef(); SiteInfo siteInfo = siteService.getSite(nodeRef); String siteId = (siteInfo != null ? siteInfo.getShortName() : null); @@ -178,9 +185,12 @@ public class ActivityPosterImpl implements ActivityPoster, InitializingBean { // post only for nodes within sites NodeRef parentNodeRef = nodeService.getPrimaryParent(nodeRef).getParentRef(); - + String path = null; - if (fileInfo.isFolder()) + boolean isFolder = isFolder(nodeRef); + String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + if(isFolder) { NodeRef documentLibrary = siteService.getContainer(siteId, SiteService.DOCUMENT_LIBRARY); path = "/"; @@ -196,7 +206,7 @@ public class ActivityPosterImpl implements ActivityPoster, InitializingBean } } } - postFileFolderActivity((fileInfo.isFolder() ? ActivityType.FOLDER_ADDED : ActivityType.FILE_ADDED), path, parentNodeRef, nodeRef, siteId, fileInfo.getName()); + postFileFolderActivity((isFolder ? ActivityType.FOLDER_ADDED : ActivityType.FILE_ADDED), path, parentNodeRef, nodeRef, siteId, name); } } } @@ -207,7 +217,7 @@ public class ActivityPosterImpl implements ActivityPoster, InitializingBean @Override public void postFileFolderUpdated(boolean isFolder, NodeRef nodeRef) { - if(activitiesEnabled && hiddenAspect.getVisibility(Client.cmis, nodeRef) == Visibility.Visible) + if(activitiesEnabled && !hiddenAspect.hasHiddenAspect(nodeRef)) { SiteInfo siteInfo = siteService.getSite(nodeRef); String siteId = (siteInfo != null ? siteInfo.getShortName() : null); diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java index 3a32bfb062..38e2766d2f 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceImpl.java @@ -1064,30 +1064,16 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr FileInfo fileInfo = connector.getFileFolderService().create( parentInfo.getNodeRef(), name, type.getAlfrescoClass()); NodeRef nodeRef = fileInfo.getNodeRef(); - connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); connector.applyPolicies(nodeRef, type, policies); connector.applyACL(nodeRef, type, addAces, removeAces); - connector.getActivityPoster().postFileFolderAdded(fileInfo); + connector.getActivityPoster().postFileFolderAdded(nodeRef); return nodeRef.getId(); } - private String stripEncoding(String mimeType) - { - String ret = mimeType; - - int idx = mimeType.indexOf(";"); - if(idx != -1) - { - ret = mimeType.substring(0, idx); - } - - return ret; - } - @Override public String createDocument( String repositoryId, final Properties properties, String folderId, @@ -1128,27 +1114,40 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr throw new CmisConstraintException("This document type is not versionable!"); } - // copy stream to temp file - // OpenCMIS does this for us .... - final File tempFile = copyToTempFile(contentStream); - final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); - FileInfo fileInfo = connector.getFileFolderService().create( parentInfo.getNodeRef(), name, type.getAlfrescoClass()); NodeRef nodeRef = fileInfo.getNodeRef(); + connector.setProperties(nodeRef, type, properties, new String[] { PropertyIds.NAME, PropertyIds.OBJECT_TYPE_ID }); connector.applyPolicies(nodeRef, type, policies); connector.applyACL(nodeRef, type, addAces, removeAces); // handle content - if (contentStream != null) + File tempFile = null; + try { - // write content - ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - String mimeType = stripEncoding(contentStream.getMimeType()); - writer.setMimetype(mimeType); - writer.setEncoding(encoding.name()); - writer.putContent(tempFile); + if (contentStream != null) + { + // write content + String mimeType = parseMimeType(contentStream); + + // copy stream to temp file + // OpenCMIS does this for us .... + tempFile = copyToTempFile(contentStream); + final Charset encoding = (tempFile == null ? null : getEncoding(tempFile, contentStream.getMimeType())); + + ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); + writer.setMimetype(mimeType); + writer.setEncoding(encoding.name()); + writer.putContent(tempFile); + } + } + finally + { + if(tempFile != null) + { + removeTempFile(tempFile); + } } connector.extractMetadata(nodeRef); @@ -1158,11 +1157,9 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr connector.applyVersioningState(nodeRef, versioningState); - removeTempFile(tempFile); - String objectId = connector.createObjectId(nodeRef); - connector.getActivityPoster().postFileFolderAdded(fileInfo); + connector.getActivityPoster().postFileFolderAdded(nodeRef); return objectId; } @@ -1214,7 +1211,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr connector.applyVersioningState(nodeRef, versioningState); - connector.getActivityPoster().postFileFolderAdded(fileInfo); + connector.getActivityPoster().postFileFolderAdded(nodeRef); return connector.createObjectId(nodeRef); } @@ -1309,6 +1306,27 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr } } } + + private String parseMimeType(ContentStream contentStream) + { + String mimeType = null; + + String tmp = contentStream.getMimeType(); + if(tmp != null) + { + int idx = tmp.indexOf(";"); + if(idx != -1) + { + mimeType = tmp.substring(0, idx).trim(); + } + else + { + mimeType = tmp; + } + } + + return mimeType; + } @Override public void setContentStream( @@ -1349,7 +1367,8 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr try { ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); + String mimeType = parseMimeType(contentStream); + writer.setMimetype(mimeType); writer.setEncoding(encoding.name()); writer.putContent(tempFile); } @@ -1897,7 +1916,7 @@ public class AlfrescoCmisServiceImpl extends AbstractCmisService implements Alfr { // write content ContentWriter writer = connector.getFileFolderService().getWriter(nodeRef); - writer.setMimetype(contentStream.getMimeType()); + writer.setMimetype(parseMimeType(contentStream)); writer.setEncoding(encoding.name()); writer.putContent(tempFile); } diff --git a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java index a7385d8fac..dda5f7f55c 100644 --- a/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java +++ b/source/java/org/alfresco/opencmis/AlfrescoCmisServiceInterceptor.java @@ -32,6 +32,8 @@ import java.util.List; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.util.FileFilterMode; +import org.alfresco.util.FileFilterMode.Client; import org.alfresco.util.TempFileProvider; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -127,11 +129,15 @@ public class AlfrescoCmisServiceInterceptor implements MethodInterceptor persistedContentStreams.add(persistedContentStream); } } + + FileFilterMode.setClient(Client.cmis); ret = invocation.proceed(); } finally { + FileFilterMode.clearClient(); + service.afterCall(); // cleanup persisted content streams @@ -169,6 +175,12 @@ public class AlfrescoCmisServiceInterceptor implements MethodInterceptor } } + /** + * Persisted content stream, for use in retrying transactions. + * + * @author steveglover + * + */ private static class PersistedContentStream implements ContentStream { private File tempFile = null; @@ -250,7 +262,10 @@ public class AlfrescoCmisServiceInterceptor implements MethodInterceptor if (stream.getStream() != null) { OutputStream out = new BufferedOutputStream(new FileOutputStream(tempFile), bufferSize); - InputStream in = new BufferedInputStream(stream.getStream(), bufferSize); + //InputStream in = new BufferedInputStream(stream.getStream(), bufferSize); + // Temporary work around for bug in InternalTempFileInputStream which auto closes during read + // BufferedInputStream subsequent use of available() throws an exception. + InputStream in = stream.getStream(); byte[] buffer = new byte[bufferSize]; int i; diff --git a/source/java/org/alfresco/opencmis/CMISConnector.java b/source/java/org/alfresco/opencmis/CMISConnector.java index 57636c0234..470afd95e0 100644 --- a/source/java/org/alfresco/opencmis/CMISConnector.java +++ b/source/java/org/alfresco/opencmis/CMISConnector.java @@ -855,7 +855,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen ActivityInfo activityInfo = null; // post activity after removal of the node - postActivity &= hiddenAspect.getVisibility(Client.cmis, nodeRef) == Visibility.Visible; + postActivity &= !hiddenAspect.hasHiddenAspect(nodeRef); if(postActivity) { // get this information before the node is deleted @@ -1080,9 +1080,16 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { Map props = new HashMap(); props.put(ContentModel.PROP_INITIAL_VERSION, false); - props.put(ContentModel.PROP_AUTO_VERSION, false); + props.put(ContentModel.PROP_AUTO_VERSION, true); nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); } + + Map versionProperties = new HashMap(5); + versionProperties.put(VersionModel.PROP_VERSION_TYPE, VersionType.MAJOR); + versionProperties.put(VersionModel.PROP_DESCRIPTION, "Initial Version"); + + versionService.createVersion(nodeRef, versionProperties); + getCheckOutCheckInService().checkout(nodeRef); } else if ((versioningState == VersioningState.MAJOR) || (versioningState == VersioningState.MINOR)) @@ -1091,7 +1098,7 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { Map props = new HashMap(); props.put(ContentModel.PROP_INITIAL_VERSION, false); - props.put(ContentModel.PROP_AUTO_VERSION, false); + props.put(ContentModel.PROP_AUTO_VERSION, true); nodeService.addAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE, props); } @@ -1567,8 +1574,16 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { for (Object o : ((List) value)) { - propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, - convertAspectPropertyValue(o))); + if(o != null) + { + propertyValues.add(new CmisExtensionElementImpl(CMIS_NAMESPACE, "value", null, + convertAspectPropertyValue(o))); + } + else + { + logger.warn("Unexpected null entry in list value for property " + propertyDefintion.getDisplayName() + + ", value = " + value); + } } } else @@ -1812,17 +1827,18 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen try { - result.add(createCMISObject(createNodeInfo(assocRef), null, false, IncludeRelationships.NONE, - RENDITION_NONE, false, false)); + result.add(createCMISObject(createNodeInfo(assocRef), null, false, IncludeRelationships.NONE, + RENDITION_NONE, false, false)); + } + catch(AccessDeniedException e) + { + // PUBLICAPI-110 + // ok, just skip it } catch(CmisObjectNotFoundException e) { // ignore objects that have not been found (perhaps because their type is unknown to CMIS) } - catch (AccessDeniedException e) - { - // skip - } // TODO: Somewhere this has not been wrapped correctly catch (net.sf.acegisecurity.AccessDeniedException e) { @@ -2447,19 +2463,21 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen List extensions = properties.getExtensions(); if (extensions != null) { + boolean isNameChanging = properties.getProperties().containsKey(PropertyIds.NAME); + for (CmisExtensionElement extension : extensions) { if (ALFRESCO_EXTENSION_NAMESPACE.equals(extension.getNamespace()) && SET_ASPECTS.equals(extension.getName())) { - setAspectProperties(nodeRef, extension); + setAspectProperties(nodeRef, isNameChanging, extension); break; } } } } - private void setAspectProperties(NodeRef nodeRef, CmisExtensionElement aspectExtension) + private void setAspectProperties(NodeRef nodeRef, boolean isNameChanging, CmisExtensionElement aspectExtension) { if (aspectExtension.getChildren() == null) { @@ -2576,24 +2594,68 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen for (String aspect : aspectsToRemove) { aspectType = aspect; + TypeDefinitionWrapper type = getType(aspect); if (type == null) { throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); } - nodeService.removeAspect(nodeRef, type.getAlfrescoName()); + + QName typeName = type.getAlfrescoName(); + // if aspect is hidden aspect, remove only if hidden node is not client controlled + if(typeName.equals(ContentModel.ASPECT_HIDDEN)) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect only if client controlled + nodeService.removeAspect(nodeRef, typeName); + } + + + +// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) +// { +// nodeService.removeAspect(nodeRef, typeName); +// } + } + else + { + nodeService.removeAspect(nodeRef, typeName); + } } for (String aspect : aspectsToAdd) { aspectType = aspect; + TypeDefinitionWrapper type = getType(aspect); if (type == null) { throw new CmisInvalidArgumentException("Invalid aspect: " + aspectType); } - nodeService.addAspect(nodeRef, type.getAlfrescoName(), - Collections. emptyMap()); + + QName typeName = type.getAlfrescoName(); + // if aspect is hidden aspect, remove only if hidden node is not client controlled + if(typeName.equals(ContentModel.ASPECT_HIDDEN)) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect only if client controlled + nodeService.addAspect(nodeRef, type.getAlfrescoName(), + Collections. emptyMap()); + } + +// if(!isNameChanging && !hiddenAspect.isClientControlled(nodeRef) && !aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) +// { +// nodeService.addAspect(nodeRef, type.getAlfrescoName(), +// Collections. emptyMap()); +// } + } + else + { + nodeService.addAspect(nodeRef, type.getAlfrescoName(), + Collections. emptyMap()); + } } } catch (InvalidAspectException e) @@ -2610,12 +2672,35 @@ public class CMISConnector implements ApplicationContextAware, ApplicationListen { if (property.getValue().isEmpty()) { - nodeService.removeProperty(nodeRef, property.getKey()); + if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey())) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect property only if client controlled + nodeService.removeProperty(nodeRef, property.getKey()); + } + } + else + { + nodeService.removeProperty(nodeRef, property.getKey()); + } } else { - nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property - .getValue().get(0) : (Serializable) property.getValue()); + if(HiddenAspect.HIDDEN_PROPERTIES.contains(property.getKey())) + { + if(hiddenAspect.isClientControlled(nodeRef) || aspectProperties.containsKey(ContentModel.PROP_CLIENT_CONTROLLED)) + { + // manipulate hidden aspect property only if client controlled + nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property + .getValue().get(0) : (Serializable) property.getValue()); + } + } + else + { + nodeService.setProperty(nodeRef, property.getKey(), property.getValue().size() == 1 ? property + .getValue().get(0) : (Serializable) property.getValue()); + } } } } diff --git a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java index 38d690a50b..b4476d9930 100644 --- a/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java +++ b/source/java/org/alfresco/opencmis/CMISNodeInfoImpl.java @@ -291,40 +291,43 @@ public class CMISNodeInfoImpl implements CMISNodeInfo return; } - if (versionLabel == null) - { - if (isDocument()) - { - if(connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - } - else - { - objecVariant = CMISObjectVariant.CURRENT_VERSION; - } - - // Is it un-versioned, or currently versioned? - Version currentVersion = connector.getVersionService().getCurrentVersion(nodeRef); - if (currentVersion != null) - { - versionLabel = currentVersion.getVersionLabel(); - versionHistory = connector.getVersionService().getVersionHistory(nodeRef); - } - else - { - versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; - } - - objectId = getGuid(currentNodeId) + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; - currentObjectId = objectId; - hasPWC = (connector.getLockService().getLockType(nodeRef) == LockType.READ_ONLY_LOCK); - } else - { - objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; - } - return; - } + if (versionLabel == null) + { + if (isFolder()) + { + objecVariant = CMISObjectVariant.FOLDER; + } else if (isDocument()) + { + // for a document, absence of a version label implies the current (head) version + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + if (versionHistory == null) + { + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + } + else + { + Version headVersion = versionHistory.getHeadVersion(); + versionLabel = headVersion.getVersionLabel(); + } + + objectId = getGuid(currentNodeId) + CMISConnector.ID_SEPERATOR + versionLabel; + currentObjectId = objectId; + hasPWC = (connector.getLockService().getLockType(nodeRef) == LockType.READ_ONLY_LOCK); + } + } else + { + objecVariant = CMISObjectVariant.NOT_A_CMIS_OBJECT; + } + return; + } // check if it has PWC label if (versionLabel.equals(CMISConnector.PWC_VERSION_LABEL)) @@ -371,7 +374,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo return; } - + try { // the node is versioned, determine whether the versionLabel refers to the head version or a @@ -421,7 +424,7 @@ public class CMISNodeInfoImpl implements CMISNodeInfo { objecVariant = CMISObjectVariant.NOT_EXISTING; } - + // check if checked out hasPWC = connector.getCheckOutCheckInService().isCheckedOut(getCurrentNodeNodeRef()); } @@ -456,17 +459,19 @@ public class CMISNodeInfoImpl implements CMISNodeInfo objecVariant = CMISObjectVariant.NOT_EXISTING; return; } - - if (connector.filter(nodeRef)) - { - objecVariant = CMISObjectVariant.NOT_EXISTING; - return; - } - + if (isFolder()) { - objecVariant = CMISObjectVariant.FOLDER; - objectId = getGuid(nodeRef.toString()); + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + return; + } + else + { + objecVariant = CMISObjectVariant.FOLDER; + } + objectId = nodeRef.getId(); currentObjectId = objectId; return; } @@ -486,10 +491,15 @@ public class CMISNodeInfoImpl implements CMISNodeInfo checkedOut = nodeRef; } - objecVariant = CMISObjectVariant.PWC; - - objectId = getGuid(checkedOut.toString()) + CMISConnector.ID_SEPERATOR + CMISConnector.PWC_VERSION_LABEL; - + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.PWC; + } + objectId = checkedOut.getId() + CMISConnector.ID_SEPERATOR + CMISConnector.PWC_VERSION_LABEL; versionLabel = CMISConnector.PWC_VERSION_LABEL; currentObjectId = connector.createObjectId(checkedOut); currentNodeId = checkedOut.toString(); @@ -498,15 +508,45 @@ public class CMISNodeInfoImpl implements CMISNodeInfo } // check version - if(connector.getVersionService().isAVersion(nodeRef)) + versionHistory = connector.getVersionService().getVersionHistory(nodeRef); + if (versionHistory == null) { - analyseVersionNode(); + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = CMISObjectVariant.CURRENT_VERSION; + } + objectId = nodeRef.getId() + CMISConnector.ID_SEPERATOR + CMISConnector.UNVERSIONED_VERSION_LABEL; + versionLabel = CMISConnector.UNVERSIONED_VERSION_LABEL; + currentObjectId = objectId; } else { - analyseCurrentVersion(nodeRef); + Version headVersion = versionHistory.getHeadVersion(); + + versionLabel = (String) connector.getNodeService().getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); + + objectId = headVersion.getVersionedNodeRef().getId() + CMISConnector.ID_SEPERATOR + versionLabel; + + currentObjectId = headVersion.getVersionedNodeRef().getId() + CMISConnector.ID_SEPERATOR + + headVersion.getVersionLabel(); + currentNodeId = headVersion.getVersionedNodeRef().toString(); + + if(connector.filter(nodeRef)) + { + objecVariant = CMISObjectVariant.NOT_EXISTING; + } + else + { + objecVariant = (headVersion.getVersionLabel().equals(versionLabel) ? CMISObjectVariant.CURRENT_VERSION + : CMISObjectVariant.VERSION); + } } + hasPWC = connector.getCheckOutCheckInService().isCheckedOut(nodeRef); } protected void analyseAssociationRef() diff --git a/source/java/org/alfresco/opencmis/CMISTest.java b/source/java/org/alfresco/opencmis/CMISTest.java index f88e7016cb..44d7e7096f 100644 --- a/source/java/org/alfresco/opencmis/CMISTest.java +++ b/source/java/org/alfresco/opencmis/CMISTest.java @@ -203,28 +203,55 @@ public class CMISTest RepositoryInfo repo = repositories.get(0); repositoryId = repo.getId(); - // create content properties + // create simple text plain content PropertiesImpl properties = new PropertiesImpl(); String objectTypeId = "cmis:document"; properties.addProperty(new PropertyIdImpl(PropertyIds.OBJECT_TYPE_ID, objectTypeId)); String fileName = "textFile" + GUID.generate(); properties.addProperty(new PropertyStringImpl(PropertyIds.NAME, fileName)); - - // create content stream ContentStreamImpl contentStream = new ContentStreamImpl(fileName, MimetypeMap.MIMETYPE_TEXT_PLAIN, "Simple text plain document"); - - // create simple text plain content String objectId = cmisService.create(repositoryId, properties, repositoryHelper.getCompanyHome().getId(), contentStream, VersioningState.MAJOR, null, null); Holder objectIdHolder = new Holder(objectId); + String path = "/" + fileName; // create content stream with undefined mimetype and file name - ContentStreamImpl contentStreamHTML = new ContentStreamImpl(null, null, " Hello

Test html

"); - cmisService.setContentStream(repositoryId, objectIdHolder, true, null, contentStreamHTML, null); + { + ContentStreamImpl contentStreamHTML = new ContentStreamImpl(null, null, " Hello

Test html

"); + cmisService.setContentStream(repositoryId, objectIdHolder, true, null, contentStreamHTML, null); + + // check mimetype + ObjectData objectData = cmisService.getObjectByPath(repositoryId, path, null, false, IncludeRelationships.NONE, null, false, false, null); + objectId = objectData.getId(); + String contentType = cmisService.getObjectInfo(repositoryId, objectId).getContentType(); + assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_HTML, contentType); + } - // check mimetype - boolean mimetypeHTML = cmisService.getObjectInfo(repositoryId, objectId).getContentType().equals(MimetypeMap.MIMETYPE_HTML); - assertTrue("Mimetype is not defined correctly.", mimetypeHTML); + // create content stream with mimetype and encoding + { + String mimeType = MimetypeMap.MIMETYPE_TEXT_PLAIN + "; charset=UTF-8"; + ContentStreamImpl contentStreamHTML = new ContentStreamImpl(null, mimeType, " Hello

Test html

"); + cmisService.setContentStream(repositoryId, objectIdHolder, true, null, contentStreamHTML, null); + + // check mimetype + ObjectData objectData = cmisService.getObjectByPath(repositoryId, path, null, false, IncludeRelationships.NONE, null, false, false, null); + String contentType = cmisService.getObjectInfo(repositoryId, objectData.getId()).getContentType(); + assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_TEXT_PLAIN, contentType); + } + + // checkout/checkin object with mimetype and encoding + { + objectIdHolder.setValue(objectId); + cmisService.checkOut(repositoryId, objectIdHolder, null, new Holder()); + String mimeType = MimetypeMap.MIMETYPE_HTML + "; charset=UTF-8"; + ContentStreamImpl contentStreamHTML = new ContentStreamImpl(null, mimeType, " Hello

Test html

"); + cmisService.checkIn(repositoryId, objectIdHolder, false, null, contentStreamHTML, "checkin", null, null, null, null); + + // check mimetype + ObjectData objectData = cmisService.getObjectByPath(repositoryId, path, null, false, IncludeRelationships.NONE, null, false, false, null); + String contentType = cmisService.getObjectInfo(repositoryId, objectData.getId()).getContentType(); + assertEquals("Mimetype is not defined correctly.", MimetypeMap.MIMETYPE_HTML, contentType); + } } finally { diff --git a/source/java/org/alfresco/repo/action/executer/AbstractMailActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/AbstractMailActionExecuterTest.java new file mode 100644 index 0000000000..e866b68c20 --- /dev/null +++ b/source/java/org/alfresco/repo/action/executer/AbstractMailActionExecuterTest.java @@ -0,0 +1,271 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.action.executer; + +import java.io.IOException; +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeMessage; + +import org.alfresco.repo.management.subsystems.ApplicationContextFactory; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.preference.PreferenceService; +import org.alfresco.util.test.junitrules.AlfrescoPerson; +import org.alfresco.util.test.junitrules.ApplicationContextInit; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.context.ApplicationContext; + +/** + * Provides tests for the MailActionExecuter class. Most of this logic was in MailActionExecuterTest. + * Cloud code now includes extra tests and different rule config. + * Unfortunately this is messy due to the extensive use of static variables and Junit rules annotations. + * This class contains most of the test code, child classes actually setup the ClassRules and test fixtures, of + * particular importance is the static setupRuleChain() method in the child classes. The name is just a convention because + * it can't actually be enforced. The setupRuleChain() actually creates the users, as well as ordering the rules. + * You will see the AlfrescoPerson variables below are initialized as null, the assumption is that the child classes will + * create these users before they are needed (in the setupRuleChain() method), again this can't be enforced :(. + * + */ +public abstract class AbstractMailActionExecuterTest { + + // Rule to initialise the default Alfresco spring configuration + public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); + + + // Rules to create test users, these are actually created in the setupRuleChain() method of child classes. + public static AlfrescoPerson BRITISH_USER = null; + public static AlfrescoPerson FRENCH_USER = null; + public static AlfrescoPerson AUSTRALIAN_USER = null; + + protected static ActionService ACTION_SERVICE; + protected static MailActionExecuter ACTION_EXECUTER; + protected static PreferenceService PREFERENCE_SERVICE; + + protected static boolean WAS_IN_TEST_MODE; + + public static void setupTests(ApplicationContext appCtx) + { + ACTION_SERVICE = appCtx.getBean("ActionService", ActionService.class); + ACTION_EXECUTER = appCtx.getBean("OutboundSMTP", ApplicationContextFactory.class).getApplicationContext().getBean("mail", MailActionExecuter.class); + PREFERENCE_SERVICE = appCtx.getBean("PreferenceService", PreferenceService.class); + + WAS_IN_TEST_MODE = ACTION_EXECUTER.isTestMode(); + ACTION_EXECUTER.setTestMode(true); + + AuthenticationUtil.setRunAsUserSystem(); + + // All these test users are in the same tenant - either they're enterprise where there's only one, + // or they're cloud, where they have the same email domain + final String tenantId = getUsersHomeTenant(FRENCH_USER.getUsername()); + TenantUtil.runAsSystemTenant(new TenantRunAsWork() + { + @Override public Object doWork() throws Exception + { + final Map preferences = new HashMap(); + + preferences.put("locale", "fr"); + PREFERENCE_SERVICE.setPreferences(FRENCH_USER.getUsername(), preferences); + + preferences.clear(); + preferences.put("locale", "en_GB"); + PREFERENCE_SERVICE.setPreferences(BRITISH_USER.getUsername(), preferences); + + preferences.clear(); + preferences.put("locale", "en_AU"); + PREFERENCE_SERVICE.setPreferences(AUSTRALIAN_USER.getUsername(), preferences); + + return null; + } + }, tenantId); + } + + private static String getUsersHomeTenant(String userName) + { + boolean thisIsCloud = false; + try + { + // FIXME This is awful, but JUnit's insistence on static modifiers for various lifecycle methods has driven me to it... + thisIsCloud = (Class.forName("org.alfresco.module.org_alfresco_module_cloud.registration.RegistrationService") != null); + } + catch(ClassNotFoundException ignoreIfThrown) + { + // Intentionally empty + } + + String result = TenantService.DEFAULT_DOMAIN; + + // Even if we get email address-style user names in an enterprise system, those are not to be given home tenants. + if (thisIsCloud) + { + String[] elems = userName.split("@"); + result = elems[1]; + } + + return result; + + } + + public static void tearDownTests() + { + ACTION_EXECUTER.setTestMode(WAS_IN_TEST_MODE); + } + + @Test public void testUnknownRecipientUnknownSender() throws IOException, MessagingException + { + // PARAM_TO variant + Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); + mailAction.setParameterValue(MailActionExecuter.PARAM_FROM, "some.body@example.com"); + mailAction.setParameterValue(MailActionExecuter.PARAM_TO, "some.bodyelse@example.com"); + + mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Testing"); + mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, "alfresco/templates/mail/test.txt.ftl"); + + mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)getModel()); + + ACTION_SERVICE.executeAction(mailAction, null); + + MimeMessage message = ACTION_EXECUTER.retrieveLastTestMessage(); + Assert.assertNotNull(message); + Assert.assertEquals("Hello Jan 1, 1970", (String)message.getContent()); + } + + @Test public void testUnknownRecipientUnknownSender_ToMany() throws IOException, MessagingException + { + // PARAM_TO_MANY variant - this code path currently has separate validation FIXME fix this. + Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); + mailAction.setParameterValue(MailActionExecuter.PARAM_FROM, "some.body@example.com"); + mailAction.setParameterValue(MailActionExecuter.PARAM_TO_MANY, "some.bodyelse@example.com"); + + mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Testing"); + mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, "alfresco/templates/mail/test.txt.ftl"); + + mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)getModel()); + + ACTION_SERVICE.executeAction(mailAction, null); + + MimeMessage message = ACTION_EXECUTER.retrieveLastTestMessage(); + Assert.assertNotNull(message); + Assert.assertEquals("Hello Jan 1, 1970", (String)message.getContent()); + } + + private Serializable getModel() + { + Map model = new HashMap(); + + model.put("epoch", new Date(0)); + return (Serializable)model; + } + + @Test public void testFrenchRecipient() throws IOException, MessagingException + { + String from = "some.body@example.com"; + Serializable recipients = (Serializable)Arrays.asList(FRENCH_USER.getUsername()); + String subject = ""; + String template = "alfresco/templates/mail/test.txt.ftl"; + + MimeMessage message = sendMessage(from, recipients, subject, template); + + Assert.assertNotNull(message); + Assert.assertEquals("Bonjour 1 janv. 1970", (String)message.getContent()); + } + + protected MimeMessage sendMessage(String from, Serializable recipients, String subject, String template) { + Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); + mailAction.setParameterValue(MailActionExecuter.PARAM_TO_MANY, recipients); + + return sendMessage(from, subject, template, mailAction); + } + + protected MimeMessage sendMessage(String from, String subject, String template, final Action mailAction) { + if (from != null) + { + mailAction.setParameterValue(MailActionExecuter.PARAM_FROM, from); + } + mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, subject); + mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, template); + mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, getModel()); + + RetryingTransactionHelper txHelper = APP_CONTEXT_INIT.getApplicationContext().getBean("retryingTransactionHelper", RetryingTransactionHelper.class); + + return txHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override + public MimeMessage execute() throws Throwable + { + ACTION_SERVICE.executeAction(mailAction, null); + + return ACTION_EXECUTER.retrieveLastTestMessage(); + } + }, true); + } + + protected MimeMessage sendMessage(String from, String to, String subject, String template) { + Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); + mailAction.setParameterValue(MailActionExecuter.PARAM_TO, to); + return sendMessage(from, subject, template, mailAction); + } + + @Test public void testUnknowRecipientAustralianSender() throws IOException, MessagingException + { + String from = AUSTRALIAN_USER.getUsername(); + String to = "some.body@example.com"; + String subject = "Testing"; + String template = "alfresco/templates/mail/test.txt.ftl"; + + MimeMessage message = sendMessage(from, to, subject, template); + + Assert.assertNotNull(message); + Assert.assertEquals("G'Day 01/01/1970", (String)message.getContent()); +} + + @Test public void testSendingTestMessageWithNoCurrentUser() + { + try + { + // run with no current user + AuthenticationUtil.clearCurrentSecurityContext(); + + Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); + mailAction.setParameterValue(MailActionExecuter.PARAM_TO, "some.body@eaxmple.com"); + mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Testing"); + mailAction.setParameterValue(MailActionExecuter.PARAM_TEXT, "This is a test message."); + + ACTION_EXECUTER.executeImpl(mailAction, null); + } + finally + { + // restore system user as current user + AuthenticationUtil.setRunAsUserSystem(); + } + } + +} diff --git a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java index ddca2334d4..02b0c465b7 100644 --- a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java +++ b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java @@ -47,6 +47,7 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra private LockService lockService; private NodeService baseNodeService; private DictionaryService dictionaryService; + private NodeService mlAwareNodeService; /** Indicate if the action status should be tracked or not (default false) */ private boolean trackStatus = false; @@ -74,7 +75,12 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra } } - public void setLockService(LockService lockService) + public void setMlAwareNodeService(NodeService mlAwareNodeService) + { + this.mlAwareNodeService = mlAwareNodeService; + } + + public void setLockService(LockService lockService) { this.lockService = lockService; } @@ -235,7 +241,7 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra // Only execute the action if this action is read only or the actioned upon node reference doesn't // have a lock applied for this user. - if ((baseNodeService == null || actionedUponNodeRef == null || baseNodeService.exists(actionedUponNodeRef)) && // Not all actions are node based + if ((baseNodeService == null || actionedUponNodeRef == null || mlAwareNodeService.exists(actionedUponNodeRef)) && // Not all actions are node based (ignoreLock || !LockUtils.isLockedAndReadOnly(actionedUponNodeRef, lockService))) { // Execute the implementation diff --git a/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedderTest.java b/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedderTest.java index 0581036577..4ea795d93e 100644 --- a/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedderTest.java +++ b/source/java/org/alfresco/repo/action/executer/ContentMetadataEmbedderTest.java @@ -66,6 +66,7 @@ public class ContentMetadataEmbedderTest extends BaseSpringTest private ContentService contentService; private DictionaryService dictionaryService; private MimetypeService mimetypeService; + private MetadataExtracterRegistry metadataExtracterRegistry; private StoreRef testStoreRef; private NodeRef rootNodeRef; private NodeRef nodeRef; @@ -81,6 +82,7 @@ public class ContentMetadataEmbedderTest extends BaseSpringTest this.contentService = (ContentService) this.applicationContext.getBean("contentService"); this.dictionaryService = (DictionaryService) this.applicationContext.getBean("dictionaryService"); this.mimetypeService = (MimetypeService) this.applicationContext.getBean("mimetypeService"); + this.metadataExtracterRegistry = (MetadataExtracterRegistry) this.applicationContext.getBean("metadataExtracterRegistry"); AuthenticationComponent authenticationComponent = (AuthenticationComponent)applicationContext.getBean("authenticationComponent"); authenticationComponent.setSystemUserAsCurrentUser(); @@ -103,7 +105,11 @@ public class ContentMetadataEmbedderTest extends BaseSpringTest cw.putContent(AbstractContentTransformerTest.loadQuickTestFile("pdf")); // Get the executer instance - this.executer = (ContentMetadataEmbedder) this.applicationContext.getBean("embed-metadata"); + this.executer = new ContentMetadataEmbedder(); + this.executer.setNodeService(nodeService); + this.executer.setContentService(contentService); + this.executer.setMetadataExtracterRegistry(metadataExtracterRegistry); + this.executer.setApplicableTypes(new String[] { ContentModel.TYPE_CONTENT.toString() }); } /** diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java index e56c44b79e..ee843fd475 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuter.java @@ -538,6 +538,9 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if (to != null && to.length() != 0) { messageRef[0].setTo(to); + + // Note: there is no validation on the username to check that it actually is an email address. + // TODO Fix this. } else { @@ -560,9 +563,15 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if (authorities != null && authorities.size() != 0) { List recipients = new ArrayList(authorities.size()); + + if (logger.isTraceEnabled()) { logger.trace(authorities.size() + " recipient(s) for mail"); } + for (String authority : authorities) { - AuthorityType authType = AuthorityType.getAuthorityType(authority); + final AuthorityType authType = AuthorityType.getAuthorityType(authority); + + if (logger.isTraceEnabled()) { logger.trace(" authority type: " + authType); } + if (authType.equals(AuthorityType.USER)) { if (personService.personExists(authority) == true) @@ -571,12 +580,25 @@ public class MailActionExecuter extends ActionExecuterAbstractBase String address = (String)nodeService.getProperty(person, ContentModel.PROP_EMAIL); if (address != null && address.length() != 0 && validateAddress(address)) { + if (logger.isTraceEnabled()) { logger.trace("Recipient (person) exists in Alfresco with known email."); } recipients.add(address); } + else + { + if (logger.isTraceEnabled()) { logger.trace("Recipient (person) exists in Alfresco without known email."); } + // If the username looks like an email address, we'll use that. + if (validateAddress(authority)) { recipients.add(authority); } + } + } + else + { + if (logger.isTraceEnabled()) { logger.trace("Recipient does not exist in Alfresco."); } + if (validateAddress(authority)) { recipients.add(authority); } } } else if (authType.equals(AuthorityType.GROUP) || authType.equals(AuthorityType.EVERYONE)) { + if (logger.isTraceEnabled()) { logger.trace("Recipient is a group..."); } // Notify all members of the group Set users; if (authType.equals(AuthorityType.GROUP)) @@ -597,12 +619,25 @@ public class MailActionExecuter extends ActionExecuterAbstractBase if (address != null && address.length() != 0) { recipients.add(address); + if (logger.isTraceEnabled()) { logger.trace(" Group member email is known."); } } + else + { + if (logger.isTraceEnabled()) { logger.trace(" Group member email not known."); } + if (validateAddress(authority)) { recipients.add(userAuth); } + } + } + else + { + if (logger.isTraceEnabled()) { logger.trace(" Group member person not found"); } + if (validateAddress(authority)) { recipients.add(userAuth); } } } } } + if (logger.isTraceEnabled()) { logger.trace(recipients.size() + " valid recipient(s)."); } + if(recipients.size() > 0) { messageRef[0].setTo(recipients.toArray(new String[recipients.size()])); @@ -997,6 +1032,7 @@ public class MailActionExecuter extends ActionExecuterAbstractBase } } + @SuppressWarnings("unchecked") private List> getRecipients(Action ruleAction) { @@ -1038,13 +1074,11 @@ public class MailActionExecuter extends ActionExecuterAbstractBase AuthorityType authType = AuthorityType.getAuthorityType(authority); if (authType.equals(AuthorityType.USER)) { - if (personExists(authority)) + // Formerly, this code checked personExists(auth) but we now support emailing addresses who are not yet Alfresco users. + if (authority != null && authority.length() != 0 && validateAddress(authority)) { - if (authority != null && authority.length() != 0 && validateAddress(authority)) - { - Locale locale = getLocaleForUser(authority); - recipients.add(new Pair(authority, locale)); - } + Locale locale = getLocaleForUser(authority); + recipients.add(new Pair(authority, locale)); } } else if (authType.equals(AuthorityType.GROUP) || authType.equals(AuthorityType.EVERYONE)) @@ -1092,20 +1126,19 @@ public class MailActionExecuter extends ActionExecuterAbstractBase return recipients; } - @SuppressWarnings("deprecation") public boolean personExists(final String user) { boolean exists = false; - String domain = getDomain(user); - if (tenantService.getTenant(domain) != null) - { - exists = TenantUtil.runAsTenant(new TenantRunAsWork() - { - public Boolean doWork() throws Exception - { - return personService.personExists(user); - } - }, domain); + String domain = tenantService.getPrimaryDomain(user); // get primary tenant + if (domain != null) + { + exists = TenantUtil.runAsTenant(new TenantRunAsWork() + { + public Boolean doWork() throws Exception + { + return personService.personExists(user); + } + }, domain); } else { @@ -1114,20 +1147,19 @@ public class MailActionExecuter extends ActionExecuterAbstractBase return exists; } - @SuppressWarnings("deprecation") public NodeRef getPerson(final String user) { NodeRef person = null; - String domain = getDomain(user); - if (tenantService.getTenant(domain) != null) - { - person = TenantUtil.runAsTenant(new TenantRunAsWork() - { - public NodeRef doWork() throws Exception - { - return personService.getPerson(user); - } - }, domain); + String domain = tenantService.getPrimaryDomain(user); // get primary tenant + if (domain != null) + { + person = TenantUtil.runAsTenant(new TenantRunAsWork() + { + public NodeRef doWork() throws Exception + { + return personService.getPerson(user); + } + }, domain); } else { @@ -1136,43 +1168,54 @@ public class MailActionExecuter extends ActionExecuterAbstractBase return person; } - @SuppressWarnings("deprecation") + /** + * Gets the specified user's preferred locale, if available. + * + * @param user the username of the user whose locale is sought. + * @return the preferred locale for that user, if available, else null. The result would be null + * e.g. if the user does not exist in the system. + */ private Locale getLocaleForUser(final String user) { Locale locale = null; - String domain = getDomain(user); - if (tenantService.getTenant(domain) != null) - { - locale = TenantUtil.runAsTenant(new TenantRunAsWork() + String localeString = null; + + // get primary tenant for the specified user. + // + // This can have one of (at least) 3 values currently: + // 1. In single-tenant (community/enterprise) this will be the empty string. + // 2. In the cloud, for a username such as this: joe.soap@acme.com: + // 2A. If the acme.com tenant exists in the system, the primary domain is "acme.com" + // 2B. Id the acme.xom tenant does not exist in the system, the primary domain is null. + String domain = tenantService.getPrimaryDomain(user); + + if (domain != null) + { + // If the domain is not null, then the user exists in the system and we may get a preferred locale. + localeString = TenantUtil.runAsSystemTenant(new TenantRunAsWork() { - public Locale doWork() throws Exception + public String doWork() throws Exception { - return getLocaleForUserImpl(user); + return (String) preferenceService.getPreference(user, "locale"); } }, domain); } else { - return getLocaleForUserImpl(user); + // If the domain is null, then the beahviour here varies depending on whether it's a single tenant or multi-tenant cloud. + if (personExists(user)) + { + localeString = (String) preferenceService.getPreference(user, "locale"); + } + // else leave it as null - there's no tenant, no user for that username, so we can't get a preferred locale. } - return locale; - } - - private Locale getLocaleForUserImpl(String user) - { - Locale locale = null; - String localeString = (String)preferenceService.getPreference(user, "locale"); + if (localeString != null) { locale = StringUtils.parseLocaleString(localeString); } - return locale; - } - private String getDomain(String user) - { - String[] parts = user.split("@"); - return parts.length == 1 ? "" : parts[1].toLowerCase(I18NUtil.getLocale()); + return locale; } /** diff --git a/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java index 953bd5cf77..284afd54a4 100644 --- a/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/MailActionExecuterTest.java @@ -18,170 +18,50 @@ */ package org.alfresco.repo.action.executer; -import java.io.IOException; -import java.io.Serializable; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.Map; - -import javax.mail.MessagingException; -import javax.mail.internet.MimeMessage; - -import org.alfresco.repo.management.subsystems.ApplicationContextFactory; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.preference.PreferenceService; import org.alfresco.util.test.junitrules.AlfrescoPerson; -import org.alfresco.util.test.junitrules.ApplicationContextInit; import org.junit.AfterClass; -import org.junit.Assert; import org.junit.BeforeClass; import org.junit.ClassRule; -import org.junit.Test; import org.junit.rules.RuleChain; import org.springframework.context.ApplicationContext; -public class MailActionExecuterTest { - - // Rule to initialise the default Alfresco spring configuration - public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); - - // Rules to create 2 test users. - public static AlfrescoPerson AUSTRALIAN_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "AustralianUser@test.com"); - public static AlfrescoPerson BRITISH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "EnglishUser@test.com"); - public static AlfrescoPerson FRENCH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "FrenchUser@test.com"); - public static AlfrescoPerson UNKNOWN_USER1 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknownUser1@test.com"); - public static AlfrescoPerson UNKNOWN_USER2 = new AlfrescoPerson(APP_CONTEXT_INIT, "UnknowUser2@test.com"); - +/** + * Provides tests for the MailActionExecuter class. The logic is now in AbstractMailActionExecuterTest. + * See the Javadoc for AbstractMailActionExecuterTest. The setupRuleChain() method is very important as it + * really setup the class including creating the users. + * + */ +public class MailActionExecuterTest extends AbstractMailActionExecuterTest { + // Tie them together in a static Rule Chain - @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(APP_CONTEXT_INIT) - .around(AUSTRALIAN_USER) - .around(BRITISH_USER) - .around(FRENCH_USER) - .around(UNKNOWN_USER1) - .around(UNKNOWN_USER2); - - private static ActionService ACTION_SERVICE; - private static MailActionExecuter ACTION_EXECUTER; - private static PreferenceService PREFERENCE_SERVICE; - - private static boolean WAS_IN_TEST_MODE; + @ClassRule public static RuleChain ruleChain = setupRuleChain(); @BeforeClass public static void setup() { ApplicationContext appCtx = APP_CONTEXT_INIT.getApplicationContext(); - ACTION_SERVICE = appCtx.getBean("ActionService", ActionService.class); - ACTION_EXECUTER = appCtx.getBean("OutboundSMTP", ApplicationContextFactory.class).getApplicationContext().getBean("mail", MailActionExecuter.class); - PREFERENCE_SERVICE = appCtx.getBean("PreferenceService", PreferenceService.class); - - WAS_IN_TEST_MODE = ACTION_EXECUTER.isTestMode(); - ACTION_EXECUTER.setTestMode(true); - - AuthenticationUtil.setRunAsUserSystem(); - - Map preferences = new HashMap(); - - preferences.put("locale", "fr"); - PREFERENCE_SERVICE.setPreferences(FRENCH_USER.getUsername(), preferences); - - preferences.clear(); - preferences.put("locale", "en_GB"); - PREFERENCE_SERVICE.setPreferences(BRITISH_USER.getUsername(), preferences); - - preferences.clear(); - preferences.put("locale", "en_AU"); - PREFERENCE_SERVICE.setPreferences(AUSTRALIAN_USER.getUsername(), preferences); - + setupTests(appCtx); } - - @AfterClass + + @AfterClass public static void tearDown() { - ACTION_EXECUTER.setTestMode(WAS_IN_TEST_MODE); + tearDownTests(); } - - @Test public void testUnknownRecipientUnknownSender() throws IOException, MessagingException - { - Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); - mailAction.setParameterValue(MailActionExecuter.PARAM_FROM, "some.body@example.com"); - mailAction.setParameterValue(MailActionExecuter.PARAM_TO, "some.bodyelse@example.com"); - - mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Testing"); - mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, "alfresco/templates/mail/test.txt.ftl"); - - mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, (Serializable)getModel()); - - ACTION_SERVICE.executeAction(mailAction, null); - - MimeMessage message = ACTION_EXECUTER.retrieveLastTestMessage(); - Assert.assertNotNull(message); - Assert.assertEquals("Hello Jan 1, 1970", (String)message.getContent()); - } - - private Serializable getModel() - { - Map model = new HashMap(); - - model.put("epoch", new Date(0)); - return (Serializable)model; + + /** + * Sets up both users and the RuleChain. + * @return RuleChain + */ + private static RuleChain setupRuleChain() { + BRITISH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "englishuser@test.com"); + FRENCH_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "frenchuser@test.com"); + AUSTRALIAN_USER = new AlfrescoPerson(APP_CONTEXT_INIT, "australianuser@test.com"); + + return RuleChain.outerRule(APP_CONTEXT_INIT) + .around(AUSTRALIAN_USER) + .around(BRITISH_USER) + .around(FRENCH_USER); } - - @Test public void testFrenchRecipient() throws IOException, MessagingException - { - Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); - mailAction.setParameterValue(MailActionExecuter.PARAM_FROM, "some.body@example.com"); - mailAction.setParameterValue(MailActionExecuter.PARAM_TO_MANY, (Serializable)Arrays.asList(FRENCH_USER.getUsername())); - - mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, ""); - mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, "alfresco/templates/mail/test.txt.ftl"); - mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, getModel()); - - ACTION_SERVICE.executeAction(mailAction, null); - - MimeMessage message = ACTION_EXECUTER.retrieveLastTestMessage(); - Assert.assertNotNull(message); - Assert.assertEquals("Bonjour 1 janv. 1970", (String)message.getContent()); - } - - @Test public void testUnknowRecipientAustralianSender() throws IOException, MessagingException - { - Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); - mailAction.setParameterValue(MailActionExecuter.PARAM_FROM, AUSTRALIAN_USER.getUsername()); - mailAction.setParameterValue(MailActionExecuter.PARAM_TO, "some.body@eaxmple.com"); - - mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Testing"); - mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE, "alfresco/templates/mail/test.txt.ftl"); - mailAction.setParameterValue(MailActionExecuter.PARAM_TEMPLATE_MODEL, getModel()); - - ACTION_SERVICE.executeAction(mailAction, null); - - MimeMessage message = ACTION_EXECUTER.retrieveLastTestMessage(); - Assert.assertNotNull(message); - Assert.assertEquals("G'Day 01/01/1970", (String)message.getContent()); - } - - @Test public void testSendingTestMessageWithNoCurrentUser() - { - try - { - // run with no current user - AuthenticationUtil.clearCurrentSecurityContext(); - - Action mailAction = ACTION_SERVICE.createAction(MailActionExecuter.NAME); - mailAction.setParameterValue(MailActionExecuter.PARAM_TO, "some.body@eaxmple.com"); - mailAction.setParameterValue(MailActionExecuter.PARAM_SUBJECT, "Testing"); - mailAction.setParameterValue(MailActionExecuter.PARAM_TEXT, "This is a test message."); - - ACTION_EXECUTER.executeImpl(mailAction, null); - } - finally - { - // restore system user as current user - AuthenticationUtil.setRunAsUserSystem(); - } - } - + } diff --git a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java index 80d3bae200..6524963fe0 100644 --- a/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java +++ b/source/java/org/alfresco/repo/action/executer/TransformActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -178,6 +178,9 @@ public class TransformActionExecuter extends ActionExecuterAbstractBase } options = newTransformationOptions(ruleAction, actionedUponNodeRef); + // getExecuteAsychronously() is not true for async convert content rules, so using Thread name + // options.setUse(ruleAction.getExecuteAsychronously() ? "asyncRule" :"syncRule"); + options.setUse(Thread.currentThread().getName().contains("Async") ? "asyncRule" :"syncRule"); if (null == contentService.getTransformer(contentReader.getContentUrl(), contentReader.getMimetype(), contentReader.getSize(), mimeType, options)) { throw new RuleServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, contentReader.getMimetype(), mimeType)); diff --git a/source/java/org/alfresco/repo/action/executer/TransformActionExecuterTest.java b/source/java/org/alfresco/repo/action/executer/TransformActionExecuterTest.java index 1d519f8c0c..99660d8476 100644 --- a/source/java/org/alfresco/repo/action/executer/TransformActionExecuterTest.java +++ b/source/java/org/alfresco/repo/action/executer/TransformActionExecuterTest.java @@ -20,6 +20,7 @@ package org.alfresco.repo.action.executer; import static org.junit.Assert.assertEquals; +import java.util.Collection; import java.util.List; import java.util.Map; @@ -109,4 +110,5 @@ class DummyMimetypeService implements MimetypeService public String guessMimetype(String filename,ContentReader reader){ return null; } public boolean isText(String mimetype) { return false; } public String getMimetypeIfNotMatches(ContentReader reader) { return null; } + public Collection getMimetypes(String extension) { return null; } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java index 519cfb1076..c4952f02f1 100644 --- a/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java +++ b/source/java/org/alfresco/repo/activities/ActivityServiceImpl.java @@ -245,6 +245,15 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean { try { + // NOTE: siteId is optional + ParameterCheck.mandatoryString("feedUserId", feedUserId); + ParameterCheck.mandatoryString("format", format); + + if(!userNamesAreCaseSensitive) + { + feedUserId = feedUserId.toLowerCase(); + } + String currentUser = getCurrentUser(); if (! ((currentUser == null) || (authorityService.isAdminAuthority(currentUser)) || @@ -259,7 +268,9 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean siteId = tenantService.getName(siteId); } - PagingResults activityFeedEntries = feedDAO.selectPagedUserFeedEntries(feedUserId, format, siteId, excludeThisUser, excludeOtherUsers, minFeedId, pagingRequest); + String networkId = tenantService.getCurrentUserDomain(); + + PagingResults activityFeedEntries = feedDAO.selectPagedUserFeedEntries(feedUserId, networkId, format, siteId, excludeThisUser, excludeOtherUsers, minFeedId, pagingRequest); return activityFeedEntries; } catch (SQLException se) @@ -285,7 +296,7 @@ public class ActivityServiceImpl implements ActivityService, InitializingBean { feedUserId = feedUserId.toLowerCase(); } - + String currentUser = getCurrentUser(); if (! ((currentUser == null) || (authorityService.isAdminAuthority(currentUser)) || diff --git a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java index 55f0488ee5..c0585985af 100644 --- a/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java +++ b/source/java/org/alfresco/repo/activities/feed/AbstractFeedGenerator.java @@ -44,7 +44,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator private static Log logger = LogFactory.getLog(AbstractFeedGenerator.class); /** The name of the lock used to ensure that feed generator does not run on more than one node at the same time */ - private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "org.alfresco.repo.activities.feed.AbstractFeedGenerator"); + private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "FeedGenerator"); /** The time this lock will persist in the database (60 sec but refreshed at regular intervals) */ private static final long LOCK_TTL = 1000 * 60; @@ -164,7 +164,6 @@ public abstract class AbstractFeedGenerator implements FeedGenerator } LockCallback lockCallback = new LockCallback(); - String lockToken = null; try { @@ -190,7 +189,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator // Job being done by another process if (logger.isDebugEnabled()) { - logger.debug("Activities feed generator already underway"); + logger.debug("Activities feed generator already underway: " + LOCK_QNAME); } } catch (Throwable e) @@ -229,7 +228,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator running.set(false); if (logger.isDebugEnabled()) { - logger.debug("Lock released : " + LOCK_QNAME); + logger.debug("Lock release notification: " + LOCK_QNAME); } } } @@ -244,7 +243,7 @@ public abstract class AbstractFeedGenerator implements FeedGenerator if (logger.isDebugEnabled()) { - logger.debug("lock aquired: " + lockToken); + logger.debug("lock acquired: " + LOCK_QNAME + ": " + lockToken); } return lockToken; @@ -252,18 +251,28 @@ public abstract class AbstractFeedGenerator implements FeedGenerator private void releaseLock(LockCallback lockCallback, String lockToken) { - if (lockCallback != null) + try { - lockCallback.running.set(false); - } - - if(lockToken != null) - { - jobLockService.releaseLock(lockToken, LOCK_QNAME); - - if (logger.isInfoEnabled()) + if (lockCallback != null) { - logger.debug("Lock released (refresh failed): " + LOCK_QNAME + ", lock token " + lockToken); + lockCallback.running.set(false); + } + + if (lockToken != null) + { + jobLockService.releaseLock(lockToken, LOCK_QNAME); + if (logger.isDebugEnabled()) + { + logger.debug("Lock released: " + LOCK_QNAME + ": " + lockToken); + } + } + } + catch (LockAcquisitionException e) + { + // Ignore + if (logger.isDebugEnabled()) + { + logger.debug("Lock release failed: " + LOCK_QNAME + ": " + lockToken + "(" + e.getMessage() + ")"); } } } diff --git a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java index 2013e940ed..4e118b59a2 100644 --- a/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java +++ b/source/java/org/alfresco/repo/activities/post/lookup/PostLookup.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; import org.alfresco.model.ContentModel; import org.alfresco.repo.activities.ActivityType; @@ -70,8 +71,8 @@ public class PostLookup /** The name of the lock used to ensure that post lookup does not run on more than one node at the same time */ private static final QName LOCK_QNAME = QName.createQName(NamespaceService.SYSTEM_MODEL_1_0_URI, "ActivityPostLookup"); - /** The time this lock will persist in the database (30 sec but refreshed at regular intervals) */ - private static final long LOCK_TTL = 1000 * 30; + /** The time this lock will persist in the database (60 sec but refreshed at regular intervals) */ + private static final long LOCK_TTL = 1000 * 60; private ActivityPostDAO postDAO; private NodeService nodeService; @@ -82,8 +83,6 @@ public class PostLookup private SiteService siteService; private JobLockService jobLockService; - private volatile boolean busy; - public static final String JSON_NODEREF_LOOKUP = "nodeRefL"; // requires additional lookup public static final String JSON_NODEREF = "nodeRef"; @@ -184,22 +183,23 @@ public class PostLookup { checkProperties(); - if (busy) + // Avoid running when in read-only mode + if (!transactionService.getAllowWrite()) { - if (logger.isInfoEnabled()) + if (logger.isTraceEnabled()) { - logger.info("Still busy ..."); + logger.trace("Post lookup not running due to read-only server"); } return; } - + long start = System.currentTimeMillis(); String lockToken = null; + LockCallback lockCallback = new LockCallback(); try { if (jobLockService != null) { - JobLockRefreshCallback lockCallback = new LockCallback(); lockToken = acquireLock(lockCallback); } @@ -279,7 +279,7 @@ public class PostLookup } finally { - releaseLock(lockToken); + releaseLock(lockCallback, lockToken); } } @@ -764,29 +764,26 @@ public class PostLookup private class LockCallback implements JobLockRefreshCallback { + final AtomicBoolean running = new AtomicBoolean(true); + @Override public boolean isActive() { - return busy; + return running.get(); } @Override - public void lockReleased() + public synchronized void lockReleased() { - // note: currently the cycle will try to complete (even if refresh failed) - synchronized(this) + if (logger.isDebugEnabled()) { - if (logger.isTraceEnabled()) - { - logger.trace("Lock released (refresh failed): " + LOCK_QNAME); - } - - busy = false; + logger.debug("Lock release notification: " + LOCK_QNAME); } + running.set(false); } } - private String acquireLock(JobLockRefreshCallback lockCallback) throws LockAcquisitionException + private synchronized String acquireLock(LockCallback lockCallback) throws LockAcquisitionException { // Try to get lock String lockToken = jobLockService.getLock(LOCK_QNAME, LOCK_TTL); @@ -794,30 +791,38 @@ public class PostLookup // Got the lock - now register the refresh callback which will keep the lock alive jobLockService.refreshLock(lockToken, LOCK_QNAME, LOCK_TTL, lockCallback); - busy = true; - - if (logger.isTraceEnabled()) + if (logger.isDebugEnabled()) { - logger.trace("Lock aquired: " + lockToken); + logger.debug("Lock acquired: " + LOCK_QNAME + ": "+ lockToken); } return lockToken; } - private void releaseLock(String lockToken) + private synchronized void releaseLock(LockCallback lockCallback, String lockToken) { try { - busy = false; + if (lockCallback != null) + { + lockCallback.running.set(false); + } - if (lockToken != null ) { jobLockService.releaseLock(lockToken, LOCK_QNAME); } + if (lockToken != null ) + { + jobLockService.releaseLock(lockToken, LOCK_QNAME); + if (logger.isDebugEnabled()) + { + logger.debug("Lock released: " + LOCK_QNAME + ": " + lockToken); + } + } } catch (LockAcquisitionException e) { // Ignore - if (logger.isTraceEnabled()) + if (logger.isDebugEnabled()) { - logger.trace("Can't release lock: "+e); + logger.debug("Lock release failed: " + LOCK_QNAME + ": " + lockToken + "(" + e.getMessage() + ")"); } } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/AVMToADMRemoteStorePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/AVMToADMRemoteStorePatch.java index cf2959ac2d..1537629262 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/AVMToADMRemoteStorePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/AVMToADMRemoteStorePatch.java @@ -629,7 +629,7 @@ public class AVMToADMRemoteStorePatch extends AbstractPatch surfConfigRef = ref.getChildRef(); // surf-config needs to be hidden - applies index control aspect as part of the hidden aspect - hiddenAspect.hideNode(ref.getChildRef()); + hiddenAspect.hideNode(ref.getChildRef(), false, false, false); } catch (DuplicateChildNodeNameException dupErr) { diff --git a/source/java/org/alfresco/repo/avm/AVMNodeService.java b/source/java/org/alfresco/repo/avm/AVMNodeService.java index 8073b92b78..79d22517c5 100644 --- a/source/java/org/alfresco/repo/avm/AVMNodeService.java +++ b/source/java/org/alfresco/repo/avm/AVMNodeService.java @@ -307,7 +307,7 @@ public class AVMNodeService extends AbstractNodeServiceImpl implements NodeServi { // TODO Need to find out if this is important and if so // need to capture Transaction IDs. - return new NodeRef.Status(nodeRef, "Unknown", null, !exists(nodeRef)); + return new NodeRef.Status(null, nodeRef, "Unknown", null, !exists(nodeRef)); } /** diff --git a/source/java/org/alfresco/repo/cache/AbstractAsynchronouslyRefreshedCache.java b/source/java/org/alfresco/repo/cache/AbstractAsynchronouslyRefreshedCache.java index 62e1630128..01d635322a 100644 --- a/source/java/org/alfresco/repo/cache/AbstractAsynchronouslyRefreshedCache.java +++ b/source/java/org/alfresco/repo/cache/AbstractAsynchronouslyRefreshedCache.java @@ -463,15 +463,16 @@ public abstract class AbstractAsynchronouslyRefreshedCache implements Asynchr private void doCall() throws Exception { Refresh refresh = setUpRefresh(); - if (logger.isDebugEnabled()) - { - logger.debug("Building cache for tenant" + refresh.getTenantId()); - } if (refresh == null) { return; } + if (logger.isDebugEnabled()) + { + logger.debug("Building cache for tenant" + refresh.getTenantId()); + } + try { doRefresh(refresh); diff --git a/source/java/org/alfresco/repo/cache/CacheTest.java b/source/java/org/alfresco/repo/cache/CacheTest.java new file mode 100644 index 0000000000..cc09107fd7 --- /dev/null +++ b/source/java/org/alfresco/repo/cache/CacheTest.java @@ -0,0 +1,1140 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.cache; + +import java.sql.SQLException; +import java.util.Collection; + +import javax.transaction.Status; +import javax.transaction.UserTransaction; + +import junit.framework.TestCase; + +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.repo.transaction.TransactionListenerAdapter; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.apache.commons.lang.mutable.MutableLong; +import org.springframework.context.ApplicationContext; + +/** + * @see org.alfresco.repo.cache.TransactionalCache + * + * @author Derek Hulley + */ +public class CacheTest extends TestCase +{ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext( + new String[] { + "classpath:cache-test/cache-test-context.xml", + ApplicationContextHelper.CONFIG_LOCATIONS[0]}); + + private ServiceRegistry serviceRegistry; + private SimpleCache standaloneCache; + private SimpleCache backingCache; + private TransactionalCache transactionalCache; + private SimpleCache objectCache; + + @SuppressWarnings("unchecked") + @Override + public void setUp() throws Exception + { + if (AlfrescoTransactionSupport.getTransactionReadState() != TxnReadState.TXN_NONE) + { + fail("A transaction is still running"); + } + + serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + standaloneCache = (SimpleCache) ctx.getBean("simpleCache1"); + backingCache = (SimpleCache) ctx.getBean("backingCache"); + transactionalCache = (TransactionalCache) ctx.getBean("transactionalCache"); + objectCache = (SimpleCache) ctx.getBean("objectCache"); + + // Make sure that the backing cache is empty + backingCache.clear(); + + // Make the cache mutable (default) + transactionalCache.setMutable(true); + transactionalCache.setAllowEqualsChecks(false); + } + + @Override + public void tearDown() + { + serviceRegistry = null; + standaloneCache = null; + backingCache = null; + transactionalCache = null; + } + + public void testSetUp() throws Exception + { + assertNotNull(serviceRegistry); + assertNotNull(backingCache); + assertNotNull(standaloneCache); + assertNotNull(transactionalCache); + assertNotNull(objectCache); + } + + public void testObjectCache() throws Exception + { + objectCache.put("A", this); + Object obj = objectCache.get("A"); + assertTrue("Object not cached properly", this == obj); + } + + public void testEhcacheAdaptors() throws Exception + { + backingCache.put("A", "AAA"); + assertNull("Second cache should not have first's present", standaloneCache.get("A")); + + assertEquals("AAA", backingCache.get("A")); + + Collection keys = backingCache.getKeys(); + assertEquals("Backing cache didn't return correct number of keys", 1, keys.size()); + + backingCache.remove("A"); + assertNull(backingCache.get("A")); + } + + public void testTransactionalCacheNoTxn() throws Exception + { + String key = "B"; + String value = "BBB"; + // no transaction - do a put + transactionalCache.put(key, value); + // check that the value appears in the backing cache, backingCache + assertEquals("Backing cache not used for put when no transaction present", value, backingCache.get(key)); + + // remove the value from the backing cache and check that it is removed from the transaction cache + backingCache.remove(key); + assertNull("Backing cache not used for removed when no transaction present", transactionalCache.get(key)); + + // add value into backing cache + backingCache.put(key, value); + // remove it from the transactional cache + transactionalCache.remove(key); + // check that it is gone from the backing cache + assertNull("Non-transactional remove didn't go to backing cache", backingCache.get(key)); + } + + private static final String NEW_GLOBAL_ONE = "new_global_one"; + private static final String NEW_GLOBAL_TWO = "new_global_two"; + private static final String NEW_GLOBAL_THREE = "new_global_three"; + private static final String UPDATE_TXN_THREE = "updated_txn_three"; + private static final String UPDATE_TXN_FOUR = "updated_txn_four"; + + public void testRollbackCleanup() throws Exception + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + RetryingTransactionHelper txnHelper = transactionService.getRetryingTransactionHelper(); + + // Add items to the global cache + backingCache.put(NEW_GLOBAL_ONE, NEW_GLOBAL_ONE); + + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + private int throwCount = 0; + public Object execute() throws Throwable + { + transactionalCache.put(NEW_GLOBAL_TWO, NEW_GLOBAL_TWO); + transactionalCache.remove(NEW_GLOBAL_ONE); + + String key = "B"; + String value = "BBB"; + // no transaction - do a put + transactionalCache.put(key, value); + // Blow up + if (throwCount < 5) + { + throwCount++; + throw new SQLException("Dummy"); + } + else + { + throw new Exception("Fail"); + } + } + }; + try + { + txnHelper.doInTransaction(callback); + } + catch (Exception e) + { + // Expected + } + + assertFalse("Remove not done after rollback", transactionalCache.contains(NEW_GLOBAL_ONE)); + assertFalse("Update happened after rollback", transactionalCache.contains(NEW_GLOBAL_TWO)); + } + + public void testTransactionalCacheWithSingleTxn() throws Throwable + { + // add item to global cache + backingCache.put(NEW_GLOBAL_ONE, NEW_GLOBAL_ONE); + backingCache.put(NEW_GLOBAL_TWO, NEW_GLOBAL_TWO); + backingCache.put(NEW_GLOBAL_THREE, NEW_GLOBAL_THREE); + + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + try + { + // begin a transaction + txn.begin(); + + // remove 1 from the cache + transactionalCache.remove(NEW_GLOBAL_ONE); + assertFalse("Item was not removed from txn cache", transactionalCache.contains(NEW_GLOBAL_ONE)); + assertNull("Get didn't return null", transactionalCache.get(NEW_GLOBAL_ONE)); + assertTrue("Item was removed from backing cache", backingCache.contains(NEW_GLOBAL_ONE)); + + // read 2 from the cache + assertEquals("Item not read from backing cache", NEW_GLOBAL_TWO, transactionalCache.get(NEW_GLOBAL_TWO)); + // Change the backing cache + backingCache.put(NEW_GLOBAL_TWO, NEW_GLOBAL_TWO + "-updated"); + // Ensure read-committed + assertEquals("Read-committed not preserved", NEW_GLOBAL_TWO, transactionalCache.get(NEW_GLOBAL_TWO)); + + // update 3 in the cache + transactionalCache.put(UPDATE_TXN_THREE, "XXX"); + assertEquals("Item not updated in txn cache", "XXX", transactionalCache.get(UPDATE_TXN_THREE)); + assertFalse( + "Item was put into backing cache", + backingCache.contains(UPDATE_TXN_THREE)); + + // check that the keys collection is correct + Collection transactionalKeys = transactionalCache.getKeys(); + assertFalse("Transactionally removed item found in keys", transactionalKeys.contains(NEW_GLOBAL_ONE)); + assertTrue("Transactionally added item not found in keys", transactionalKeys.contains(UPDATE_TXN_THREE)); + + // Register a post-commit cache reader to make sure that nothing blows up if the cache is hit in post-commit + PostCommitCacheReader listenerReader = new PostCommitCacheReader(transactionalCache, UPDATE_TXN_THREE); + AlfrescoTransactionSupport.bindListener(listenerReader); + + // Register a post-commit cache reader to make sure that nothing blows up if the cache is hit in post-commit + PostCommitCacheWriter listenerWriter = new PostCommitCacheWriter(transactionalCache, UPDATE_TXN_FOUR, "FOUR"); + AlfrescoTransactionSupport.bindListener(listenerWriter); + + // commit the transaction + txn.commit(); + + // Check the post-commit stressers + if (listenerReader.e != null) + { + throw listenerReader.e; + } + if (listenerWriter.e != null) + { + throw listenerWriter.e; + } + + // check that backing cache was updated with the in-transaction changes + assertFalse("Item was not removed from backing cache", backingCache.contains(NEW_GLOBAL_ONE)); + assertNull("Item could still be fetched from backing cache", backingCache.get(NEW_GLOBAL_ONE)); + assertEquals("Item not updated in backing cache", "XXX", backingCache.get(UPDATE_TXN_THREE)); + + // Check that the transactional cache serves get requests + assertEquals("Transactional cache must serve post-commit get requests", "XXX", + transactionalCache.get(UPDATE_TXN_THREE)); + } + catch (Throwable e) + { + if (txn.getStatus() == Status.STATUS_ACTIVE) + { + txn.rollback(); + } + throw e; + } + } + + /** + * This transaction listener attempts to read from the cache in the afterCommit phase. Technically the + * transaction has finished, but the transaction resources are still available. + * + * @author Derek Hulley + * @since 2.1 + */ + private class PostCommitCacheReader extends TransactionListenerAdapter + { + private final SimpleCache transactionalCache; + private final String key; + private Throwable e; + private PostCommitCacheReader(SimpleCache transactionalCache, String key) + { + this.transactionalCache = transactionalCache; + this.key = key; + } + @Override + public void afterCommit() + { + try + { + transactionalCache.get(key); + } + catch (Throwable e) + { + this.e = e; + return; + } + } + } + + /** + * This transaction listener attempts to write to the cache in the afterCommit phase. Technically the + * transaction has finished, but the transaction resources are still available. + * + * @author Derek Hulley + * @since 2.1 + */ + private class PostCommitCacheWriter extends TransactionListenerAdapter + { + private final SimpleCache transactionalCache; + private final String key; + private final Object value; + private Throwable e; + private PostCommitCacheWriter(SimpleCache transactionalCache, String key, Object value) + { + this.transactionalCache = transactionalCache; + this.key = key; + this.value = value; + } + @Override + public void afterCommit() + { + try + { + transactionalCache.put(key, value); + transactionalCache.remove(key); + transactionalCache.clear(); + } + catch (Throwable e) + { + this.e = e; + return; + } + } + } + + public void testTransactionalCacheDisableSharedCaches() throws Throwable + { + // add item to global cache + backingCache.put(NEW_GLOBAL_ONE, NEW_GLOBAL_ONE); + backingCache.put(NEW_GLOBAL_TWO, NEW_GLOBAL_TWO); + backingCache.put(NEW_GLOBAL_THREE, NEW_GLOBAL_THREE); + + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + try + { + // begin a transaction + txn.begin(); + + // Go directly past ALL shared caches + transactionalCache.setDisableSharedCacheReadForTransaction(true); + + // Try to get results in shared caches + assertNull("Read of mutable shared cache MUST NOT use backing cache", transactionalCache.get(NEW_GLOBAL_ONE)); + assertNull("Value should not be in any cache", transactionalCache.get(UPDATE_TXN_THREE)); + + // Update the transactional caches + transactionalCache.put(NEW_GLOBAL_TWO, "An update"); + transactionalCache.put(UPDATE_TXN_THREE, UPDATE_TXN_THREE); + + // Try to get results in shared caches + assertNull("Read of mutable shared cache MUST NOT use backing cache", transactionalCache.get(NEW_GLOBAL_ONE)); + assertEquals("Value should be in transactional cache", "An update", transactionalCache.get(NEW_GLOBAL_TWO)); + assertEquals("Value should be in transactional cache", UPDATE_TXN_THREE, transactionalCache.get(UPDATE_TXN_THREE)); + + txn.commit(); + + // Now check that values were not written through for any caches + assertEquals("Out-of-txn read must return shared value", NEW_GLOBAL_ONE, transactionalCache.get(NEW_GLOBAL_ONE)); + assertNull("Value should be removed from shared cache", transactionalCache.get(NEW_GLOBAL_TWO)); + assertEquals("New values must be written to shared cache", UPDATE_TXN_THREE, transactionalCache.get(UPDATE_TXN_THREE)); + } + catch (Throwable e) + { + if (txn.getStatus() == Status.STATUS_ACTIVE) + { + txn.rollback(); + } + throw e; + } + } + + /** + * Preloads the cache, then performs a simultaneous addition of N new values and + * removal of the N preloaded values. + * + * @param cache + * @param objectCount + * @return Returns the time it took in nanoseconds. + */ + public long runPerformanceTestOnCache(SimpleCache cache, int objectCount) + { + // preload + for (int i = 0; i < objectCount; i++) + { + String key = Integer.toString(i); + Integer value = new Integer(i); + cache.put(key, value); + } + + // start timer + long start = System.nanoTime(); + for (int i = 0; i < objectCount; i++) + { + String key = Integer.toString(i); + cache.remove(key); + // add a new value + key = Integer.toString(i + objectCount); + Integer value = new Integer(i + objectCount); + cache.put(key, value); + } + // stop + long stop = System.nanoTime(); + + return (stop - start); + } + + /** + * Tests a straight Ehcache adapter against a transactional cache both in and out + * of a transaction. This is done repeatedly, pushing the count up. + */ + public void testPerformance() throws Exception + { + for (int i = 0; i < 6; i++) + { + int count = (int) Math.pow(10D, (double)i); + + // test standalone + long timePlain = runPerformanceTestOnCache(standaloneCache, count); + + // do transactional cache in a transaction + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + txn.begin(); + long timeTxn = runPerformanceTestOnCache(transactionalCache, count); + long commitStart = System.nanoTime(); + txn.commit(); + long commitEnd = System.nanoTime(); + long commitTime = (commitEnd - commitStart); + // add this to the cache's performance overhead + timeTxn += commitTime; + + // report + System.out.println("Cache performance test: \n" + + " count: " + count + "\n" + + " direct: " + timePlain/((long)count) + " ns\\count \n" + + " transaction: " + timeTxn/((long)count) + " ns\\count"); + } + } + + /** + * Time how long it takes to create and complete a whole lot of transactions + */ + public void testInitializationPerformance() throws Exception + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + long start = System.nanoTime(); + int count = 10000; + for (int i = 0; i < count; i++) + { + UserTransaction txn = transactionService.getUserTransaction(); + try + { + txn.begin(); + transactionalCache.contains("A"); + } + finally + { + try { txn.rollback(); } catch (Throwable ee) {ee.printStackTrace();} + } + } + long end = System.nanoTime(); + + // report + System.out.println( + "Cache initialization performance test: \n" + + " count: " + count + "\n" + + " transaction: " + (end-start)/((long)count) + " ns\\count"); + } + + /** + * @see #testPerformance() + */ + public static void main(String ... args) + { + try + { + CacheTest test = new CacheTest(); + test.setUp(); + System.out.println("Press any key to run initialization test ..."); + System.in.read(); + test.testInitializationPerformance(); + System.out.println("Press any key to run performance test ..."); + System.in.read(); + test.testPerformance(); + System.out.println("Press any key to shutdown ..."); + System.in.read(); + test.tearDown(); + } + catch (Throwable e) + { + e.printStackTrace(); + } + finally + { + ApplicationContextHelper.closeApplicationContext(); + } + } + + /** + * Starts off with a null in the backing cache and adds a value to the + * transactional cache. There should be no problem with this. + */ + public void testNullValue() throws Throwable + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + + txn.begin(); + + backingCache.put("A", null); + transactionalCache.put("A", "AAA"); + + try + { + txn.commit(); + } + catch (Throwable e) + { + try {txn.rollback();} catch (Throwable ee) {} + throw e; + } + } + + /** + * Add 50K objects into the transactional cache and checks that the first object added + * has been discarded. + */ + public void testMaxSizeOverrun() throws Exception + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + try + { + txn.begin(); + + Object startValue = new Integer(-1); + String startKey = startValue.toString(); + transactionalCache.put(startKey, startValue); + + assertEquals("The start value isn't correct", startValue, transactionalCache.get(startKey)); + + for (int i = 0; i < 205000; i++) + { + Object value = Integer.valueOf(i); + String key = value.toString(); + transactionalCache.put(key, value); + } + + // Is the start value here? + Object checkStartValue = transactionalCache.get(startKey); + // Now, the cache should no longer contain the first value + assertNull("The start value didn't drop out of the cache", checkStartValue); + + txn.commit(); + } + finally + { + try { txn.rollback(); } catch (Throwable ee) {} + } + } + + /** Execute the callback and ensure that the backing cache is left with the expected value */ + private void executeAndCheck( + RetryingTransactionCallback callback, + boolean readOnly, + String key, + Object expectedValue) throws Throwable + { + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(readOnly); + try + { + txn.begin(); + callback.execute(); + txn.commit(); + } + finally + { + try { txn.rollback(); } catch (Throwable ee) {} + } + Object actualValue = backingCache.get(key); + assertEquals("Backing cache value was not correct", expectedValue, actualValue); + + // Clear the backing cache to ensure that subsequent tests don't run into existing data + backingCache.clear(); + } + + private static final String DEFINITIVE_ONE = "def_one"; + private static final String DEFINITIVE_TWO = "def_two"; + private static final String DEFINITIVE_THREE = "def_three"; + + /** Lock values and ensure they don't get modified */ + public void testValueLockingInTxn() throws Exception + { + // add item to global cache + backingCache.put(DEFINITIVE_TWO, "initial_two"); + backingCache.put(DEFINITIVE_THREE, "initial_three"); + + TransactionService transactionService = serviceRegistry.getTransactionService(); + UserTransaction txn = transactionService.getUserTransaction(); + try + { + // begin a transaction + txn.begin(); + + // Add + { + assertEquals(null, transactionalCache.get(DEFINITIVE_ONE)); + // Add it + transactionalCache.put(DEFINITIVE_ONE, DEFINITIVE_ONE); + assertFalse("Key should not be locked, yet.", transactionalCache.isValueLocked(DEFINITIVE_ONE)); + // Mark it as definitive + transactionalCache.lockValue(DEFINITIVE_ONE); + assertTrue("Key should be locked.", transactionalCache.isValueLocked(DEFINITIVE_ONE)); + // Attempt update + transactionalCache.put(DEFINITIVE_ONE, "update_one"); + assertEquals("Update values should be locked.", DEFINITIVE_ONE, transactionalCache.get(DEFINITIVE_ONE)); + } + + // Update + { + assertEquals("initial_two", transactionalCache.get(DEFINITIVE_TWO)); + // Update it + transactionalCache.put(DEFINITIVE_TWO, DEFINITIVE_TWO); + assertFalse("Key should not be locked, yet.", transactionalCache.isValueLocked(DEFINITIVE_TWO)); + // Mark it as definitive + transactionalCache.lockValue(DEFINITIVE_TWO); + assertTrue("Key should be locked.", transactionalCache.isValueLocked(DEFINITIVE_TWO)); + // Attempt update + transactionalCache.put(DEFINITIVE_TWO, "update_two"); + assertEquals("Update values should be locked.", DEFINITIVE_TWO, transactionalCache.get(DEFINITIVE_TWO)); + // Attempt removal + transactionalCache.remove(DEFINITIVE_TWO); + assertEquals("Update values should be locked.", DEFINITIVE_TWO, transactionalCache.get(DEFINITIVE_TWO)); + } + + // Remove + { + assertEquals("initial_three", transactionalCache.get(DEFINITIVE_THREE)); + // Remove it + transactionalCache.remove(DEFINITIVE_THREE); + assertFalse("Key should not be locked, yet.", transactionalCache.isValueLocked(DEFINITIVE_THREE)); + // Mark it as definitive + transactionalCache.lockValue(DEFINITIVE_THREE); + assertTrue("Key should be locked.", transactionalCache.isValueLocked(DEFINITIVE_THREE)); + // Attempt update + transactionalCache.put(DEFINITIVE_THREE, "add_three"); + assertEquals("Removal should be locked.", null, transactionalCache.get(DEFINITIVE_THREE)); + } + + txn.commit(); + + } + finally + { + try { txn.rollback(); } catch (Throwable ee) {} + } + } + + private static final String COMMON_KEY = "A"; + private static final MutableLong VALUE_ONE_A = new MutableLong(1L); + private static final MutableLong VALUE_ONE_B = new MutableLong(1L); + private static final MutableLong VALUE_TWO_A = new MutableLong(2L); + /** + *
    + *
  • Add to the transaction cache
  • + *
  • Add to the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentAddAgainstAdd()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); + backingCache.put(COMMON_KEY, VALUE_ONE_B); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + } + /** + *
    + *
  • Add to the transaction cache
  • + *
  • Add to the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentAddAgainstAddSame()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); + backingCache.put(COMMON_KEY, VALUE_ONE_A); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Object handle is match + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Object handle is match + } + /** + *
    + *
  • Add to the transaction cache
  • + *
  • Clear the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentAddAgainstClear()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); + backingCache.clear(); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Mutable: Add back to backing cache + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_A); // Immutable: Add back to backing cache + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Update the transactional cache
  • + *
  • Update the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentUpdateAgainstUpdate()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); + backingCache.put(COMMON_KEY, VALUE_TWO_A); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Shared cache value checked failed + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Shared cache value checked failed + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_TWO_A); // Immutable: Assume backing cache is correct + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Update the transactional cache
  • + *
  • Update the backing cache with a null value
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentUpdateAgainstUpdateNull()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); + backingCache.put(COMMON_KEY, null); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Update the transactional cache with a null value
  • + *
  • Update the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentUpdateNullAgainstUpdate()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, null); + backingCache.put(COMMON_KEY, VALUE_ONE_B); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Update the transactional cache
  • + *
  • Remove from the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentUpdateAgainstRemove()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); + backingCache.remove(COMMON_KEY); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Update the transactional cache
  • + *
  • Clear the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentUpdateAgainstClear()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.put(COMMON_KEY, VALUE_ONE_B); + backingCache.clear(); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Add back to backing cache + } + /** + *
    + *
  • Remove from the backing cache
  • + *
  • Remove from the transactional cache
  • + *
  • Add to the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentRemoveAgainstUpdate_NoPreExisting()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.remove(COMMON_KEY); + transactionalCache.remove(COMMON_KEY); + backingCache.put(COMMON_KEY, VALUE_ONE_B); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + } + /** + *
    + *
  • Remove from the backing cache
  • + *
  • Add to the transactional cache
  • + *
  • Add to the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentAddAgainstAdd_NoPreExisting()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.remove(COMMON_KEY); + transactionalCache.put(COMMON_KEY, VALUE_ONE_A); + backingCache.put(COMMON_KEY, VALUE_ONE_B); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Mutable: Shared cache value checked + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + executeAndCheck(callback, true, COMMON_KEY, VALUE_ONE_B); // Immutable: Assume backing cache is correct + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Remove from the transactional cache
  • + *
  • Add to the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentRemoveAgainstUpdate_PreExisting()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.remove(COMMON_KEY); + backingCache.put(COMMON_KEY, VALUE_ONE_B); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Pessimistic removal + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Pessimistic removal + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Remove from the transactional cache
  • + *
  • Remove from the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentRemoveAgainstRemove()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.remove(COMMON_KEY); + backingCache.remove(COMMON_KEY); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Remove from backing cache + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Remove from backing cache + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Remove from backing cache + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Remove from backing cache + } + /** + *
    + *
  • Add to the backing cache
  • + *
  • Remove from the transactional cache
  • + *
  • Clear the backing cache
  • + *
  • Commit
  • + *
+ */ + public void testConcurrentRemoveAgainstClear()throws Throwable + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Object execute() throws Throwable + { + backingCache.put(COMMON_KEY, VALUE_ONE_A); + transactionalCache.remove(COMMON_KEY); + backingCache.clear(); + return null; + } + }; + transactionalCache.setAllowEqualsChecks(false); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Nothing to do + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Nothing to do + + transactionalCache.setAllowEqualsChecks(true); + transactionalCache.setMutable(true); + executeAndCheck(callback, false, COMMON_KEY, null); // Mutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Mutable: Nothing to do + transactionalCache.setMutable(false); + executeAndCheck(callback, false, COMMON_KEY, null); // Immutable: Nothing to do + executeAndCheck(callback, true, COMMON_KEY, null); // Immutable: Nothing to do + } +} diff --git a/source/java/org/alfresco/repo/cache/DefaultAsynchronouslyRefreshedCacheRegistry.java b/source/java/org/alfresco/repo/cache/DefaultAsynchronouslyRefreshedCacheRegistry.java index b5ecee5e30..47312b56d3 100644 --- a/source/java/org/alfresco/repo/cache/DefaultAsynchronouslyRefreshedCacheRegistry.java +++ b/source/java/org/alfresco/repo/cache/DefaultAsynchronouslyRefreshedCacheRegistry.java @@ -26,7 +26,6 @@ import org.apache.commons.logging.LogFactory; /** * Base registry implementation - * (Should be genericised somewhere..) * * @author Andy */ @@ -36,17 +35,12 @@ public class DefaultAsynchronouslyRefreshedCacheRegistry implements Asynchronous private List listeners = new LinkedList(); - /* - * (non-Javadoc) - * @see org.alfresco.repo.cache.AsynchronouslyRefreshedCacheRegistry#register(org.alfresco.repo.cache. - * RefreshableCacheListener) - */ @Override public void register(RefreshableCacheListener listener) { if(logger.isDebugEnabled()) { - logger.debug("Listener added for "+listener.getCacheId()); + logger.debug("Listener added for " + listener.getCacheId()); } listeners.add(listener); } @@ -54,14 +48,13 @@ public class DefaultAsynchronouslyRefreshedCacheRegistry implements Asynchronous public void broadcastEvent(RefreshableCacheEvent event, boolean toAll) { // If the system is up and running, broadcast the event immediately - for (RefreshableCacheListener listener : this.listeners) { if (toAll) { if(logger.isDebugEnabled()) { - logger.debug("Delivering to "+listener); + logger.debug("Delivering event (" + event + ") to listener (" + listener + ")."); } listener.onRefreshableCacheEvent(event); } @@ -71,13 +64,11 @@ public class DefaultAsynchronouslyRefreshedCacheRegistry implements Asynchronous { if(logger.isDebugEnabled()) { - logger.debug("Specific Delivery to "+listener); + logger.debug("Delivering event (" + event + ") to listener (" + listener + ")."); } listener.onRefreshableCacheEvent(event); } } } - } - } diff --git a/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java b/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java index 378de127bc..8f6c0f61e9 100644 --- a/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java +++ b/source/java/org/alfresco/repo/cache/RefreshableCacheListener.java @@ -22,19 +22,20 @@ package org.alfresco.repo.cache; * API to listen to cache events * * @author Andy - * */ public interface RefreshableCacheListener { /** - * Receive an event - * @param refreshableCacheEvent + * Callback made when a cache refresh occurs + * + * @param the cache event */ public void onRefreshableCacheEvent(RefreshableCacheEvent refreshableCacheEvent); /** - * Cache id so broadcast can be constrained to matching caches - * @return + * Cache id so broadcast can be constrained to matching caches + * + * @return the cache ID */ public String getCacheId(); } diff --git a/source/java/org/alfresco/repo/cache/TransactionalCache.java b/source/java/org/alfresco/repo/cache/TransactionalCache.java index c41e83d163..cde8e21bb6 100644 --- a/source/java/org/alfresco/repo/cache/TransactionalCache.java +++ b/source/java/org/alfresco/repo/cache/TransactionalCache.java @@ -71,7 +71,7 @@ import org.springframework.beans.factory.InitializingBean; * @author Derek Hulley */ public class TransactionalCache - implements SimpleCache, TransactionListener, InitializingBean + implements LockingCache, TransactionListener, InitializingBean { private static final String RESOURCE_KEY_TXN_DATA = "TransactionalCache.TxnData"; @@ -246,6 +246,7 @@ public class TransactionalCache // create and initialize caches data.updatedItemsCache = new LRULinkedHashMap>(23); data.removedItemsCache = new HashSet(13); + data.lockedItemsCache = new HashSet(13); data.isReadOnly = AlfrescoTransactionSupport.getTransactionReadState() == TxnReadState.TXN_READ_ONLY; // ensure that we get the transaction callbacks as we have bound the unique @@ -256,6 +257,9 @@ public class TransactionalCache return data; } + /** + * @see #setDisableSharedCacheReadForTransaction(boolean) + */ public boolean getDisableSharedCacheReadForTransaction() { if (AlfrescoTransactionSupport.getTransactionId() != null) @@ -364,7 +368,7 @@ public class TransactionalCache Collection backingCacheKeys = new HashSet(backingKeys.size()); for (K backingKey : backingKeys) { - backingCacheKeys.add(getCacheKey(backingKey)); + backingCacheKeys.add(getTenantAwareCacheKey(backingKey)); } keys.addAll(backingCacheKeys); } @@ -412,6 +416,75 @@ public class TransactionalCache { return (V) sharedCache.get(key); } + + /** + * @param txnData the existing data associated with the transaction + * @param key a tenant-aware key + * @return true if the key is locked + * + * @see HashSet#contains(Object) + * @see HashSet#size() + */ + private final boolean isValueLocked(TransactionData txnData, Serializable key) + { + /* + * Locking will be very infrequent. Calculating the hashcode of the key + * and using it to determine whether the lockedItemsCache contains the key is an + * unnecessary overhead; use the size() method for a slightly faster answer + * in the bulk of cases. + */ + return (txnData.lockedItemsCache.size() > 0 && txnData.lockedItemsCache.contains(key)); + } + + @Override + public boolean isValueLocked(K keyIn) + { + if (AlfrescoTransactionSupport.getTransactionId() != null) + { + final Serializable key = getTenantAwareCacheKey(keyIn); + TransactionData txnData = getTransactionData(); + return txnData.lockedItemsCache.contains(key); + } + else + { + // No transaction; we can't have locked it + return false; + } + } + + @Override + public void lockValue(K keyIn) + { + if (AlfrescoTransactionSupport.getTransactionId() != null) + { + final Serializable key = getTenantAwareCacheKey(keyIn); + TransactionData txnData = getTransactionData(); + txnData.lockedItemsCache.add(key); + return; + } + else + { + // No transaction; we can't lock it + return; + } + } + + @Override + public void unlockValue(K keyIn) + { + if (AlfrescoTransactionSupport.getTransactionId() != null) + { + final Serializable key = getTenantAwareCacheKey(keyIn); + TransactionData txnData = getTransactionData(); + txnData.lockedItemsCache.remove(key); + return; + } + else + { + // No transaction; we can't unlock it + return; + } + } /** * Checks the per-transaction caches for the object before going to the shared cache. @@ -419,7 +492,7 @@ public class TransactionalCache */ public V get(K keyIn) { - final Serializable key = getCacheKey(keyIn); + final Serializable key = getTenantAwareCacheKey(keyIn); boolean ignoreSharedCache = false; // are we in a transaction? @@ -520,7 +593,7 @@ public class TransactionalCache @SuppressWarnings("unchecked") public void put(K keyIn, V value) { - final Serializable key = getCacheKey(keyIn); + final Serializable key = getTenantAwareCacheKey(keyIn); // are we in a transaction? if (AlfrescoTransactionSupport.getTransactionId() == null) // not in transaction @@ -551,6 +624,18 @@ public class TransactionalCache " value: " + value); } } + else if (isValueLocked(txnData, key)) + { + // The key has been locked + if (isDebugEnabled) + { + logger.debug( + "Ignoring put after detecting locked key: \n" + + " cache: " + this + "\n" + + " key: " + key + "\n" + + " value: " + value); + } + } else { // we have an active transaction - add the item into the updated cache for this transaction @@ -607,7 +692,7 @@ public class TransactionalCache */ public void remove(K keyIn) { - final Serializable key = getCacheKey(keyIn); + final Serializable key = getTenantAwareCacheKey(keyIn); // are we in a transaction? if (AlfrescoTransactionSupport.getTransactionId() == null) // not in transaction @@ -636,6 +721,17 @@ public class TransactionalCache " key: " + key); } } + else if (isValueLocked(txnData, key)) + { + // The key has been locked + if (isDebugEnabled) + { + logger.debug( + "Ignoring remove after detecting locked key: \n" + + " cache: " + this + "\n" + + " key: " + key); + } + } else { // is the shared cache going to be cleared? @@ -705,9 +801,10 @@ public class TransactionalCache } else { - // the shared cache must be cleared at the end of the transaction + // The shared cache must be cleared at the end of the transaction // and also serves to ensure that the shared cache will be ignored - // for the remainder of the transaction + // for the remainder of the transaction. + // We do, however, keep all locked values locked. txnData.isClearOn = true; txnData.updatedItemsCache.clear(); txnData.removedItemsCache.clear(); @@ -1115,6 +1212,7 @@ public class TransactionalCache { private LRULinkedHashMap> updatedItemsCache; private Set removedItemsCache; + private Set lockedItemsCache; private boolean haveIssuedFullWarning; private boolean isClearOn; private boolean isClosed; @@ -1150,7 +1248,14 @@ public class TransactionalCache } } - private Serializable getCacheKey(final K key) + /** + * Convert the key to a tenant-specific key if the cache is tenant-aware and + * the current thread is running in the context of a tenant. + * + * @param key the key to convert + * @return a key that separates tenant-specific values + */ + private Serializable getTenantAwareCacheKey(final K key) { if (isTenantAware) { diff --git a/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java b/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java index fbef82d337..1e13dc2adb 100644 --- a/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java +++ b/source/java/org/alfresco/repo/content/ContentMinimalContextTestSuite.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -51,6 +51,7 @@ import org.alfresco.repo.content.transform.StringExtractingContentTransformerTes import org.alfresco.repo.content.transform.TextMiningContentTransformerTest; import org.alfresco.repo.content.transform.TextToPdfContentTransformerTest; import org.alfresco.repo.content.transform.TikaAutoContentTransformerTest; +import org.alfresco.repo.content.transform.TransformerConfigTestSuite; import org.alfresco.repo.content.transform.magick.ImageMagickContentTransformerTest; import org.alfresco.service.cmr.repository.TemporalSourceOptionsTest; import org.alfresco.service.cmr.repository.TransformationOptionLimitsTest; @@ -96,6 +97,9 @@ public class ContentMinimalContextTestSuite extends TestSuite suite.addTest(new JUnit4TestAdapter(TransformationOptionLimitsTest.class)); suite.addTest(new JUnit4TestAdapter(TransformationOptionPairTest.class)); + // Transformer Config + suite.addTest(new JUnit4TestAdapter(TransformerConfigTestSuite.class)); + // Source options suite.addTest(new JUnit4TestAdapter(TemporalSourceOptionsTest.class)); diff --git a/source/java/org/alfresco/repo/content/ContentServiceImpl.java b/source/java/org/alfresco/repo/content/ContentServiceImpl.java index 16fb377c01..465ae355fa 100644 --- a/source/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/source/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -22,7 +22,6 @@ import java.io.File; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; @@ -110,7 +109,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa private ContentTransformer imageMagickContentTransformer; /** Should we consider zero byte content to be the same as no content? */ private boolean ignoreEmptyContent; - private boolean transformerFailover; + private boolean transformerFailover = true; /** * The policy component @@ -183,8 +182,8 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa * Allows fail over form one transformer to another when there is * more than one transformer available. The cost is that the output * of the transformer must go to a temporary file in case it fails. - * @param transformerFailover {@code true} indicate that fail over - * should take place. + * @param transformerFailover {@code true} (the default) indicate + * that fail over should take place. */ public void setTransformerFailover(boolean transformerFailover) { @@ -611,7 +610,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa // look for a transformer transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype, options); List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); - transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.transform(...)"); + transformerDebug.availableTransformers(transformers, sourceSize, options, "ContentService.transform(...)"); int count = transformers.size(); if (count == 0) @@ -634,7 +633,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa if (transformerDebug.isEnabled()) { transformerDebug.popAvailable(); - debugActiveTransformers(sourceMimetype, targetMimetype, sourceSize, options); + debugTransformations(sourceMimetype, targetMimetype, sourceSize, options); } } } @@ -785,7 +784,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa // look for a transformer transformerDebug.pushAvailable(sourceUrl, sourceMimetype, targetMimetype, options); List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); - transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.getTransformer(...)"); + transformerDebug.availableTransformers(transformers, sourceSize, options, "ContentService.getTransformer(...)"); return transformers.isEmpty() ? null : transformers; } finally @@ -799,109 +798,20 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa * if it is creates TransformerDebug that lists all the supported mimetype transformation for * each transformer. */ - private void debugActiveTransformers(String sourceMimetype, String targetMimetype, + private void debugTransformations(String sourceMimetype, String targetMimetype, long sourceSize, TransformationOptions transformOptions) { // check the file name if (MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) && - MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype) && - transformerDebug.getFileName(transformOptions, true, 0).contains("debugTransformers.txt")) + MimetypeMap.MIMETYPE_IMAGE_PNG.equals(targetMimetype)) { - debugActiveTransformersByTransformer(); - debugActiveTransformersByMimetypes(); - } - } - - /** - * Creates TransformerDebug that lists all the supported mimetype transformation for each transformer. - */ - private void debugActiveTransformersByTransformer() - { - try - { - transformerDebug.pushMisc(); - transformerDebug.debug("Active and inactive transformers"); - TransformationOptions options = new TransformationOptions(); - - for (ContentTransformer transformer: transformerRegistry.getTransformers()) + String fileName = transformerDebug.getFileName(transformOptions, true, 0); + if (fileName != null && fileName.contains("debugTransformers.txt")) { - try - { - transformerDebug.pushMisc(); - int mimetypePairCount = 0; - boolean first = true; - for (String sourceMimetype : mimetypeService.getMimetypes()) - { - for (String targetMimetype : mimetypeService.getMimetypes()) - { - if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)) - { - long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes( - sourceMimetype, targetMimetype, options); - transformerDebug.activeTransformer(++mimetypePairCount, transformer, - sourceMimetype, targetMimetype, maxSourceSizeKBytes, first); - first = false; - } - } - } - if (first) - { - transformerDebug.inactiveTransformer(transformer); - } - } - finally - { - transformerDebug.popMisc(); - } + transformerDebug.transformationsByTransformer(null, false, false, null); + transformerDebug.transformationsByExtension(null, null, false, false, false, null); } } - finally - { - transformerDebug.popMisc(); - } - } - - /** - * Creates TransformerDebug that lists all available transformers for each mimetype combination. - */ - private void debugActiveTransformersByMimetypes() - { - try - { - transformerDebug.pushMisc(); - transformerDebug.debug("Transformers for each mimetype combination"); - TransformationOptions options = new TransformationOptions(); - - for (String sourceMimetype : mimetypeService.getMimetypes()) - { - for (String targetMimetype : mimetypeService.getMimetypes()) - { - try - { - transformerDebug.pushMisc(); - int transformerCount = 0; - for (ContentTransformer transformer: transformerRegistry.getTransformers()) - { - if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)) - { - long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes( - sourceMimetype, targetMimetype, options); - transformerDebug.activeTransformer(sourceMimetype, targetMimetype, - transformerCount, transformer, maxSourceSizeKBytes, transformerCount++ == 0); - } - } - } - finally - { - transformerDebug.popMisc(); - } - } - } - } - finally - { - transformerDebug.popMisc(); - } } /** @@ -932,7 +842,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } if (transformerDebug.isEnabled()) { - transformerDebug.availableTransformers(transformers, -1, + transformerDebug.availableTransformers(transformers, -1, options, "ContentService.getMaxSourceSizeBytes() = "+transformerDebug.fileSize(maxSourceSize*1024)); } return (maxSourceSize > 0) ? maxSourceSize * 1024 : maxSourceSize; @@ -993,7 +903,7 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa // look for a transformer transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, targetMimetype, options); List transformers = getActiveTransformers(sourceMimetype, sourceSize, targetMimetype, options); - transformerDebug.availableTransformers(transformers, sourceSize, "ContentService.isTransformable(...)"); + transformerDebug.availableTransformers(transformers, sourceSize, options, "ContentService.isTransformable(...)"); return transformers.size() > 0; } diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java index 7b57ab97be..65bd3b7b28 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java @@ -467,4 +467,33 @@ public abstract class AbstractContentTransformer implements ContentTransformer, { return (beanName == null) ? getClass().getSimpleName() : beanName; } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((beanName == null) ? 0 : beanName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + AbstractContentTransformer other = (AbstractContentTransformer) obj; + if (beanName == null) + { + if (other.beanName != null) + return false; + } + else if (!beanName.equals(other.beanName)) + return false; + return true; + } } diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java index 995876ac40..3a2fa6554e 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer2.java @@ -28,7 +28,6 @@ import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.TransformationOptions; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.factory.InitializingBean; /** * Provides basic services for {@link org.alfresco.repo.content.transform.ContentTransformer} @@ -45,8 +44,9 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo private static final Log logger = LogFactory.getLog(AbstractContentTransformer2.class); private ContentTransformerRegistry registry; + private boolean registerTransformer; - private ThreadLocal depth = new ThreadLocal() + private static ThreadLocal depth = new ThreadLocal() { @Override protected Integer initialValue() @@ -74,12 +74,22 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo this.registry = registry; } + /** + * @param registerTransformer as been available for selection. + * If {@code false} this indicates that the transformer may only be + * used as part of another transformer. + */ + public void setRegisterTransformer(boolean registerTransformer) + { + this.registerTransformer = registerTransformer; + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(this.getClass().getSimpleName()) - .append("[ average=").append(transformerConfig.getStatistics(this, null, null).getAverageTime()).append("ms") + .append("[ average=").append(transformerConfig.getStatistics(this, null, null, true).getAverageTime()).append("ms") .append("]"); return sb.toString(); } @@ -93,16 +103,21 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo public void register() { super.register(); - if (registry == null) { logger.warn("Property 'registry' has not been set. Ignoring auto-registration: \n" + - " transformer: " + this); - return; + " transformer: " + this.getName()); + } + else if (registerTransformer) + { + registry.addTransformer(this); + } + else + { + registry.addComponentTransformer(this); + logger.debug("Property 'registerTransformer' have not been set, so transformer (" + + this.getName() + ") may only be used as a component of a complex transformer."); } - - // register this instance for the fallback case - registry.addTransformer(this); } /** @@ -198,6 +213,10 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo // Transform transformInternal(reader, writer, options); + + // record time + long after = System.currentTimeMillis(); + recordTime(sourceMimetype, targetMimetype, after - before); } catch (ContentServiceTransientException cste) { @@ -227,7 +246,8 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo // Make sure that this transformation gets set back i.t.o. time taken. // This will ensure that transformers that compete for the same transformation // will be prejudiced against transformers that tend to fail - recordError(sourceMimetype, targetMimetype); + long after = System.currentTimeMillis(); + recordError(sourceMimetype, targetMimetype, after - before); // Ask Tika to detect the document, and report back on if // the current mime type is plausible @@ -276,10 +296,6 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo } } - // record time - long after = System.currentTimeMillis(); - recordTime(sourceMimetype, targetMimetype, after - before); - // done if (logger.isDebugEnabled()) { @@ -292,7 +308,7 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo } finally { - depth.set(depth.get()+1); + depth.set(depth.get()-1); } } @@ -309,7 +325,7 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo */ public synchronized long getTransformationTime() { - return transformerConfig.getStatistics(this, null, null).getAverageTime(); + return transformerConfig.getStatistics(this, null, null, true).getAverageTime(); } /** @@ -317,7 +333,7 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo */ public synchronized long getTransformationTime(String sourceMimetype, String targetMimetype) { - return transformerConfig.getStatistics(this, sourceMimetype, targetMimetype).getAverageTime(); + return transformerConfig.getStatistics(this, sourceMimetype, targetMimetype, true).getAverageTime(); } /** @@ -340,15 +356,14 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo * @param sourceMimetype * @param targetMimetype * @param transformationTime the time it took to perform the transformation. - * The value may be 0. */ protected final synchronized void recordTime(String sourceMimetype, String targetMimetype, long transformationTime) { - transformerConfig.getStatistics(this, sourceMimetype, targetMimetype).recordTime(transformationTime); + transformerConfig.getStatistics(this, sourceMimetype, targetMimetype, true).recordTime(transformationTime); if (depth.get() == 1) { - transformerConfig.getStatistics(null, sourceMimetype, targetMimetype).recordTime(transformationTime); + transformerConfig.getStatistics(null, sourceMimetype, targetMimetype, true).recordTime(transformationTime); } } @@ -357,13 +372,15 @@ public abstract class AbstractContentTransformer2 extends AbstractContentTransfo * long time, so that it is less likely to be called again. * @param sourceMimetype * @param targetMimetype + * @param transformationTime the time it took to perform the transformation. */ - protected final synchronized void recordError(String sourceMimetype, String targetMimetype) + protected final synchronized void recordError(String sourceMimetype, String targetMimetype, + long transformationTime) { - transformerConfig.getStatistics(this, sourceMimetype, targetMimetype).recordError(); + transformerConfig.getStatistics(this, sourceMimetype, targetMimetype, true).recordError(transformationTime); if (depth.get() == 1) { - transformerConfig.getStatistics(null, sourceMimetype, targetMimetype).recordError(); + transformerConfig.getStatistics(null, sourceMimetype, targetMimetype, true).recordError(transformationTime); } } } diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java index fa6ddef6a8..2e05117cf8 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java @@ -31,7 +31,6 @@ import java.util.Map.Entry; import org.alfresco.repo.content.AbstractContentReader; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; -import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.TransformationOptionLimits; import org.alfresco.service.cmr.repository.TransformationOptions; @@ -66,7 +65,7 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme /** * Indicates if 'page' limits are supported. */ - public void setPageLimitsSuported(boolean pageLimitsSupported) + public void setPageLimitsSupported(boolean pageLimitsSupported) { this.pageLimitsSupported = pageLimitsSupported; } @@ -106,7 +105,8 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme /** * Indicates if this transformer is able to transform the given source mimetype - * to the target mimetype. + * to the target mimetype. If overridden, consider also overriding + * {@link ContentTransformerHelper#getComments(boolean)}. */ @Override public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) @@ -161,10 +161,8 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme } /** - * Gets the timeout (ms) on the InputStream after which an IOExecption is thrown - * to terminate very slow transformations or a subprocess is terminated (killed). - * @return timeoutMs in milliseconds. If less than or equal to zero (the default) - * there is no timeout. + * @deprecated use @link {@link #getLimits(String, String, TransformationOptions)#getTimeoutMs()} + * which allows the limits to be selected based on mimetype and use. */ protected long getTimeoutMs() { @@ -180,10 +178,8 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme } /** - * Gets the limit in terms of the amount of data read (by time) to limit transformations where - * only the start of the content is needed. After this limit is reached the InputStream reports - * end of file. - * @return readLimitBytes if less than or equal to zero (the default) there is no limit. + * @deprecated use @link {@link #getLimits(String, String, TransformationOptions)#getReadLimitTimeMs()} + * which allows the limits to be selected based on mimetype and use. */ protected long getReadLimitTimeMs() { @@ -199,10 +195,8 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme } /** - * Gets the maximum source content size, to skip transformations where - * the source is just too large to expect it to perform. If the source is larger - * the transformer indicates it is not available. - * @return maxSourceSizeKBytes if less than or equal to zero (the default) there is no limit. + * @deprecated use @link {@link #getLimits(String, String, TransformationOptions)#getMaxSourceSizeKBytes()} + * which allows the limits to be selected based on mimetype and use. */ protected long getMaxSourceSizeKBytes() { @@ -218,10 +212,8 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme } /** - * Gets the limit in terms of the about of data read to limit transformations where - * only the start of the content is needed. After this limit is reached the InputStream reports - * end of file. - * @return readLimitKBytes if less than or equal to zero (the default) no limit should be applied. + * @deprecated use @link {@link #getLimits(String, String, TransformationOptions)#getReadLimitKBytes()} + * which allows the limits to be selected based on mimetype and use. */ protected long getReadLimitKBytes() { @@ -237,8 +229,8 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme } /** - * Get the maximum number of pages read before an exception is thrown. - * @return If less than or equal to zero (the default) no limit should be applied. + * @deprecated use @link {@link #getLimits(String, String, TransformationOptions)#getMaxPages()} + * which allows the limits to be selected based on mimetype and use. */ protected int getMaxPages() { @@ -254,8 +246,8 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme } /** - * Get the page limit before returning EOF. - * @return If less than or equal to zero (the default) no limit should be applied. + * @deprecated use @link {@link #getLimits(String, String, TransformationOptions)#getPageLimit()} + * which allows the limits to be selected based on mimetype and use. */ protected int getPageLimit() { @@ -271,11 +263,12 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme } /** - * Returns max and limit values for time, size and pages in a single operation. + * @deprecated use @link {@link #getLimits(String, String, TransformationOptions)} which allows the + * limits to be selected based on mimetype and use. */ protected TransformationOptionLimits getLimits() { - return transformerConfig.getLimits(this, null, null); + return transformerConfig.getLimits(this, null, null, null); } /** @@ -337,7 +330,7 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme TransformationOptions options) { return (reader == null || writer == null) - ? getLimits().combine(options.getLimits()) + ? transformerConfig.getLimits(this, null, null, options.getUse()).combine(options.getLimits()) : getLimits(reader.getMimetype(), writer.getMimetype(), options); } @@ -349,7 +342,7 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme protected TransformationOptionLimits getLimits(String sourceMimetype, String targetMimetype, TransformationOptions options) { - TransformationOptionLimits limits = transformerConfig.getLimits(this, sourceMimetype, targetMimetype); + TransformationOptionLimits limits = transformerConfig.getLimits(this, sourceMimetype, targetMimetype, (options == null ? null : options.getUse())); return (options == null) ? limits : limits.combine(options.getLimits()); } diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java index 7a779f2e3d..cc1a8a692b 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -271,26 +271,6 @@ public class AbstractContentTransformerLimitsTest transformer.getMaxSourceSizeKBytes(A, B, options)); } - @Test - // Combination where transformer limit is used - public void testGetMaxSourceSizeKBytesCombinationTransUsed() throws Exception - { - long kValue = 12; - long byteValue = kValue*1024; - - // Not set mimetype limits yet - assertTrue("No limits so should have been ok", - transformer.isTransformableSize(A, byteValue+1, B, options)); - - transformer.setMaxSourceSizeKBytes(kValue); - limits.setMaxSourceSizeKBytes(kValue+1); - addMimetypeLimits(A, B, limits); - transformer.setMimetypeLimits(mimetypeLimits); - transformer.register(); - assertEquals("Expected to have transformer set value returned", kValue, - transformer.getMaxSourceSizeKBytes(A, B, options)); - } - @Test // Combination where mimetype limit is used public void testGetMaxSourceSizeKBytesCombinationMimetypeUsed() throws Exception @@ -310,7 +290,7 @@ public class AbstractContentTransformerLimitsTest assertEquals("Expected to have transformer set value returned", kValue, transformer.getMaxSourceSizeKBytes(A, B, options)); } - + @Test // Check no limit when page limit set on a transformer that does not support page limit // maxSourceSizeKbytes value should be ignored if a page limit is in use @@ -323,16 +303,17 @@ public class AbstractContentTransformerLimitsTest assertTrue("No limits so should have been ok", transformer.isTransformableSize(A, byteValue+1, B, options)); - transformer.setPageLimitsSuported(false); + transformer.setPageLimitsSupported(false); transformer.setMaxSourceSizeKBytes(kValue); limits.setMaxSourceSizeKBytes(kValue+1); limits.setPageLimit(1); addMimetypeLimits(A, B, limits); transformer.setMimetypeLimits(mimetypeLimits); transformer.register(); - assertEquals("Expected to ignore the page limit as the transformer does not support it", kValue, + assertEquals("Expected to ignore the page limit as the transformer does not support it", kValue+1, transformer.getMaxSourceSizeKBytes(A, B, options)); } + @Test // Check no limit when page limit set on a transformer that does support page limit // maxSourceSizeKbytes value should be ignored if a page limit is in use @@ -345,7 +326,7 @@ public class AbstractContentTransformerLimitsTest assertTrue("No limits so should have been ok", transformer.isTransformableSize(A, byteValue+1, B, options)); - transformer.setPageLimitsSuported(true); + transformer.setPageLimitsSupported(true); transformer.setMaxSourceSizeKBytes(kValue); limits.setMaxSourceSizeKBytes(kValue+1); limits.setPageLimit(1); @@ -407,28 +388,6 @@ public class AbstractContentTransformerLimitsTest assertFalse("Size is greater than limit so should have failed", transformer.isTransformableSize(A, byteValue+1, B, options)); } - - @Test - // Combination where transformer limit is used - public void testIsTransformableSizeCombinationTransUsed() throws Exception - { - long kValue = 12; - long byteValue = kValue*1024; - - // Not set mimetype limits yet - assertTrue("No limits so should have been ok", - transformer.isTransformableSize(A, byteValue+1, B, options)); - - transformer.setMaxSourceSizeKBytes(kValue); - limits.setMaxSourceSizeKBytes(kValue+1); - addMimetypeLimits(A, B, limits); - transformer.setMimetypeLimits(mimetypeLimits); - transformer.register(); - assertTrue("Size is equal to limit so should have been ok", - transformer.isTransformableSize(A, byteValue, B, options)); - assertFalse("Size is greater than limit so should have failed", - transformer.isTransformableSize(A, byteValue+1, B, options)); - } @Test // Combination where mimetype limit is used @@ -459,7 +418,7 @@ public class AbstractContentTransformerLimitsTest long byteValue = kValue*1024; transformer.setMaxSourceSizeKBytes(kValue); - transformer.setPageLimitsSuported(true); + transformer.setPageLimitsSupported(true); transformer.register(); // Test works as normal before setting the pageLimit diff --git a/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java b/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java index 779c987855..4cd11272bb 100644 --- a/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/AppleIWorksContentTransformer.java @@ -63,7 +63,13 @@ public class AppleIWorksContentTransformer extends AbstractContentTransformer2 // This is because iWorks 09+ files are zip files containing embedded jpeg/pdf previews. return TARGET_MIMETYPES.contains(targetMimetype) && IWORKS_MIMETYPES.contains(sourceMimetype); } - + + @Override + public String getComments(boolean available) + { + return getCommentsOnlySupports(IWORKS_MIMETYPES, TARGET_MIMETYPES, available); + } + @Override protected void transformInternal(ContentReader reader, ContentWriter writer, diff --git a/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java b/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java index 31bfba5a93..bcbb14f807 100644 --- a/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java @@ -79,4 +79,13 @@ public class BinaryPassThroughContentTransformer extends AbstractContentTransfor } } } + + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getComments(available)); + sb.append("# Only supports streaming to the same type but excludes txt\n"); + return sb.toString(); + } } diff --git a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java index 68055c1728..8bc08a64ee 100644 --- a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java @@ -23,7 +23,6 @@ import java.io.File; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.ArrayDeque; -import java.util.ArrayList; import java.util.Collections; import java.util.Deque; import java.util.Iterator; @@ -45,7 +44,6 @@ import org.alfresco.util.TempFileProvider; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.InitializingBean; /** @@ -509,4 +507,37 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple { return Collections.unmodifiableList(intermediateMimetypes); } + + /** + * Returns the transformer properties predefined (hard coded or implied) by this transformer. + */ + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getComments(available)); + sb.append("# "); + sb.append(TransformerConfig.CONTENT); + sb.append(getName()); + sb.append(TransformerConfig.PIPELINE); + sb.append('='); + Iterator iterator = intermediateMimetypes.iterator(); + for (ContentTransformer transformer: transformers) + { + sb.append(transformer != null ? getSimpleName(transformer) : TransformerConfig.ANY); + if (iterator.hasNext()) + { + sb.append(TransformerConfig.PIPE); + String mimetype = iterator.next(); + if (mimetype != null && mimetype.length() != 0) + { + String extension = getMimetypeService().getExtension(mimetype); + sb.append(extension); + } + sb.append(TransformerConfig.PIPE); + } + } + sb.append('\n'); + return sb.toString(); + } } diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ContentTransformer.java index 200db7ab86..4279e4ba85 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformer.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.content.transform; +import java.util.List; import java.util.Map; import org.alfresco.repo.content.ContentWorker; @@ -71,6 +72,22 @@ public interface ContentTransformer extends ContentWorker */ public boolean isTransformableSize(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options); + /** + * Overridden to supply a comment or String of commented out transformation properties + * that specify any (hard coded or implied) supported transformations. Used + * when providing a list of properties to an administrators who may be setting + * other transformation properties, via JMX. Consider overriding if + * {link {@link AbstractContentTransformerLimits#isTransformableMimetype(String, String, TransformationOptions)} + * or {@link ContentTransformerWorker#isTransformable(String, String, TransformationOptions)} + * have been overridden. + * See {@link #getCommentsOnlySupports(List, List, boolean)} which may be used to help construct a comment. + * @param available indicates if the transformer has been registered and is available to be selected. + * {@code false} indicates that the transformer is only available as a component of a + * complex transformer. + * @return one line per property. The simple transformer name is returned by default as a comment. + */ + public String getComments(boolean available); + /** * Returns the maximum source size (in KBytes) allowed given the supplied values. * @return 0 if the the transformation is disabled, -1 if there is no limit, otherwise the size in KBytes. diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java index 44233e76c1..6a49829096 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerHelper.java @@ -21,6 +21,7 @@ package org.alfresco.repo.content.transform; import static org.alfresco.repo.content.transform.TransformerConfig.ANY; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; @@ -75,7 +76,7 @@ public class ContentTransformerHelper implements BeanNameAware deprecatedSupportedTransformations(explicitTransformations, null); // TODO Should suggest properties that indicate lower priority transformers should be unsupported. // This is for completeness rather than needed as the priority will avoid the non explicit - // transformers from being used. Explicit transformers are given a priority of 5 rather than 10. + // transformers from being used. Explicit transformers are given a priority of 50 rather than 100. } /** @@ -168,6 +169,18 @@ public class ContentTransformerHelper implements BeanNameAware return (beanName == null) ? getClass().getSimpleName() : beanName; } + /** + * Returns the simple form of the transformer name, which has had the normal + * "transformer." prefix to the Spring bean name removed. + */ + public static String getSimpleName(ContentTransformer transformer) + { + String transformerName = transformer.getName(); + return transformerName.startsWith(TransformerConfig.TRANSFORMER) + ? transformerName.substring(TransformerConfig.TRANSFORMER.length()) + : transformerName; + } + /** * Called by deprecated property setter methods that should no longer be called * by Spring configuration as the values are now set using global properties. @@ -194,6 +207,7 @@ public class ContentTransformerHelper implements BeanNameAware { if (deprecatedSetterMessages != null) { + StringBuilder sb = new StringBuilder(); for (String suffixAndValue: deprecatedSetterMessages) { String propertyNameAndValue = TransformerConfig.CONTENT+beanName+'.'+suffixAndValue; @@ -214,9 +228,8 @@ public class ContentTransformerHelper implements BeanNameAware } logger.error(propertyNameAndValue); - // Add them to the subsystem's properties anyway (even though an MBean reset would clear them), - // so that existing unit tests work. - transformerConfig.setProperty(propertyNameAndValue); + sb.append(propertyNameAndValue); + sb.append('\n'); } else { @@ -224,6 +237,12 @@ public class ContentTransformerHelper implements BeanNameAware } } deprecatedSetterMessages = null; + if (sb.length() > 0) + { + // Add subsystem's properties anyway (even though an MBean reset would clear them), + // so that existing unit tests work. + transformerConfig.setProperties(sb.toString()); + } } } @@ -254,4 +273,147 @@ public class ContentTransformerHelper implements BeanNameAware { return getName(); } + + /** + * Overridden to supply a comment or String of commented out transformation properties + * that specify any (hard coded or implied) supported transformations. Used + * when providing a list of properties to an administrators who may be setting + * other transformation properties, via JMX. Consider overriding if + * {link {@link AbstractContentTransformerLimits#isTransformableMimetype(String, String, TransformationOptions)} + * or {@link ContentTransformerWorker#isTransformable(String, String, TransformationOptions)} + * have been overridden. + * See {@link #getCommentsOnlySupports(List, List, boolean)} which may be used to help construct a comment. + * @param available indicates if the transformer has been registered and is available to be selected. + * {@code false} indicates that the transformer is only available as a component of a + * complex transformer. + * @return one line per property. The simple transformer name is returned by default as a comment. + */ + public String getComments(boolean available) + { + return getCommentNameAndAvailable(available); + } + + /** + * Helper method for {@link #getComments(boolean) to + * create a line that indicates which source and target mimetypes + * it supports. + * @param sourceMimetypes + * @param targetMimetypes + * @param available TODO + * @return a String of the form "# only supports xxx, yyy or zzz to aaa or bb\n". + */ + protected String getCommentsOnlySupports(List sourceMimetypes, List targetMimetypes, boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(getCommentNameAndAvailable(available)); + sb.append("# Only supports "); + sb.append(getExtensions(sourceMimetypes)); + sb.append(" to "); + sb.append(getExtensions(targetMimetypes)); + sb.append("\n"); + return sb.toString(); + } + + /** + * Returns the transformer's simple name and an indication if the transformer is not + * available for selection. + */ + private String getCommentNameAndAvailable(boolean available) + { + String name = this instanceof ContentTransformer ? getSimpleName((ContentTransformer)this) : getName(); + StringBuilder sb = new StringBuilder(); + sb.append(getCommentName(name)); + if (!available) + { + sb.append("# "); + sb.append(TransformerConfig.CONTENT); + sb.append(getName()); + sb.append(TransformerConfig.AVAILABLE); + sb.append("=false\n"); + } + return sb.toString(); + } + + static String getCommentName(String name) + { + StringBuilder sb = new StringBuilder(); + sb.append("# "); + sb.append(name); + sb.append('\n'); + sb.append("# "); + for (int i = name.length(); i > 0; i--) + { + sb.append('-'); + } + sb.append('\n'); + return sb.toString(); + } + + /** + * Helper method for {@link #getComments(boolean) to + * create a line that indicates which source and target mimetypes + * it supports. + * @param sourceMimetype + * @param targetMimetype + * @param available TODO + * @return a String of the form "# only supports xxx to aaa\n". + */ + protected String onlySupports(String sourceMimetype, String targetMimetype, boolean available) + { + return getCommentsOnlySupports( + Arrays.asList(new String[] {sourceMimetype}), + Arrays.asList(new String[] {targetMimetype}), available); + } + + /** + * Returns a comma separated String of mimetype file extensions. + */ + private String getExtensions(List origMimetypes) + { + // Only use the mimetypes we have registered + List mimetypes = new ArrayList(origMimetypes); + mimetypes.retainAll(getMimetypeService().getMimetypes()); + + StringBuilder sb = new StringBuilder(); + int j = mimetypes.size(); + int i=1; + for (String mimetype: mimetypes) + { + sb.append(getMimetypeService().getExtension(mimetype)); + if (i < j) + { + sb.append(++i < j ? ", " : " or "); + } + } + return sb.toString(); + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((beanName == null) ? 0 : beanName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + ContentTransformerHelper other = (ContentTransformerHelper) obj; + if (beanName == null) + { + if (other.beanName != null) + return false; + } + else if (!beanName.equals(other.beanName)) + return false; + return true; + } } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java index f796060704..ea870a20c4 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java @@ -20,10 +20,7 @@ package org.alfresco.repo.content.transform; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.alfresco.service.cmr.repository.TransformationOptions; import org.apache.commons.logging.Log; @@ -48,6 +45,7 @@ public class ContentTransformerRegistry private static final Log logger = LogFactory.getLog(ContentTransformerRegistry.class); private List transformers; + private List allTransformers; private final TransformerSelector transformerSelector; @@ -58,6 +56,7 @@ public class ContentTransformerRegistry { this.transformerSelector = transformerSelector; this.transformers = new ArrayList(70); + this.allTransformers = new ArrayList(70); } /** @@ -68,19 +67,65 @@ public class ContentTransformerRegistry public void addTransformer(ContentTransformer transformer) { transformers.add(transformer); + allTransformers.add(transformer); // done if (logger.isDebugEnabled()) { logger.debug("Registered general transformer: \n" + - " transformer: " + transformer); + " transformer: " + transformer.getName() + " (" + transformer + ")"); } } + /** + * Records a transformer that can NOT be queried for applicability, but may be + * included as a component of complex transformers. + * @param transformer a content transformer + */ + public void addComponentTransformer(ContentTransformer transformer) + { + allTransformers.add(transformer); + } + + /** + * @return a list of transformers that may be queried to check for applicability. + */ public List getTransformers() { return Collections.unmodifiableList(transformers); } + /** + * @return a list of all transformers, including those that only exist as a + * component of another transformer. + */ + public List getAllTransformers() + { + return Collections.unmodifiableList(allTransformers); + } + + /** + * Returns a transformer identified by name. + * @throws IllegalArgumentException if transformerName is not found. + */ + public ContentTransformer getTransformer(String transformerName) + { + if (transformerName != null) + { + for (ContentTransformer transformer: allTransformers) + { + if (transformerName.equals(transformer.getName())) + { + return transformer; + } + } + throw new IllegalArgumentException("Unknown transformer: "+ + (transformerName.startsWith(TransformerConfig.TRANSFORMER) + ? transformerName.substring(TransformerConfig.TRANSFORMER.length()) + : transformerName)); + } + return null; + } + /** * @deprecated use overloaded version with sourceSize parameter. */ diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java index 590d1a1b08..a30628a727 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java @@ -142,23 +142,19 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe public void testPerformanceRetrieval() throws Exception { // Until the threshold (3) is reached by each transformer with the same priority it will - // be tried that many times in the order defined. 20, 30, 10, 25, 25 + // be tried that many times in the order defined. 20, 30, 10, 25a, 25b for (int i=1; i<=3; i++) { long expectedTime = i == 1 ? 0L : 20L; ContentTransformer transformer1 = dummyRegistry.getTransformer(A, -1, D, OPTIONS); assertEquals(i+" incorrect transformation time", expectedTime, transformer1.getTransformationTime(A, D)); ad20.transformInternal(null, null, null); - } - for (int i=1; i<=3; i++) - { - long expectedTime = i == 1 ? 0L : 30L; - ContentTransformer transformer1 = dummyRegistry.getTransformer(A, -1, D, OPTIONS); + + expectedTime = i == 1 ? 0L : 30L; + transformer1 = dummyRegistry.getTransformer(A, -1, D, OPTIONS); assertEquals(i+" incorrect transformation time", expectedTime, transformer1.getTransformationTime(A, D)); ad30.transformInternal(null, null, null); - } - for (int i=1; i<=3; i++) - { + ad10.transformInternal(null, null, null); ad25a.transformInternal(null, null, null); ad25b.transformInternal(null, null, null); @@ -258,6 +254,7 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe this.sourceMimetype = sourceMimetype; this.targetMimetype = targetMimetype; this.transformationTime = transformationTime; + setRegisterTransformer(true); setBeanName(name+'.'+System.currentTimeMillis()%100000); // register @@ -297,14 +294,6 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe // just update the transformation time super.recordTime(sourceMimetype, targetMimetype, transformationTime); } - -// /** -// * @return Returns the fixed dummy average transformation time -// */ -// public synchronized long getTransformationTime(String sourceMimetype, String targetMimetype) -// { -// return transformationTime; -// } } @Override diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java index ef3e43f7b7..b24734c73e 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java @@ -46,10 +46,17 @@ public interface ContentTransformerWorker public String getVersionString(); /** - * @see ContentTransformer#isTransformable(String, String, TransformationOptions) + * Unlike {@link ContentTransformer#isTransformable(String, String, TransformationOptions)} + * should not include the transformer name, as that is added by the ContentTransformer in + * the parent context. */ public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** + * @see ContentTransformer#getComments(boolean) + */ + public String getComments(boolean available); + /** * @see ContentTransformer#transform(ContentReader, ContentWriter, TransformationOptions) */ diff --git a/source/java/org/alfresco/repo/content/transform/EMLTransformer.java b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java index 8866123dd8..ae425dbfbd 100644 --- a/source/java/org/alfresco/repo/content/transform/EMLTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java @@ -21,6 +21,7 @@ package org.alfresco.repo.content.transform; import java.io.IOException; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Properties; import javax.mail.MessagingException; @@ -62,6 +63,12 @@ public class EMLTransformer extends AbstractContentTransformer2 } } + @Override + public String getComments(boolean available) + { + return onlySupports(MimetypeMap.MIMETYPE_RFC822, MimetypeMap.MIMETYPE_TEXT_PLAIN, available); + } + @Override protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception diff --git a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java index dfe9ee2c73..a9a9fd502f 100644 --- a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java @@ -22,7 +22,6 @@ import java.io.File; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; -import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; @@ -143,6 +142,7 @@ public class FailoverContentTransformer extends AbstractContentTransformer2 impl return result; } + @SuppressWarnings("deprecation") public boolean isExplicitTransformation(String sourceMimetype, String targetMimetype, TransformationOptions options) { boolean result = true; @@ -249,4 +249,31 @@ public class FailoverContentTransformer extends AbstractContentTransformer2 impl throw transformationException; } } + + /** + * Returns the transformer properties predefined (hard coded or implied) by this transformer. + */ + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getComments(available)); + sb.append("# "); + sb.append(TransformerConfig.CONTENT); + sb.append(getName()); + sb.append(TransformerConfig.FAILOVER); + sb.append('='); + boolean first = true; + for (ContentTransformer transformer: transformers) + { + if (!first) + { + sb.append(TransformerConfig.PIPE); + } + first = false; + sb.append(transformer != null ? getSimpleName(transformer) : TransformerConfig.ANY); + } + sb.append('\n'); + return sb.toString(); + } } diff --git a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java index 96559d278c..87062f9b41 100644 --- a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java @@ -20,6 +20,7 @@ package org.alfresco.repo.content.transform; import java.io.File; import java.net.URLConnection; +import java.util.Arrays; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.service.cmr.repository.ContentReader; @@ -78,6 +79,12 @@ public class HtmlParserContentTransformer extends AbstractContentTransformer2 } } + @Override + public String getComments(boolean available) + { + return onlySupports(MimetypeMap.MIMETYPE_HTML, MimetypeMap.MIMETYPE_TEXT_PLAIN, available); + } + public void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception { diff --git a/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java b/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java index 183ae521ad..312ce325e3 100644 --- a/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java @@ -22,6 +22,7 @@ import info.bliki.wiki.filter.Encoder; import info.bliki.wiki.model.WikiModel; import info.bliki.wiki.tags.ATag; +import java.util.Arrays; import java.util.List; import org.alfresco.repo.content.MimetypeMap; @@ -92,6 +93,12 @@ public class MediaWikiContentTransformer extends AbstractContentTransformer2 } } + @Override + public String getComments(boolean available) + { + return onlySupports(MimetypeMap.MIMETYPE_TEXT_MEDIAWIKI, MimetypeMap.MIMETYPE_HTML, available); + } + /** * @see org.alfresco.repo.content.transform.AbstractContentTransformer2#transformInternal(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter, org.alfresco.service.cmr.repository.TransformationOptions) */ diff --git a/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformer.java b/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformer.java index fec2c3b5ef..06f6a5e4b3 100644 --- a/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/OOXMLThumbnailContentTransformer.java @@ -82,6 +82,15 @@ public class OOXMLThumbnailContentTransformer extends AbstractContentTransformer return TARGET_MIMETYPES.contains(targetMimetype) && OOXML_MIMETYPES.contains(sourceMimetype); } + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getComments(available)); + sb.append("# Only supports extraction of embedded jpegs from OOXML formats\n"); + return sb.toString(); + } + @Override protected void transformInternal(ContentReader reader, ContentWriter writer, diff --git a/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java b/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java index 73260dddd8..75df736e6e 100644 --- a/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java +++ b/source/java/org/alfresco/repo/content/transform/OOoContentTransformerHelper.java @@ -206,6 +206,12 @@ public abstract class OOoContentTransformerHelper extends ContentTransformerHelp } } + @Override + public String getComments(boolean available) + { + return "# Transformations supported by OpenOffice/LibreOffice\n"; + } + /** * This method produces an empty PDF file at the specified File location. * Apache's PDFBox is used to create the PDF file. diff --git a/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java index 4d937cc98c..07ba51521f 100644 --- a/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java @@ -64,6 +64,15 @@ public class ProxyContentTransformer extends AbstractContentTransformer2 return this.worker.isTransformable(sourceMimetype, targetMimetype, options); } + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getComments(available)); + sb.append(worker.getComments(false)); + return sb.toString(); + } + protected void transformInternal(ContentReader reader, ContentWriter writer, TransformationOptions options) throws Exception { diff --git a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java index 8edaeefd77..4fcf5396fb 100644 --- a/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/RuntimeExecutableContentTransformerWorker.java @@ -173,6 +173,12 @@ public class RuntimeExecutableContentTransformerWorker extends ContentTransforme return available; } + @Override + public String getComments(boolean available) + { + return ""; + } + /** * Signals whether this transformer is available. * diff --git a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java index facab76cab..ed394f0797 100644 --- a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java @@ -76,6 +76,17 @@ public class StringExtractingContentTransformer extends AbstractContentTransform } } + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append(super.getComments(available)); + sb.append("# Only supports transformation of js and mimetypes starting with \""); + sb.append(PREFIX_TEXT); + sb.append("\" to txt.\n"); + return sb.toString(); + } + /** * Text to text conversions are done directly using the content reader and writer string * manipulation methods. diff --git a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java index 32676ace1a..e1f6db2d6c 100644 --- a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java @@ -25,6 +25,7 @@ import java.io.InputStreamReader; import java.io.OutputStream; import java.io.Reader; import java.nio.charset.Charset; +import java.util.Arrays; import java.util.Map; import org.alfresco.error.AlfrescoRuntimeException; @@ -57,7 +58,7 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 public TextToPdfContentTransformer() { - setPageLimitsSuported(true); + setPageLimitsSupported(true); transformer = new PagedTextToPDF(); } @@ -117,6 +118,14 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 } } + @Override + public String getComments(boolean available) + { + return getCommentsOnlySupports( + Arrays.asList(new String[] {MimetypeMap.MIMETYPE_TEXT_PLAIN, MimetypeMap.MIMETYPE_TEXT_CSV, MimetypeMap.MIMETYPE_XML}), + Arrays.asList(new String[] {MimetypeMap.MIMETYPE_PDF}), available); + } + @Override protected void transformInternal( ContentReader reader, diff --git a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java index a43cbf6392..8dbde80dd2 100644 --- a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformerTest.java @@ -54,6 +54,7 @@ public class TextToPdfContentTransformerTest extends AbstractContentTransformerT transformer.setStandardFont("Times-Roman"); transformer.setFontSize(20); transformer.setPageLimit(-1); + transformer.setBeanName("transformer.test"+System.currentTimeMillis()%100000); transformer.register(); } diff --git a/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java b/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java index f2ab0dce87..23e1102040 100644 --- a/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java @@ -62,6 +62,12 @@ import org.xml.sax.ContentHandler; public abstract class TikaPoweredContentTransformer extends AbstractContentTransformer2 { private static final Log logger = LogFactory.getLog(TikaPoweredContentTransformer.class); + private static final List TARGET_MIMETYPES = Arrays.asList(new String[] { + MimetypeMap.MIMETYPE_TEXT_PLAIN, + MimetypeMap.MIMETYPE_HTML, + MimetypeMap.MIMETYPE_XHTML, + MimetypeMap.MIMETYPE_XML}); + protected List sourceMimeTypes; /** @@ -70,7 +76,7 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans protected static final String LINE_BREAK = "\r\n"; public static final String WRONG_FORMAT_MESSAGE_ID = "transform.err.format_or_password"; - protected TikaPoweredContentTransformer(List sourceMimeTypes) { + protected TikaPoweredContentTransformer(List sourceMimeTypes) { this.sourceMimeTypes = sourceMimeTypes; } protected TikaPoweredContentTransformer(String[] sourceMimeTypes) { @@ -99,10 +105,7 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans return false; } - if(MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype) || - MimetypeMap.MIMETYPE_HTML.equals(targetMimetype) || - MimetypeMap.MIMETYPE_XHTML.equals(targetMimetype) || - MimetypeMap.MIMETYPE_XML.equals(targetMimetype)) + if (TARGET_MIMETYPES.contains(targetMimetype)) { // We can output to this return true; @@ -114,6 +117,12 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans } } + @Override + public String getComments(boolean available) + { + return getCommentsOnlySupports(sourceMimeTypes, TARGET_MIMETYPES, available); + } + /** * Returns an appropriate Tika ContentHandler for the * requested content type. Normally you'll let this diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfig.java b/source/java/org/alfresco/repo/content/transform/TransformerConfig.java index 77fc836a12..f34351f1fe 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerConfig.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfig.java @@ -65,6 +65,14 @@ public interface TransformerConfig */ static final String SUMMARY_TRANSFORMER_NAME = "SUMMARY"; + /** + * An optional separator appended after the normal suffix and following value that + * identifies the 'use' or 'application' of the property. Example uses include 'index' + * 'doclib' and 'preview'. The corresponding configuration value is only used in the + * context of the specified usage. + */ + static final String USE = ".use."; + /** * The separator between the transformer name and two mimetype extensions in a property name. */ @@ -76,6 +84,33 @@ public interface TransformerConfig */ static final String MIMETYPES = ".mimetypes."; + /** + * Both extension and minetype separators. + */ + public static String[] SEPARATORS = new String[] {EXTENSIONS , MIMETYPES}; + + /** + * The suffix to property names for creating dynamic complex transformers + */ + public static final String PIPELINE = ".pipeline"; + + /** + * The suffix to property names for creating dynamic failover transformers + */ + public static final String FAILOVER = ".failover"; + + /** + * The suffix to property names to indicate that a transformer is available. + * If not specified, defaults to true, indicating it may be selected rather + * only being available as a component of another transformer. + */ + public static final String AVAILABLE = ".available"; + + /** + * Separator between transformers and mimetype extensions in a dynamic compound property value. + */ + public static final char PIPE = '|'; + /** * The suffix to property names for supported and unsupported combinations. */ @@ -122,15 +157,15 @@ public interface TransformerConfig /** * To support the historical concept of EXPLICIT transformers, all such transformers - * are given a {@link PRIORITY_EXPLICIT} (5). By default transformers have a default of 10. + * are given a {@link PRIORITY_EXPLICIT} (50). By default transformers have a default of 10. * A value of 5 allows better transformers to be added later. */ - public int PRIORITY_EXPLICIT = 5; + public int PRIORITY_EXPLICIT = 50; /** - * By default transformers have a default of 10. + * By default transformers have a priority of 100. */ - public int PRIORITY_DEFAULT = 10; + public int PRIORITY_DEFAULT = 100; /** * Suffixes to property names used to define transformation limits @@ -143,11 +178,53 @@ public interface TransformerConfig READ_LIMIT_TIME_MS, PAGE_LIMIT }); + + /** + * Suffix pairs (max and limit values) to property names used to define transformation limits + */ + public final String[][] LIMIT_PAIR_SUFFIXES = new String[][] + { + {MAX_SOURCE_SIZE_K_BYTES, READ_LIMIT_K_BYTES}, + {TIMEOUT_MS, READ_LIMIT_TIME_MS}, + {MAX_PAGES, PAGE_LIMIT} + }; + + /** + * All suffixes to property names used to transformer configuration + */ + static final Collection ALL_SUFFIXES = Arrays.asList(new String [] { + MAX_SOURCE_SIZE_K_BYTES, + TIMEOUT_MS, + MAX_PAGES, + READ_LIMIT_K_BYTES, + READ_LIMIT_TIME_MS, + PAGE_LIMIT, + SUPPORTED, + PRIORITY, + ERROR_TIME, + INITIAL_TIME, + INITIAL_COUNT, + THRESHOLD_COUNT, + FAILOVER, + PIPELINE + }); /** * No suffixes to property names used to define transformer settings. */ public static final Collection NO_SUFFIXES = Collections.singletonList(""); + + static final String ENTRIES = "entries"; + + /** + * The number of debug lines to keep. Turned off if <= 0. + */ + public static final String DEBUG_ENTRIES = TRANSFORMER+"debug."+ENTRIES; + + /** + * The number of log lines to keep. Turned off if <= 0. + */ + public static final String LOG_ENTRIES = TRANSFORMER+"log."+ENTRIES; /** * Returns a transformer property value. @@ -157,12 +234,32 @@ public interface TransformerConfig String getProperty(String name); /** - * Sets a transformer property value. This will be stored in the database but on an MBean - * reset would be cleared. - * - * @param propertyNameAndValue + * Returns a sorted set of all transformer properties, their values and includes + * comments about the properties. + * @param changesOnly only custom values will be included. + * @return a multi-line String which is never {code null}. */ - void setProperty(String propertyNameAndValue); + String getProperties(boolean changesOnly); + + /** + * Removes transformer properties. + * + * @param propertyNames new line separated names. Any values will be ignored. + * @return the number of properties removed. + * @throws IllegalArgumentException if the properties were not set or the + * list contains errors. + */ + int removeProperties(String propertyNames); + + /** + * Sets a transformer property values. These will be stored in the database but on an MBean + * reset is cleared. + * + * @param propertyNamesAndValues new line separated name and values + * @return the number of properties set. + * @throws IllegalArgumentException the list contains errors. + */ + int setProperties(String propertyNamesAndValues); /** * Returns and creates if needed the {@link TransformerStatistics} object for the combination of @@ -174,9 +271,10 @@ public interface TransformerConfig * @param transformer the transformer for which data is being recorded. * @param sourceMimetype the source mimetype. * @param targetMimetype the source mimetype. + * @param createNew indicates if a new object should be created if it does not exist. * @return the requested {@link TransformerStatistics}. */ - public TransformerStatistics getStatistics(ContentTransformer transformer, String sourceMimetype, String targetMimetype); + public TransformerStatistics getStatistics(ContentTransformer transformer, String sourceMimetype, String targetMimetype, boolean createNew); /** * Returns the limits defined for the combination of transformer, sourceMimetype and targetMimetype. @@ -185,9 +283,11 @@ public interface TransformerConfig * @param transformer * @param sourceMimetype * @param targetMimetype + * @param use to which the limits will be put. For example "index", "webpreview", "doclib", "syncRule", + * "aysncRule". {@code null} is the default. * @return the combined (takes into account defaults from higher levels) limits for the combination. */ - public TransformationOptionLimits getLimits(ContentTransformer transformer, String sourceMimetype, String targetMimetype); + public TransformationOptionLimits getLimits(ContentTransformer transformer, String sourceMimetype, String targetMimetype, String use); /** * Returns true if the supplied mimetype transformation pair is allowed by the list of supported @@ -206,7 +306,7 @@ public interface TransformerConfig * @param sourceMimetype * @param targetMimetype * @return the priority. To support the historical concept of EXPLICIT transformers, all such transformers - * are given a {@link PRIORITY_EXPLICIT} (5). By default transformers have a default of 10. + * are given a {@link PRIORITY_EXPLICIT} (50). By default transformers have a default of 100. */ public int getPriority(ContentTransformer contentTransformerHelper, String sourceMimetype, String targetMimetype); diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigImpl.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigImpl.java index ea19f49c7b..e26d3ebf73 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerConfigImpl.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigImpl.java @@ -18,12 +18,12 @@ */ package org.alfresco.repo.content.transform; +import java.util.Properties; + import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.TransformationOptionLimits; import org.alfresco.service.cmr.repository.TransformationOptions; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationEvent; import org.springframework.extensions.surf.util.AbstractLifecycleBean; @@ -35,6 +35,14 @@ import org.springframework.extensions.surf.util.AbstractLifecycleBean; public class TransformerConfigImpl extends AbstractLifecycleBean implements TransformerConfig { private MimetypeService mimetypeService; + + private ContentTransformerRegistry transformerRegistry; + + // Log + private TransformerLog transformerLog; + + // Log Debug + private TransformerDebugLog transformerDebugLog; // Holds statistics about each transformer, sourceMimeType and targetMimetype combination. // A null transformer is the system wide value. Null sourceMimeType and targetMimetype values are @@ -64,9 +72,16 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran private TransformerConfigProperty initialAverageTimes; private TransformerConfigProperty initialCounts; + private TransformerPropertySetter propertySetter; + // Needed to read properties. private ChildApplicationContextFactory subsystemFactory; + // Needed to read global properties. + private Properties globalProperties; + + private TransformerProperties transformerProperties; + /** * Sets of the mimetype service. * @@ -77,25 +92,51 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran this.mimetypeService = mimetypeService; } + public void setContentTransformerRegistry(ContentTransformerRegistry transformerRegistry) + { + this.transformerRegistry = transformerRegistry; + } + + public void setTransformerLog(TransformerLog transformerLog) + { + this.transformerLog = transformerLog; + } + + public void setTransformerDebugLog(TransformerDebugLog transformerDebugLog) + { + this.transformerDebugLog = transformerDebugLog; + } + + public void setGlobalProperties(Properties globalProperties) + { + this.globalProperties = globalProperties; + } + /** * Called by spring after bean is initialised. */ public void initialise() { + ChildApplicationContextFactory subsystem = getSubsystem(); + transformerProperties = new TransformerProperties(subsystem, globalProperties); + + // TODO add dynamic 'pipeline' and 'failover' transformers + statistics= new TransformerConfigStatistics(this, mimetypeService); - limits = new TransformerConfigLimits(getSubsystem(), mimetypeService); - supported = new TransformerConfigSupported(getSubsystem(), mimetypeService); - priorities = new TransformerConfigProperty(getSubsystem(), mimetypeService, PRIORITY, Integer.toString(PRIORITY_DEFAULT)); - thresholdCounts = new TransformerConfigProperty(getSubsystem(), mimetypeService, THRESHOLD_COUNT, "3"); - errorTimes = new TransformerConfigProperty(getSubsystem(), mimetypeService, ERROR_TIME, "120000"); - initialAverageTimes = new TransformerConfigProperty(getSubsystem(), mimetypeService, INITIAL_TIME, "0"); - initialCounts = new TransformerConfigProperty(getSubsystem(), mimetypeService, INITIAL_COUNT, "100000"); + limits = new TransformerConfigLimits(transformerProperties, mimetypeService); + supported = new TransformerConfigSupported(transformerProperties, mimetypeService); + priorities = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, Integer.toString(PRIORITY_DEFAULT)); + thresholdCounts = new TransformerConfigProperty(transformerProperties, mimetypeService, THRESHOLD_COUNT, "3"); + errorTimes = new TransformerConfigProperty(transformerProperties, mimetypeService, ERROR_TIME, "120000"); + initialAverageTimes = new TransformerConfigProperty(transformerProperties, mimetypeService, INITIAL_TIME, "0"); + initialCounts = new TransformerConfigProperty(transformerProperties, mimetypeService, INITIAL_COUNT, "100000"); + propertySetter = new TransformerPropertySetter(transformerProperties, mimetypeService, transformerRegistry); } /** * Returns the 'transformers' subsystem which among other things holds transformer properties. */ - private synchronized ChildApplicationContextFactory getSubsystem() + synchronized ChildApplicationContextFactory getSubsystem() { if (subsystemFactory == null) { @@ -120,28 +161,38 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran @Override public String getProperty(String name) { - return getSubsystem().getProperty(name); + return transformerProperties.getProperty(name); + } + + @Override + public String getProperties(boolean changesOnly) + { + return new TransformerPropertyGetter(changesOnly, transformerProperties, mimetypeService, + transformerRegistry, transformerLog, transformerDebugLog).toString(); } /** * {@inheritDoc} */ @Override - public void setProperty(String propertyNameAndValue) + public int setProperties(String propertyNamesAndValues) { - int i = propertyNameAndValue.indexOf('='); - String name = i != -1 ? propertyNameAndValue.substring(0, i) : propertyNameAndValue; - String value = i != -1 ? propertyNameAndValue.substring(i+1) : ""; - getSubsystem().setProperty(name, value); + return propertySetter.setProperties(propertyNamesAndValues); } + @Override + public int removeProperties(String propertyNames) + { + return propertySetter.removeProperties(propertyNames); + } + /** * {@inheritDoc} */ @Override - public TransformerStatistics getStatistics(ContentTransformer transformer, String sourceMimetype, String targetMimetype) + public TransformerStatistics getStatistics(ContentTransformer transformer, String sourceMimetype, String targetMimetype, boolean createNew) { - return statistics.getStatistics(transformer, sourceMimetype, targetMimetype); + return statistics.getStatistics(transformer, sourceMimetype, targetMimetype, createNew); } /** @@ -149,9 +200,9 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran */ @Override public TransformationOptionLimits getLimits(ContentTransformer transformer, String sourceMimetype, - String targetMimetype) + String targetMimetype, String use) { - return limits.getLimits(transformer, sourceMimetype, targetMimetype); + return limits.getLimits(transformer, sourceMimetype, targetMimetype, use); } /** @@ -170,7 +221,21 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran @Override public int getPriority(ContentTransformer transformer, String sourceMimetype, String targetMimetype) { - return priorities.getInt(transformer, sourceMimetype, targetMimetype); + try + { + return priorities.getInt(transformer, sourceMimetype, targetMimetype); + } + catch (NumberFormatException e1) + { + try + { + return priorities.getInt(null, null, null); + } + catch (NumberFormatException e2) + { + return 0; + } + } } /** @@ -179,7 +244,21 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran @Override public int getThresholdCount(ContentTransformer transformer, String sourceMimetype, String targetMimetype) { - return thresholdCounts.getInt(transformer, sourceMimetype, targetMimetype); + try + { + return thresholdCounts.getInt(transformer, sourceMimetype, targetMimetype); + } + catch (NumberFormatException e1) + { + try + { + return thresholdCounts.getInt(null, null, null); + } + catch (NumberFormatException e2) + { + return 0; + } + } } /** @@ -187,7 +266,21 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran */ long getErrorTime(ContentTransformer transformer, String sourceMimetype, String targetMimetype) { - return errorTimes.getLong(transformer, sourceMimetype, targetMimetype); + try + { + return errorTimes.getLong(transformer, sourceMimetype, targetMimetype); + } + catch (NumberFormatException e1) + { + try + { + return errorTimes.getInt(null, null, null); + } + catch (NumberFormatException e2) + { + return 0; + } + } } /** @@ -196,7 +289,21 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran */ long getInitialAverageTime(ContentTransformer transformer, String sourceMimetype, String targetMimetype) { - return initialAverageTimes.getLong(transformer, sourceMimetype, targetMimetype); + try + { + return initialAverageTimes.getLong(transformer, sourceMimetype, targetMimetype); + } + catch (NumberFormatException e1) + { + try + { + return initialAverageTimes.getInt(null, null, null); + } + catch (NumberFormatException e2) + { + return 0; + } + } } /** @@ -205,6 +312,20 @@ public class TransformerConfigImpl extends AbstractLifecycleBean implements Tran */ int getInitialCount(ContentTransformer transformer, String sourceMimetype, String targetMimetype) { - return initialCounts.getInt(transformer, sourceMimetype, targetMimetype); + try + { + return initialCounts.getInt(transformer, sourceMimetype, targetMimetype); + } + catch (NumberFormatException e1) + { + try + { + return initialCounts.getInt(null, null, null); + } + catch (NumberFormatException e2) + { + return 0; + } + } } } diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigImplTest.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigImplTest.java new file mode 100644 index 0000000000..ac62140cc9 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigImplTest.java @@ -0,0 +1,327 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockMimetypes; +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockProperties; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.TreeSet; + +import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.springframework.context.ApplicationContext; + +/** + * Test class for TransformerConfigImpl. This class generally calls onto + * secondary classes to handle the request, so most test simply check that + * the real method is called. + * + * @author Alan Davis + */ +public class TransformerConfigImplTest +{ + @Mock + private ApplicationContext applicationContext; + + @Mock + private MimetypeService mimetypeService; + + @Mock + private ContentTransformerRegistry transformerRegistry; + + @Mock + private TransformerLog transformerLog; + + @Mock + private TransformerDebugLog transformerDebugLog; + + @Mock + private ChildApplicationContextFactory subsystem; + + @Mock + private TransformerProperties transformerProperties; + + @Mock + private TransformationOptions options; + + @Mock + private + ContentTransformer transformer1; + + private TransformerConfigImpl config; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + Properties globalProperties = new Properties(); + + config = new TransformerConfigImpl() + { + @Override + synchronized ChildApplicationContextFactory getSubsystem() + { + return subsystem; + } + }; + config.setApplicationContext(applicationContext); + config.setMimetypeService(mimetypeService); + config.setContentTransformerRegistry(transformerRegistry); + config.setTransformerLog(transformerLog); + config.setTransformerDebugLog(transformerDebugLog); + config.setGlobalProperties(globalProperties); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png"); + + finishSetup(); + } + + private void finishSetup() + { + when(transformer1.getName()).thenReturn("transformer.abc"); + when(transformerRegistry.getTransformer("transformer.abc")).thenReturn(transformer1); + when(transformer1.getComments(false)).thenReturn(""); + + config.initialise(); + } + + /** + * Mock up the responses from the subsystem so that it returns all the supplied + * property names and values. + * @param subsystem to mock the return values + * @param namesAndValues a sequence of property names and values. + * @throws IllegalStateException if there is not a value for every property + */ + public static void mockProperties(ChildApplicationContextFactory subsystem, String... namesAndValues) + { + if (namesAndValues.length % 2 != 0) + { + // Not using IllegalArgumentException as this is thrown by classes under test + throw new java.lang.IllegalStateException("There should be a value for every property"); + } + + final Set propertyNames = new TreeSet(); + for (int i=0; i < namesAndValues.length; i+=2) + { + propertyNames.add(namesAndValues[i]); + when(subsystem.getProperty(namesAndValues[i])).thenReturn(namesAndValues[i+1]); + } + when(subsystem.getPropertyNames()).thenReturn(propertyNames); + } + + @Test + public void getPropertyTest() + { + when(subsystem.getProperty("abc")).thenReturn("xyz"); + String actual = config.getProperty("abc"); + assertEquals("xyz", actual); + } + + @Test + public void getPropertiesTest() + { + when(transformerLog.getPropertyName()).thenReturn("transformer.log.entries"); + when(transformerDebugLog.getPropertyName()).thenReturn("transformer.debug.entries"); + + String actual = config.getProperties(false); + assertEquals("# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "transformer.log.entries=0 # default=50\n" + + "# transformer.debug.entries=0\n", actual); + } + + @Test + public void setPropertiesTest() + { + config.setProperties("transformer.debug.entries=56\ntransformer.log.entries=76"); + Map expected = new HashMap(); + expected.put("transformer.debug.entries", "56"); + expected.put("transformer.log.entries", "76"); + verify(subsystem).setProperties(expected); + } + + @Test + public void removePropertiesTest() + { + mockProperties(subsystem, "content.transformer.abc.extensions.pdf.png.maxPages", "23"); + finishSetup(); + + config.removeProperties("content.transformer.abc.extensions.pdf.png.maxPages"); + Set expected = new HashSet(); + expected.add("content.transformer.abc.extensions.pdf.png.maxPages"); + verify(subsystem).removeProperties(expected); + } + + @Test + public void getStatisticsTest() + { + TransformerStatistics actual = config.getStatistics(transformer1, "application/pdf", "image/png", true); + actual.recordTime(100); + actual.recordTime(200); + actual = config.getStatistics(transformer1, "application/pdf", "image/png", false); + assertEquals(150, actual.getAverageTime()); + } + + @Test + public void getLimitsTest() + { + mockProperties(subsystem, "content.transformer.abc.extensions.pdf.png.maxPages", "23"); + finishSetup(); + + TransformationOptionLimits actual = config.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(23, actual.getMaxPages()); + } + + @Test + public void isSupportedTransformationTest() + { + mockProperties(subsystem, "content.transformer.abc.extensions.pdf.png.suppoprted", "true"); + finishSetup(); + + assertTrue(config.isSupportedTransformation(transformer1, "application/pdf", "image/png", options)); + } + + @Test + public void getPriorityTest() + { + mockProperties(subsystem, + "content.transformer.default.priority", "22", + "content.transformer.abc.extensions.pdf.png.priority", "67"); + finishSetup(); + + assertEquals(67, config.getPriority(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getPriorityBadTest() + { + mockProperties(subsystem, + "content.transformer.default.priority", "22", + "content.transformer.abc.extensions.pdf.png.priority", "bad"); + finishSetup(); + + assertEquals(22, config.getPriority(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getThresholdCountTest() + { + mockProperties(subsystem, + "content.transformer.default.thresholdCount", "22", + "content.transformer.abc.extensions.pdf.png.thresholdCount", "67"); + finishSetup(); + + assertEquals(67, config.getThresholdCount(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getThresholdCountBadTest() + { + mockProperties(subsystem, + "content.transformer.default.thresholdCount", "22", + "content.transformer.abc.extensions.pdf.png.thresholdCount", "bad"); + finishSetup(); + + assertEquals(22, config.getThresholdCount(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getErrorTimeTest() + { + mockProperties(subsystem, + "content.transformer.default.errorTime", "22", + "content.transformer.abc.extensions.pdf.png.errorTime", "67"); + finishSetup(); + + assertEquals(67, config.getErrorTime(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getErrorTimeBadTest() + { + mockProperties(subsystem, + "content.transformer.default.errorTime", "22", + "content.transformer.abc.extensions.pdf.png.errorTime", "bad"); + finishSetup(); + + assertEquals(22, config.getErrorTime(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getInitialAverageTimeTest() + { + mockProperties(subsystem, + "content.transformer.default.time", "22", + "content.transformer.abc.extensions.pdf.png.time", "67"); + finishSetup(); + + assertEquals(67, config.getInitialAverageTime(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getInitialAverageTimeBadTest() + { + mockProperties(subsystem, + "content.transformer.default.time", "22", + "content.transformer.abc.extensions.pdf.png.time", "bad"); + finishSetup(); + + assertEquals(22, config.getInitialAverageTime(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getInitialCountTest() + { + mockProperties(subsystem, + "content.transformer.default.count", "22", + "content.transformer.abc.extensions.pdf.png.count", "67"); + finishSetup(); + + assertEquals(67, config.getInitialCount(transformer1, "application/pdf", "image/png")); + } + + @Test + public void getInitialCountBadTest() + { + mockProperties(subsystem, + "content.transformer.default.count", "22", + "content.transformer.abc.extensions.pdf.png.count", "bad"); + finishSetup(); + + assertEquals(22, config.getInitialCount(transformer1, "application/pdf", "image/png")); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigLimits.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigLimits.java index 9f03ef00ca..4e1c17acad 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerConfigLimits.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigLimits.java @@ -23,10 +23,9 @@ import static org.alfresco.repo.content.transform.TransformerConfig.DEFAULT_TRAN import static org.alfresco.repo.content.transform.TransformerConfig.LIMIT_SUFFIXES; import java.util.Collection; -import java.util.HashMap; import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; -import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.TransformationOptionLimits; @@ -37,99 +36,190 @@ import org.alfresco.service.cmr.repository.TransformationOptionLimits; */ public class TransformerConfigLimits extends TransformerPropertyNameExtractor { - // Holds configured (entries only exist if configured rather than for all possible combinations) - // limits for for transformer, sourceMimeType and targetMimetype combination. + // Initially only holds configured (entries only exist if configured rather than for + // all possible combinations) limits for each use, transformer, sourceMimeType and targetMimetype + // combination. These initial entries are added to as other combinations are requested. // A null transformer is the system wide value. SourceMimeType and targetMimetype may be 'ANY' // values to act as wild cards. - private Map> limits; + private Map>> limitsMap; - public TransformerConfigLimits(ChildApplicationContextFactory subsystem, MimetypeService mimetypeService) + public TransformerConfigLimits(TransformerProperties transformerProperties, MimetypeService mimetypeService) { - setLimits(subsystem, mimetypeService); + setLimits(transformerProperties, mimetypeService); } /** * Sets the transformer limits created from system properties. */ - private void setLimits(ChildApplicationContextFactory subsystem, MimetypeService mimetypeService) + private void setLimits(TransformerProperties transformerProperties, MimetypeService mimetypeService) { - limits = new HashMap>(); + limitsMap = new ConcurrentHashMap>>(); // Gets all the transformer, source and target combinations in properties that define limits. + Map + transformerSourceTargetSuffixValues = + getTransformerSourceTargetValuesMap(LIMIT_SUFFIXES, true, true, transformerProperties, mimetypeService); Collection properties = - getTransformerSourceTargetValues(LIMIT_SUFFIXES, true, subsystem, mimetypeService); + transformerSourceTargetSuffixValues.values(); // Add the system wide default just in case it is not included, as we always need this one - TransformationOptionLimits options = getOrCreateTransformerOptionLimits(DEFAULT_TRANSFORMER, ANY, ANY); - - // Populate the transformer limits - for (TransformerSourceTargetSuffixValue property: properties) + TransformationOptionLimits limits = getOrCreateTransformerOptionLimits(DEFAULT_TRANSFORMER, ANY, ANY, null); + + // Populate the transformer limits. Done in several passes so that values may be defaulted + // from one level to the next. + for (int pass=0; pass<=7; pass++) { - options = getOrCreateTransformerOptionLimits(property.transformerName, - property.sourceMimetype, property.targetMimetype); - setTransformationOptionsFromProperties(options, property.value, property.suffix); + for (TransformerSourceTargetSuffixValue property: properties) + { + int origLevel = getLevel(property.transformerName, property.sourceMimetype, property.use); + if (pass == origLevel) + { + String transformerName = (property.transformerName == null) + ? DEFAULT_TRANSFORMER : property.transformerName; + limits = getOrCreateTransformerOptionLimits(transformerName, + property.sourceMimetype, property.targetMimetype, property.use); + setTransformationLimitsFromProperties(limits, property.value, property.suffix); + } + } } } /** - * Returns the TransformationOptionLimits for the transformer and mimetype combination, + * Returns the TransformationOptionLimits for the use, transformer and mimetype combination, * creating and adding one if not already included. */ private TransformationOptionLimits getOrCreateTransformerOptionLimits(String transformerName, - String sourceMimetype, String targetMimetype) + String sourceMimetype, String targetMimetype, String use) { - DoubleMap mimetypeLimits; - mimetypeLimits = limits.get(transformerName); + use = use == null ? ANY : use; + return getOrCreateTransformerOptionLimitsInternal(transformerName, sourceMimetype, targetMimetype, use, use); + } + + private TransformationOptionLimits getOrCreateTransformerOptionLimitsInternal(String transformerName, + String sourceMimetype, String targetMimetype, String origUse, String use) + { + Map> transformerLimits = limitsMap.get(origUse); + if (transformerLimits == null) + { + transformerLimits = new ConcurrentHashMap>(); + limitsMap.put(origUse, transformerLimits); + } + + DoubleMap mimetypeLimits = transformerLimits.get(transformerName); if (mimetypeLimits == null) { mimetypeLimits = new DoubleMap(ANY, ANY); - limits.put(transformerName, mimetypeLimits); + transformerLimits.put(transformerName, mimetypeLimits); } - TransformationOptionLimits options = mimetypeLimits.getNoWildcards(sourceMimetype, targetMimetype); - if (options == null) + TransformationOptionLimits limits = mimetypeLimits.getNoWildcards(sourceMimetype, targetMimetype); + if (limits == null) { - options = new TransformationOptionLimits(); - mimetypeLimits.put(sourceMimetype, targetMimetype, options); + // Try the wildcard version, and use any match as the basis for a new entry + limits = mimetypeLimits.get(sourceMimetype, targetMimetype); + + limits = newTransformationOptionLimits(transformerName, sourceMimetype, targetMimetype, limits, origUse, use); + mimetypeLimits.put(sourceMimetype, targetMimetype, limits); } - return options; + return limits; } - private void setTransformationOptionsFromProperties(TransformationOptionLimits options, + /** + * Creates a new TransformationOptionLimits for the use, transformer and mimetype combination, + * defaulting values from lower levels. + * @param wildCardLimits if not null this is a limit found using a wildcard so should + * form the basis of the new object. + */ + private TransformationOptionLimits newTransformationOptionLimits(String transformerName, + String sourceMimetype, String targetMimetype, TransformationOptionLimits wildCardLimits, + String origUse, String use) + { + int origLevel = getLevel(transformerName, sourceMimetype, use); + + TransformationOptionLimits limits = new TransformationOptionLimits(); + if (wildCardLimits != null) + { + wildCardLimits.defaultTo(limits); + } + + int inc = (origLevel+1) % 2 + 1; // step = 1 if use is set otherwise 2 + for (int level=0; level transformerLimits = limits.get(name); - - TransformationOptionLimits limits = (transformerLimits == null) ? null : transformerLimits.get(sourceMimetype, targetMimetype); - - // Individual transformer limits might not exist. - TransformationOptionLimits transformerWideLimits = (transformerLimits == null) ? null : transformerLimits.get(ANY, ANY); - limits = (limits == null) ? transformerWideLimits : transformerWideLimits == null ? limits : transformerWideLimits.combine(limits); - - // If a non recursive call - if (transformer != null) - { - // System wide 'default' limits should exist. - TransformationOptionLimits systemWideLimits = getLimits(null, sourceMimetype, targetMimetype); - limits = (limits == null) ? systemWideLimits : systemWideLimits.combine(limits); - } - - return limits; + return getOrCreateTransformerOptionLimits(transformerName, sourceMimetype, targetMimetype, use); } } diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigLimitsTest.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigLimitsTest.java new file mode 100644 index 0000000000..9366f89ce3 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigLimitsTest.java @@ -0,0 +1,253 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockMimetypes; +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockProperties; +import static org.junit.Assert.assertEquals; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerConfigLimits. + * + * @author Alan Davis + */ +public class TransformerConfigLimitsTest +{ + @Mock + private TransformerProperties transformerProperties; + + @Mock + private MimetypeService mimetypeService; + + private ContentTransformer transformer1; + + private TransformerConfigLimits extractor; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + transformer1 = new DummyContentTransformer("transformer.transformer1"); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png", + "text/plain", "txt"); + } + + @Test + // A value is specified for a transformer and mimetypes + public void transformerMimetypesTest() + { + mockProperties(transformerProperties, "content.transformer.transformer1.extensions.pdf.png.maxSourceSizeKBytes", "10"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + } + + @Test + // A value is specified for a transformer + public void transformerTest() + { + mockProperties(transformerProperties, "content.transformer.transformer1.maxSourceSizeKBytes", "10"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + } + + @Test + // A value is specified as a transformer default with mimetypes + public void defaultMimetypesTest() + { + mockProperties(transformerProperties, "content.transformer.default.extensions.pdf.png.maxSourceSizeKBytes", "10"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + } + + @Test + // A value is specified as a transformer default without mimetypes + public void defaultTest() + { + mockProperties(transformerProperties, "content.transformer.default.maxSourceSizeKBytes", "10"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + } + + // --------------------------------------- + + @Test + // A value is specified for a transformer, mimetypes and use + public void transformerMimetypesUseTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.extensions.pdf.png.maxSourceSizeKBytes", "10", + "content.transformer.transformer1.extensions.pdf.png.maxSourceSizeKBytes.use.index", "20"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + + limits = extractor.getLimits(transformer1, "application/pdf", "image/png", "index"); + assertEquals(20, limits.getMaxSourceSizeKBytes()); + } + + @Test + // A value is specified for a transformer and use + public void transformerUseTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.maxSourceSizeKBytes", "10", + "content.transformer.transformer1.maxSourceSizeKBytes.use.index", "20"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + + limits = extractor.getLimits(transformer1, "application/pdf", "image/png", "index"); + assertEquals(20, limits.getMaxSourceSizeKBytes()); + } + + @Test + // A value is specified as a transformer default with mimetypes and use + public void defaultMimetypesUseTest() + { + mockProperties(transformerProperties, + "content.transformer.default.extensions.pdf.png.maxSourceSizeKBytes", "10", + "content.transformer.default.extensions.pdf.png.maxSourceSizeKBytes.use.index", "20"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + + limits = extractor.getLimits(transformer1, "application/pdf", "image/png", "index"); + assertEquals(20, limits.getMaxSourceSizeKBytes()); + } + + @Test + // A value is specified as a transformer default without mimetypes but with a use + public void defaultUseTest() + { + mockProperties(transformerProperties, + "content.transformer.default.maxSourceSizeKBytes", "10", + "content.transformer.default.maxSourceSizeKBytes.use.index", "20"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + + limits = extractor.getLimits(transformer1, "application/pdf", "image/png", "index"); + assertEquals(20, limits.getMaxSourceSizeKBytes()); + } + + // --------------------------------------- + + @Test + // Checks that transformer defaults are combined to construct mimetype specific values + public void transformerDefaultsUsedInMimetypesTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.extensions.pdf.png.maxSourceSizeKBytes", "10", + "content.transformer.transformer1.timeoutMs", "10000"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + assertEquals(10000L, limits.getTimeoutMs()); + } + + @Test + // Checks that transformer mimetype values override transformer defaults even if unlimited. + // This was new in 4.2. Prior to this they would have been combined, because of the need to + // always specify them in the spring configuration, which was removed in 4.2. + public void dontCombineTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.extensions.pdf.png.maxSourceSizeKBytes", "-1", + "content.transformer.transformer1.maxSourceSizeKBytes", "20"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(-1, limits.getMaxSourceSizeKBytes()); + } + + @Test + // Checks that system wide defaults (and system wide mimetype defaults) have been used to construct + // transformer specific values. + public void systemWideDefaultsUsedInTransformersTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.extensions.pdf.png.maxSourceSizeKBytes", "10", + "content.transformer.transformer1.maxSourceSizeKBytes", "15", + "content.transformer.default.timeoutMs", "120000", + "content.transformer.default.extensions.txt.png.pageLimit", "1"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits pdfToPngLimits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, pdfToPngLimits.getMaxSourceSizeKBytes()); + assertEquals(120000L, pdfToPngLimits.getTimeoutMs()); + assertEquals(-1, pdfToPngLimits.getPageLimit()); + + TransformationOptionLimits txtToPngLimits = extractor.getLimits(transformer1, "text/plain", "image/png", null); + assertEquals(15, txtToPngLimits.getMaxSourceSizeKBytes()); + assertEquals(120000L, txtToPngLimits.getTimeoutMs()); + assertEquals(1, txtToPngLimits.getPageLimit()); + } + + @Test + // Checks wildcard usage at the transformer level + public void transformerWildcardTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.extensions.*.png.maxSourceSizeKBytes", "10"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits limits = extractor.getLimits(transformer1, "application/pdf", "image/png", null); + assertEquals(10, limits.getMaxSourceSizeKBytes()); + } + + @Test + // Checks wildcard usage at the system wide level + public void systemWideWildcardTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.maxSourceSizeKBytes", "15", + "content.transformer.default.timeoutMs", "120000", + "content.transformer.default.extensions.txt.*.pageLimit", "1"); + + extractor = new TransformerConfigLimits(transformerProperties, mimetypeService); + TransformationOptionLimits txtToPngLimits = extractor.getLimits(transformer1, "text/plain", "image/png", null); + assertEquals(15, txtToPngLimits.getMaxSourceSizeKBytes()); + assertEquals(120000L, txtToPngLimits.getTimeoutMs()); + assertEquals(1, txtToPngLimits.getPageLimit()); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigMBean.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigMBean.java new file mode 100644 index 0000000000..c188d8a34d --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigMBean.java @@ -0,0 +1,100 @@ +/* + * Copyright 2005-2013 Alfresco Software, Ltd. All rights reserved. + * + * License rights for this program may be obtained from Alfresco Software, Ltd. + * pursuant to a written agreement and any use of this program without such an + * agreement is prohibited. + */ +package org.alfresco.repo.content.transform; + +/** + * A management interface for monitoring Content Transformer configuration + * and statistics. + * + * @author Alan Davis + */ +public interface TransformerConfigMBean +{ + /** + * Lists the names of all top level transformers. + */ + public String[] getTransformerNames(); + + /** + * Lists all configured mimetypes, proceeded by its primary file extension. + */ + public String[] getExtensionsAndMimetypes(); + + /** + * Lists all possible transformations sorted by Transformer name. + * @param transformerName to be checked. If null all transformers are included. + * @param use or context in which the transformation will be used ("doclib", + * "index", "webpreview", "syncRule", "asyncRule"...) or null for the default. + */ + public String getTransformationsByTransformer(String transformerName, String use); + + /** + * Lists all possible transformations sorted by source and then target mimetype extension. + * @param sourceExtension to be checked. If null all source mimetypes are included. + * @param targetExtension to be checked. If null all target mimetypes are included. + * @param use or context in which the transformation will be used ("doclib", + * "index", "webpreview", "syncRule", "asyncRule"...) or null for the default. + */ + public String getTransformationsByExtension(String sourceExtension, String targetExtension, String use); + + /** + * Lists the transformation statistics for the current node. + * @param transformerName to be checked. If null all transformers are included. + * @param sourceExtension to be checked. If null all source mimetypes are included. + * @param targetExtension to be checked. If null all target mimetypes are included. + */ + public String getTransformationStatistics(String transformerName, String sourceExtension, String targetExtension); + + /** + * Returns the last n entries in the transformation log. + */ + public String[] getTransformationLog(int n); + + /** + * Returns the last n entries in the transformation debug log. + */ + public String[] getTransformationDebugLog(int n); + + /** + * Returns custom and default transformer propertiest. + * @param listAll list both default and custom values, otherwise includes + * only custom values. + */ + public String getProperties(boolean listAll); + + /** + * Adds or replaces new transformer properties. + * @param propertyNamesAndValues + * @returns a confirmation or failure message + */ + public String setProperties(String propertyNamesAndValues); + + /** + * Removes transformer properties. + * @param propertyNames to be removed. Any values after the property name are ignored. + * @returns a confirmation or failure message + */ + String removeProperties(String propertyNames); + + /** + * Transforms a small test file from one mimetype to another and then shows the debug of the + * transform, which would indicate if it was successful or even if it was possible. + * @param transformerName to be used. If not specified the ContentService is used to select one. + * @param sourceExtension used to identify the mimetype + * @param targetExtension used to identify the mimetype + * @param use or context in which to test the transformation ("doclib", "index", "webpreview", + * "syncRule", "asyncRule"...) or blank for the default."; + * @return Text indicating if the transform was possible and any debug + */ + public String testTransform(String transformerName, String sourceExtension, String targetExtension, String use); + + /** + * Returns a description of each method and its parameters. + */ + public String help(); +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigMBeanImpl.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigMBeanImpl.java new file mode 100644 index 0000000000..0fd82af4d9 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigMBeanImpl.java @@ -0,0 +1,429 @@ +/* + * Copyright 2005-2013 Alfresco Software, Ltd. All rights reserved. + * + * License rights for this program may be obtained from Alfresco Software, Ltd. + * pursuant to a written agreement and any use of this program without such an + * agreement is prohibited. + */ +package org.alfresco.repo.content.transform; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.service.cmr.repository.MimetypeService; + +/** + * Implements the JMX interface for monitoring Content Transformer configuration + * and statistics. + * + * @author Alan Davis + */ +public class TransformerConfigMBeanImpl implements TransformerConfigMBean +{ + private static final String NO_TRANSFORMATIONS_TO_REPORT = "No transformations to report"; + private ContentTransformerRegistry transformerRegistry; + private TransformerDebug transformerDebug; + private TransformerConfig transformerConfig; + private MimetypeService mimetypeService; + private TransformerLog transformerLog; + private TransformerDebugLog transformerDebugLog; + + public void setContentTransformerRegistry(ContentTransformerRegistry transformerRegistry) + { + this.transformerRegistry = transformerRegistry; + } + + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } + + public void setTransformerConfig(TransformerConfig transformerConfig) + { + this.transformerConfig = transformerConfig; + } + + public void setMimetypeService(MimetypeService mimetypeService) + { + this.mimetypeService = mimetypeService; + } + + public void setTransformerLog(TransformerLog transformerLog) + { + this.transformerLog = transformerLog; + } + + public void setTransformerDebugLog(TransformerDebugLog transformerDebugLog) + { + this.transformerDebugLog = transformerDebugLog; + } + + @Override + public String[] getTransformerNames() + { + List transformerNames = new ArrayList(); + Collection transformers = transformerDebug.sortTransformersByName(null); + for (ContentTransformer transformer: transformers) + { + String name = transformer.getName(); + name = name.startsWith(TransformerConfig.TRANSFORMER) + ? name.substring(TransformerConfig.TRANSFORMER.length()) + : name; + transformerNames.add(name); + } + return transformerNames.toArray(new String[transformerNames.size()]); + } + + @Override + public String[] getExtensionsAndMimetypes() + { + List extensionsAndMimetypes = new ArrayList(); + for (String mimetype: mimetypeService.getMimetypes(null)) + { + String extension = mimetypeService.getExtension(mimetype); + extensionsAndMimetypes.add(extension+" - "+mimetype); + } + return extensionsAndMimetypes.toArray(new String[extensionsAndMimetypes.size()]); + } + + @Override + public String getTransformationsByTransformer(String simpleTransformerName, String use) + { + use = nullDefaultParam(use); + try + { + // Need to be able to generate 4.1.4ish output to compare with previous + // releases without too much effort cutting and pasting to change the order + return "41".equals(simpleTransformerName) + ? transformerDebug.transformationsByTransformer(null, true, false, use) + : transformerDebug.transformationsByTransformer( + getTransformerNameParam(simpleTransformerName), true, true, use); + } + catch (IllegalArgumentException e) + { + return e.getMessage(); + } + } + + @Override + public String getTransformationsByExtension(String sourceExtension, String targetExtension, String use) + { + use = nullDefaultParam(use); + try + { + // 41: Need to be able to generate 4.1.4ish output to compare with previous + // releases without too much effort cutting and pasting to change the order + // 00: (prefix) Finds only non deterministic transformations + if ("41".equals(sourceExtension)) + { + return transformerDebug.transformationsByExtension(null, null, true, false, false, null); + } + else + { + boolean onlyNonDeterministic = false; + if (sourceExtension != null && sourceExtension.startsWith("00")) + { + onlyNonDeterministic = true; + sourceExtension = sourceExtension.substring(2); + } + return transformerDebug.transformationsByExtension(nullDefaultLowerParam(sourceExtension), + nullDefaultLowerParam(targetExtension), true, true, onlyNonDeterministic, use); + } + } + catch (IllegalArgumentException e) + { + return e.getMessage(); + } + } + + @Override + public String getTransformationStatistics(String simpleTransformerName, String sourceExtension, String targetExtension) + { + try + { + StringBuilder sb = new StringBuilder(); + + String transformerName = getTransformerNameParam(simpleTransformerName); + sourceExtension = nullDefaultLowerParam(sourceExtension); + targetExtension = nullDefaultLowerParam(targetExtension); + + Collection transformers = transformerDebug.sortTransformersByName(transformerName); + Collection sourceMimetypes = transformerDebug.getSourceMimetypes(sourceExtension); + Collection targetMimetypes = transformerDebug.getTargetMimetypes(sourceExtension, targetExtension, sourceMimetypes); + + // Only report system wide mimetype summary if transformer not specified + boolean includeSystemWideSummary = transformerName == null; + if (includeSystemWideSummary) + { + getTransformationStatistics(sourceExtension, targetExtension, sb, + null, sourceMimetypes, targetMimetypes, false); + } + + for (ContentTransformer transformer: transformers) + { + getTransformationStatistics(sourceExtension, targetExtension, sb, + transformer, sourceMimetypes, targetMimetypes, includeSystemWideSummary); + } + + if (sb.length() == 0) + { + sb.append(NO_TRANSFORMATIONS_TO_REPORT); + } + + return sb.toString(); + } + catch (IllegalArgumentException e) + { + return e.getMessage(); + } + } + + private void getTransformationStatistics(String sourceExtension, String targetExtension, + StringBuilder sb, ContentTransformer transformer, Collection sourceMimetypes, + Collection targetMimetypes, boolean includeSystemWideSummary) + { + AtomicInteger counter = new AtomicInteger(0); + int i = sb.length(); + + for (String sourceMimetype: sourceMimetypes) + { + for (String targetMimetype: targetMimetypes) + { + getTransformationStatistics(sb, transformer, sourceMimetype, targetMimetype, counter, includeSystemWideSummary); + } + } + + // Only report transformer summary if there is more than one to summarise + // and we were asked for all + if (sourceExtension == null && targetExtension == null && counter.get() > 1) + { + StringBuilder sb2 = new StringBuilder(); + getTransformationStatistics(sb2, transformer, null, null, counter, includeSystemWideSummary); + sb2.append('\n'); + sb.insert((i == 0 ? 0 : i+2), sb2); + } + } + + private void getTransformationStatistics(StringBuilder sb, ContentTransformer transformer, + String sourceMimetype, String targetMimetype, AtomicInteger counter, + boolean includeSystemWideSummary) + { + TransformerStatistics statistics = transformerConfig.getStatistics(transformer, + sourceMimetype, targetMimetype, false); + if (statistics != null) + { + long count = statistics.getCount(); + if (count > 0) + { + if (sb.length() > 0) + { + sb.append('\n'); + } + if (counter.incrementAndGet() == 1 && includeSystemWideSummary) + { + sb.append('\n'); + } + + sb.append(statistics.getTransformerName()); + sb.append(' '); + sb.append(statistics.getSourceExt()); + sb.append(' '); + sb.append(statistics.getTargetExt()); + sb.append(" count="); + sb.append(count); + sb.append(" errors="); + sb.append(statistics.getErrorCount()); + sb.append(" averageTime="); + sb.append(statistics.getAverageTime()); + sb.append(" ms"); + } + } + } + + @Override + public String[] getTransformationLog(int n) + { + String[] entries = transformerLog.getEntries(n); + return entries.length == 0 + ? new String[] {NO_TRANSFORMATIONS_TO_REPORT} + : entries; + } + + @Override + public String[] getTransformationDebugLog(int n) + { + String[] entries = transformerDebugLog.getEntries(n); + return entries.length == 0 + ? new String[] {NO_TRANSFORMATIONS_TO_REPORT} + : entries; + } + + @Override + public String getProperties(boolean listAll) + { + return transformerConfig.getProperties(!listAll); + } + + @Override + public String setProperties(String propertyNamesAndValues) + { + + try + { + return "Properties added or changed: "+ + transformerConfig.setProperties(nullDefaultParam(propertyNamesAndValues)); + } + catch (IllegalArgumentException e) + { + return e.getMessage(); + } + } + + @Override + public String removeProperties(String propertyNames) + { + try + { + return "Properties removed: "+ + transformerConfig.removeProperties(nullDefaultParam(propertyNames)); + } + catch (IllegalArgumentException e) + { + return e.getMessage(); + } + } + + @Override + public String testTransform(final String simpleTransformerName, String sourceExtension, + String targetExtension, String use) + { + use = nullDefaultParam(use); + try + { + String transformerName = getTransformerNameParam(simpleTransformerName); + return transformerName == null + ? transformerDebug.testTransform( sourceExtension, targetExtension, use) + : transformerDebug.testTransform(transformerName, sourceExtension, targetExtension, use); + } + catch (IllegalArgumentException e) + { + return e.getMessage(); + } + } + + /** + * Returns a full transformer name given a simple transformer name parameter. + * @param simpleTransformerName the name of the transformer without the + * {@link TransformerConfig#TRANSFORMER} prefix. + * @return a null or a full transformer name + */ + private String getTransformerNameParam(String simpleTransformerName) + { + simpleTransformerName = nullDefaultParam(simpleTransformerName); + String transformerName = simpleTransformerName == null + ? null + : simpleTransformerName.startsWith(TransformerConfig.TRANSFORMER) + ? simpleTransformerName + : TransformerConfig.TRANSFORMER+simpleTransformerName; + + // Throws an IllegalArgumentException if unknown + transformerRegistry.getTransformer(transformerName); + + return transformerName; + } + + /** + * Changes the default JConsole parameter value "String" (and the zero length + * String) to null and forces other values to lower case. + */ + private String nullDefaultLowerParam(String parameter) + { + parameter = nullDefaultParam(parameter); + if (parameter != null) + { + parameter = parameter.toLowerCase(); + } + return parameter; + } + + /** + * Changes the default JConsole parameter value "String" (and the zero length + * String) to null. + */ + private String nullDefaultParam(String parameter) + { + if ("String".equals(parameter) || "".equals(parameter) || parameter == null) + { + parameter = null; + } + return parameter; + } + + @Override + public String help() + { + return "getProperties(listAll)\n" + + " Lists all transformer properties that are set.\n" + + " - listAll if true, list both default and custom values, otherwise includes\n" + + " only custom values\n" + + "\n" + + "setProperties(propertyNamesAndValues)\n" + + " Adds or replaces transformer properties.\n" + + " - propertyNamesAndValues to be set. May include comments but these are removed.\n" + + " To clear a custom values, set its value back to the default.\n" + + " To remove a custom property use removeProperties(...)\n" + + "\n" + + "getTransformationDebugLog(n)\n" + + " Lists the latest entries in the transformation debug log.\n" + + " - n the number of entries to include. If blank all available entries are listed\n" + + "\n" + + "getTransformationLog(n)\n" + + " Lists the latest entries in the transformation log.\n" + + " - n the number of entries to include. If blank all available entries are listed\n" + + "\n" + + "getTransformationStatistics(transformerName, sourceExtension, targetExtension)\n" + + " Lists the transformation statistics for the current node.\n" + + " - transformerName to be checked. If blank all transformers are included\n" + + " - sourceExtension to be checked. If blank all source mimetypes are included\n" + + " - targetExtension to be checked. If blank all target mimetypes are included\n" + + "\n" + + "getExtensionsAndMimetypes()\n" + + " Lists all configured mimetypes and the primary file extension\n" + + "\n" + + "getTransformerNames()\n" + + " Lists the names of all top level transformers\n" + + "\n" + + "testTransform(transformerName, sourceExtension, targetExtension, use)\n" + + " Transforms a small test file from one mimetype to another and then shows the \n" + + " debug of the transform, which would indicate if it was successful or even if \n" + + " it was possible.\n" + + " - transformerName to be used. If blank the ContentService is used to select one.\n" + + " - sourceExtension used to identify the mimetype\n" + + " - targetExtension used to identify the mimetype\n" + + " - use or context in which to test the transformation (\"doclib\",\n" + + " \"index\", \"webpreview\", \"syncRule\", \"asyncRule\"...) or blank for\n" + + " the default.\n" + + "removeProperties(String propertyNames)\n" + + " Removes transformer properties.\n" + + " - propertyNames to be removed. May include = after the property name.\n" + + " The value is ignored. Only custom properties should be removed.\n" + + "\n" + + "getTransformationsByExtension(sourceExtension, targetExtension, use)\n" + + " Lists all possible transformations sorted by source and then target mimetype\n" + + " extension.\n" + + " - sourceExtension to be checked. If blank all source mimetypes are included\n" + + " - targetExtension to be checked. If blank all target mimetypes are included.\n" + + " - use or context in which the transformation will be used (\"doclib\",\n" + + " \"index\", \"webpreview\", \"syncRule\", \"asyncRule\"...) or blank for\n" + + " the default.\n" + + "\n" + + "getTransformationsByTransformer(transformerName, use)\n" + + " Lists all possible transformations sorted by Transformer name\n" + + " - transformerName to be checked. If blank all transformers are included\n" + + " - use or context in which the transformation will be used (\"doclib\",\n" + + " \"index\", \"webpreview\", \"syncRule\", \"asyncRule\"...) or blank for\n" + + " the default."; + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigMBeanImplTest.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigMBeanImplTest.java new file mode 100644 index 0000000000..4f95451148 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigMBeanImplTest.java @@ -0,0 +1,420 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockMimetypes; +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyString; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerConfigMBeanImpl. + * + * @author Alan Davis + */ +public class TransformerConfigMBeanImplTest +{ + @Mock + private ContentTransformerRegistry transformerRegistry; + + @Mock + private TransformerDebug transformerDebug; + + @Mock + private TransformerConfig transformerConfig; + + @Mock + private MimetypeService mimetypeService; + + // Would like to use mockito, see getTransformationLogTest(), + // but getting the error WrongTypeOfReturnValue: String[] cannot be returned by getUpperMaxEntries() + // looks like the wrong method is being stubbed, when stubbing getEntries() + private TransformerLog transformerLog = new TransformerLog() + { + @Override + public String[] getEntries(int n) + { + return logEntries.toArray(new String[logEntries.size()]); + } + }; + + // Would like to use mockito, see transformerLog comment above + private TransformerDebugLog transformerDebugLog = new TransformerDebugLog() + { + @Override + public String[] getEntries(int n) + { + return logEntries.toArray(new String[logEntries.size()]); + } + }; + + private final List logEntries = new ArrayList(); + + private TransformerConfigMBeanImpl mbean; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + mbean = new TransformerConfigMBeanImpl(); + mbean.setContentTransformerRegistry(transformerRegistry); + mbean.setTransformerDebug(transformerDebug); + mbean.setTransformerConfig(transformerConfig); + mbean.setMimetypeService(mimetypeService); + mbean.setTransformerLog(transformerLog); + mbean.setTransformerDebugLog(transformerDebugLog); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png", + "text/plain", "txt"); + } + + @Test + // Just testing that the transformer names have the "transformer." prefix stripped. + public void getTransformerNamesTest() + { + when(transformerDebug.sortTransformersByName(null)).thenReturn( + Arrays.asList(new ContentTransformer[] + { + (ContentTransformer) new DummyContentTransformer("transformer.transformer1"), + (ContentTransformer) new DummyContentTransformer("transformer2"), + (ContentTransformer) new DummyContentTransformer("transformer.transformer3") + })); + + String[] actual = mbean.getTransformerNames(); + String[] expected = new String[] { "transformer1", "transformer2", "transformer3" }; + assertArrayEquals(expected, actual); + } + + @Test + public void getExtensionsAndMimetypesTest() + { + when(mimetypeService.getMimetypes(null)).thenReturn(Arrays.asList(new String[] { "application/pdf", "image/png" })); + when(mimetypeService.getExtension("application/pdf")).thenReturn("pdf"); + when(mimetypeService.getExtension("image/png")).thenReturn("png"); + + String[] actual = mbean.getExtensionsAndMimetypes(); + String[] expected = new String[] { "pdf - application/pdf", "png - image/png" }; + assertArrayEquals(expected, actual); + } + + @Test + public void getTransformationsByTransformerTest() + { + setupForGetTransformationsBtTransformer(); + assertEquals("One result", mbean.getTransformationsByTransformer("transformer1", null)); + } + + @Test + public void getTransformationsByTransformerBadNameTest() + { + setupForGetTransformationsBtTransformer(); + assertEquals("unknown transformer", mbean.getTransformationsByTransformer("TRANSFORMER1", null)); + } + + @Test + public void getTransformationsByTransformerNullTest() + { + setupForGetTransformationsBtTransformer(); + assertEquals("Lots of results", mbean.getTransformationsByTransformer(null, null)); + } + + @Test + public void getTransformationsByTransformerJConsoleStringTest() + { + // "String" (the default JConsole value) is mapped to null + setupForGetTransformationsBtTransformer(); + assertEquals("Lots of results", mbean.getTransformationsByTransformer("String", null)); + } + + @Test + public void getTransformationsByTransformerJConsoleBlankTest() + { + // "" is mapped to null - Can't set a null in JConsole + setupForGetTransformationsBtTransformer(); + assertEquals("Lots of results", mbean.getTransformationsByTransformer("", null)); + } + + private void setupForGetTransformationsBtTransformer() + { + when(transformerDebug.transformationsByTransformer("transformer.transformer1", true, true, null)).thenReturn("One result"); + when(transformerDebug.transformationsByTransformer(null, true, true, null)).thenReturn("Lots of results"); + when(transformerRegistry.getTransformer("transformer.transformer1")).thenReturn(new DummyContentTransformer("transformer.transformer1")); + when(transformerRegistry.getTransformer(null)).thenReturn(null); + when(transformerRegistry.getTransformer("transformer.TRANSFORMER1")).thenThrow(new IllegalArgumentException("unknown transformer")); + } + + @Test + public void getTransformationsByExtensionTest() + { + setupForGetTransformationsByExtension(); + assertEquals("One result", mbean.getTransformationsByExtension("pdf", "png", null)); + } + + @Test + public void getTransformationsByExtensionUpperCaseTest() + { + setupForGetTransformationsByExtension(); + assertEquals("One result", mbean.getTransformationsByExtension("PDF", "PNG", null)); + } + + @Test + public void getTransformationsByExtensionNullSourceTest() + { + setupForGetTransformationsByExtension(); + assertEquals("Lots of results to png", mbean.getTransformationsByExtension(null, "PNG", null)); + } + + @Test + public void getTransformationsByExtensionNullTargetTest() + { + setupForGetTransformationsByExtension(); + assertEquals("Lots of results from pdf", mbean.getTransformationsByExtension("pdf", null, null)); + } + + private void setupForGetTransformationsByExtension() + { + when(transformerDebug.transformationsByExtension("pdf", "png", true, true, false, null)).thenReturn("One result"); + when(transformerDebug.transformationsByExtension(null, "png", true, true, false, null)).thenReturn("Lots of results to png"); + when(transformerDebug.transformationsByExtension("pdf", null, true, true, false, null)).thenReturn("Lots of results from pdf"); + } + + @Test + public void getTransformationStatisticsTransformer1FromToTest() + { + setupForGetTransformationStatistics(); + // Should not be a transformer summary as there might be other transforms and the + // totals would not add up + assertEquals( + "transformer.transformer1 pdf png count=10 errors=0 averageTime=200 ms", + mbean.getTransformationStatistics("transformer1", "pdf", "png")); + } + + @Test + public void getTransformationStatisticsTransformer1AllTest() + { + setupForGetTransformationStatistics(); + // Should be transformer summaries as all transforms were requested and the + // totals will add up + assertEquals( + "transformer.transformer1 * * count=30 errors=0 averageTime=133 ms\n" + + "transformer.transformer1 pdf png count=10 errors=0 averageTime=200 ms\n" + + "transformer.transformer1 txt png count=20 errors=0 averageTime=100 ms", + mbean.getTransformationStatistics("transformer1", null, null)); + } + + @Test + public void getTransformationStatisticsFromToTest() + { + setupForGetTransformationStatistics(); + // Should be an overall summary as the transformer is not specified + // Should not be a transformer summary as there 'might' be other transforms and the + // totals would not add up + assertEquals( + "SUMMARY pdf png count=10 errors=0 averageTime=200 ms\n" + + "SUMMARY txt png count=24 errors=0 averageTime=234 ms\n" + + "\n" + + "transformer.transformer1 pdf png count=10 errors=0 averageTime=200 ms\n" + + "transformer.transformer1 txt png count=20 errors=0 averageTime=100 ms\n" + + "\n" + + "transformer.transformer2 txt png count=4 errors=0 averageTime=654 ms", + mbean.getTransformationStatistics(null, null, "png")); + } + + @Test + public void getTransformationStatisticsAllTest() + { + setupForGetTransformationStatistics(); + // Should be an overall summary as the transformer is not specified + // Should be a transformer1 summary but not for transformer2 as it only has done txt->png + assertEquals( + "SUMMARY * * count=34 errors=0 averageTime=222 ms\n" + + "SUMMARY pdf png count=10 errors=0 averageTime=200 ms\n" + + "SUMMARY txt png count=24 errors=0 averageTime=234 ms\n" + + "\n" + + "transformer.transformer1 * * count=30 errors=0 averageTime=133 ms\n" + + "transformer.transformer1 pdf png count=10 errors=0 averageTime=200 ms\n" + + "transformer.transformer1 txt png count=20 errors=0 averageTime=100 ms\n" + + "\n" + + "transformer.transformer2 txt png count=4 errors=0 averageTime=654 ms", + mbean.getTransformationStatistics(null, null, null)); + } + + @Test + public void getTransformationStatisticsNoneTest() + { + setupForGetTransformationStatistics(); + assertEquals( + "No transformations to report", + mbean.getTransformationStatistics("transformer1", "png", "pdf")); + } + + @SuppressWarnings("unchecked") + private void setupForGetTransformationStatistics() + { + ContentTransformer transformer1 = (ContentTransformer) new DummyContentTransformer("transformer.transformer1"); + ContentTransformer transformer2 = (ContentTransformer) new DummyContentTransformer("transformer.transformer2"); + + when(transformerDebug.sortTransformersByName("transformer.transformer1")).thenReturn( + Arrays.asList(new ContentTransformer[] {transformer1})); + when(transformerDebug.sortTransformersByName(null)).thenReturn( + Arrays.asList(new ContentTransformer[] {transformer1, transformer2})); + + when(transformerDebug.getSourceMimetypes("pdf")).thenReturn(Collections.singletonList("application/pdf")); + when(transformerDebug.getSourceMimetypes("png")).thenReturn(Collections.singletonList("image/png")); + when(transformerDebug.getSourceMimetypes("txt")).thenReturn(Collections.singletonList("text/plain")); + when(transformerDebug.getSourceMimetypes(null)).thenReturn(Arrays.asList(new String[] {"application/pdf", "image/png", "text/plain"})); + + when(transformerDebug.getTargetMimetypes(anyString(), eq("pdf"), (Collection) any())).thenReturn(Collections.singletonList("application/pdf")); + when(transformerDebug.getTargetMimetypes(anyString(), eq("png"), (Collection) any())).thenReturn(Collections.singletonList("image/png")); + when(transformerDebug.getTargetMimetypes(anyString(), eq("txt"), (Collection) any())).thenReturn(Collections.singletonList("text/plain")); + when(transformerDebug.getTargetMimetypes(anyString(), (String)eq(null), (Collection) any())).thenReturn(Arrays.asList(new String[] {"application/pdf", "image/png", "text/plain"})); + + when(transformerConfig.getStatistics(null, null, null, false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "*", "*", null, null, 130000, 222, 34)); + when(transformerConfig.getStatistics(null, "application/pdf", "image/png", false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "application/pdf", "image/png", null, null, 130001, 200, 10)); + when(transformerConfig.getStatistics(null, "text/plain", "image/png", false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "text/plain", "image/png", null, null, 130002, 234, 24)); + + when(transformerConfig.getStatistics(transformer1, "application/pdf", "image/png", false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "application/pdf", "image/png", transformer1, null, 120000, 200, 10)); + when(transformerConfig.getStatistics(transformer1, "text/plain", "image/png", false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "text/plain", "image/png", transformer1, null, 120001, 100, 20)); + when(transformerConfig.getStatistics(transformer1, null, null, false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "*", "*", transformer1, null, 120002, 133, 30)); + + when(transformerConfig.getStatistics(transformer2, "text/plain", "image/png", false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "text/plain", "image/png", transformer2, null, 120003, 654, 4)); + when(transformerConfig.getStatistics(transformer2, null, null, false)).thenReturn( + new TransformerStatisticsImpl(mimetypeService, "*", "*", transformer2, null, 120004, 654, 4)); + } + + @Test + public void getTransformationLogTest() + { +// when(transformerLog.getEntries(5)).thenReturn(new String[] {"test message 1", "test message 2"}); + logEntries.add("test message 1"); + logEntries.add("test message 2"); + assertArrayEquals(new String[] {"test message 1", "test message 2"}, mbean.getTransformationLog(5)); + } + + @Test + public void getTransformationLogZeroTest() + { +// when(transformerLog.getEntries(5)).thenReturn(new String[0]); + assertArrayEquals(new String[] {"No transformations to report"}, mbean.getTransformationLog(5)); + } + + @Test + public void getTransformationDebugLogTest() + { +// when(transformerDebugLog.getEntries(5)).thenReturn(new String[] {"test message 1", "test message 2"}); + logEntries.add("test message 1"); + logEntries.add("test message 2"); + assertArrayEquals(new String[] {"test message 1", "test message 2"}, mbean.getTransformationDebugLog(5)); + } + + @Test + public void getTransformationDebugLogZeroTest() + { +// when(transformerDebugLog.getEntries(5)).thenReturn(new String[0]); + assertArrayEquals(new String[] {"No transformations to report"}, mbean.getTransformationDebugLog(5)); + } + + @Test + public void getPropertiesTest() + { + when(transformerConfig.getProperties(false)).thenReturn("some properties"); + assertEquals("some properties", mbean.getProperties(true)); + } + + @Test + public void setPropertiesTest() + { + when(transformerConfig.setProperties("abc")).thenReturn(12); + assertEquals("Properties added or changed: 12", mbean.setProperties("abc")); + } + + @Test + public void setPropertiesDataProblemTest() + { + when(transformerConfig.setProperties("abc=12\nabc=1")).thenThrow(new IllegalArgumentException("abc has been specified more than once")); + assertEquals("abc has been specified more than once", mbean.setProperties("abc=12\nabc=1")); + } + + @Test + public void removePropertiesTest() + { + when(transformerConfig.removeProperties("abc")).thenReturn(1); + assertEquals("Properties removed: 1", mbean.removeProperties("abc")); + } + + @Test + public void removePropertiesDataProblemTest() + { + when(transformerConfig.removeProperties("abc")).thenThrow(new IllegalArgumentException("Unexpected property: abc Does not exist")); + assertEquals("Unexpected property: abc Does not exist", mbean.removeProperties("abc")); + } + + @Test + public void testTransformAnyTransformerTest() + { + when(transformerDebug.testTransform("pdf", "png", null)).thenReturn("debug output"); + assertEquals("debug output", mbean.testTransform("String", "pdf", "png", null)); + } + + @Test + public void testTransformAnyTransformerBadExtensionTest() + { + when(transformerDebug.testTransform("bad", "png", null)).thenThrow(new IllegalArgumentException("Unknown source extension: bad")); + assertEquals("Unknown source extension: bad", mbean.testTransform(null, "bad", "png", null)); + } + + @Test + public void testTransformTest() + { + when(transformerDebug.testTransform("transformer.transformer1", "pdf", "png", null)).thenReturn("debug output"); + assertEquals("debug output", mbean.testTransform("transformer1", "pdf", "png", null)); + } + + @Test + public void testTransformBadExtensionTest() + { + when(transformerDebug.testTransform("transformer.transformer1", "bad", "png", null)).thenThrow(new IllegalArgumentException("Unknown source extension: bad")); + assertEquals("Unknown source extension: bad", mbean.testTransform("transformer1", "bad", "png", null)); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigProperty.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigProperty.java index d6caca388c..a2081ef238 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerConfigProperty.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigProperty.java @@ -21,16 +21,14 @@ package org.alfresco.repo.content.transform; import static org.alfresco.repo.content.transform.TransformerConfig.ANY; import static org.alfresco.repo.content.transform.TransformerConfig.DEFAULT_TRANSFORMER; -import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.service.cmr.repository.MimetypeService; /** - * Provides access to single transformer configuration property depending on the + * Provides access to a single transformer configuration property depending on the * transformer and source and target mimetypes, falling back to defaults. * * @author Alan Davis @@ -39,43 +37,44 @@ public class TransformerConfigProperty extends TransformerPropertyNameExtractor { private Map> values; - public TransformerConfigProperty(ChildApplicationContextFactory subsystem, + public TransformerConfigProperty(TransformerProperties transformerProperties, MimetypeService mimetypeService, String propertySuffix, String defaultValue) { - setValues(subsystem, mimetypeService, propertySuffix, defaultValue); + setValues(transformerProperties, mimetypeService, propertySuffix, defaultValue); } /** * Sets the transformer values created from system properties. */ - private void setValues(ChildApplicationContextFactory subsystem, MimetypeService mimetypeService, + private void setValues(TransformerProperties transformerProperties, MimetypeService mimetypeService, String suffix, String defaultValue) { values = new HashMap>(); // Gets all the transformer, source and target combinations in properties that define // this value. - Collection properties = - getTransformerSourceTargetValues(Collections.singletonList(suffix), true, subsystem, mimetypeService); + Map properties = + getTransformerSourceTargetValuesMap(Collections.singletonList(suffix), true, false, transformerProperties, mimetypeService); // Add the system wide default if it does not exist, as we always need this one TransformerSourceTargetSuffixValue transformerSourceTargetValue = - new TransformerSourceTargetSuffixValue(DEFAULT_TRANSFORMER, ANY, ANY, suffix, defaultValue, mimetypeService); - if (properties.contains(transformerSourceTargetValue.key())) + new TransformerSourceTargetSuffixValue(DEFAULT_TRANSFORMER, ANY, ANY, suffix, null, defaultValue, mimetypeService); + TransformerSourceTargetSuffixKey key = transformerSourceTargetValue.key(); + if (!properties.containsKey(key)) { - properties.add(transformerSourceTargetValue); + properties.put(key, transformerSourceTargetValue); } // Populate the transformer values - for (TransformerSourceTargetSuffixValue property: properties) + for (TransformerSourceTargetSuffixValue property: properties.values()) { - DoubleMap mimetypeLimits = this.values.get(property.transformerName); - if (mimetypeLimits == null) + DoubleMap mimetypeValues = values.get(property.transformerName); + if (mimetypeValues == null) { - mimetypeLimits = new DoubleMap(ANY, ANY); - this.values.put(property.transformerName, mimetypeLimits); + mimetypeValues = new DoubleMap(ANY, ANY); + values.put(property.transformerName, mimetypeValues); } - mimetypeLimits.put(property.sourceMimetype, property.targetMimetype, property.value); + mimetypeValues.put(property.sourceMimetype, property.targetMimetype, property.value); } } @@ -108,11 +107,13 @@ public class TransformerConfigProperty extends TransformerPropertyNameExtractor } public long getLong(ContentTransformer transformer, String sourceMimetype, String targetMimetype) + throws NumberFormatException { return Long.parseLong(getString(transformer, sourceMimetype, targetMimetype)); } public int getInt(ContentTransformer transformer, String sourceMimetype, String targetMimetype) + throws NumberFormatException { return Integer.parseInt(getString(transformer, sourceMimetype, targetMimetype)); } diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigPropertyTest.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigPropertyTest.java new file mode 100644 index 0000000000..678a76581a --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigPropertyTest.java @@ -0,0 +1,196 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerConfig.ANY; +import static org.alfresco.repo.content.transform.TransformerConfig.PRIORITY; +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockMimetypes; +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockProperties; +import static org.junit.Assert.assertEquals; + +import java.math.BigInteger; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerConfigProperty. + * + * @author Alan Davis + */ +public class TransformerConfigPropertyTest +{ + @Mock + private TransformerProperties transformerProperties; + + @Mock + private MimetypeService mimetypeService; + + private TransformerConfigProperty extractor; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png"); + } + + @Test + public void simpleTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.priority", "87"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + int actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), null, null); + assertEquals("transformer default priority", 87, actual); + } + + @Test + public void getSystemWideDefaultTest() + { + mockProperties(transformerProperties, "content.transformer.default.priority", "87"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + int actual = extractor.getInt(null, null, null); + assertEquals("default priority", 87, actual); + } + + @Test + public void getSystemWideDefaultWithAnyTest() + { + // Same as getSystemWideDefaultTest except getInt uses ANY rather than null + mockProperties(transformerProperties, "content.transformer.default.priority", "87"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + int actual = extractor.getInt(null, ANY, ANY); + assertEquals("default priority", 87, actual); + } + + @Test + public void useSystemWideDefaultTest() + { + mockProperties(transformerProperties, "content.transformer.default.priority", "87"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + int actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), null, null); + assertEquals("default priority", 87, actual); + } + + @Test + public void useNonExistentSystemWideDefaultTest() + { + mockProperties(transformerProperties); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + int actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), null, null); + assertEquals("default priority", 55, actual); + } + + @Test + public void mimetypesTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.mimetypes.application/pdf.image/png.priority", "87"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + int actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + assertEquals("transformer default priority", 87, actual); + } + + @Test + public void extensionsTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.extensions.pdf.png.priority", "87"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + int actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + assertEquals("transformer default priority", 87, actual); + } + + @Test + public void multiplePropertiesTest() + { + mockProperties(transformerProperties, + "content.transformer.default.priority", "11", + "content.transformer.abc.extensions.pdf.png.priority", "22", + "content.transformer.abc.xyz.extensions.pdf.png.priority", "33"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + + int actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.qaz"), "application/pdf", "image/png"); + assertEquals("default", 11, actual); + actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png"); + assertEquals("abc", 22, actual); + actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + assertEquals("abc.xyz", 33, actual); + } + + @Test + public void longTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.extensions.pdf.png.priority", "1234567890"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + long actual = extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + assertEquals("transformer default priority", 1234567890L, actual); + } + + @Test(expected=NumberFormatException.class) + public void badIntTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.extensions.pdf.png.priority", + Long.toString(((long)Integer.MAX_VALUE)+1)); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + } + + @Test(expected=NumberFormatException.class) + public void badIntAbcTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.extensions.pdf.png.priority", "abc"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + extractor.getInt((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + } + + @Test(expected=NumberFormatException.class) + public void badLongTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.extensions.pdf.png.priority", + new BigInteger(""+Long.MAX_VALUE).add(BigInteger.ONE).toString()); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + extractor.getLong((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + } + + @Test(expected=NumberFormatException.class) + public void badLongAbcTest() + { + mockProperties(transformerProperties, "content.transformer.abc.xyz.extensions.pdf.png.priority", "abc"); + + extractor = new TransformerConfigProperty(transformerProperties, mimetypeService, PRIORITY, "55"); + extractor.getLong((ContentTransformer) new DummyContentTransformer("transformer.abc.xyz"), "application/pdf", "image/png"); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigStatistics.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigStatistics.java index 434428f271..92ab8b8d76 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerConfigStatistics.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigStatistics.java @@ -27,9 +27,17 @@ import java.util.Map; import org.alfresco.service.cmr.repository.MimetypeService; /** - * Provides a place to store and access statistics about transformers, source and target mimetypes combinations. - * It also provides summaries for transformers as a whole and the system as a whole. - * + * Provides a place to store statistics about: + * a) the combination of transformer, source and target mimetype; + * b) a summary for each transformer; + * c) a summary of top level transformations (++) for each combination of + * source and target mimetype; + * d) a summary of all top level transformations. + * These values are not shared across the cluster but are node specific.

+ * + * ++ Top level transformations don't include transformations performed as part + * of another transformation. + * * @author Alan Davis */ public class TransformerConfigStatistics @@ -42,7 +50,7 @@ public class TransformerConfigStatistics // transformer wide summaries. private Map> statistics = new HashMap>(); - + public TransformerConfigStatistics(TransformerConfigImpl transformerConfigImpl, MimetypeService mimetypeService) { @@ -50,7 +58,7 @@ public class TransformerConfigStatistics this.mimetypeService = mimetypeService; } - public TransformerStatistics getStatistics(ContentTransformer transformer, String sourceMimetype, String targetMimetype) + public TransformerStatistics getStatistics(ContentTransformer transformer, String sourceMimetype, String targetMimetype, boolean createNew) { if (sourceMimetype == null) { @@ -67,6 +75,14 @@ public class TransformerConfigStatistics String name = (transformer == null) ? SUMMARY_TRANSFORMER_NAME : transformer.getName(); DoubleMap mimetypeStatistics = statistics.get(name); + if (!createNew) + { + transformerStatistics = (mimetypeStatistics == null) + ? null + : mimetypeStatistics.getNoWildcards(sourceMimetype, targetMimetype); + return transformerStatistics; + } + if (mimetypeStatistics == null) { // Create the summary for the transformer as a whole @@ -82,7 +98,7 @@ public class TransformerConfigStatistics } else { - // Not looking for the summary, so will have to create if not found or the summary is returned + // Not looking for the summary, so will have to create it if not found or the summary is returned transformerStatistics = mimetypeStatistics.get(sourceMimetype, targetMimetype); if (transformerStatistics == null || transformerStatistics.isSummary()) { @@ -109,5 +125,4 @@ public class TransformerConfigStatistics return transformerStatistics; } - } diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigStatisticsTest.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigStatisticsTest.java new file mode 100644 index 0000000000..c34634371d --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigStatisticsTest.java @@ -0,0 +1,250 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockMimetypes; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerConfigStatistics. + * + * @author Alan Davis + */ +public class TransformerConfigStatisticsTest +{ + @Mock + private TransformerConfigImpl transformerConfig; + + @Mock + private MimetypeService mimetypeService; + + private ContentTransformer transformer1; + + private TransformerConfigStatistics stats; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + stats = new TransformerConfigStatistics(transformerConfig, mimetypeService); + + transformer1 = new DummyContentTransformer("transformer.transformer1"); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png", + "text/plain", "txt"); + } + + @Test + public void createTest() + { + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + assertTrue(statistics != null); + assertEquals(0L, statistics.getAverageTime()); + assertEquals(0L, statistics.getCount()); + assertEquals(0L, statistics.getErrorCount()); + assertEquals("pdf", statistics.getSourceExt()); + assertEquals("png", statistics.getTargetExt()); + assertEquals("transformer.transformer1", statistics.getTransformerName()); + } + + @Test + public void createSetAverageTimeTest() + { + when(transformerConfig.getInitialAverageTime(transformer1, "application/pdf", "image/png")).thenReturn(12345L); + when(transformerConfig.getInitialCount(transformer1, "application/pdf", "image/png")).thenReturn(123); + + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + assertTrue(statistics != null); + assertEquals(12345L, statistics.getAverageTime()); + assertEquals(123L, statistics.getCount()); + } + + @Test + public void create0CountOnSetAverageTimeTest() + { + // getInitialCount should be defaulted to 0 if the average time is set and there is no default + when(transformerConfig.getInitialAverageTime(transformer1, "application/pdf", "image/png")).thenReturn(12345L); + + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + assertTrue(statistics != null); + assertEquals(12345L, statistics.getAverageTime()); + assertEquals(0L, statistics.getCount()); + } + + @Test + public void createSetErrorTimeTest() + { + when(transformerConfig.getErrorTime(transformer1, "application/pdf", "image/png")).thenReturn(12345L); + + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + statistics.recordError(100L); // 100 does not get used if errorTime is set + + assertEquals(12345L, statistics.getAverageTime()); + assertEquals(1L, statistics.getCount()); + assertEquals(1L, statistics.getErrorCount()); + } + + @Test + public void createNoSetErrorTimeTest() + { + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + statistics.recordError(100L); + + assertEquals(100L, statistics.getAverageTime()); + assertEquals(1L, statistics.getCount()); + assertEquals(1L, statistics.getErrorCount()); + } + + @Test + public void mayCreateButExistsTest() + { + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + statistics.recordTime(100L); + + // Call again (with createNew=true) and then make sure it is the same object returned + statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + assertEquals(100L, statistics.getAverageTime()); + } + + @Test + public void mayNotCreateButExistsTest() + { + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + statistics.recordTime(100L); + + // Call again (with createNew=false) and then make sure it is the same object returned + statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", false); + assertEquals(100L, statistics.getAverageTime()); + } + + @Test + public void doesNotExistTest() + { + assertEquals(null, stats.getStatistics(transformer1, "application/pdf", "image/png", false)); + } + + @Test + // i.e. the system wide summary + public void nullTransformerTest() + { + TransformerStatistics statistics = stats.getStatistics(null, "application/pdf", "image/png", true); + assertEquals("SUMMARY", statistics.getTransformerName()); + } + + @Test + public void nullMimetypesTest() + { + TransformerStatistics statistics = stats.getStatistics(transformer1, null, null, true); + assertEquals("transformer.transformer1", statistics.getTransformerName()); + assertEquals("*", statistics.getSourceExt()); + assertEquals("*", statistics.getTargetExt()); + } + + @Test + public void createSummaryTooTest() + { + // Same as createTest() + TransformerStatistics statistics = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + + // The summary for the transformer should also have been created + statistics = stats.getStatistics(transformer1, "*", "*", false); + assertTrue(statistics != null); + assertEquals(0L, statistics.getAverageTime()); + assertEquals(0L, statistics.getCount()); + assertEquals(0L, statistics.getErrorCount()); + assertEquals("*", statistics.getSourceExt()); // pdf in createTest + assertEquals("*", statistics.getTargetExt()); // png in createTest + assertEquals("transformer.transformer1", statistics.getTransformerName()); + } + + @Test + // check the transformer summary gets reused + public void transformerSummaryTest() + { + // Create pdf->png and summary from transformer1 + TransformerStatistics pdfToPng = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + TransformerStatistics summary = stats.getStatistics(transformer1, "*", "*", false); + + // Create txt->png for transformer1 + TransformerStatistics txtToPng = stats.getStatistics(transformer1, "text/plain", "image/png", true); + + pdfToPng.recordTime(100); + txtToPng.recordTime(200); + + assertEquals(2, summary.getCount()); + assertEquals(150L, summary.getAverageTime()); + } + + @Test + // check the system wide statistics gather up values + public void systemWideSummaryTest() + { + TransformerStatistics transformer1PdfToPng = stats.getStatistics(transformer1, "application/pdf", "image/png", true); + TransformerStatistics transformer1TxtToPng = stats.getStatistics(transformer1, "text/plain", "image/png", true); + + ContentTransformer transformer2 = new DummyContentTransformer("transformer.transformer2"); + TransformerStatistics transformer2PdfToPng = stats.getStatistics(transformer2, "application/pdf", "image/png", true); + TransformerStatistics transformer2TxtToPdf = stats.getStatistics(transformer2, "text/plain", "application/pdf", true); + + TransformerStatistics summaryPdfToPng = stats.getStatistics(null, "application/pdf", "image/png", true); + TransformerStatistics summaryTxtToPng = stats.getStatistics(null, "text/plain", "image/png", true); + TransformerStatistics summaryTxtToPdf = stats.getStatistics(null, "text/plain", "application/pdf", true); + TransformerStatistics summary = stats.getStatistics(null, "*", "*", false); + + // Run a few transforms + recordTime(summaryPdfToPng, transformer1PdfToPng, 100); + recordTime(summaryPdfToPng, transformer1PdfToPng, 100); + recordTime(summaryPdfToPng, transformer2PdfToPng, 400); + + recordTime(summaryTxtToPng, transformer1TxtToPng, 200); + + recordTime(summaryTxtToPdf, transformer2TxtToPdf, 400); + + // Check summaries + assertEquals(5, summary.getCount()); + assertEquals(3, summaryPdfToPng.getCount()); + assertEquals(1, summaryTxtToPng.getCount()); + assertEquals(1, summaryTxtToPdf.getCount()); + + assertEquals(200L, summaryPdfToPng.getAverageTime()); + assertEquals(200L, summaryTxtToPng.getAverageTime()); + assertEquals(400L, summaryTxtToPdf.getAverageTime()); + assertEquals(240L, summary.getAverageTime()); // 100+100+400+200+400 = 1200 + } + + // Calls both transformer and summary recordTime() methods in the same way + // AbstractContentTransformer2.recordTime(String, String, long) would do for + // a top level transformation + private void recordTime(TransformerStatistics summaryAToB, TransformerStatistics transformerAToB, long time) + { + transformerAToB.recordTime(time); + summaryAToB.recordTime(time); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigSupported.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigSupported.java index f8b2585a3c..70d71f1ef0 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerConfigSupported.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigSupported.java @@ -26,7 +26,6 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; -import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.TransformationOptions; @@ -43,22 +42,22 @@ public class TransformerConfigSupported extends TransformerPropertyNameExtractor // SourceMimetype and targetMimetype may be 'ANY' values to act as wild cards. private Map supported; - public TransformerConfigSupported(ChildApplicationContextFactory subsystem, MimetypeService mimetypeService) + public TransformerConfigSupported(TransformerProperties transformerProperties, MimetypeService mimetypeService) { - setSupported(subsystem, mimetypeService); + setSupported(transformerProperties, mimetypeService); } /** * Sets the supported/unsupported mimetype transformations created from system properties. */ - private void setSupported(ChildApplicationContextFactory subsystem, MimetypeService mimetypeService) + private void setSupported(TransformerProperties transformerProperties, MimetypeService mimetypeService) { supported = new HashMap(); // Gets all the supported and unsupported transformer, source and target combinations Collection properties = getTransformerSourceTargetValues(Collections.singletonList(SUPPORTED), - false, subsystem, mimetypeService); + false, false, transformerProperties, mimetypeService); // Populate the transformer values for (TransformerSourceTargetSuffixValue property: properties) diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigSupportedTest.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigSupportedTest.java new file mode 100644 index 0000000000..565c23e93c --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigSupportedTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockMimetypes; +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockProperties; +import static org.junit.Assert.assertEquals; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerConfigSupported. + * + * @author Alan Davis + */ +public class TransformerConfigSupportedTest +{ + @Mock + private TransformerProperties transformerProperties; + + @Mock + private MimetypeService mimetypeService; + + @Mock + private TransformationOptions options; + + private TransformerConfigSupported extractor; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png"); + } + + @Test + public void supportedTest() + { + mockProperties(transformerProperties, "content.transformer.abc.extensions.pdf.png.supported", "true"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", true, supported); + } + + @Test + public void unsupportedTest() + { + mockProperties(transformerProperties, "content.transformer.abc.extensions.pdf.png.supported", "false"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", false, supported); + } + + @Test + public void mixedCaseTrueTest() + { + mockProperties(transformerProperties, "content.transformer.abc.extensions.pdf.png.supported", "tRUE"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", true, supported); + } + + @Test + public void unsupportedMimetypesTest() + { + mockProperties(transformerProperties, "content.transformer.abc.mimetypes.application/pdf.image/png.supported", "false"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", false, supported); + } + + @Test + public void badValueIsUnsupportedTest() + { + mockProperties(transformerProperties, "content.transformer.abc.extensions.pdf.png.supported", "bad"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", false, supported); + } + + @Test + public void otherMimetypesTest() + { + mockProperties(transformerProperties, "content.transformer.abc.extensions.png.pdf.supported", "false"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", true, supported); + } + + @Test + public void supportedByDefaultTest() + { + mockProperties(transformerProperties); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", true, supported); + } + + @Test + public void multipleTest() + { + mockProperties(transformerProperties, + "content.transformer.abc.extensions.pdf.png.supported", "false", + "content.transformer.def.extensions.pdf.png.supported", "true", + "content.transformer.xyz.extensions.pdf.png.supported", "false"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("abc supported", false, supported); + supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.def"), "application/pdf", "image/png", options); + assertEquals("def supported", true, supported); + supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.xyz"), "application/pdf", "image/png", options); + assertEquals("xyz supported", false, supported); + } + + @Test + public void bothUnsupportedAndSupportedTest() + { + // mimetypes should win if both are supplied + mockProperties(transformerProperties, + "content.transformer.abc.extensions.pdf.png.supported", "false", + "content.transformer.abc.mimetypes.application/pdf.image/png.supported", "true"); + + extractor = new TransformerConfigSupported(transformerProperties, mimetypeService); + boolean supported = extractor.isSupportedTransformation((ContentTransformer) new DummyContentTransformer("transformer.abc"), "application/pdf", "image/png", options); + assertEquals("supported", true, supported); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerConfigTestSuite.java b/source/java/org/alfresco/repo/content/transform/TransformerConfigTestSuite.java new file mode 100644 index 0000000000..7aaddc2e88 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerConfigTestSuite.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import org.alfresco.util.LogAdapterTest; +import org.alfresco.util.LogTeeTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +/** + * @author adavis + * + */ +@RunWith(Suite.class) +@SuiteClasses({ + TransformerConfigStatisticsTest.class, + TransformerConfigLimitsTest.class, + TransformerConfigSupportedTest.class, + TransformerConfigPropertyTest.class, + TransformerPropertyNameExtractorTest.class, + TransformerPropertyGetterTest.class, + TransformerPropertySetterTest.class, + + LogAdapterTest.class, + LogTeeTest.class, + + TransformerLoggerTest.class, + TransformerLogTest.class, + TransformerDebugLogTest.class, + + TransformerConfigImplTest.class, + TransformerConfigMBeanImplTest.class, + + TransformerSelectorImplTest.class}) + +/** + * Test classes in the Transformers subsystem + * + * @author Alan Davis + */ +public class TransformerConfigTestSuite +{ +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerDebug.java b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java index 9227eb2204..3d98724054 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerDebug.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java @@ -18,22 +18,41 @@ */ package org.alfresco.repo.content.transform; +import java.io.File; +import java.io.IOException; +import java.net.URL; import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.regex.Pattern; +import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.filestore.FileContentReader; +import org.alfresco.repo.content.filestore.FileContentWriter; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.ContentWriter; import org.alfresco.service.cmr.repository.MimetypeService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.util.EqualsHelper; +import org.alfresco.util.LogTee; +import org.alfresco.util.TempFileProvider; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.util.ResourceUtils; /** * Debugs transformers selection and activity.

@@ -55,10 +74,12 @@ import org.apache.commons.logging.LogFactory; */ public class TransformerDebug { - private static final Log logger = LogFactory.getLog(TransformerDebug.class); - private static final TransformerLog info = new TransformerLog(); + private static final String FINISHED_IN = "Finished in "; private static final String NO_TRANSFORMERS = "No transformers"; + private final Log logger; + private final Log info; + private enum Call { AVAILABLE, @@ -80,6 +101,7 @@ public class TransformerDebug private final Deque stack = new ArrayDeque(); private final Deque isTransformableStack = new ArrayDeque(); private boolean debugOutput = true; + private StringBuilder sb; public static Deque getStack() { @@ -103,6 +125,16 @@ public class TransformerDebug thisThreadInfo.debugOutput = debugOutput; return orig; } + + public static StringBuilder getStringBuilder() + { + return threadInfo.get().sb; + } + + public static void setStringBuilder(StringBuilder sb) + { + threadInfo.get().sb = sb; + } } private static class Frame @@ -225,16 +257,29 @@ public class TransformerDebug private final NodeService nodeService; private final MimetypeService mimetypeService; + private final ContentTransformerRegistry transformerRegistry; private final TransformerConfig transformerConfig; + private ContentService contentService; /** * Constructor */ - public TransformerDebug(NodeService nodeService, MimetypeService mimetypeService, TransformerConfig transformerConfig) + public TransformerDebug(NodeService nodeService, MimetypeService mimetypeService, + ContentTransformerRegistry transformerRegistry, TransformerConfig transformerConfig, + Log transformerLog, Log transformerDebugLog) { this.nodeService = nodeService; this.mimetypeService = mimetypeService; + this.transformerRegistry = transformerRegistry; this.transformerConfig = transformerConfig; + + logger = new LogTee(LogFactory.getLog(TransformerDebug.class), transformerDebugLog); + info = new LogTee(LogFactory.getLog(TransformerLog.class), transformerLog); + } + + public void setContentService(ContentService contentService) + { + this.contentService = contentService; } /** @@ -307,7 +352,7 @@ public class TransformerDebug if (callType == Call.TRANSFORM) { // Log the basic info about this transformation - logBasicDetails(frame, sourceSize, transformerName, (ourStack.size() == 1)); + logBasicDetails(frame, sourceSize, options.getUse(), transformerName, (ourStack.size() == 1)); } } @@ -341,7 +386,8 @@ public class TransformerDebug /** * Called once all available transformers have been identified. */ - public void availableTransformers(List transformers, long sourceSize, String calledFrom) + public void availableTransformers(List transformers, long sourceSize, + TransformationOptions options, String calledFrom) { if (isEnabled()) { @@ -363,9 +409,8 @@ public class TransformerDebug frame.setSourceSize(sourceSize); // Log the basic info about this transformation - logBasicDetails(frame, sourceSize, - calledFrom + ((transformers.size() == 0) ? " NO transformers" : ""), - firstLevel); + logBasicDetails(frame, sourceSize, options.getUse(), + calledFrom + ((transformers.size() == 0) ? " NO transformers" : ""), firstLevel); // Report available and unavailable transformers char c = 'a'; @@ -395,8 +440,13 @@ public class TransformerDebug private String gePriority(ContentTransformer transformer, String sourceMimetype, String targetMimetype) { - String priority = '[' + Integer.toString(transformerConfig.getPriority(transformer, sourceMimetype, targetMimetype)) + ']'; - priority = spaces(4-priority.length())+priority; + String priority = + '[' + + (isComponentTransformer(transformer) + ? "---" + : Integer.toString(transformerConfig.getPriority(transformer, sourceMimetype, targetMimetype))) + + ']'; + priority = spaces(5-priority.length())+priority; return priority; } @@ -457,7 +507,7 @@ public class TransformerDebug return longestNameLength; } - private void logBasicDetails(Frame frame, long sourceSize, String message, boolean firstLevel) + private void logBasicDetails(Frame frame, long sourceSize, String use, String message, boolean firstLevel) { // Log the source URL, but there is no point if the parent has logged it if (frame.fromUrl != null && (firstLevel || frame.id != 1)) @@ -469,7 +519,8 @@ public class TransformerDebug String fileName = getFileName(frame.options, firstLevel, sourceSize); log(getMimetypeExt(frame.sourceMimetype)+getMimetypeExt(frame.targetMimetype) + ((fileName != null) ? fileName+' ' : "")+ - ((sourceSize >= 0) ? fileSize(sourceSize)+' ' : "") + message); + ((sourceSize >= 0) ? fileSize(sourceSize)+' ' : "") + + (firstLevel && use != null ? "-- "+use+" -- " : "") + message); } /** @@ -536,7 +587,7 @@ public class TransformerDebug boolean firstLevel = size == 1; if (!suppressFinish && (firstLevel || logger.isTraceEnabled())) { - log("Finished in " + ms + + log(FINISHED_IN + ms + (frame.callType == Call.AVAILABLE ? " Transformer NOT called" : "") + (firstLevel ? "\n" : ""), firstLevel); @@ -566,18 +617,24 @@ public class TransformerDebug debug = firstLevel; level = "INFO"; failureReason = NO_TRANSFORMERS; - if (frame.unavailableTransformers != null) + + // If trace and trace is disabled do nothing + if (debug || info.isTraceEnabled()) { - level = "WARN"; - long smallestMaxSourceSizeKBytes = Long.MAX_VALUE; - for (UnavailableTransformer unavailable: frame.unavailableTransformers) + // Work out size reason that there are no transformers + if (frame.unavailableTransformers != null) { - if (smallestMaxSourceSizeKBytes > unavailable.maxSourceSizeKBytes) + level = "WARN"; + long smallestMaxSourceSizeKBytes = Long.MAX_VALUE; + for (UnavailableTransformer unavailable: frame.unavailableTransformers) { - smallestMaxSourceSizeKBytes = unavailable.maxSourceSizeKBytes; + if (smallestMaxSourceSizeKBytes > unavailable.maxSourceSizeKBytes) + { + smallestMaxSourceSizeKBytes = unavailable.maxSourceSizeKBytes; + } } + failureReason = "No transformers as file is > "+fileSize(smallestMaxSourceSizeKBytes*1024); } - failureReason = "No transformers as file is > "+fileSize(smallestMaxSourceSizeKBytes*1024); } } else if (frame.callType == Call.TRANSFORM) @@ -587,10 +644,17 @@ public class TransformerDebug // Use TRACE logging for all but the first TRANSFORM debug = size == 1 || (size == 2 && ThreadInfo.getStack().peekLast().callType != Call.TRANSFORM); } +// Comment out for the moment +// else if (firstLevel && frame.callType == Call.AVAILABLE) +// { +// level = "INFO"; +// debug = true; +// failureReason = "checking availability"; +// } if (level != null) { - infoLog(getReference(debug), sourceExt, targetExt, level, fileName, sourceSize, transformerName, failureReason, ms, debug); + infoLog(getReference(debug, false), sourceExt, targetExt, level, fileName, sourceSize, transformerName, failureReason, ms, debug); } } } @@ -599,16 +663,23 @@ public class TransformerDebug long sourceSize, String transformerName, String failureReason, String ms, boolean debug) { String message = - " "+reference + + reference + sourceExt + targetExt + (level == null ? "" : level+' ') + (fileName == null ? "" : fileName) + (sourceSize >= 0 ? ' '+fileSize(sourceSize) : "") + - (transformerName == null ? "" : ' '+transformerName) + ' '+ms + - (failureReason == null ? "" : '\n'+failureReason.trim()); - info.log(message, debug); + (transformerName == null ? "" : ' '+transformerName) + + (failureReason == null ? "" : ' '+failureReason.trim()); + if (debug) + { + info.debug(message); + } + else + { + info.trace(message); + } } /** @@ -617,7 +688,7 @@ public class TransformerDebug public boolean isEnabled() { // Don't check ThreadInfo.getDebugOutput() as availableTransformers() may upgrade from trace to debug. - return logger.isDebugEnabled() || info.isDebugEnabled(); + return logger.isDebugEnabled() || info.isDebugEnabled() || ThreadInfo.getStringBuilder() != null; } /** @@ -711,11 +782,26 @@ public class TransformerDebug { if (debug && ThreadInfo.getDebugOutput() && logger.isDebugEnabled()) { - logger.debug(getReference(false)+message, t); + logger.debug(getReference(false, false)+message, t); } else if (logger.isTraceEnabled()) { - logger.trace(getReference(false)+message, t); + logger.trace(getReference(false, false)+message, t); + } + + if (debug) + { + StringBuilder sb = ThreadInfo.getStringBuilder(); + if (sb != null) + { + sb.append(getReference(false, true)); + sb.append(message); + if (t != null) + { + sb.append(t.getMessage()); + } + sb.append('\n'); + } } } @@ -729,23 +815,310 @@ public class TransformerDebug return t; } + /** + * Returns the current StringBuilder (if any) being used to capture debug + * information for the current Thread. + */ + public StringBuilder getStringBuilder() + { + return ThreadInfo.getStringBuilder(); + } + + /** + * Sets the StringBuilder to be used to capture debug information for the + * current Thread. + */ + public void setStringBuilder(StringBuilder sb) + { + ThreadInfo.setStringBuilder(sb); + } + + /** + * Returns a String and /or debug that provides a list of supported transformations for each + * transformer. + * @param transformerName restricts the list to one transformer. Unrestricted if null. + * @param toString indicates that a String value should be returned in addition to any debug. + * @param format42 indicates the old 4.1.4 format should be used which did not order the transformers + * and only included top level transformers. + * @param use to which the transformation will be put (such as "Index", "Preview", null). + */ + public String transformationsByTransformer(String transformerName, boolean toString, boolean format42, String use) + { + // Do not generate this type of debug if already generating other debug to a StringBuilder + // (for example a test transform). + if (getStringBuilder() != null) + { + return null; + } + + Collection transformers = format42 || transformerName != null + ? sortTransformersByName(transformerName) + : transformerRegistry.getTransformers(); + Collection sourceMimetypes = format42 + ? getSourceMimetypes(null) + : mimetypeService.getMimetypes(); + Collection targetMimetypes = format42 + ? sourceMimetypes + : mimetypeService.getMimetypes(); + + TransformationOptions options = new TransformationOptions(); + options.setUse(use); + StringBuilder sb = null; + try + { + if (toString) + { + sb = new StringBuilder(); + setStringBuilder(sb); + } + pushMisc(); + for (ContentTransformer transformer: transformers) + { + try + { + pushMisc(); + int mimetypePairCount = 0; + boolean first = true; + for (String sourceMimetype: sourceMimetypes) + { + for (String targetMimetype: targetMimetypes) + { + if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)) + { + long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes( + sourceMimetype, targetMimetype, options); + activeTransformer(++mimetypePairCount, transformer, + sourceMimetype, targetMimetype, maxSourceSizeKBytes, first); + first = false; + } + } + } + if (first) + { + inactiveTransformer(transformer); + } + } + finally + { + popMisc(); + } + } + } + finally + { + popMisc(); + setStringBuilder(null); + } + stripFinishedLine(sb); + return stripLeadingNumber(sb); + } + + /** + * Returns a String and /or debug that provides a list of supported transformations + * sorted by source and target mimetype extension. + * @param sourceExtension restricts the list to one source extension. Unrestricted if null. + * @param targetExtension restricts the list to one target extension. Unrestricted if null. + * @param toString indicates that a String value should be returned in addition to any debug. + * @param format42 indicates the new 4.2 rather than older 4.1.4 format should be used. + * The 4.1.4 format did not order the transformers or mimetypes and only included top + * level transformers. + * @param onlyNonDeterministic if true only report transformations where there is more than + * one transformer available with the same priority. + * @param use to which the transformation will be put (such as "Index", "Preview", null). + */ + public String transformationsByExtension(String sourceExtension, String targetExtension, boolean toString, + boolean format42, boolean onlyNonDeterministic, String use) + { + // Do not generate this type of debug if already generating other debug to a StringBuilder + // (for example a test transform). + if (getStringBuilder() != null) + { + return null; + } + + Collection transformers = format42 && !onlyNonDeterministic + ? sortTransformersByName(null) + : transformerRegistry.getTransformers(); + Collection sourceMimetypes = format42 || sourceExtension != null + ? getSourceMimetypes(sourceExtension) + : mimetypeService.getMimetypes(); + Collection targetMimetypes = format42 || targetExtension != null + ? getTargetMimetypes(sourceExtension, targetExtension, sourceMimetypes) + : mimetypeService.getMimetypes(); + + TransformationOptions options = new TransformationOptions(); + options.setUse(use); + StringBuilder sb = null; + try + { + if (toString) + { + sb = new StringBuilder(); + setStringBuilder(sb); + } + pushMisc(); + for (String sourceMimetype: sourceMimetypes) + { + for (String targetMimetype: targetMimetypes) + { + // Find available transformers + List availableTransformer = new ArrayList(); + for (ContentTransformer transformer: transformers) + { + if (transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)) + { + availableTransformer.add(transformer); + } + } + + // Sort by priority + final String currSourceMimetype = sourceExtension; + final String currTargetMimetype = targetExtension; + Collections.sort(availableTransformer, new Comparator() + { + @Override + public int compare(ContentTransformer transformer1, ContentTransformer transformer2) + { + return transformerConfig.getPriority(transformer1, currSourceMimetype, currTargetMimetype) - + transformerConfig.getPriority(transformer2, currSourceMimetype, currTargetMimetype); + } + }); + + // Do we need to produce any output? + int size = availableTransformer.size(); + int priority = size >= 2 + ? transformerConfig.getPriority(availableTransformer.get(0), sourceMimetype, targetMimetype) + : -1; + if (!onlyNonDeterministic || (size >= 2 && priority == + transformerConfig.getPriority(availableTransformer.get(1), sourceMimetype, targetMimetype))) + { + // Log the transformers + try + { + pushMisc(); + int transformerCount = 0; + for (ContentTransformer transformer: availableTransformer) + { + if (!onlyNonDeterministic || transformerCount < 2 || + priority == transformerConfig.getPriority(transformer, sourceMimetype, targetMimetype)) + { + long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes( + sourceMimetype, targetMimetype, options); + activeTransformer(sourceMimetype, targetMimetype, + transformerCount, transformer, maxSourceSizeKBytes, transformerCount++ == 0); + } + } + } + finally + { + popMisc(); + } + } + } + } + } + finally + { + popMisc(); + setStringBuilder(null); + } + stripFinishedLine(sb); + return stripLeadingNumber(sb); + } + + /** + * Removes the final "Finished in..." message from a StringBuilder + * @param sb + */ + private void stripFinishedLine(StringBuilder sb) + { + if (sb != null) + { + int i = sb.lastIndexOf(FINISHED_IN); + if (i != -1) + { + sb.setLength(i); + i = sb.lastIndexOf("\n", i); + sb.setLength(i != -1 ? i : 0); + } + } + } + + /** + * Strips the leading number in a reference + */ + private String stripLeadingNumber(StringBuilder sb) + { + return sb == null + ? null + : Pattern.compile("^\\d+\\.", Pattern.MULTILINE).matcher(sb).replaceAll(""); + } + + /** + * Returns a collection of mimetypes ordered by extension, but unlike the version in MimetypeService + * throws an exception if the sourceExtension is supplied but does not match a mimetype. + * @param sourceExtension to restrict the collection to one entry + * @throws IllegalArgumentException if there is no match. The message indicates this. + */ + public Collection getSourceMimetypes(String sourceExtension) + { + Collection sourceMimetypes = mimetypeService.getMimetypes(sourceExtension); + if (sourceMimetypes.isEmpty()) + { + throw new IllegalArgumentException("Unknown source extension "+sourceExtension); + } + return sourceMimetypes; + } + + /** + * Identical to getSourceMimetypes for the target, but avoids doing the look up if the sourceExtension + * is the same as the tragetExtension, so will have the same result. + * @param sourceExtension used to restrict the sourceMimetypes + * @param targetExtension to restrict the collection to one entry + * @param sourceMimetypes that match the sourceExtension + * @throws IllegalArgumentException if there is no match. The message indicates this. + */ + public Collection getTargetMimetypes(String sourceExtension, String targetExtension, + Collection sourceMimetypes) + { + Collection targetMimetypes = + (targetExtension == null && sourceExtension == null) || + (targetExtension != null && targetExtension.equals(sourceExtension)) + ? sourceMimetypes + : mimetypeService.getMimetypes(targetExtension); + if (targetMimetypes.isEmpty()) + { + throw new IllegalArgumentException("Unknown target extension "+targetExtension); + } + return targetMimetypes; + } + /** * Returns a N.N.N style reference to the transformation. - * @param firstLevelOnly indicates if only the top level should be included. + * @param firstLevelOnly indicates if only the top level should be included and no extra padding. + * @param overrideFirstLevel if the first level id should just be set to 1 (used in test methods) * @return a padded (fixed length) reference. */ - private String getReference(boolean firstLevelOnly) + private String getReference(boolean firstLevelOnly, boolean overrideFirstLevel) { StringBuilder sb = new StringBuilder(""); Frame frame = null; Iterator iterator = ThreadInfo.getStack().descendingIterator(); int lengthOfFirstId = 0; + boolean firstLevel = true; while (iterator.hasNext()) { frame = iterator.next(); - if (sb.length() == 0) + if (firstLevel) { - sb.append(frame.getId()); + if (!overrideFirstLevel) + { + sb.append(frame.getId()); + } + else + { + sb.append("1"); + } lengthOfFirstId = sb.length(); if (firstLevelOnly) { @@ -754,36 +1127,69 @@ public class TransformerDebug } else { - sb.append('.'); + if (sb.length() != 0) + { + sb.append('.'); + } sb.append(frame.getId()); } + firstLevel = false; } if (frame != null) { + if (firstLevelOnly) + { + sb.append(' '); + } + else + { sb.append(spaces(13-sb.length()+lengthOfFirstId)); // Try to pad to level 7 + } } return sb.toString(); } - public String getName(ContentTransformer transformer) + private String getName(ContentTransformer transformer) { - return - (transformer instanceof AbstractContentTransformer2 - ? ((AbstractContentTransformerLimits)transformer).getBeanName() - : transformer.getClass().getSimpleName())+ - - (transformer instanceof ComplexContentTransformer - ? "<>" - : transformer instanceof FailoverContentTransformer - ? "<>" - : transformer instanceof ProxyContentTransformer - ? (((ProxyContentTransformer)transformer).getWorker() instanceof RuntimeExecutableContentTransformerWorker) - ? "<>" - : "<>" - : ""); + String name = + transformer instanceof ContentTransformerHelper + ? ContentTransformerHelper.getSimpleName(transformer) + : transformer.getClass().getSimpleName(); + + String type = + (transformer instanceof ComplexContentTransformer + ? "Complex" + : transformer instanceof FailoverContentTransformer + ? "Failover" + : transformer instanceof ProxyContentTransformer + ? (((ProxyContentTransformer)transformer).getWorker() instanceof RuntimeExecutableContentTransformerWorker) + ? "Runtime" + : "Proxy" + : ""); + + boolean componentTransformer = isComponentTransformer(transformer); + + StringBuilder sb = new StringBuilder(name); + if (componentTransformer || type.length() > 0) + { + sb.append("<<"); + sb.append(type); + if (componentTransformer) + { + sb.append("Component"); + } + sb.append(">>"); + } + + return sb.toString(); } + private boolean isComponentTransformer(ContentTransformer transformer) + { + return !transformerRegistry.getTransformers().contains(transformer); + } + public String getFileName(TransformationOptions options, boolean firstLevel, long sourceSize) { String fileName = null; @@ -886,31 +1292,153 @@ public class TransformerDebug return sb.toString(); } -} - -class TransformerLog -{ - private static final Log logger = LogFactory.getLog(TransformerLog.class); - - void log(String message, boolean debug) + + /** + * Returns a sorted list of all transformers sorted by name. + * @param transformerName to restrict the collection to one entry + * @return a new Collection of sorted transformers + * @throws IllegalArgumentException if transformerName is not found. + */ + public Collection sortTransformersByName(String transformerName) { - if (debug) + Collection transformers = (transformerName != null) + ? Collections.singleton(transformerRegistry.getTransformer(transformerName)) + : transformerRegistry.getAllTransformers(); + + SortedMap map = new TreeMap(); + for (ContentTransformer transformer: transformers) { - logger.debug(message); + String name = transformer.getName(); + map.put(name, transformer); } - else + Collection sorted = map.values(); + return sorted; + } + + public String testTransform(String sourceExtension, String targetExtension, String use) + { + return new TestTransform() { - logger.trace(message); + protected void transform(ContentReader reader, ContentWriter writer, TransformationOptions options) + { + contentService.transform(reader, writer, options); + } + }.run(sourceExtension, targetExtension, use); + } + + public String testTransform(final String transformerName, String sourceExtension, + String targetExtension, String use) + { + final ContentTransformer transformer = transformerRegistry.getTransformer(transformerName); + return new TestTransform() + { + protected String isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + return transformer.isTransformable(sourceMimetype, sourceSize, targetMimetype, options) + ? null + : transformerName+" does not support this transformation."; + } + + protected void transform(ContentReader reader, ContentWriter writer, TransformationOptions options) + { + transformer.transform(reader, writer, options); + } + }.run(sourceExtension, targetExtension, use); + } + + private abstract class TestTransform + { + String run(String sourceExtension, String targetExtension, String use) + { + String debug; + + String sourceMimetype = getMimetype(sourceExtension, true); + String targetMimetype = getMimetype(targetExtension, false); + File sourceFile = loadQuickTestFile(sourceExtension); + if (sourceFile == null) + { + throw new IllegalArgumentException("There is no test file with a "+sourceExtension+" extension."); + } + + ContentReader reader = new FileContentReader(sourceFile); + reader.setMimetype(sourceMimetype); + File tempFile = TempFileProvider.createTempFile( + "TestTransform_" + sourceExtension + "_", "." + targetExtension); + ContentWriter writer = new FileContentWriter(tempFile); + writer.setMimetype(targetMimetype); + + long sourceSize = reader.getSize(); + TransformationOptions options = new TransformationOptions(); + options.setUse(use); + + debug = isTransformable(sourceMimetype, sourceSize, targetMimetype, options); + if (debug == null) + { + StringBuilder sb = new StringBuilder(); + try + { + setStringBuilder(sb); + transform(reader, writer, options); + } + catch (AlfrescoRuntimeException e) + { + sb.append(e.getMessage()); + } + finally + { + setStringBuilder(null); + } + debug = sb.toString(); + } + return debug; + } + + private String getMimetype(String extension, boolean isSource) + { + String mimetype = null; + if (extension != null) + { + Iterator iterator = mimetypeService.getMimetypes(extension).iterator(); + if (iterator.hasNext()) + { + mimetype = iterator.next(); + } + } + if (mimetype == null) + { + throw new IllegalArgumentException("Unknown "+(isSource ? "source" : "target")+" extension: "+extension); + } + return mimetype; + } + + protected String isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + return null; + } + + protected abstract void transform(ContentReader reader, ContentWriter writer, TransformationOptions options); + + /** + * Load one of the "The quick brown fox" files from the classpath. + * @param extension required, eg txt for the file quick.txt + * @return Returns a test resource loaded from the classpath or null if + * no resource could be found. + */ + private File loadQuickTestFile(String extension) + { + try + { + URL url = this.getClass().getClassLoader().getResource("quick/quick." + extension); + if (url == null) + { + return null; + } + return ResourceUtils.getFile(url); + } + catch (IOException e) + { + return null; + } } } - - public boolean isDebugEnabled() - { - return logger.isDebugEnabled(); - } - - public boolean isTraceEnabled() - { - return logger.isTraceEnabled(); - } } diff --git a/source/java/org/alfresco/repo/content/transform/TransformerDebugLog.java b/source/java/org/alfresco/repo/content/transform/TransformerDebugLog.java new file mode 100644 index 0000000000..ae5818ff72 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerDebugLog.java @@ -0,0 +1,140 @@ +/* + * Copyright (C) 2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import java.util.Date; +import java.util.Deque; +import java.util.Iterator; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.apache.commons.logging.Log; + +/** + * Implementation of a {@link Log} that logs messages to a structure accessible via + * {@link TransformerConfigMBean#getTransformationDebug(int)}.

+ * + * @author Alan Davis + */ +public class TransformerDebugLog extends TransformerLogger +{ + private static Pattern END_OF_REQUEST_ID_PATTERN = Pattern.compile("[^0-9]"); + + /** + * {@inheritDoc}

+ * Returns 20 as this is debug and we must currently walk the whole structure each time + * an new request id is added. + */ + @Override + protected int getUpperMaxEntries() + { + return 100; + } + + /** + * Overridden to specify the property name that specifies the maximum number of entries. + */ + @Override + protected String getPropertyName() + { + return TransformerConfig.DEBUG_ENTRIES; + } + + @Override + protected void addOrModify(Deque entries, Object message) + { + String msg = (String)message; + String requestId = getRequestId(msg); + if (requestId != null) + { + Iterator iterator = entries.descendingIterator(); + while (iterator.hasNext()) + { + DebugEntry entry = iterator.next(); + if (requestId.equals(entry.requestId)) + { + entry.addLine(msg); + return; + } + } + entries.add(new DebugEntry(requestId, msg)); + } + } + + /** + * Returns the request id from the debug message. This is the integer at + * the start of the message. + */ + private String getRequestId(String message) + { + String requestId = null; + if (message != null) + { + Matcher matcher = END_OF_REQUEST_ID_PATTERN.matcher(message); + if (matcher.find()) + { + int i = matcher.start(); + requestId = message.substring(0, i); + } + } + return requestId; + } +} + +// Collects multiple lines of debug for the same transformer request. +class DebugEntry +{ + final String requestId; + private final StringBuilder sb = new StringBuilder(); + boolean complete = false; + + DebugEntry(String requestId, String message) + { + this.requestId = requestId; + sb.append(requestId); + sb.append(" "); + sb.append(TransformerLogger.DATE_FORMAT.format(new Date())); + + addLine(message); + } + + void addLine(String message) + { + sb.append('\n'); + sb.append(message); + complete = message.contains("Finished in"); + } + + public String toString() + { + String string; + if (complete) + { + string = sb.toString(); + } + else + { + int length = sb.length(); + sb.append("\n <<-- INCOMPLETE -->>"); + string = sb.toString(); + sb.setLength(length); + } + return string; + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerDebugLogTest.java b/source/java/org/alfresco/repo/content/transform/TransformerDebugLogTest.java new file mode 100644 index 0000000000..669ca49c3a --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerDebugLogTest.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +import java.util.Date; +import java.util.Deque; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerDebugLog. + * + * @author Alan Davis + */ +public class TransformerDebugLogTest +{ + @Mock + private TransformerDebug transformerDebug; + + @Mock + private TransformerConfig transformerConfig; + + private TransformerDebugLog log; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + log = new TransformerDebugLog(); + log.setTransformerDebug(transformerDebug); + log.setTransformerConfig(transformerConfig); + } + + private void assertEntriesEquals(String[] expected, String[] actual) + { + for (int i=actual.length-1; i >= 0; i--) + { + // Strip the fist line with the date + int beginIndex = actual[i].indexOf('\n'); + actual[i] = actual[i].substring(beginIndex+1); + } + assertArrayEquals(expected, actual); + } + + + @Test + public void noEntriesDisabledTest() + { + assertArrayEquals(new String[] {"No entries are available. transformer.debug.entries must be set to a number between 1 and 100"}, log.getEntries(10)); + } + + @Test + public void oneTest() + { + when(transformerConfig.getProperty("transformer.debug.entries")).thenReturn("3"); + log.debug("56 one"); + log.debug("56 Finished in 23 ms"); + + assertEntriesEquals(new String[] {"56 one\n56 Finished in 23 ms"}, log.getEntries(10)); + } + + @Test + public void incompleteTest() + { + when(transformerConfig.getProperty("transformer.debug.entries")).thenReturn("3"); + log.debug("56 one"); + + assertEntriesEquals(new String[] {"56 one\n <<-- INCOMPLETE -->>"}, log.getEntries(10)); + } + + @Test + public void nullEntryTest() + { + when(transformerConfig.getProperty("transformer.debug.entries")).thenReturn("3"); + log.debug(null); + + assertEntriesEquals(new String[] {}, log.getEntries(10)); + } + + @Test + public void zeroLengthIdEntryTest() + { + when(transformerConfig.getProperty("transformer.debug.entries")).thenReturn("3"); + log.debug("one"); // as the 1st char is not a digit the id is taken to be "" + + assertEntriesEquals(new String[] {"one\n <<-- INCOMPLETE -->>"}, log.getEntries(10)); + } + + @Test + public void twoAndAHalfTest() + { + when(transformerConfig.getProperty("transformer.debug.entries")).thenReturn("3"); + log.debug("56 one"); + log.debug("56 Finished in 23 ms"); + log.debug("57 one"); + log.debug("57 two"); + log.debug("57 Finished in 123 ms"); + log.debug("58 one"); + log.debug("58 two"); + + assertEntriesEquals(new String[] + { + "58 one\n58 two\n <<-- INCOMPLETE -->>", + "57 one\n57 two\n57 Finished in 123 ms", + "56 one\n56 Finished in 23 ms" + }, log.getEntries(10)); + } + + @Test + public void mixupOrderTest() + { + // The sequence will still be based on the first entry of each request, + // but subsequent debug may be out of order. + when(transformerConfig.getProperty("transformer.debug.entries")).thenReturn("3"); + log.debug("56 one"); + log.debug("57 one"); + log.debug("56 Finished in 23 ms"); + log.debug("57 two"); + log.debug("58 one"); + log.debug("57 Finished in 123 ms"); + log.debug("58 two"); + + assertEntriesEquals(new String[] + { + "58 one\n58 two\n <<-- INCOMPLETE -->>", + "57 one\n57 two\n57 Finished in 123 ms", + "56 one\n56 Finished in 23 ms" + }, log.getEntries(10)); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerDebugTest.java b/source/java/org/alfresco/repo/content/transform/TransformerDebugTest.java new file mode 100644 index 0000000000..1f5573b8d1 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerDebugTest.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.junit.Assert.*; + +import org.junit.BeforeClass; +import org.junit.Test; + +/** + * Test class for TransformerDebug. + * + * @author Alan Davis + */ +public class TransformerDebugTest +{ + + @BeforeClass + public static void setUpBeforeClass() throws Exception + { + } + + @Test + public void test() + { + fail("Not yet implemented"); + } + +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerLog.java b/source/java/org/alfresco/repo/content/transform/TransformerLog.java new file mode 100644 index 0000000000..f17b7166d6 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerLog.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import java.util.Date; +import java.util.Deque; + +import org.apache.commons.logging.Log; + +/** + * Implementation of a {@link Log} that logs messages to a structure accessible via + * {@link TransformerConfigMBean#getTransformationLog(int)}.

+ * + * @author Alan Davis + */ +public class TransformerLog extends TransformerLogger +{ + /** + * {@inheritDoc}

+ * Returns 100 as this is currently held in memory. + */ + @Override + protected int getUpperMaxEntries() + { + return 1000; + } + + /** + * Overridden to specify the property name that specifies the maximum number of entries. + */ + @Override + protected String getPropertyName() + { + return TransformerConfig.LOG_ENTRIES; + } + + @Override + protected void addOrModify(Deque entries, Object message) + { + StringBuilder sb = new StringBuilder(); + sb.append(TransformerLogger.DATE_FORMAT.format(new Date())); + sb.append(' '); + sb.append(message); + + entries.add(sb.toString()); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerLogTest.java b/source/java/org/alfresco/repo/content/transform/TransformerLogTest.java new file mode 100644 index 0000000000..3dc3c8f125 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerLogTest.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.junit.Assert.assertArrayEquals; +import static org.mockito.Mockito.when; + +import java.util.Date; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerLog. + * + * @author Alan Davis + */ +public class TransformerLogTest +{ + @Mock + private TransformerDebug transformerDebug; + + @Mock + private TransformerConfig transformerConfig; + + private TransformerLog log; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + log = new TransformerLog(); + log.setTransformerDebug(transformerDebug); + log.setTransformerConfig(transformerConfig); + } + + private void assertEntriesEquals(String[] expected, String[] actual) + { + // Strip the date prefix + int beginIndex = (TransformerLogger.DATE_FORMAT.format(new Date())+' ').length(); + for (int i=actual.length-1; i >= 0; i--) + { + actual[i] = actual[i].substring(beginIndex); + } + assertArrayEquals(expected, actual); + } + + @Test + public void noEntriesDisabledTest() + { + assertArrayEquals(new String[] {"No entries are available. transformer.log.entries must be set to a number between 1 and 1000"}, log.getEntries(10)); + } + + @Test + public void oneEntryTest() + { + when(transformerConfig.getProperty("transformer.log.entries")).thenReturn("3"); + log.debug("one"); + + assertEntriesEquals(new String[] {"one"}, log.getEntries(10)); + } + + @Test + public void fiveEntryTest() + { + when(transformerConfig.getProperty("transformer.log.entries")).thenReturn("3"); + + log.debug("one"); + log.debug("two"); + log.debug("three"); + log.debug("four"); + log.debug("five"); + + assertEntriesEquals(new String[] {"five", "four", "three"}, log.getEntries(10)); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerLogger.java b/source/java/org/alfresco/repo/content/transform/TransformerLogger.java new file mode 100644 index 0000000000..ec7623c640 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerLogger.java @@ -0,0 +1,218 @@ +/* + * Copyright (C) 2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import java.text.SimpleDateFormat; +import java.util.Deque; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.Properties; + +import org.alfresco.util.LogAdapter; +import org.apache.commons.logging.Log; + +/** + * Implementation of {@link Log} that logs messages to an internal structure which + * only keeps a predefined number of entries. + * + * Only supports debug level logging. + * + * @author Alan Davis + */ +abstract class TransformerLogger extends LogAdapter +{ + static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("d MMM yyyy HH:mm:ss"); + + private TransformerDebug transformerDebug; + private TransformerConfig transformerConfig; + + private int maxEntries = -1; + private Deque entries = new LinkedList(); + + TransformerLogger() + { + super(null); + } + + public void setTransformerDebug(TransformerDebug transformerDebug) + { + this.transformerDebug = transformerDebug; + } + + /** + * @param transformerConfig used to access the property. + */ + public void setTransformerConfig(TransformerConfig transformerConfig) + { + this.transformerConfig = transformerConfig; + } + + /** + * Returns an int from a property. + * @param propertyName + */ + private int getProperty(TransformerConfig transformerConfig, String propertyName, int min, int max) + { + int i = 0; + String value = transformerConfig.getProperty(propertyName); + if (value != null) + { + try + { + i = Integer.parseInt(value); + if (i < 0) + { + i = 0; + } + } + catch (NumberFormatException e) + { + i = 0; + } + } + i = Math.min(Math.max(i, min), max); + return i; + } + + private int getMaxEntries() + { + if (maxEntries < 0) + { + maxEntries = getProperty(transformerConfig, getPropertyName(), 0, getUpperMaxEntries()); + } + return maxEntries; + } + + /** + * Enabled if number of transformer debug entries is greater than 0 + * AND there is not a test function generating output. + */ + @Override + public boolean isDebugEnabled() + { + return getMaxEntries() > 0 && transformerDebug.getStringBuilder() == null; + } + + /** + * @param message to be appended to the log + * @param throwable ignored + */ + @Override + public void debug(Object message, Throwable throwable) + { + if (isDebugEnabled()) + { + synchronized(entries) + { + addOrModify(entries, message); + for (int size = entries.size(); size > maxEntries; size--) + { + remove(entries); + } + } + } + } + + /** + * Overridden in sub classes to add a new entry or modify and existing one + * @param message + */ + protected abstract void addOrModify(Deque entries, Object message); + + /** + * Removes an entry. By default removes the first (oldest) entry by may be overridden. + */ + protected void remove(Deque entries) + { + entries.removeFirst(); + } + + /** + * Returns the log entries. + * @param n the maximum number of entries to return. All if n is smaller or equal to zero. + */ + public String[] getEntries(int n) + { + if (getMaxEntries() > 0) + { + n = n <= 0 ? Integer.MAX_VALUE : n; + synchronized(entries) + { + n = Math.min(Math.max(Math.min(maxEntries, n), 0), entries.size()); + String[] array = new String[n]; + Iterator iterator = entries.descendingIterator(); + for (int i = 0; i < n; i++) + { + array[i] = iterator.next().toString(); + } + return array; + } + } + else + { + return new String[] { "No entries are available. "+ + getPropertyName()+" must be set to a number between 1 and "+ + getUpperMaxEntries()}; + } + } + + /** + * The transformer property and value used to set this logger. This is commented + * out if the property has not been overridden (default is 0). + * @param defaultProperties the transformer.properties file values that may be overridden. + * @return the transformer property and value. + */ + public String getPropertyAndValue(Properties defaultProperties) + { + getMaxEntries(); + String value = Integer.toString(maxEntries); + + String propertyName = getPropertyName(); + String defaultValue = defaultProperties == null ? null : defaultProperties.getProperty(propertyName); + defaultValue = defaultValue == null ? "0" : defaultValue; + + StringBuilder sb = new StringBuilder(); + boolean isDefaultValue = value.equals(defaultValue); + if (isDefaultValue) + { + sb.append("# "); + } + sb.append(propertyName); + sb.append('='); + sb.append(value); + if (!isDefaultValue) + { + sb.append(" # default="); + sb.append(defaultValue); + } + + return sb.toString(); + } + + /** + * Overridden to specify the maximum value the maxEntries property may set. + * Generally quite a small number as values are stored in memory. + */ + protected abstract int getUpperMaxEntries(); + + /** + * Overridden to specify the property name that specifies the maximum number of entries. + */ + protected abstract String getPropertyName(); +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerLoggerTest.java b/source/java/org/alfresco/repo/content/transform/TransformerLoggerTest.java new file mode 100644 index 0000000000..7e5f7c88ed --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerLoggerTest.java @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; + +import java.util.Deque; +import java.util.Properties; +import java.util.concurrent.atomic.AtomicInteger; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerLogger. + * + * @author Alan Davis + */ +public class TransformerLoggerTest +{ + @Mock + private TransformerDebug transformerDebug; + + @Mock + private TransformerConfig transformerConfig; + + private TransformerLogger log; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + log = newLogger(new AtomicInteger(1), transformerDebug, transformerConfig); + } + + private TransformerLogger newLogger(final AtomicInteger numberOfMessagesToAdd, + TransformerDebug transformerDebug, TransformerConfig transformerConfig) + { + TransformerLogger log = new TransformerLogger() + { + @Override + protected void addOrModify(Deque entries, Object message) + { + int i = numberOfMessagesToAdd.get(); + if (i > 0) + { + for (; i > 0; i--) + { + entries.add((String)message); + } + } + else if (i < 0) + { + for (; i < 0; i++) + { + entries.removeLast(); + } + } + else + { + entries.removeLast(); + entries.add((String)message); + } + } + + @Override + protected int getUpperMaxEntries() + { + return 176; + } + + @Override + protected String getPropertyName() + { + return "property.name"; + } + }; + log.setTransformerDebug(transformerDebug); + log.setTransformerConfig(transformerConfig); + return log; + } + + @Test + public void propertyExistsTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("5"); + assertEquals("property.name=5 # default=0", log.getPropertyAndValue(null)); + } + + @Test + public void propertyExistsAndDefaultSetTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("5"); + Properties properties = new Properties(); + properties.setProperty("property.name", "23"); + assertEquals("property.name=5 # default=23", log.getPropertyAndValue(properties)); + } + + @Test + public void propertyDoesNotExistTest() + { + assertEquals("# property.name=0", log.getPropertyAndValue(null)); + } + + @Test + public void propertyNegativeTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("-2"); + assertEquals("# property.name=0", log.getPropertyAndValue(null)); + } + + @Test + public void propertyGreaterThanMax() + { + when(transformerConfig.getProperty("property.name")).thenReturn("1000"); + assertEquals("property.name=176 # default=0", log.getPropertyAndValue(null)); + } + + @Test + public void propertyBadTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("abc"); + assertEquals("# property.name=0", log.getPropertyAndValue(null)); + } + + @Test + public void isDebugEnabled0EntriesTest() + { + // when(transformerConfig.getProperty("property.name")).thenReturn("0"); - default to this + assertFalse(log.isDebugEnabled()); + } + + @Test + public void isDebugEnabled5EntriesTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("5"); + assertTrue(log.isDebugEnabled()); + } + + @Test + public void isDebugEnabledHasStringBuilderTest() + { + when(transformerDebug.getStringBuilder()).thenReturn(new StringBuilder()); + when(transformerConfig.getProperty("property.name")).thenReturn("5"); + assertFalse(log.isDebugEnabled()); + } + + @Test + public void noEntriesTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("3"); + assertArrayEquals(new String[] {}, log.getEntries(10)); + } + + @Test + public void noEntriesDisabledTest() + { + assertArrayEquals(new String[] {"No entries are available. property.name must be set to a number between 1 and 176"}, log.getEntries(10)); + } + + @Test + public void oneEntryTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("3"); + log.debug("one"); + + assertArrayEquals(new String[] {"one"}, log.getEntries(10)); + } + + @Test + // newest entry first + public void fiveEntryTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("3"); + + log.debug("one"); + log.debug("two"); + log.debug("three"); + log.debug("four"); + log.debug("five"); + + assertArrayEquals(new String[] {"five", "four", "three"}, log.getEntries(10)); + } + + @Test + // <= 0 indicates return all + public void limit0Test() + { + when(transformerConfig.getProperty("property.name")).thenReturn("3"); + + log.debug("one"); + log.debug("two"); + log.debug("three"); + + assertArrayEquals(new String[] {"three", "two", "one"}, log.getEntries(0)); + } + + + @Test + // < 0 indicates return all - most current first + public void limitAllTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("3"); + + log.debug("one"); + log.debug("two"); + log.debug("three"); + + assertArrayEquals(new String[] {"three", "two", "one"}, log.getEntries(-1)); + } + + @Test + // Returns latest + public void limit1Test() + { + when(transformerConfig.getProperty("property.name")).thenReturn("3"); + + log.debug("one"); + log.debug("two"); + log.debug("three"); + + assertArrayEquals(new String[] {"three"}, log.getEntries(1)); + } + + @Test + public void limitMoreThanEntriesTest() + { + when(transformerConfig.getProperty("property.name")).thenReturn("3"); + + log.debug("one"); + log.debug("two"); + log.debug("three"); + + assertArrayEquals(new String[] {"three", "two", "one"}, log.getEntries(5)); + } + + @Test + // Adds two entries on each debug call, but logger must still limit size! + public void addTwoEntriesTest() + { + AtomicInteger numberOfMessagesToAdd = new AtomicInteger(2); + log = newLogger(numberOfMessagesToAdd, transformerDebug, transformerConfig); + when(transformerConfig.getProperty("property.name")).thenReturn("5"); + + log.debug("one"); + assertArrayEquals(new String[] {"one", "one"}, log.getEntries(10)); + + log.debug("two"); + assertArrayEquals(new String[] {"two", "two", "one", "one"}, log.getEntries(10)); + + log.debug("three"); + assertArrayEquals(new String[] {"three", "three", "two", "two", "one"}, log.getEntries(10)); + } + + @Test + public void addRemoveModifyTest() + { + // Same as addTwoEntriesTest but without hitting the buffer limit + AtomicInteger numberOfMessagesToAdd = new AtomicInteger(2); + log = newLogger(numberOfMessagesToAdd, transformerDebug, transformerConfig); + when(transformerConfig.getProperty("property.name")).thenReturn("10"); + log.debug("one"); + log.debug("two"); + log.debug("three"); + assertArrayEquals(new String[] {"three", "three", "two", "two", "one", "one"}, log.getEntries(10)); + + // Remove both threes and a two + numberOfMessagesToAdd.set(-3); + log.debug("four"); + assertArrayEquals(new String[] {"two", "one", "one"}, log.getEntries(10)); + + // Add a five + numberOfMessagesToAdd.set(1); + log.debug("five"); + assertArrayEquals(new String[] {"five", "two", "one", "one"}, log.getEntries(10)); + + // Replace the five + numberOfMessagesToAdd.set(0); + log.debug("six"); + assertArrayEquals(new String[] {"six", "two", "one", "one"}, log.getEntries(10)); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerProperties.java b/source/java/org/alfresco/repo/content/transform/TransformerProperties.java new file mode 100644 index 0000000000..4a49ad1ce2 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerProperties.java @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Collection; +import java.util.HashSet; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Provides access to transformer properties which come from the Transformer sub system AND + * the those that start with "content.transformer." in the parent context.

+ * + * By default a subsystem only provides properties defined within itself and only those + * properties may be overridden by alfresco.global.properties. New properties may not be added. + * As this class allows this to happen for the Transformers subsystem. + * + * @author Alan Davis + */ +public class TransformerProperties +{ + private static final String TRANSFORMERS_PROPERTIES = "alfresco/subsystems/Transformers/default/transformers.properties"; + + private static Log logger = LogFactory.getLog(TransformerProperties.class); + + private final ChildApplicationContextFactory subsystem; + + private final Properties globalProperties; + + TransformerProperties(ChildApplicationContextFactory subsystem, Properties globalProperties) + { + this.subsystem = subsystem; + this.globalProperties = globalProperties; + } + + public String getProperty(String name) + { + String value = subsystem.getProperty(name); + if (value == null) + { + value = globalProperties.getProperty(name); + } + return value; + } + + /** + * Returns the default properties from the transformers.properties file. These may be overridden by customers in + * other property files and JMX. + */ + public Properties getDefaultProperties() + { + Properties defaultProperties = new Properties(); + InputStream propertiesStream = getClass().getClassLoader().getResourceAsStream(TRANSFORMERS_PROPERTIES); + if (propertiesStream != null) + { + try + { + defaultProperties.load(propertiesStream); + } + catch (IOException e) + { + logger.error("Could not read "+TRANSFORMERS_PROPERTIES+" so all properties will appear to be overridden by the customer", e); + } + } + else + { + logger.error("Could not find "+TRANSFORMERS_PROPERTIES+" so all properties will appear to be overridden by the customer"); + } + return defaultProperties; + } + + public Set getPropertyNames() + { + Set propertyNames = new HashSet(subsystem.getPropertyNames()); + for (String name: globalProperties.stringPropertyNames()) + { + if (name.startsWith(TransformerConfig.PREFIX) && !propertyNames.contains(name)) + { + propertyNames.add(name); + } + } + + return propertyNames; + } + + public void setProperties(Map map) + { + subsystem.setProperties(map); + } + + public void removeProperties(Collection propertyNames) + { + subsystem.removeProperties(propertyNames); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerPropertyGetter.java b/source/java/org/alfresco/repo/content/transform/TransformerPropertyGetter.java new file mode 100644 index 0000000000..70bc1689fe --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerPropertyGetter.java @@ -0,0 +1,261 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; + +import org.alfresco.service.cmr.repository.MimetypeService; + + +/** + * Provides access to a String representation of all transformer properties and values + * where these are sorted into groups. + * + * @author Alan Davis + */ +public class TransformerPropertyGetter +{ + private final String string; + + public TransformerPropertyGetter(boolean changesOnly, TransformerProperties transformerProperties, + MimetypeService mimetypeService, ContentTransformerRegistry transformerRegistry, + TransformerLog transformerLog, TransformerDebugLog transformerDebugLog) + { + List availableTransformers = transformerRegistry.getTransformers(); + StringBuilder sb = new StringBuilder(); + + // Log entries + appendLoggerSetting(sb, changesOnly, transformerLog, transformerDebugLog, transformerProperties); + + // Default transformer + Set alreadySpecified = new HashSet(); + appendConfiguredTransformerSettings(sb, changesOnly, transformerProperties, mimetypeService, availableTransformers, + transformerRegistry, true, alreadySpecified, + "# Default transformer settings\n" + + "# ============================\n"); + + // Other transformers with configuration properties + appendConfiguredTransformerSettings(sb, changesOnly, transformerProperties, mimetypeService, availableTransformers, + transformerRegistry, false, alreadySpecified, + "# Transformers with configuration settings\n" + + "# ========================================\n" + + "# Commented out settings are hard coded values for information purposes\n"); + + // Other transformers without configuration properties + if (!changesOnly) + { + appendUnconfiguredTransformerSettings(sb, mimetypeService, alreadySpecified, + availableTransformers, transformerRegistry); + } + + string = sb.toString(); + } + + private void appendLoggerSetting(StringBuilder sb, boolean changesOnly, TransformerLog transformerLog, + TransformerDebugLog transformerDebugLog, TransformerProperties transformerProperties) + { + Properties defaultProperties = transformerProperties.getDefaultProperties(); + String logEntries = transformerLog.getPropertyAndValue(defaultProperties); + String debugEntries = transformerDebugLog.getPropertyAndValue(defaultProperties); + boolean logEntriesChanged = !logEntries.startsWith("#"); + boolean debugEntriesChanged = !debugEntries.startsWith("#"); + + if (!changesOnly || logEntriesChanged || debugEntriesChanged) + { + sb.append("# LOG and DEBUG history sizes\n"); + sb.append("# ===========================\n"); + sb.append("# Use small values as these logs are held in memory. 0 to disable.\n"); + if (!changesOnly || logEntriesChanged) + { + sb.append(logEntries); + sb.append("\n"); + } + if (!changesOnly || debugEntriesChanged) + { + sb.append(debugEntries); + sb.append("\n"); + } + } + } + + private void appendConfiguredTransformerSettings(final StringBuilder sb, final boolean changesOnly, + TransformerProperties transformerProperties, MimetypeService mimetypeService, + final List availableTransformers, + final ContentTransformerRegistry transformerRegistry, + final boolean defaultTransformer, final Set alreadySpecified, + final String header) + { + final StringBuilder prefix = new StringBuilder(); + final StringBuilder general = new StringBuilder(); + final StringBuilder mimetypes = new StringBuilder(); + final AtomicInteger start = new AtomicInteger(-1); + final AtomicReference currentName = new AtomicReference(); + final Properties defaultProperties = transformerProperties.getDefaultProperties(); + + new TransformerPropertyNameExtractor() + { + /** + * Uses the propertyName and values rather than expanding the property name into multiple + * entries, in order to build up the string. + */ + @Override + protected void handleProperty( + String name, + String separator, + String firstExpression, + String secondExpression, + String suffix, + String use, + String value, + String propertyName, + Map transformerSourceTargetSuffixValues, MimetypeService mimetypeService) + { + if (defaultTransformer == TransformerConfig.DEFAULT_TRANSFORMER.equals(name)) + { + String defaultValue = defaultProperties == null ? null : defaultProperties.getProperty(propertyName); + boolean isDefaultValue = value.equals(defaultValue); + if (!changesOnly || !isDefaultValue) + { + String prevName = currentName.getAndSet(name); + if (prevName != null && !prevName.equals(name)) + { + appendTransformerSettings(sb, start, prevName, prefix, general, mimetypes, mimetypeService, + alreadySpecified, availableTransformers, transformerRegistry, defaultTransformer, header); + } + + StringBuilder tmp = + separator != null + ? mimetypes + : TransformerConfig.PIPELINE.equals(suffix) + ? prefix + : general; + if (isDefaultValue) + { + tmp.append("# "); + } + tmp.append(propertyName); + tmp.append('='); + tmp.append(value); + if (!isDefaultValue && defaultValue != null) + { + tmp.append(" # default="); + tmp.append(defaultValue); + } + tmp.append("\n"); + } + } + } + }.getTransformerSourceTargetValues(TransformerConfig.ALL_SUFFIXES, true, true, transformerProperties, mimetypeService); + String currName = currentName.get(); + if (currName != null) + { + if (defaultTransformer == TransformerConfig.DEFAULT_TRANSFORMER.equals(currName)) + { + appendTransformerSettings(sb, start, currName, prefix, general, mimetypes, mimetypeService, + alreadySpecified, availableTransformers, transformerRegistry, defaultTransformer, header); + } + } + } + + private void appendTransformerSettings(StringBuilder sb, AtomicInteger start, String transformerName, StringBuilder prefix, + StringBuilder general, StringBuilder mimetypes, MimetypeService mimetypeService, Set alreadySpecified, + List availableTransformers, ContentTransformerRegistry transformerRegistry, + boolean defaultTransformer, String header) + { + if (start.get() == -1) + { + if (sb.length() != 0) + { + sb.append('\n'); + } + sb.append(header); + start.set(sb.length()); + } + + if (!defaultTransformer) + { + alreadySpecified.add(transformerName); + ContentTransformer transformer; + prefix.insert(0, '\n'); + try + { + transformer = transformerRegistry.getTransformer(transformerName); + boolean available = availableTransformers.contains(transformer); + prefix.insert(1, transformer.getComments(available)); + } + catch (IllegalArgumentException e) + { + if (transformerName.startsWith(TransformerConfig.TRANSFORMER)) + { + transformerName = transformerName.substring(TransformerConfig.TRANSFORMER.length()); + } + prefix.insert(1, ContentTransformerHelper.getCommentName(transformerName)+"# Unregistered transformer\n"); + transformer = null; + } + + sb.append(prefix.toString()); + } + + sb.append(general.toString()); + sb.append(mimetypes.toString()); + + // Reset for next one + prefix.setLength(0); + general.setLength(0); + mimetypes.setLength(0); + } + + private void appendUnconfiguredTransformerSettings(StringBuilder sb, MimetypeService mimetypeService, + Set alreadySpecified, List availableTransformers, + ContentTransformerRegistry transformerRegistry) + { + boolean first = true; + for (ContentTransformer transformer: transformerRegistry.getAllTransformers()) + { + String name = transformer.getName(); + if (!alreadySpecified.contains(name)) + { + if (first) + { + sb.append("\n"); + sb.append("# Transformers without extra configuration settings\n"); + sb.append("# =================================================\n\n"); + first = false; + } + else + { + sb.append('\n'); + } + boolean available = availableTransformers.contains(transformer); + sb.append(transformer.getComments(available)); + } + } + } + + public String toString() + { + return string; + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerPropertyGetterTest.java b/source/java/org/alfresco/repo/content/transform/TransformerPropertyGetterTest.java new file mode 100644 index 0000000000..a20ea27877 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerPropertyGetterTest.java @@ -0,0 +1,417 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockMimetypes; +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.mockProperties; +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Properties; + +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerPropertyGetter. + * + * @author Alan Davis + */ +public class TransformerPropertyGetterTest +{ + @Mock + private TransformerProperties transformerProperties; + + @Mock + private MimetypeService mimetypeService; + + @Mock + ContentTransformerRegistry transformerRegistry; + + @Mock + TransformerLog transformerLog; + + @Mock + TransformerDebugLog transformerDebugLog; + + private ContentTransformer transformer1; + private ContentTransformer transformer2; + private ContentTransformer transformer3; + + private class DummyContentTransformer2 extends AbstractContentTransformer2 { + private final String name; + + DummyContentTransformer2(String name) + { + this.name = name; + } + + @Override + public String getName() + { + return name; + } + + @Override + protected void transformInternal(ContentReader reader, ContentWriter writer, + TransformationOptions options) throws Exception + { + }}; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + transformer1 = new DummyContentTransformer2("transformer.transformer1"); + transformer2 = new DummyContentTransformer2("transformer.transformer2"); + transformer3 = new DummyContentTransformer2("transformer.transformer3"); + + when(transformerLog.getPropertyName()).thenReturn("transformer.log.entries"); + when(transformerDebugLog.getPropertyName()).thenReturn("transformer.debug.entries"); + + when(transformerProperties.getDefaultProperties()).thenReturn(new Properties()); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png", + "text/plain", "txt"); + } + + public static void mockAllTransformers(ContentTransformerRegistry transformerRegistry, ContentTransformer... transformers) + { + List allTransformers = new ArrayList(); + for (ContentTransformer transformer: transformers) + { + allTransformers.add(transformer); + } + when(transformerRegistry.getAllTransformers()).thenReturn(allTransformers); + } + + @Test + public void logEntriesTest() + { + String actual = new TransformerPropertyGetter(false, transformerProperties, + mimetypeService, transformerRegistry, transformerLog, transformerDebugLog).toString(); + + assertEquals("# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "# transformer.log.entries=0\n" + + "# transformer.debug.entries=0\n", actual); + } + + @Test + public void defaultTest() + { + mockProperties(transformerProperties, + "content.transformer.default.priority", "100", + "content.transformer.default.count", "0", + "content.transformer.default.maxPages.use.XXX", "88"); + + String actual = new TransformerPropertyGetter(false, transformerProperties, + mimetypeService, transformerRegistry, transformerLog, transformerDebugLog).toString(); + + assertEquals("# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "# transformer.log.entries=0\n" + + "# transformer.debug.entries=0\n" + + "\n" + + "# Default transformer settings\n" + + "# ============================\n" + + "content.transformer.default.count=0\n" + + "content.transformer.default.maxPages.use.XXX=88\n"+ + "content.transformer.default.priority=100\n", actual); + } + + @Test + public void defaultWithOverridesTest() + { + defaultWithOverridesTest(false, + "# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "# transformer.log.entries=0\n" + + "# transformer.debug.entries=0\n" + + "\n" + + "# Default transformer settings\n" + + "# ============================\n" + + "# content.transformer.default.count=0\n" + + "content.transformer.default.maxPages.use.XXX=88 # default=77\n"+ + "content.transformer.default.priority=100 # default=111\n"); + } + + @Test + public void defaultWithOnlyOverridesTest() + { + defaultWithOverridesTest(true, + "# Default transformer settings\n" + + "# ============================\n" + + "content.transformer.default.maxPages.use.XXX=88 # default=77\n"+ + "content.transformer.default.priority=100 # default=111\n"); + } + + private void defaultWithOverridesTest(boolean onlyChanges, String expected) + { + Properties defaultProperties = new Properties(); + defaultProperties.setProperty("content.transformer.default.priority", "111"); + defaultProperties.setProperty("content.transformer.default.count", "0"); + defaultProperties.setProperty("content.transformer.default.maxPages.use.XXX", "77"); + when(transformerProperties.getDefaultProperties()).thenReturn(defaultProperties); + + mockProperties(transformerProperties, + "content.transformer.default.priority", "100", + "content.transformer.default.count", "0", + "content.transformer.default.maxPages.use.XXX", "88"); + + String actual = new TransformerPropertyGetter(onlyChanges, transformerProperties, + mimetypeService, transformerRegistry, transformerLog, transformerDebugLog).toString(); + + assertEquals(expected, actual); + } + + @Test + public void allSuffixesTest() + { + mockProperties(transformerProperties, + "content.transformer.transformer1.timeoutMs", "1", + "content.transformer.transformer1.extensions.pdf.png.readLimitTimeMs", "2", + "content.transformer.transformer1.maxSourceSizeKBytes", "3", + "content.transformer.transformer1.extensions.pdf.png.readLimitKBytes", "4", + "content.transformer.transformer1.maxPages", "5", + "content.transformer.transformer1.extensions.pdf.png.pageLimit", "6", + "content.transformer.transformer1.maxPages.use.index", "12", + "content.transformer.transformer1.maxPages.use.webpreview", "13", + + "content.transformer.transformer1.extensions.pdf.png.supported", "true", + "content.transformer.transformer1.priority", "7", + "content.transformer.transformer1.errorTime", "8", + "content.transformer.transformer2.time", "9", + "content.transformer.transformer2.count", "10", + "content.transformer.transformer1.thresholdCount", "11", + "content.transformer.transformer1.failover", "qwe|qaz", + "content.transformer.transformer1.pipeline", "qwe|pdf|qaz"); + + when(transformerRegistry.getTransformer("transformer.transformer1")).thenReturn(transformer1); + when(transformerRegistry.getTransformer("transformer.transformer2")).thenReturn(transformer2); + when(transformerRegistry.getTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {transformer1, transformer2})); + + String actual = new TransformerPropertyGetter(false, transformerProperties, + mimetypeService, transformerRegistry, transformerLog, transformerDebugLog).toString(); + + assertEquals("# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "# transformer.log.entries=0\n" + + "# transformer.debug.entries=0\n" + + "\n" + + "# Transformers with configuration settings\n" + + "# ========================================\n" + + "# Commented out settings are hard coded values for information purposes\n" + + "\n" + + "# transformer1\n" + + "# ------------\n" + + "content.transformer.transformer1.pipeline=qwe|pdf|qaz\n" + + "content.transformer.transformer1.errorTime=8\n" + + "content.transformer.transformer1.failover=qwe|qaz\n" + + "content.transformer.transformer1.maxPages=5\n" + + "content.transformer.transformer1.maxPages.use.index=12\n"+ + "content.transformer.transformer1.maxPages.use.webpreview=13\n"+ + "content.transformer.transformer1.maxSourceSizeKBytes=3\n" + + "content.transformer.transformer1.priority=7\n" + + "content.transformer.transformer1.thresholdCount=11\n" + + "content.transformer.transformer1.timeoutMs=1\n" + + "content.transformer.transformer1.extensions.pdf.png.pageLimit=6\n" + + "content.transformer.transformer1.extensions.pdf.png.readLimitKBytes=4\n" + + "content.transformer.transformer1.extensions.pdf.png.readLimitTimeMs=2\n" + + "content.transformer.transformer1.extensions.pdf.png.supported=true\n" + + "\n" + + "# transformer2\n"+ + "# ------------\n" + + "content.transformer.transformer2.count=10\n"+ + "content.transformer.transformer2.time=9\n", actual); + } + + @Test + public void normalTest() + { + normalTest(true); + } + + @Test + // Output for a transformer that may only be used as part of another transformer + public void normalComponentTest() + { + normalTest(false); + } + + private void normalTest(boolean isAvailable) + { + AbstractContentTransformer2 simple = new AbstractContentTransformer2() { + @Override + protected void transformInternal(ContentReader reader, ContentWriter writer, + TransformationOptions options) throws Exception + { + }}; + simple.setBeanName("transformer.exampleSimple"); + + when(transformerRegistry.getAllTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {(ContentTransformer)simple})); + if (isAvailable) + { + when(transformerRegistry.getTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {(ContentTransformer)simple})); + } + + String actual = new TransformerPropertyGetter(false, transformerProperties, + mimetypeService, transformerRegistry, transformerLog, transformerDebugLog).toString(); + + assertEquals("# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "# transformer.log.entries=0\n" + + "# transformer.debug.entries=0\n" + + "\n" + + "# Transformers without extra configuration settings\n" + + "# =================================================\n" + + "\n" + + "# exampleSimple\n" + + "# -------------\n" + + (isAvailable ? "" : "# content.transformer.exampleSimple.available=false\n"), actual); + } + + @Test + public void complexTest() + { + complexTest(true); + } + + @Test + // Output for a transformer that may only be used as part of another transformer + public void complexComponentTest() + { + complexTest(false); + } + + private void complexTest(boolean isAvailable) + { + ComplexContentTransformer complex = new ComplexContentTransformer(); + complex.setTransformers(Arrays.asList(new ContentTransformer[] {transformer1, transformer2, transformer3})); + complex.setIntermediateMimetypes(Arrays.asList(new String[] {"application/pdf", null})); + complex.setBeanName("transformer.examplePipeline"); + complex.setMimetypeService(mimetypeService); + + if (isAvailable) + { + when(transformerRegistry.getTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {complex, transformer1, transformer2, transformer3})); + } + else + { + when(transformerRegistry.getTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {transformer1, transformer2, transformer3})); + } + when(transformerRegistry.getAllTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {complex, transformer1, transformer2, transformer3})); + + String actual = new TransformerPropertyGetter(false, transformerProperties, + mimetypeService, transformerRegistry, transformerLog, transformerDebugLog).toString(); + + assertEquals("# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "# transformer.log.entries=0\n" + + "# transformer.debug.entries=0\n" + + "\n" + + "# Transformers without extra configuration settings\n" + + "# =================================================\n" + + "\n" + + "# examplePipeline\n" + + "# ---------------\n" + + (isAvailable ? "" : "# content.transformer.examplePipeline.available=false\n") + + "# content.transformer.examplePipeline.pipeline=transformer1|pdf|transformer2||transformer3\n"+ + "\n" + + "# transformer1\n" + + "# ------------\n" + + "\n" + + "# transformer2\n" + + "# ------------\n" + + "\n" + + "# transformer3\n" + + "# ------------\n", actual); + } + + @Test + public void failoverTest() + { + failoverTest(true); + } + + @Test + // Output for a failover transformer that may only be used as part of another transformer + public void failoverComponentTest() + { + failoverTest(false); + } + + private void failoverTest(boolean isAvailable) + { + FailoverContentTransformer failover = new FailoverContentTransformer(); + failover.setTransformers(Arrays.asList(new ContentTransformer[] {transformer1, transformer2})); + failover.setBeanName("transformer.exampleFailover"); + + if (isAvailable) + { + when(transformerRegistry.getTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {failover, transformer1, transformer2})); + } + else + { + when(transformerRegistry.getTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {transformer1, transformer2})); + } + when(transformerRegistry.getAllTransformers()).thenReturn(Arrays.asList(new ContentTransformer[] {failover, transformer1, transformer2})); + + String actual = new TransformerPropertyGetter(false, transformerProperties, + mimetypeService, transformerRegistry, transformerLog, transformerDebugLog).toString(); + + assertEquals("# LOG and DEBUG history sizes\n" + + "# ===========================\n" + + "# Use small values as these logs are held in memory. 0 to disable.\n" + + "# transformer.log.entries=0\n" + + "# transformer.debug.entries=0\n" + + "\n" + + "# Transformers without extra configuration settings\n" + + "# =================================================\n" + + "\n" + + "# exampleFailover\n" + + "# ---------------\n" + + (isAvailable ? "" : "# content.transformer.exampleFailover.available=false\n") + + "# content.transformer.exampleFailover.failover=transformer1|transformer2\n" + + "\n" + + "# transformer1\n" + + "# ------------\n" + + "\n" + + "# transformer2\n" + + "# ------------\n", actual); + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractor.java b/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractor.java index 8224c60249..d0f26d6e3b 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractor.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractor.java @@ -31,9 +31,7 @@ import java.util.Map; import java.util.regex.Matcher; import java.util.regex.Pattern; -import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.util.Triple; /** * Provides access to transformer property names and values. @@ -42,7 +40,6 @@ import org.alfresco.util.Triple; */ public abstract class TransformerPropertyNameExtractor { - private static String[] SEPARATORS = new String[] {TransformerConfig.EXTENSIONS , TransformerConfig.MIMETYPES}; private static Pattern EXTENSIONS_SEPARATOR = Pattern.compile("[^]\\\\]\\."); private static String[] NO_EXT_MATCH = new String[0]; @@ -56,54 +53,88 @@ public abstract class TransformerPropertyNameExtractor * Must start with a '.' if there is a suffix. * @param includeSummary if true will also look for property names without the separator, * source mimetype and target mimetype. - * @param subsystem that provides the properties + * @param includeUse if true, additionally checks for specific usage values that override + * the normal defaults. Such properties have a suffix of ".use." where + * is a value such as "index", "webpreview", "doclib", "syncRule", "aysncRule". + * @param transformerProperties that provides the properties * @param mimetypeService */ protected Collection getTransformerSourceTargetValues(Collection suffixes, - boolean includeSummary, ChildApplicationContextFactory subsystem, MimetypeService mimetypeService) + boolean includeSummary, boolean includeUse, TransformerProperties transformerProperties, MimetypeService mimetypeService) + { + return new ArrayList( + getTransformerSourceTargetValuesMap(suffixes, includeSummary, includeUse, transformerProperties, mimetypeService).values()); + } + + /** + * Returns a map to access transformer name, source extension and target extension to + * transformer name, source extension, target extension and value, created from property + * names that defined transformation limits. When the separator indicates + * it is followed by a regular expression that matches multiple mimetypes, more than one + * value may be added. When there is a value defined for specific extensions it wins over + * any regular expression value. + * @param suffixes possible endings to the property names after the target mimetype extension. + * Must start with a '.' if there is a suffix. + * @param includeSummary if true will also look for property names without the separator, + * source mimetype and target mimetype. + * @param includeUse if true, additionally checks for specific usage values that override + * the normal defaults. Such properties have a suffix of ".use." where + * is a value such as "index", "webpreview", "doclib", "syncRule", "aysncRule". + * @param transformerProperties that provides the properties + * @param mimetypeService + */ + protected Map getTransformerSourceTargetValuesMap(Collection suffixes, + boolean includeSummary, boolean includeUse, TransformerProperties transformerProperties, MimetypeService mimetypeService) { Map transformerSourceTargetSuffixValues = new HashMap(); - for (String propertyName: subsystem.getPropertyNames()) + List propertyNames = new ArrayList(transformerProperties.getPropertyNames()); + Collections.sort(propertyNames); + for (String propertyName: propertyNames) { if (propertyName.startsWith(PREFIX)) { + String use = null; + String propertyNameWithoutUse = propertyName; + if (includeUse) + { + int i = propertyName.lastIndexOf(TransformerConfig.USE); + if (i != -1) + { + int j = i+TransformerConfig.USE.length(); + if (propertyName.length()-j > 0) + { + use = propertyName.substring(j); + propertyNameWithoutUse = propertyName.substring(0, i); + } + } + } + suffixesLoop: for (String suffix: suffixes) { - if (propertyName.endsWith(suffix)) + if (propertyNameWithoutUse.endsWith(suffix)) { - String value = subsystem.getProperty(propertyName); - String name = propertyName.substring(CONTENT.length(), propertyName.length()-suffix.length()); + String value = transformerProperties.getProperty(propertyName); + String transformerName = propertyNameWithoutUse.substring(CONTENT.length(), propertyNameWithoutUse.length()-suffix.length()); boolean separatorMatch = false; - for (String separator: SEPARATORS) + for (String separator: TransformerConfig.SEPARATORS) { - int i = name.lastIndexOf(separator); + int i = transformerName.lastIndexOf(separator); if (i != -1) { separatorMatch = true; - String extensions = name.substring(i+separator.length()); + String extensions = transformerName.substring(i+separator.length()); String[] ext = splitExt(extensions); if (ext.length == 2) { - name = name.substring(0, i); - List sourceExtensions = (separator == TransformerConfig.EXTENSIONS) - ? getMatchingExtensionsFromExtensions(ext[0], mimetypeService) - : getMatchingExtensionsFromMimetypes( ext[0], mimetypeService); - List targetExtensions = (separator == TransformerConfig.EXTENSIONS) - ? getMatchingExtensionsFromExtensions(ext[1], mimetypeService) - : getMatchingExtensionsFromMimetypes( ext[1], mimetypeService); - for (String sourceExt : sourceExtensions) - { - for (String targetExt : targetExtensions) - { - addTransformerSourceTargetValue(transformerSourceTargetSuffixValues, - (separator == TransformerConfig.MIMETYPES), - name, sourceExt, targetExt, suffix, - value, mimetypeService); - } - } + transformerName = transformerName.substring(0, i); + String firstExpression = ext[0]; + String secondExpression = ext[1]; + handleProperty(transformerName, + separator, firstExpression, secondExpression, + suffix, use, value, propertyName, transformerSourceTargetSuffixValues, mimetypeService); break suffixesLoop; } } @@ -111,8 +142,7 @@ public abstract class TransformerPropertyNameExtractor if (!separatorMatch && includeSummary) { - addTransformerSourceTargetValue(transformerSourceTargetSuffixValues, false, name, ANY, ANY, - suffix, value, mimetypeService); + handleProperty(transformerName, null, null, null, suffix, use, value, propertyName, transformerSourceTargetSuffixValues, mimetypeService); break suffixesLoop; } } @@ -120,7 +150,45 @@ public abstract class TransformerPropertyNameExtractor } } - return transformerSourceTargetSuffixValues.values(); + return transformerSourceTargetSuffixValues; + } + + /** + * Handles a property to add values to the supplied transformerSourceTargetSuffixValues. + * If the separator is null, this indicates that the property provides a transformer + * wide value, so firstExpression and secondExpression should also be ignored. + */ + protected void handleProperty(String transformerName, String separator, + String firstExpression, String secondExpression, String suffix, String use, String value, + String propertyName, + Map transformerSourceTargetSuffixValues, MimetypeService mimetypeService) + { + if (separator == null) + { + addTransformerSourceTargetValue(transformerSourceTargetSuffixValues, + false, + transformerName, ANY, ANY, suffix, + use, value, mimetypeService); + } + else + { + List sourceExtensions = (separator == TransformerConfig.EXTENSIONS) + ? getMatchingExtensionsFromExtensions(firstExpression, mimetypeService) + : getMatchingExtensionsFromMimetypes( firstExpression, mimetypeService); + List targetExtensions = (separator == TransformerConfig.EXTENSIONS) + ? getMatchingExtensionsFromExtensions(secondExpression, mimetypeService) + : getMatchingExtensionsFromMimetypes( secondExpression, mimetypeService); + for (String sourceExt : sourceExtensions) + { + for (String targetExt : targetExtensions) + { + addTransformerSourceTargetValue(transformerSourceTargetSuffixValues, + (separator == TransformerConfig.MIMETYPES), + transformerName, sourceExt, targetExt, suffix, + use, value, mimetypeService); + } + } + } } /** @@ -131,11 +199,11 @@ public abstract class TransformerPropertyNameExtractor */ private void addTransformerSourceTargetValue( Map transformerSourceTargetSuffixValues, - boolean mimetypeProperty, String name, String sourceExt, String targetExt, String suffix, - String value, MimetypeService mimetypeService) + boolean mimetypeProperty, String transformerName, String sourceExt, String targetExt, String suffix, + String use, String value, MimetypeService mimetypeService) { TransformerSourceTargetSuffixValue transformerSourceTargetSuffixValue = - new TransformerSourceTargetSuffixValue(name, sourceExt, targetExt, suffix, value, mimetypeService); + new TransformerSourceTargetSuffixValue(transformerName, sourceExt, targetExt, suffix, use, value, mimetypeService); TransformerSourceTargetSuffixKey key = transformerSourceTargetSuffixValue.key(); if (mimetypeProperty || !transformerSourceTargetSuffixValues.containsKey(key)) @@ -149,7 +217,7 @@ public abstract class TransformerPropertyNameExtractor * that is not escaped (preceded by a back slash '\'). * This is to allow regular expressions to be used for mimetypes. */ - static String[] splitExt(String extensions) + String[] splitExt(String extensions) { String[] ext = NO_EXT_MATCH; Matcher matcher = EXTENSIONS_SEPARATOR.matcher(extensions); @@ -167,7 +235,7 @@ public abstract class TransformerPropertyNameExtractor * Returns a regex Pattern for the supplied expression where '*' represents zero * or more characters. */ - static Pattern pattern(String expression) + Pattern pattern(String expression) { // Turn the pattern into a regular expression where any special regex // characters have no meaning and then get any * values to represent @@ -183,7 +251,7 @@ public abstract class TransformerPropertyNameExtractor * @param mimetypeService * @return the list of extensions of mimetypes that match */ - private List getMatchingExtensionsFromMimetypes( + List getMatchingExtensionsFromMimetypes( String expression, MimetypeService mimetypeService) { if (ANY.equals(expression)) @@ -211,7 +279,7 @@ public abstract class TransformerPropertyNameExtractor * @param mimetypeService * @return the list of extensions that match */ - private List getMatchingExtensionsFromExtensions( + List getMatchingExtensionsFromExtensions( String expression, MimetypeService mimetypeService) { if (ANY.equals(expression)) @@ -230,6 +298,25 @@ public abstract class TransformerPropertyNameExtractor } return matchingMimetypes; } + + /** + * Returns a transformer property value if it exists from the supplied map. + * @param transformerName of the transformer + * @param sourceExt {@code null} indicates this is a transformer wide property. + * @param targetExt + * @param suffix + * @param use + * @param transformerSourceTargetSuffixValues map of values + * @return the value or {@code null} if not set. + */ + protected String getProperty(String transformerName, String sourceExt, String targetExt, + String suffix, String use, Map transformerSourceTargetSuffixValues) + { + TransformerSourceTargetSuffixKey key = new TransformerSourceTargetSuffixKey(transformerName, + (sourceExt == null ? ANY : sourceExt), (targetExt == null ? ANY : targetExt), suffix, use); + TransformerSourceTargetSuffixValue value = transformerSourceTargetSuffixValues.get(key); + return value == null ? null : value.value; + } } class TransformerSourceTargetSuffixKey @@ -238,13 +325,25 @@ class TransformerSourceTargetSuffixKey final String sourceExt; final String targetExt; final String suffix; + final String use; - public TransformerSourceTargetSuffixKey(String transformerName, String sourceExt, String targetExt, String suffix) + // sourceExt and targetExt should never be null, but be set to ANY + public TransformerSourceTargetSuffixKey(String transformerName, String sourceExt, String targetExt, String suffix, String use) { this.transformerName = transformerName; this.sourceExt = sourceExt; this.targetExt = targetExt; this.suffix = suffix; + this.use = use; + } + + public String toString() + { + return transformerName+(sourceExt.equals(ANY) && targetExt.equals(ANY) + ? "" + : TransformerConfig.EXTENSIONS+sourceExt+'.'+targetExt)+ + suffix+ + (use == null ? "" : TransformerConfig.USE + use); } @Override @@ -256,6 +355,7 @@ class TransformerSourceTargetSuffixKey result = prime * result + ((suffix == null) ? 0 : suffix.hashCode()); result = prime * result + ((targetExt == null) ? 0 : targetExt.hashCode()); result = prime * result + ((transformerName == null) ? 0 : transformerName.hashCode()); + result = prime * result + ((use == null) ? 0 : use.hashCode()); return result; } @@ -297,56 +397,49 @@ class TransformerSourceTargetSuffixKey } else if (!transformerName.equals(other.transformerName)) return false; + if (use == null) + { + if (other.use != null) + return false; + } + else if (!use.equals(other.use)) + return false; return true; } } -class TransformerSourceTargetSuffixValue +class TransformerSourceTargetSuffixValue extends TransformerSourceTargetSuffixKey { - final String transformerName; - final String sourceExt; - final String targetExt; - final String suffix; final String value; - final String sourceMimetype; final String targetMimetype; + // sourceExt and targetExt should never be null, but be set to ANY public TransformerSourceTargetSuffixValue(String transformerName, String sourceExt, - String targetExt, String suffix, String value, MimetypeService mimetypeService) + String targetExt, String suffix, String use, String value, MimetypeService mimetypeService) { - this.transformerName = transformerName; - this.sourceExt = sourceExt; - this.targetExt = targetExt; - this.suffix = suffix; + super(transformerName, sourceExt, targetExt, suffix, use); + this.value = value; - this.sourceMimetype = ANY.equals(sourceExt) ? ANY : mimetypeService.getMimetype(sourceExt); this.targetMimetype = ANY.equals(targetExt) ? ANY : mimetypeService.getMimetype(targetExt); } public TransformerSourceTargetSuffixKey key() { - return new TransformerSourceTargetSuffixKey(transformerName, sourceExt, targetExt, suffix); + return new TransformerSourceTargetSuffixKey(transformerName, sourceExt, targetExt, suffix, use); } public String toString() { - return transformerName+(sourceExt.equals(ANY) && targetExt.equals(ANY) - ? "" - : TransformerConfig.EXTENSIONS+sourceExt+'.'+targetExt)+ - suffix+'='+value; + return super.toString()+'='+value; } @Override public int hashCode() { final int prime = 31; - int result = 1; - result = prime * result + ((sourceExt == null) ? 0 : sourceExt.hashCode()); - result = prime * result + ((suffix == null) ? 0 : suffix.hashCode()); - result = prime * result + ((targetExt == null) ? 0 : targetExt.hashCode()); - result = prime * result + ((transformerName == null) ? 0 : transformerName.hashCode()); + int result = super.hashCode(); result = prime * result + ((value == null) ? 0 : value.hashCode()); return result; } @@ -354,47 +447,10 @@ class TransformerSourceTargetSuffixValue @Override public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) + if (!super.equals(obj)) return false; TransformerSourceTargetSuffixValue other = (TransformerSourceTargetSuffixValue) obj; - if (sourceExt == null) - { - if (other.sourceExt != null) - return false; - } - else if (!sourceExt.equals(other.sourceExt)) - return false; - if (suffix == null) - { - if (other.suffix != null) - return false; - } - else if (!suffix.equals(other.suffix)) - return false; - if (targetExt == null) - { - if (other.targetExt != null) - return false; - } - else if (!targetExt.equals(other.targetExt)) - return false; - if (transformerName == null) - { - if (other.transformerName != null) - return false; - } - else if (!transformerName.equals(other.transformerName)) - return false; - if (value == null) - { - if (other.value != null) - return false; - } - else if (!value.equals(other.value)) + if (!value.equals(other.value)) return false; return true; } diff --git a/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractorTest.java b/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractorTest.java index 9a3da515d4..c7dbd9813a 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractorTest.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerPropertyNameExtractorTest.java @@ -18,19 +18,132 @@ */ package org.alfresco.repo.content.transform; +import static org.alfresco.repo.content.transform.TransformerConfig.ANY; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeSet; +import java.util.concurrent.atomic.AtomicInteger; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.junit.Before; import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; /** - * Test class for some of the regex methods. + * Test class for some of the regex methods and TransformerPropertyNameExtractor in general. * * @author Alan Davis */ public class TransformerPropertyNameExtractorTest { + @Mock + private TransformerProperties transformerProperties; + + @Mock + private MimetypeService mimetypeService; + + private Map map; + + private TestTransformerPropertyNameExtractor extractor; + + private Collection suffixes = Collections.singleton(".suffix"); + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + extractor = new TestTransformerPropertyNameExtractor(); + + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png"); + } + + /** + * Mock up the responses from the subsystem so that it returns all the supplied + * property names and values. + * @param transformerProperties to mock the return values + * @param namesAndValues a sequence of property names and values. + * @throws IllegalStateException if there is not a value for every property + */ + public static void mockProperties(TransformerProperties transformerProperties, String... namesAndValues) + { + if (namesAndValues.length % 2 != 0) + { + // Not using IllegalArgumentException as this is thrown by classes under test + throw new java.lang.IllegalStateException("There should be a value for every property"); + } + + final Set propertyNames = new TreeSet(); + for (int i=0; i < namesAndValues.length; i+=2) + { + propertyNames.add(namesAndValues[i]); + when(transformerProperties.getProperty(namesAndValues[i])).thenReturn(namesAndValues[i+1]); + } + when(transformerProperties.getPropertyNames()).thenReturn(propertyNames); + } + + /** + * Mock up the responses from the mimetypeService so that it: + * a) returns all the supplied mimetypes + * b) returns the extension given the mimetype + * c) returns the mimetype given the extension. + * @param mimetypesAndExtensions sequence of mimetypes and extenstions. + * @param transformerProperties to mock the return values + * @throws IllegalStateException if there is not an extension for every mimetype + */ + public static void mockMimetypes(MimetypeService mimetypeService, String... mimetypesAndExtensions) + { + if (mimetypesAndExtensions.length % 2 != 0) + { + // Not using IllegalArgumentException as this is thrown by classes under test + throw new java.lang.IllegalStateException("There should be an extension for every mimetype"); + } + + final Set allMimetypes = new HashSet(); + for (int i=0; i < mimetypesAndExtensions.length; i+=2) + { + allMimetypes.add(mimetypesAndExtensions[i]); + when(mimetypeService.getExtension(mimetypesAndExtensions[i])).thenReturn(mimetypesAndExtensions[i+1]); + when(mimetypeService.getMimetype(mimetypesAndExtensions[i+1])).thenReturn(mimetypesAndExtensions[i]); + } + when(mimetypeService.getMimetypes()).thenReturn(new ArrayList(allMimetypes)); + } + + /** + * Asserts two List are equal after they are sorted. + * @param msg to be included in the assertEquals call. + * @param expected list + * @param actual list + */ + public void assertEqualsLists(String msg, List expected, List actual) + { + if (expected.size() > 1) + { + Collections.sort(expected); + } + if (actual.size() > 1) + { + Collections.sort(actual); + } + + assertEquals(msg, expected, actual); + } + @Test public void textSplitExt() { @@ -46,7 +159,7 @@ public class TransformerPropertyNameExtractorTest String expectedSource = args[1]; String expectedTarget = args[2]; - String[] sourceTarget = TransformerPropertyNameExtractor.splitExt(input); + String[] sourceTarget = extractor.splitExt(input); assertEquals("length", sourceTarget.length, 2); assertEquals("source", expectedSource, sourceTarget[0]); assertEquals("target", expectedTarget, sourceTarget[1]); @@ -56,27 +169,524 @@ public class TransformerPropertyNameExtractorTest @Test public void testPattern() { - assertTrue( TransformerPropertyNameExtractor.pattern("ABC").matcher("ABC").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("ABC").matcher("x").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("ABC").matcher("ABCD").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("ABC").matcher("DABC").matches()); + assertTrue( extractor.pattern("ABC").matcher("ABC").matches()); + assertFalse(extractor.pattern("ABC").matcher("x").matches()); + assertFalse(extractor.pattern("ABC").matcher("ABCD").matches()); + assertFalse(extractor.pattern("ABC").matcher("DABC").matches()); - assertTrue( TransformerPropertyNameExtractor.pattern("*B").matcher("B").matches()); - assertTrue( TransformerPropertyNameExtractor.pattern("*B").matcher("xxB").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("*B").matcher("xxBx").matches()); - assertFalse( TransformerPropertyNameExtractor.pattern("B*").matcher("").matches()); + assertTrue( extractor.pattern("*B").matcher("B").matches()); + assertTrue( extractor.pattern("*B").matcher("xxB").matches()); + assertFalse(extractor.pattern("*B").matcher("xxBx").matches()); + assertFalse(extractor.pattern("B*").matcher("").matches()); - assertTrue( TransformerPropertyNameExtractor.pattern("C*").matcher("C").matches()); - assertTrue( TransformerPropertyNameExtractor.pattern("C*").matcher("CxxB").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("C*").matcher("xxBx").matches()); + assertTrue( extractor.pattern("C*").matcher("C").matches()); + assertTrue( extractor.pattern("C*").matcher("CxxB").matches()); + assertFalse(extractor.pattern("C*").matcher("xxBx").matches()); - assertTrue(TransformerPropertyNameExtractor.pattern("D*E*F").matcher("DEF").matches()); - assertTrue(TransformerPropertyNameExtractor.pattern("D*E*F").matcher("DxxExxF").matches()); - assertTrue(TransformerPropertyNameExtractor.pattern("D*E*F").matcher("D*E*F").matches()); + assertTrue(extractor.pattern("D*E*F").matcher("DEF").matches()); + assertTrue(extractor.pattern("D*E*F").matcher("DxxExxF").matches()); + assertTrue(extractor.pattern("D*E*F").matcher("D*E*F").matches()); - assertTrue( TransformerPropertyNameExtractor.pattern("A+").matcher("A+").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("A+").matcher("AA").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("A+").matcher("AAA").matches()); - assertFalse(TransformerPropertyNameExtractor.pattern("A+").matcher("A+A").matches()); + assertTrue( extractor.pattern("A+").matcher("A+").matches()); + assertFalse(extractor.pattern("A+").matcher("AA").matches()); + assertFalse(extractor.pattern("A+").matcher("AAA").matches()); + assertFalse(extractor.pattern("A+").matcher("A+A").matches()); + } + + @Test + public void getMatchingExtensionsFromMimetypesTest() + { + mockMimetypes(mimetypeService, + "mimetypeAx", "aExt", + "mimetypeBx", "bExt", + "mimetypeC", "cExt"); + + String[][] data = new String[][] + { + {"*C", "cExt"}, + {"*ypeAx", "aExt"}, + {"mimetype*x", "aExt bExt"}, + {"mimetypeBx", "bExt"}, + {"*mime*", "aExt bExt cExt"}, + {"*", ANY}, // special meaning on its own + {"", null}, + {"bad", null} + }; + + for (String[] entry: data) + { + List expected = entry[1] == null ? Collections.emptyList() : Arrays.asList(entry[1].split(" ")); + List actual = extractor.getMatchingExtensionsFromMimetypes(entry[0], mimetypeService); + assertEqualsLists("getMatchingExtensionsFromMimetypes("+entry[0]+")", expected, actual); + + } + } + + @Test + public void getMatchingExtensionsFromExtensionsTest() + { + mockMimetypes(mimetypeService, + "mimetypeAx", "aExt", + "mimetypeBx", "bExt", + "mimetypeC", "cExt"); + + String[][] data = new String[][] + { + {"c*", "cExt"}, + {"*a*", "aExt"}, + {"a*xt", "aExt"}, + {"*Ext", "aExt bExt cExt"}, + {"*t", "aExt bExt cExt"}, + {"*", ANY}, // special meaning on its own + {"", null}, + {"bad", null} + }; + + for (String[] entry: data) + { + List expected = entry[1] == null ? Collections.emptyList() : Arrays.asList(entry[1].split(" ")); + List actual = extractor.getMatchingExtensionsFromExtensions(entry[0], mimetypeService); + assertEqualsLists("getMatchingExtensionsFromMimetypes("+entry[0]+")", expected, actual); + } + } + + @Test + public void transformerSourceTargetSuffixKeyHashCodeEqualsTest() + { + // Test data + String[][] data = new String[][] + { + {"transformerName", "sourceExt", "targetExt", ".suffix", null}, + {"XXXXXXXXXXXXXXX", "sourceExt", "targetExt", ".suffix", null}, + {"transformerName", "XXXXXXXXX", "targetExt", ".suffix", null}, + {"transformerName", "sourceExt", "XXXXXXXXX", ".suffix", null}, + {"transformerName", "sourceExt", "targetExt", ".XXXXXX", null}, + {"transformerName", "sourceExt", "targetExt", ".suffix", "index"}, + {"XXXXXXXXXXXXXXX", "sourceExt", "targetExt", ".suffix", "index"}, + {"transformerName", "XXXXXXXXX", "targetExt", ".suffix", "index"}, + {"transformerName", "sourceExt", "XXXXXXXXX", ".suffix", "index"}, + {"transformerName", "sourceExt", "targetExt", ".XXXXXX", "index"} + }; + + for (int i=data.length+1; i >= 0; i--) + { + // Check properties are set + Object a = i == data.length+1 ? "" : i == data.length ? null : new TransformerSourceTargetSuffixKey(data[i][0], data[i][1], data[i][2], data[i][3], data[i][4]); + if (a instanceof TransformerSourceTargetSuffixKey) + { + assertEquals(i+" transformerName", ((TransformerSourceTargetSuffixKey)a).transformerName, data[i][0]); + assertEquals(i+" sourceExt", ((TransformerSourceTargetSuffixKey)a).sourceExt, data[i][1]); + assertEquals(i+" targetExt", ((TransformerSourceTargetSuffixKey)a).targetExt, data[i][2]); + assertEquals(i+" suffix", ((TransformerSourceTargetSuffixKey)a).suffix, data[i][3]); + assertEquals(i+" use", ((TransformerSourceTargetSuffixKey)a).use, data[i][4]); + } + + // Try out the hashCode and equals methods + for (int j=data.length+1; j >= 0; j--) + { + Object b = j == data.length+1 ? "" : j == data.length ? null : new TransformerSourceTargetSuffixKey(data[j][0], data[j][1], data[j][2], data[j][3], data[j][4]); + if (a != null) + { + if (i == j) + { + assertTrue( i+" "+j+" equals", a.equals(b)); + assertEquals(i+" "+j+" hashCode", a.hashCode(), b.hashCode()); + } + else + { + assertFalse( i+" "+j+" equals ", a.equals(b)); + if (b != null) + { + assertFalse(i+" "+j+" hashCode", a.hashCode() == b.hashCode()); + } + } + } + } + } + } + + @Test + public void transformerSourceTargetSuffixValueHashCodeEqualsTest() + { + mockMimetypes(mimetypeService, + "sourceMimetype", "sourceExt", + "sourceXXXXXXXX", "sourceXXX", + "targetMimetype", "targetExt", + "targetXXXXXXXX", "targetXXX"); + + // Test data + String[][] data = new String[][] + { + {"transformerName", "sourceExt", "targetExt", ".suffix", null, "value", "sourceMimetype", "targetMimetype"}, + {"XXXXXXXXXXXXXXX", "sourceExt", "targetExt", ".suffix", null, "value", "sourceMimetype", "targetMimetype"}, + {"transformerName", "sourceXXX", "targetExt", ".suffix", null, "value", "sourceXXXXXXXX", "targetMimetype"}, + {"transformerName", "sourceExt", "targetXXX", ".suffix", null, "value", "sourceMimetype", "targetXXXXXXXX"}, + {"transformerName", "sourceExt", "targetExt", ".XXXXXX", null, "value", "sourceMimetype", "targetMimetype"}, + {"transformerName", "sourceExt", "targetExt", ".suffix", null, "XXXXX", "sourceMimetype", "targetMimetype"}, + {"transformerName", "sourceExt", "targetExt", ".suffix", "index", "value", "sourceMimetype", "targetMimetype"}, + {"XXXXXXXXXXXXXXX", "sourceExt", "targetExt", ".suffix", "index", "value", "sourceMimetype", "targetMimetype"}, + {"transformerName", "sourceXXX", "targetExt", ".suffix", "index", "value", "sourceXXXXXXXX", "targetMimetype"}, + {"transformerName", "sourceExt", "targetXXX", ".suffix", "index", "value", "sourceMimetype", "targetXXXXXXXX"}, + {"transformerName", "sourceExt", "targetExt", ".XXXXXX", "index", "value", "sourceMimetype", "targetMimetype"}, + {"transformerName", "sourceExt", "targetExt", ".suffix", "index", "XXXXX", "sourceMimetype", "targetMimetype"} + }; + + for (int i=data.length+1; i >= 0; i--) + { + // Check properties are set + Object a = i == data.length+1 ? "" : i == data.length ? null : new TransformerSourceTargetSuffixValue(data[i][0], data[i][1], data[i][2], data[i][3], data[i][4], data[i][5], mimetypeService); + if (a instanceof TransformerSourceTargetSuffixValue) + { + assertEquals(i+" transformerName", data[i][0], ((TransformerSourceTargetSuffixValue)a).transformerName); + assertEquals(i+" sourceExt", data[i][1], ((TransformerSourceTargetSuffixValue)a).sourceExt); + assertEquals(i+" targetExt", data[i][2], ((TransformerSourceTargetSuffixValue)a).targetExt); + assertEquals(i+" suffix", data[i][3], ((TransformerSourceTargetSuffixValue)a).suffix); + assertEquals(i+" use", data[i][4], ((TransformerSourceTargetSuffixValue)a).use); + + assertEquals(i+" value", data[i][5], ((TransformerSourceTargetSuffixValue)a).value); + assertEquals(i+" sourceMimetype", data[i][6], ((TransformerSourceTargetSuffixValue)a).sourceMimetype); + assertEquals(i+" targetMimetype", data[i][7], ((TransformerSourceTargetSuffixValue)a).targetMimetype); + + assertEquals(i+" key", + new TransformerSourceTargetSuffixKey(data[i][0], data[i][1], data[i][2], data[i][3], data[i][4]), + ((TransformerSourceTargetSuffixValue)a).key()); + } + + // Try out the hashCode and equals methods + for (int j=data.length+1; j >= 0; j--) + { + Object b = j == data.length+1 ? "" : j == data.length ? null : new TransformerSourceTargetSuffixValue(data[j][0], data[j][1], data[j][2], data[j][3], data[j][4], data[j][5], mimetypeService); + if (a != null) + { + if (i == j) + { + assertTrue( i+" "+j+" equals", a.equals(b)); + assertEquals(i+" "+j+" hashCode", a.hashCode(), b.hashCode()); + } + else + { + assertFalse( i+" "+j+" equals ", a.equals(b)); + if (b != null) + { + assertFalse(i+" "+j+" hashCode", a.hashCode() == b.hashCode()); + } + } + } + } + } + } + + @Test + public void transformerSourceTargetSuffixValuetoStringTest() + { + mockMimetypes(mimetypeService, + "sourceMimetype", "sourceExt", + "targetMimetype", "targetExt"); + + // Test data + String[][] data = new String[][] + { + {"transformerName", "sourceExt", "targetExt", ".suffix", null, "value", "transformerName.extensions.sourceExt.targetExt.suffix=value"}, + {"transformerName", "sourceExt", "targetExt", ".suffix", "index", "value", "transformerName.extensions.sourceExt.targetExt.suffix.use.index=value"}, + {"transformerName", ANY, ANY, ".suffix", null, "value", "transformerName.suffix=value"} + }; + + for (int i=data.length-1; i >= 0; i--) + { + // Check properties are set + TransformerSourceTargetSuffixValue a = new TransformerSourceTargetSuffixValue(data[i][0], data[i][1], data[i][2], data[i][3], data[i][4], data[i][5], mimetypeService); + assertEquals(i+" toString", data[i][6], a.toString()); + } + } + + @Test + public void getPropertyTest() + { + mockMimetypes(mimetypeService, + "sourceMimetype", "sourceExt", + "sourceAAAAAAAA", "sourceAAA", + "targetMimetype", "targetExt"); + + // Test data + String[][] data = new String[][] + { + {"transformerName", "sourceExt", "targetExt", ".suffix", null, "value1"}, + {"transformerName", null, null, ".suffix", null, "value2"}, + {"transformerName", ANY, ANY, ".suffix", null, "value2"}, + {"transformerName", "sourceAAA", "targetExt", ".suffix", null, "value3"}, + {"transformerXXXX", null, null, ".suffix", null, null}, + {"transformerName", "sourceExt", "targetExt", ".suffix", "index", "value4"}, + {"transformerName", null, null, ".suffix", "index", "value5"}, + {"transformerName", ANY, ANY, ".suffix", "index", "value5"}, + {"transformerName", "sourceAAA", "targetExt", ".suffix", "index", "value6"}, + {"transformerXXXX", null, null, ".suffix", "index", null} + }; + + // Populate the test Map + Map transformerSourceTargetSuffixValues = + new HashMap(); + for (int i=data.length-1; i >= 0; i--) + { + TransformerSourceTargetSuffixValue keyValue = new TransformerSourceTargetSuffixValue(data[i][0], data[i][1], data[i][2], data[i][3], data[i][4], data[i][5], mimetypeService); + transformerSourceTargetSuffixValues.put(keyValue.key(), keyValue); + } + + // Check we can return the values including the last one which was not added so has a null value + for (int i=data.length-1; i >= 0; i--) + { + assertEquals(i+" getProperty", data[i][5], extractor.getProperty(data[i][0], data[i][1], data[i][2], data[i][3], data[i][4], transformerSourceTargetSuffixValues)); + } + } + + @Test + public void transformerWideTest() + { + mockProperties(transformerProperties, "content.transformer.abc.suffix", "value1"); + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("value1", extractor.getProperty("transformer.abc", null, null, ".suffix", null, map)); + } + + @Test + public void excludeSummaryTest() + { + mockProperties(transformerProperties, "content.transformer.abc.suffix", "value1"); + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, false, false, transformerProperties, mimetypeService); + + assertEquals(null, extractor.getProperty("transformer.abc", null, null, ".suffix", null, map)); + } + + @Test + public void extensionTest() + { + mockProperties(transformerProperties, "content.transformer.abc.extensions.pdf.png.suffix", "value1"); + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("value1", extractor.getProperty("transformer.abc", "pdf", "png", ".suffix", null, map)); + } + + @Test + public void mimetypeTest() + { + mockProperties(transformerProperties, "content.transformer.abc.mimetypes.application/pdf.image/png.suffix", "value1"); + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("value1", extractor.getProperty("transformer.abc", "pdf", "png", ".suffix", null, map)); + } + + @Test + public void multipleSuffix1Test() + { + suffixes = Arrays.asList(new String[] {".suffix1", ".suffix2", ".suffix3", ".suffix4"}); + mockProperties(transformerProperties, "content.transformer.abc.suffix1", "value1"); + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("value1", extractor.getProperty("transformer.abc", null, null, ".suffix1", null, map)); + } + + @Test + public void multipleSuffix4Test() + { + suffixes = Arrays.asList(new String[] {".suffix1", ".suffix2", ".suffix3", ".suffix4"}); + mockProperties(transformerProperties, "content.transformer.abc.suffix4", "value1"); + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("value1", extractor.getProperty("transformer.abc", null, null, ".suffix4", null, map)); + } + + @Test + public void noPrefixTest() + { + mockProperties(transformerProperties, "the.cat.sat", "on the mat"); + extractor = new TestTransformerPropertyNameExtractor(); + + assertEquals(0, extractor.getTransformerSourceTargetValues(suffixes, true, false, transformerProperties, mimetypeService).size()); + } + + @Test + public void multipleValuesTest() + { + suffixes = TransformerConfig.LIMIT_SUFFIXES; + mockProperties(transformerProperties, + "content.transformer.abc.maxSourceSizeKBytes", "1", + "content.transformer.abc.timeoutMs", "2", + "content.transformer.abc.extensions.pdf.png.maxPages", "3", + "content.transformer.x.y.mimetypes.application/pdf.image/png.maxPages", "4", + "content.transformer.abc.maxSourceSizeKBytes.use.index", "5", + "content.transformer.abc.timeoutMs.use.index", "6", + "content.transformer.abc.extensions.pdf.png.maxPages.use.index", "7", + "content.transformer.x.y.mimetypes.application/pdf.image/png.maxPages.use.index", "8"); + + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("1", extractor.getProperty("transformer.abc", null, null, ".maxSourceSizeKBytes", null, map)); + assertEquals("2", extractor.getProperty("transformer.abc", null, null, ".timeoutMs", null, map)); + assertEquals("3", extractor.getProperty("transformer.abc", "pdf", "png", ".maxPages", null, map)); + assertEquals("4", extractor.getProperty("transformer.x.y", "pdf", "png", ".maxPages", null, map)); + assertEquals(null, extractor.getProperty("transformer.abc", null, null, ".maxSourceSizeKBytes", "index", map)); + assertEquals(null, extractor.getProperty("transformer.abc", null, null, ".timeoutMs", "index", map)); + assertEquals(null, extractor.getProperty("transformer.abc", "pdf", "png", ".maxPages", "index", map)); + assertEquals(null, extractor.getProperty("transformer.x.y", "pdf", "png", ".maxPages", "index", map)); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, true, transformerProperties, mimetypeService); + + assertEquals("1", extractor.getProperty("transformer.abc", null, null, ".maxSourceSizeKBytes", null, map)); + assertEquals("2", extractor.getProperty("transformer.abc", null, null, ".timeoutMs", null, map)); + assertEquals("3", extractor.getProperty("transformer.abc", "pdf", "png", ".maxPages", null, map)); + assertEquals("4", extractor.getProperty("transformer.x.y", "pdf", "png", ".maxPages", null, map)); + assertEquals("5", extractor.getProperty("transformer.abc", null, null, ".maxSourceSizeKBytes", "index", map)); + assertEquals("6", extractor.getProperty("transformer.abc", null, null, ".timeoutMs", "index", map)); + assertEquals("7", extractor.getProperty("transformer.abc", "pdf", "png", ".maxPages", "index", map)); + assertEquals("8", extractor.getProperty("transformer.x.y", "pdf", "png", ".maxPages", "index", map)); + } + + @Test + public void wildcardTest() + { + suffixes = TransformerConfig.LIMIT_SUFFIXES; + mockProperties(transformerProperties, + "content.transformer.abc.extensions.p*.*g.maxPages", "value"); + + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("pdf,png to png", 2, map.size()); + assertEquals("value", extractor.getProperty("transformer.abc", "pdf", "png", ".maxPages", null, map)); + assertEquals("value", extractor.getProperty("transformer.abc", "png", "png", ".maxPages", null, map)); + } + + @Test + public void mimetypeWinsOverExtensionFirstTest() + { + // Extension property is provided first + suffixes = TransformerConfig.LIMIT_SUFFIXES; + mockProperties(transformerProperties, + "content.transformer.abc.extensions.pdf.png.maxPages", "extension", + "content.transformer.abc.mimetypes.application/pdf.image/png.maxPages", "mimetype"); + + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("mimetype", extractor.getProperty("transformer.abc", "pdf", "png", ".maxPages", null, map)); + } + + @Test + public void mimetypeWinsOverExtensionSecondTest() + { + // Extension property is provided second + suffixes = TransformerConfig.LIMIT_SUFFIXES; + mockProperties(transformerProperties, + "content.transformer.abc.mimetypes.application/pdf.image/png.maxPages", "mimetype", + "content.transformer.abc.extensions.pdf.png.maxPages", "extension"); + + extractor = new TestTransformerPropertyNameExtractor(); + + map = extractor.getTransformerSourceTargetValuesMap(suffixes, true, false, transformerProperties, mimetypeService); + + assertEquals("mimetype", extractor.getProperty("transformer.abc", "pdf", "png", ".maxPages", null, map)); + } + + @Test + public void callsToHandlePropertyTest() + { + final String[][] data = new String[][] + { // Must sort the property names + {"content.transformer.abc.extensions.pdf.png.suffix", "value2", "transformer.abc", ".extensions.", "pdf", "png", ".suffix", null}, + {"content.transformer.abc.extensions.pdf.png.suffix.use.index", "value2", "transformer.abc", ".extensions.", "pdf", "png", ".suffix", "index"}, + + {"content.transformer.abc.mimetypes.application/pdf.image/png.suffix", "value2", "transformer.abc", ".mimetypes.", "application/pdf", "image/png", ".suffix", null}, + {"content.transformer.abc.mimetypes.application/pdf.image/png.suffix.use.index", "value2", "transformer.abc", ".mimetypes.", "application/pdf", "image/png", ".suffix", "index"}, + + {"content.transformer.abc.suffix", "value1", "transformer.abc", null, null, null, ".suffix", null}, + {"content.transformer.abc.suffix.use.index", "value1", "transformer.abc", null, null, null, ".suffix", "index"}, + + {"xxxx.transformer.abc.extensions.pdf.png.suffix", "value2", null}, + {"xxxx.transformer.abc.extensions.pdf.png.suffix.use.index", "value2", null} + }; + + List properties = new ArrayList(); + final Set suffixes = new HashSet(); + for (String[] entry: data) + { + properties.add(entry[0]); + properties.add(entry[1]); + if (entry.length == 8) + { + suffixes.add(entry[6]); + } + } + mockProperties(transformerProperties, properties.toArray(new String[properties.size()])); + + final AtomicInteger i = new AtomicInteger(0); + extractor = new TestTransformerPropertyNameExtractor() + { + protected void handleProperty(String transformerName, String separator, + String firstExpression, String secondExpression, String suffix, String use, String value, + String propertyName, + Map transformerSourceTargetSuffixValues, MimetypeService mimetypeService) + { + // Ignore non transformer properties + int j; + do + { + j = i.getAndIncrement(); + } while(j < data.length && data[j].length != 8); + + if (j < data.length) + { + assertEquals(j+" transformerName", data[j][2], transformerName); + assertEquals(j+" separator", data[j][3], separator); + assertEquals(j+" firstExpression", data[j][4], firstExpression); + assertEquals(j+" secondExpression", data[j][5], secondExpression); + assertEquals(j+" suffix", data[j][6], suffix); + assertEquals(j+" use", data[j][7], use); + assertEquals(j+" value", data[j][1], value); + assertEquals(j+" propertyName", data[j][0], propertyName); + } + + while(j+1 < data.length && data[j+1].length < 8) + { + j = i.getAndIncrement(); + } + + super.handleProperty(transformerName, separator, firstExpression, secondExpression, suffix, use, value, propertyName, transformerSourceTargetSuffixValues, mimetypeService); + } + }; + + extractor.callGetTransformerSourceTargetValuesMap(suffixes, true, transformerProperties, mimetypeService); + + assertEquals("counter", 8, i.get()); } } + +class TestTransformerPropertyNameExtractor extends TransformerPropertyNameExtractor +{ + public Collection callGetTransformerSourceTargetValues(Collection suffixes, + boolean includeSummary, TransformerProperties transformerProperties, MimetypeService mimetypeService) + { + return getTransformerSourceTargetValues(suffixes, includeSummary, false, transformerProperties, mimetypeService); + } + + public Map callGetTransformerSourceTargetValuesMap(Collection suffixes, + boolean includeSummary, TransformerProperties transformerProperties, MimetypeService mimetypeService) + { + return getTransformerSourceTargetValuesMap(suffixes, includeSummary, true, transformerProperties, mimetypeService); + } +}; + diff --git a/source/java/org/alfresco/repo/content/transform/TransformerPropertySetter.java b/source/java/org/alfresco/repo/content/transform/TransformerPropertySetter.java new file mode 100644 index 0000000000..1e6cfb972d --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerPropertySetter.java @@ -0,0 +1,534 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.alfresco.repo.content.transform.TransformerConfig.AVAILABLE; +import static org.alfresco.repo.content.transform.TransformerConfig.CONTENT; +import static org.alfresco.repo.content.transform.TransformerConfig.ERROR_TIME; +import static org.alfresco.repo.content.transform.TransformerConfig.FAILOVER; +import static org.alfresco.repo.content.transform.TransformerConfig.INITIAL_COUNT; +import static org.alfresco.repo.content.transform.TransformerConfig.INITIAL_TIME; +import static org.alfresco.repo.content.transform.TransformerConfig.MAX_PAGES; +import static org.alfresco.repo.content.transform.TransformerConfig.MAX_SOURCE_SIZE_K_BYTES; +import static org.alfresco.repo.content.transform.TransformerConfig.PAGE_LIMIT; +import static org.alfresco.repo.content.transform.TransformerConfig.PIPELINE; +import static org.alfresco.repo.content.transform.TransformerConfig.PRIORITY; +import static org.alfresco.repo.content.transform.TransformerConfig.READ_LIMIT_K_BYTES; +import static org.alfresco.repo.content.transform.TransformerConfig.READ_LIMIT_TIME_MS; +import static org.alfresco.repo.content.transform.TransformerConfig.SUPPORTED; +import static org.alfresco.repo.content.transform.TransformerConfig.THRESHOLD_COUNT; +import static org.alfresco.repo.content.transform.TransformerConfig.TIMEOUT_MS; +import static org.alfresco.repo.content.transform.TransformerConfig.TRANSFORMER; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.util.Pair; + + + +/** + * Provides methods to set and remove transformer properties and values. + * + * @author Alan Davis + */ +public class TransformerPropertySetter +{ + /** + * Matches lines + */ + private static final Pattern LINE_SPLIT = Pattern.compile("^(.*)$", Pattern.MULTILINE); + + /** + * Matches leading white space and trailing comments + */ + private static final Pattern COMMENT = Pattern.compile("(^\\s+)|(\\s*#.*)|(\\s+$)"); + + /** + * Tries to match a space that should be a newline. JConsole has turned \n into spaces! + */ + private static Pattern NEWLINE_CORRECTION = Pattern.compile("([^#]\\s*) ((content\\.transformer\\.)|(transformer\\.))"); + + /** + * Integer Value + */ + private static final Pattern INTEGER = Pattern.compile("^-?\\d+$"); + + private final TransformerProperties transformerProperties; + private final MimetypeService mimetypeService; + private final ContentTransformerRegistry transformerRegistry; + + public TransformerPropertySetter(TransformerProperties transformerProperties, MimetypeService mimetypeService, + ContentTransformerRegistry transformerRegistry) + { + this.transformerProperties = transformerProperties; + this.mimetypeService = mimetypeService; + this.transformerRegistry = transformerRegistry; + } + + /** + * Sets transformer properties from the supplied multi line propertyNamesAndValues. + * @throws IllegalArgumentException if an unexpected line is found + */ + public int setProperties(String propertyNamesAndValues) + { + Map map = new HashMap(); + Map transformerReferences = new HashMap(); + Set dynamicTransformerNames = new HashSet(); + for (String propertyNameAndValue: extractProperties(propertyNamesAndValues, true, transformerReferences, dynamicTransformerNames)) + { + Pair pair = splitNameAndValue(propertyNameAndValue); + String propertyName = pair.getFirst(); + String value = pair.getSecond(); + if (map.containsKey(propertyName)) + { + throw new IllegalArgumentException(propertyName+" has been specified more than once"); + } + if (!value.equals(transformerProperties.getProperty(propertyName))) + { + map.put(propertyName, value); + } + } + + // Check transformer names exist or will exist + checkTransformerReferences(transformerReferences, dynamicTransformerNames); + + int size = map.size(); + if (size > 0) + { + transformerProperties.setProperties(map); + } + return size; + } + + private void checkTransformerReferences(Map transformerReferences, + Set dynamicTransformerNames) throws IllegalArgumentException + { + // Add on the static transformer names into the dynamic transformer names supplied + Set allTransformerNames = new HashSet(dynamicTransformerNames); + for (ContentTransformer transformer: transformerRegistry.getAllTransformers()) + { + String name = transformer.getName(); + allTransformerNames.add(name); + } + allTransformerNames.add(TransformerConfig.DEFAULT_TRANSFORMER); + + for (String transformerSimpleName: transformerReferences.keySet()) + { + String name = TRANSFORMER+transformerSimpleName; + if (!allTransformerNames.contains(name)) + { + String line = transformerReferences.get(transformerSimpleName); + throw unexpectedProperty("Transformer "+transformerSimpleName+" does not exist", line); + } + } + } + + /** + * Removes transformer properties from the supplied multi line propertyNames. + * @param propertyNames which optionally include a value + * @throws IllegalArgumentException if an unexpected line is found + */ + public int removeProperties(String propertyNames) + { + Set remove = new HashSet(); + Map transformerReferences = new HashMap(); + Set dynamicTransformerNames = new HashSet(); + Properties defaultProperties = transformerProperties.getDefaultProperties(); + for (String propertyNameAndValue: extractProperties(propertyNames, false, transformerReferences, dynamicTransformerNames)) + { + Pair pair = splitNameAndValue(propertyNameAndValue); + String propertyName = pair.getFirst(); + if (transformerProperties.getProperty(propertyName) == null) + { + throw unexpectedProperty("Does not exist", propertyName); + } + if (defaultProperties.getProperty(propertyName) != null) + { + throw unexpectedProperty("Is a deafult property so may not be removed", propertyName); + } + remove.add(propertyName); + } + transformerProperties.removeProperties(remove); + return remove.size(); + } + + /** + * Returns an array of transformer property names (and optional values). + * @param text to be parsed + * @param hasValue values are required, optionally exist when false. + * @param transformerReferences map of transformerReferences to a line that referenced them. + * @param dynamicTransformerNames added via properties + * @return a list of cleaned up transformer properties from the text + * @throws IllegalArgumentException if an unexpected line is found + */ + private List extractProperties(String text, boolean hasValue, Map transformerReferences, Set dynamicTransformerNames) + { + List properties = new ArrayList(); + text = fixJConsolesMissingNewlines(text); + + // Split the lines + Matcher lineMatcher = LINE_SPLIT.matcher(text); + while (lineMatcher.find()) + { + String line = lineMatcher.group(); + + // Strip comments from lines + Matcher commentMatcher = COMMENT.matcher(line); + line = commentMatcher.replaceAll(""); + + // Ignore blank lines + if (line.length() != 0) + { + String lowerLine = line.toLowerCase(); // Should we set the lower case value anyway + if (lowerLine.startsWith(TransformerConfig.PREFIX)) + { + checkTransformerProperty(hasValue, line, transformerReferences, dynamicTransformerNames); + properties.add(line); + } + else if (lowerLine.startsWith(TransformerConfig.DEBUG_ENTRIES)) + { + checkInteger(hasValue, line, TransformerConfig.DEBUG_ENTRIES.length()); + properties.add(line); + } + else if (lowerLine.startsWith(TransformerConfig.LOG_ENTRIES)) + { + checkInteger(hasValue, line, TransformerConfig.LOG_ENTRIES.length()); + properties.add(line); + } + else + { + throw unexpectedProperty("Not a transformer property", line); + } + } + } + + return properties; + } + + /** + * Multi-line Strings from JConsole have their end of line bytes replaced by a + * single space. The following method tries to put them back in.

+ * + * It tries to avoid commented out transformers. + * + * @param text to scan + * @return modified text + */ + String fixJConsolesMissingNewlines(String text) + { + Matcher newlineMatcher = NEWLINE_CORRECTION.matcher(text); + text = newlineMatcher.replaceAll("$1\n$2"); + return text; + } + + private void checkTransformerProperty(boolean hasValue, String line, + Map transformerReferences, Set dynamicTransformerNames) + { + int j = line.indexOf('='); + String propertyName = j != -1 ? line.substring(0, j) : line; + TransformerPropertyNameExtractor extractor = new TransformerPropertyNameExtractor() {}; + + boolean validPropertyName = false; + String transformerName = null; + String suffix = null; + String separator = null; + + suffixesLoop: + for (String aSuffix: TransformerConfig.ALL_SUFFIXES) + { + if (propertyName.endsWith(aSuffix)) + { + transformerName = propertyName.substring(CONTENT.length(), propertyName.length()-aSuffix.length()); + suffix = aSuffix; + boolean separatorMatch = false; + for (String aSeparator: TransformerConfig.SEPARATORS) + { + int i = transformerName.lastIndexOf(aSeparator); + if (i != -1) + { + separatorMatch = true; + String extensions = transformerName.substring(i+aSeparator.length()); + String[] ext = extractor.splitExt(extensions); + if (ext.length == 2) + { + String firstExpression = ext[0]; + String secondExpression = ext[1]; + if (aSeparator == TransformerConfig.EXTENSIONS) + { + if (extractor.getMatchingExtensionsFromExtensions(firstExpression, mimetypeService).size() == 0) + { + throw unexpectedProperty("Invalid source extension "+firstExpression, line); + } + if (extractor.getMatchingExtensionsFromExtensions(secondExpression, mimetypeService).size() == 0) + { + throw unexpectedProperty("Invalid target extension "+secondExpression, line); + } + } + else // if (separator == TransformerConfig.MIMETYPES) + { + if (extractor.getMatchingExtensionsFromMimetypes(firstExpression, mimetypeService).size() == 0) + { + throw unexpectedProperty("Invalid source mimetype "+firstExpression, line); + } + if (extractor.getMatchingExtensionsFromMimetypes(secondExpression, mimetypeService).size() == 0) + { + throw unexpectedProperty("Invalid target mimetype "+secondExpression, line); + } + } + transformerName = transformerName.substring(0, i); + separator = aSeparator; + validPropertyName = true; + break suffixesLoop; + } + } + } + separator = null; + + if (!separatorMatch) + { + validPropertyName = true; + break suffixesLoop; + } + } + } + + if (!validPropertyName) + { + throw unexpectedProperty("Possible typo in the property name", line); + } + + checkTransformerPropertyValue(hasValue, line, j, transformerName, separator, suffix, + transformerReferences, dynamicTransformerNames); + } + + private void checkTransformerPropertyValue(boolean hasValue, String line, int i, + String transformerName, String separator, String suffix, + Map transformerReferences, Set dynamicTransformerNames) + { + if (MAX_SOURCE_SIZE_K_BYTES.equals(suffix) || + TIMEOUT_MS.equals(suffix) || + READ_LIMIT_K_BYTES.equals(suffix) || + READ_LIMIT_TIME_MS.equals(suffix) || + INITIAL_COUNT.equals(suffix) || + INITIAL_TIME.equals(suffix) || + ERROR_TIME.equals(suffix)) + { + checkLong(hasValue, line, i); + } + else if (MAX_PAGES.equals(suffix) || + PAGE_LIMIT.equals(suffix) || + THRESHOLD_COUNT.equals(suffix) || + PRIORITY.equals(suffix)) + { + checkInteger(hasValue, line, i); + } + else if (SUPPORTED.equals(suffix) || + AVAILABLE.equals(suffix)) + { + checkBoolean(hasValue, line, i); + } + else if (PIPELINE.equals(suffix) || + FAILOVER.equals(suffix)) + { + dynamicTransformerNames.add(transformerName); + if (separator != null) + { + throw unexpectedProperty("Separator was not expected", line); + } + + if (PIPELINE.equals(suffix)) + { + checkPipelineValue(hasValue, line, i, transformerReferences); + } + else + { + checkFailoverValue(hasValue, line, i, transformerReferences); + } + } + } + + private void checkInteger(boolean hasValue, String line, int i) + { + String value = checkValue(hasValue, line, i); + + if (value != null) + { + if (!INTEGER.matcher(value).find()) + { + throw unexpectedProperty("Expected an integer value", line); + } + try + { + Integer.parseInt(value); + } + catch (NumberFormatException e) + { + throw unexpectedProperty("Expected an int value", line); + } + } + } + + private void checkLong(boolean hasValue, String line, int i) + { + String value = checkValue(hasValue, line, i); + + if (value != null) + { + if (!INTEGER.matcher(value).find()) + { + throw unexpectedProperty("Expected an integer value", line); + } + try + { + Long.parseLong(value); + } + catch (NumberFormatException e) + { + throw unexpectedProperty("Expected a long value", line); + } + } + } + + private void checkBoolean(boolean hasValue, String line, int i) + { + String value = checkValue(hasValue, line, i); + + if (value != null) + { + if (!value.equalsIgnoreCase("true") && + !value.equalsIgnoreCase("false")) + { + throw unexpectedProperty("Expected true or false value", line); + } + } + } + + private void checkPipelineValue(boolean hasValue, String line, int i, + Map transformerReferences) + { + String value = checkValue(hasValue, line, i); + + if (value != null) + { + String[] transformerNamesAndExtensions = value.split("\\|"); + + int count = transformerNamesAndExtensions.length; + + // Must be an even number of | characters, as they should be + // an initial transformer and then pairs of extension and transformer. + if (count < 2 || count % 2 == 0) + { + throw unexpectedProperty("Incomplete pipeline value", line); + } + + for (int j=0; j < count; j++) + { + if (j % 2 == 0) + { + // Added reference to the transformer if not ANY transformer + if (transformerNamesAndExtensions[j].length() > 0) + { + transformerReferences.put(transformerNamesAndExtensions[j], line); + } + } + else + { + String extension = transformerNamesAndExtensions[j]; + TransformerPropertyNameExtractor extractor= new TransformerPropertyNameExtractor() {}; + if (extractor.getMatchingExtensionsFromExtensions(extension, mimetypeService).size() == 0) + { + throw unexpectedProperty("Invalid intermediate extension "+extension, line); + } + } + } + } + } + + private void checkFailoverValue(boolean hasValue, String line, int i, + Map transformerReferences) + { + String value = checkValue(hasValue, line, i); + + if (value != null) + { + String[] transformerNames = value.split("\\|"); + int count = transformerNames.length; + + // Should be more than one transformer + if (count < 2) + { + throw unexpectedProperty("Can't failover if there is only on transformer", line); + } + + // Add every component + for (int j=0; j < count; j++) + { + if (transformerNames[j].length() > 0) + { + transformerReferences.put(transformerNames[j], line); + } + } + } + } + + /** + * Check that a line has an assignment of a value if hasValue is true and + * if false the assignment is optional. + * @param hasValue + * @param line + * @param i the offset where the assignment should start + * @return the value if there is an assignment. null otherwise. + * @throws IllegalArgumentException if there is a problem. + */ + private String checkValue(boolean hasValue, String line, int i) + { + int l = line.length(); + if (( hasValue && (i == -1 || l <= i || line.charAt(i) != '=')) || + (!hasValue && ( l < i || (i != -1 && l > i && line.charAt(i) != '=')))) + { + throw unexpectedProperty("Expected a value after an '=' at char "+i, line); + } + + return (hasValue || (i != -1 && l > i)) ? line.substring(i+1) : null; + } + + private IllegalArgumentException unexpectedProperty(String context, String line) throws IllegalArgumentException + { + return new IllegalArgumentException("Unexpected property: "+line+" "+context); + } + + Pair splitNameAndValue(String propertyNameAndValue) + { + int i = propertyNameAndValue.indexOf('='); + String name = i != -1 ? propertyNameAndValue.substring(0, i) : propertyNameAndValue; + String value = i != -1 ? propertyNameAndValue.substring(i+1) : ""; + Pair pair = new Pair(name, value); + return pair; + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerPropertySetterTest.java b/source/java/org/alfresco/repo/content/transform/TransformerPropertySetterTest.java new file mode 100644 index 0000000000..07652474c0 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerPropertySetterTest.java @@ -0,0 +1,677 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.junit.Assert.assertEquals; +import static org.alfresco.repo.content.transform.TransformerPropertyNameExtractorTest.*; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.Set; + +import org.alfresco.service.cmr.repository.ContentIOException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Tests TransformerPropertySetter. + * + * @author Alan Davis + */ +public class TransformerPropertySetterTest +{ + @Mock + private TransformerProperties transformerProperties; + + @Mock + private MimetypeService mimetypeService; + + @Mock + private ContentTransformerRegistry transformerRegistry; + + private TransformerPropertySetter setter; + private Properties properties; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + mockAllTransformers(transformerRegistry, "abc", "complex.PDF.Image", "transformer1", "transformer2", "transformer3"); + mockMimetypes(mimetypeService, + "application/pdf", "pdf", + "image/png", "png"); + setter = new TransformerPropertySetter(transformerProperties, mimetypeService, transformerRegistry); + + properties = new Properties(); + when(transformerProperties.getDefaultProperties()).thenReturn(properties); + } + + public static void mockAllTransformers(ContentTransformerRegistry transformerRegistry, String... shortTransformerNames) + { + List allTransformers = new ArrayList(); + for (String shortTransformerName: shortTransformerNames) + { + allTransformers.add(new DummyContentTransformer(TransformerConfig.TRANSFORMER+shortTransformerName)); + } + when(transformerRegistry.getAllTransformers()).thenReturn(allTransformers); + } + + private Map expectedProperties(String... namesAndValues) + { + Map expected = new HashMap(); + String name = null; + for (String string: namesAndValues) + { + if (name == null) + { + name = string; + } + else + { + expected.put(name, string); + name = null; + } + } + return expected; + } + + private Collection expectedNames(String... names) + { + Set expected = new HashSet(); + expected.addAll(Arrays.asList(names)); + return expected; + } + + @Test + public void simpleTest() + { + int count = setter.setProperties("transformer.log.entries=12"); + + verify(transformerProperties).setProperties(expectedProperties("transformer.log.entries", "12")); + assertEquals("Added count", 1, count); + } + + @Test + public void debugLineSplitTest() + { + int count = setter.setProperties( + "transformer.log.entries=12\n" + + "transformer.debug.entries=23"); + + verify(transformerProperties).setProperties(expectedProperties( + "transformer.debug.entries", "23", + "transformer.log.entries", "12")); + assertEquals("Added count", 2, count); + } + + @Test + public void mixedCaseTest() + { + setter.setProperties("Transformer.LOG.entries=12"); + + verify(transformerProperties).setProperties(expectedProperties("Transformer.LOG.entries", "12")); + } + + @Test + public void debugLogSizeTest() + { + setter.setProperties( + "transformer.log.entries=12\n" + + "transformer.debug.entries=-1"); + + verify(transformerProperties).setProperties(expectedProperties( + "transformer.debug.entries", "-1", + "transformer.log.entries", "12")); + } + + @Test(expected=IllegalArgumentException.class) + public void badPropertyNameTest() + { + setter.setProperties("trans.bad=11"); + } + + @Test(expected=IllegalArgumentException.class) + public void duplicateNameTest() + { + setter.setProperties( + "transformer.log.entries=12\n" + + "transformer.log.entries=-1"); + } + + @Test(expected=IllegalArgumentException.class) + public void badIntegerTest() + { + setter.setProperties("transformer.debug.entries=--1"); + } + + @Test(expected=IllegalArgumentException.class) + public void badInteger2Test() + { + setter.setProperties("transformer.debug.entries=1a"); + } + + @Test(expected=IllegalArgumentException.class) + public void noValueEqualsTest() + { + // setProiperties must have an = + setter.setProperties("transformer.debug.entries"); + } + + @Test + public void booleanTest() + { + setter.setProperties("content.transformer.complex.PDF.Image.extensions.pdf.png.supported=TruE"); + verify(transformerProperties).setProperties(expectedProperties( + "content.transformer.complex.PDF.Image.extensions.pdf.png.supported", "TruE")); + } + + @Test(expected=IllegalArgumentException.class) + public void badBooleanTest() + { + setter.setProperties("content.transformer.complex.PDF.Image.extensions.pdf.png.supported=yes"); + } + + @Test + public void defaultTransformerTest() + { + setter.setProperties( + "content.transformer.default.priority=100\n"+ + "content.transformer.default.thresholdCount=3\n"+ + "content.transformer.default.time=0\n"+ + "content.transformer.default.count=100000\n"+ + "content.transformer.default.errorTime=120000\n"+ + "content.transformer.default.timeoutMs=120000\n"+ + "content.transformer.default.readLimitTimeMs=-1\n"+ + "content.transformer.default.maxSourceSizeKBytes=-1\n"+ + "content.transformer.default.readLimitKBytes=-1\n"+ + "content.transformer.default.pageLimit=-1\n"+ + "content.transformer.default.maxPages=-1"); + + verify(transformerProperties).setProperties(expectedProperties( + "content.transformer.default.priority", "100", + "content.transformer.default.thresholdCount", "3", + "content.transformer.default.time", "0", + "content.transformer.default.count", "100000", + "content.transformer.default.errorTime", "120000", + "content.transformer.default.timeoutMs", "120000", + "content.transformer.default.readLimitTimeMs", "-1", + "content.transformer.default.maxSourceSizeKBytes", "-1", + "content.transformer.default.readLimitKBytes", "-1", + "content.transformer.default.pageLimit", "-1", + "content.transformer.default.maxPages", "-1")); + } + + @Test + public void commentAndWhiteSpaceTest() + { + setter.setProperties( + "# hi mum\n"+ + "content.transformer.default.priority=100\n"+ + " content.transformer.default.thresholdCount=3 \n"+ + "\t content.transformer.default.time=0\n"+ + "\t\t \t content.transformer.default.count=100000 # another comment\n"+ + "#\n"+ + " \n"+ + "\n"+ + "content.transformer.default.errorTime=120000\n"); + + verify(transformerProperties).setProperties(expectedProperties( + "content.transformer.default.priority", "100", + "content.transformer.default.thresholdCount", "3", + "content.transformer.default.time", "0", + "content.transformer.default.count", "100000", + "content.transformer.default.errorTime", "120000")); + } + + @Test + public void fixJConsolesMissingNewlinesTest() + { + // Variable names based on what JConsole does to newlines and what fixJConsolesMissingNewlines does to spaces + String nl2nl = "@"; + String nl2space = "~"; + String space2nl = "%"; + + String data = + "transformer.log.entries=12"+nl2nl+ + "transformer.log.entries=-1"+nl2space+ + "# a line of comment"+nl2nl+ + "content.transformer.default.priority=100"+nl2space+ + "#"+nl2space+ // Will discard the next property, but this is still best we can do + "content.transformer.default.thresholdCount=3 "+nl2nl+ + "content.transformer.default.time=0 # end of line comment "+nl2nl+ + "content.transformer.default.count=100000 "+nl2space+ + " "+space2nl+"content.transformer.default.errorTime=120000 "+nl2nl+ + "content.transformer.default.timeoutMs=120000 "+nl2nl+ + "content.transformer.default.readLimitTimeMs=-1"; + + // What we paste into JConsole + String original = data.replace(nl2nl, "\n").replace(nl2space, "\n").replace(space2nl, " "); + + // What we get from JConsole + String missingNl = data.replace(nl2nl, " ").replace(nl2space, " ").replace(space2nl, " "); + + // What we expect to recover as a result of this method + String expected = data.replace(nl2nl, "\n").replace(nl2space, " ").replace(space2nl, "\n"); + + // What we do get from fixJConsolesMissingNewlines + String actual = setter.fixJConsolesMissingNewlines(missingNl); + + String indentOriginal = ("\n"+original).replaceAll("\n", "\n "); + assertEquals("Having entered:"+indentOriginal+"\n", expected, actual); + } + + @Test(expected=IllegalArgumentException.class) + public void midNameWhiteSpaceTest() + { + setter.setProperties("transformer. log.entries=12"); + } + + @Test(expected=IllegalArgumentException.class) + public void prefixValueWhiteSpaceTest() + { + setter.setProperties("transformer.log.entries= 12"); + } + + @Test + public void namedTransformerTest() + { + setter.setProperties( + "content.transformer.abc.priority=100\n"+ + "content.transformer.abc.thresholdCount=3\n"+ + "content.transformer.abc.time=0\n"+ + "content.transformer.abc.count=100000\n"+ + "content.transformer.abc.errorTime=120000\n"+ + "content.transformer.abc.timeoutMs=120000\n"+ + "content.transformer.abc.readLimitTimeMs=-1\n"+ + "content.transformer.abc.maxSourceSizeKBytes=-1\n"+ + "content.transformer.abc.readLimitKBytes=-1\n"+ + "content.transformer.abc.pageLimit=-1\n"+ + "content.transformer.abc.maxPages=-1\n"+ + "content.transformer.abc.failover=transformer1|transformer2|transformer3\n"+ + "content.transformer.abc.pipeline=transformer1|pdf|transformer2"); + + verify(transformerProperties).setProperties(expectedProperties( + "content.transformer.abc.priority", "100", + "content.transformer.abc.thresholdCount", "3", + "content.transformer.abc.time", "0", + "content.transformer.abc.count", "100000", + "content.transformer.abc.errorTime", "120000", + "content.transformer.abc.timeoutMs", "120000", + "content.transformer.abc.readLimitTimeMs", "-1", + "content.transformer.abc.maxSourceSizeKBytes", "-1", + "content.transformer.abc.readLimitKBytes", "-1", + "content.transformer.abc.pageLimit", "-1", + "content.transformer.abc.maxPages", "-1", + "content.transformer.abc.failover", "transformer1|transformer2|transformer3", + "content.transformer.abc.pipeline", "transformer1|pdf|transformer2")); + } + + @Test + public void simpleExtensionsTest() + { + setter.setProperties("content.transformer.abc.extensions.pdf.png.maxPages=-1"); + } + + @Test + public void namedTransformerExtensionTest() + { + setter.setProperties( + "content.transformer.abc.extensions.pdf.png.priority=100\n"+ + "content.transformer.abc.extensions.pdf.png.thresholdCount=3\n"+ + "content.transformer.abc.extensions.pdf.png.time=0\n"+ + "content.transformer.abc.extensions.pdf.png.count=100000\n"+ + "content.transformer.abc.extensions.pdf.png.errorTime=120000\n"+ + "content.transformer.abc.extensions.pdf.png.timeoutMs=120000\n"+ + "content.transformer.abc.extensions.pdf.png.readLimitTimeMs=-1\n"+ + "content.transformer.abc.extensions.pdf.png.maxSourceSizeKBytes=-1\n"+ + "content.transformer.abc.extensions.pdf.png.readLimitKBytes=-1\n"+ + "content.transformer.abc.extensions.pdf.png.pageLimit=-1\n"+ + "content.transformer.abc.extensions.pdf.png.maxPages=-1\n"+ + "content.transformer.abc.extensions.pdf.png.supported=true"); + + verify(transformerProperties).setProperties(expectedProperties( + "content.transformer.abc.extensions.pdf.png.priority", "100", + "content.transformer.abc.extensions.pdf.png.thresholdCount", "3", + "content.transformer.abc.extensions.pdf.png.time", "0", + "content.transformer.abc.extensions.pdf.png.count", "100000", + "content.transformer.abc.extensions.pdf.png.errorTime", "120000", + "content.transformer.abc.extensions.pdf.png.timeoutMs", "120000", + "content.transformer.abc.extensions.pdf.png.readLimitTimeMs", "-1", + "content.transformer.abc.extensions.pdf.png.maxSourceSizeKBytes", "-1", + "content.transformer.abc.extensions.pdf.png.readLimitKBytes", "-1", + "content.transformer.abc.extensions.pdf.png.pageLimit", "-1", + "content.transformer.abc.extensions.pdf.png.maxPages", "-1", + "content.transformer.abc.extensions.pdf.png.supported", "true")); + } + + @Test(expected=IllegalArgumentException.class) + public void badSourceExtensionsTest() + { + setter.setProperties("content.transformer.abc.extensions.bad.png.maxPages=-1"); + } + + @Test(expected=IllegalArgumentException.class) + public void badTargetExtensionsTest() + { + setter.setProperties("content.transformer.abc.extensions.pdf.bad.maxPages=-1"); + } + + @Test + public void simpleMimetypeTest() + { + setter.setProperties("content.transformer.abc.mimetypes.application/pdf.image/png.maxPages=-1"); + } + + @Test + public void namedTransformerMimetypeTest() + { + setter.setProperties( + "content.transformer.abc.mimetypes.application/pdf.image/png.priority=100\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.thresholdCount=3\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.time=0\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.count=100000\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.errorTime=120000\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.timeoutMs=120000\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.readLimitTimeMs=-1\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.maxSourceSizeKBytes=-1\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.readLimitKBytes=-1\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.pageLimit=-1\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.maxPages=-1\n"+ + "content.transformer.abc.mimetypes.application/pdf.image/png.supported=true"); + + verify(transformerProperties).setProperties(expectedProperties( + "content.transformer.abc.mimetypes.application/pdf.image/png.priority", "100", + "content.transformer.abc.mimetypes.application/pdf.image/png.thresholdCount", "3", + "content.transformer.abc.mimetypes.application/pdf.image/png.time", "0", + "content.transformer.abc.mimetypes.application/pdf.image/png.count", "100000", + "content.transformer.abc.mimetypes.application/pdf.image/png.errorTime", "120000", + "content.transformer.abc.mimetypes.application/pdf.image/png.timeoutMs", "120000", + "content.transformer.abc.mimetypes.application/pdf.image/png.readLimitTimeMs", "-1", + "content.transformer.abc.mimetypes.application/pdf.image/png.maxSourceSizeKBytes", "-1", + "content.transformer.abc.mimetypes.application/pdf.image/png.readLimitKBytes", "-1", + "content.transformer.abc.mimetypes.application/pdf.image/png.pageLimit", "-1", + "content.transformer.abc.mimetypes.application/pdf.image/png.maxPages", "-1", + "content.transformer.abc.mimetypes.application/pdf.image/png.supported", "true")); + } + + @Test(expected=IllegalArgumentException.class) + public void badSourceMimetypeTest() + { + setter.setProperties("content.transformer.abc.mimetypes.bad.image/png.maxPages=-1"); + } + + @Test(expected=IllegalArgumentException.class) + public void badTargetMimetypeTest() + { + setter.setProperties("content.transformer.abc.mimetypes.application/pdf.bad.maxPages=-1"); + } + + public void pipelineTest() + { + setter.setProperties("content.transformer.abc.pipeline=transformer1|pdf|transformer2"); + } + + @Test(expected=IllegalArgumentException.class) + public void pipelineWithExtensionsTest() + { + setter.setProperties("content.transformer.abc.extensions.pdf.png.pipeline=transformer1|pdf|transformer2"); + } + + @Test(expected=IllegalArgumentException.class) + public void pipelineWithMimetypesTest() + { + setter.setProperties("content.transformer.abc.mimetypes.application/pdf.image/png.pipeline=transformer1|application/pdf|transformer2"); + } + + @Test(expected=IllegalArgumentException.class) + public void pipelineTooFewTest() + { + setter.setProperties("content.transformer.abc.pipeline=transformer1"); + } + + @Test(expected=IllegalArgumentException.class) + public void pipelineMissingTransformerTest() + { + setter.setProperties("content.transformer.abc.pipeline=transformer1|pdf|transformer2|png"); + } + + @Test(expected=IllegalArgumentException.class) + public void pipelineBadTransformerTest() + { + setter.setProperties("content.transformer.abc.pipeline=transformer1|pdf|bad"); + } + + @Test(expected=IllegalArgumentException.class) + public void pipelineBadExtensionTest() + { + setter.setProperties("content.transformer.abc.pipeline=transformer1|bad|transformer2"); + } + + public void failoverTest() + { + setter.setProperties("content.transformer.abc.failover=transformer1|transformer2"); + } + + @Test(expected=IllegalArgumentException.class) + public void failoverWithExtensionsTest() + { + setter.setProperties("content.transformer.abc.extensions.pdf.png.pipeline=transformer1|pdf|transformer2"); + } + + @Test(expected=IllegalArgumentException.class) + public void failoverWithMimetypeTest() + { + setter.setProperties("content.transformer.abc.mimetypes.application/pdf.image/png.failover=transformer1|transformer2"); + } + + @Test(expected=IllegalArgumentException.class) + public void failoverTooFewTest() + { + setter.setProperties("content.transformer.abc.failover=transformer1"); + } + + @Test(expected=IllegalArgumentException.class) + public void failoverBadTransformerTest() + { + setter.setProperties("content.transformer.abc.failover=transformer1|bad|transformer2"); + } + + @Test(expected=IllegalArgumentException.class) + public void removeUnsetPropetyTest() + { + setter.removeProperties("transformer.log.entries"); + } + + @Test + public void removeSimplePropetyTest() + { + mockProperties(transformerProperties, "transformer.log.entries", "12"); + + int count = setter.removeProperties("transformer.log.entries"); + + verify(transformerProperties).removeProperties(expectedNames("transformer.log.entries")); + assertEquals("Removed count", 1, count); + } + + @Test + // removeProperties optionally has a value + public void removeOptionalValueTest() + { + mockProperties(transformerProperties, "transformer.log.entries", "12"); + + setter.removeProperties("transformer.log.entries=12"); + } + + @Test(expected=IllegalArgumentException.class) + // removeProperties on a default property + public void removeDefaultValueTest() + { + mockProperties(transformerProperties, "transformer.log.entries", "12"); + + properties.setProperty("transformer.log.entries", "10"); + + setter.removeProperties("transformer.log.entries=12"); + } + + @Test + public void dynamicTransformerReferenceTest() + { + setter.setProperties( + "content.transformer.abc.failover=transformer1|xyz\n"+ // Reference the transformer xyz + "content.transformer.xyz.pipeline=transformer1|pdf|transformer2"); // Create the transformer xyz + } + + @Test + public void removeMultiplePropetiesTest() + { + mockProperties(transformerProperties, + "content.transformer.default.priority", "100", + "content.transformer.default.thresholdCount", "3", + "content.transformer.default.time", "0", + "content.transformer.default.count", "100000", + "content.transformer.default.errorTime", "120000"); + + int count = setter.removeProperties( + "# hi mum\n"+ + "content.transformer.default.priority=100\n"+ + " content.transformer.default.thresholdCount=3 \n"+ + "\t content.transformer.default.time=0\n"+ + "\t\t \t content.transformer.default.count=100000 # another comment\n"+ + "#\n"+ + " \n"+ + "\n"+ + "content.transformer.default.errorTime=120000\n"); + + verify(transformerProperties).removeProperties(expectedNames( + "content.transformer.default.priority", + "content.transformer.default.thresholdCount", + "content.transformer.default.time", + "content.transformer.default.count", + "content.transformer.default.errorTime")); + assertEquals("Removed count", 5, count); + } +} + +class DummyContentTransformer implements ContentTransformer +{ + private final String name; + + DummyContentTransformer(String name) + { + this.name = name; + } + + @Override + public String getName() + { + return name; + } + + @Override + public boolean isTransformable(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return false; + } + + @Override + public boolean isTransformable(String sourceMimetype, long sourceSize, + String targetMimetype, TransformationOptions options) + { + return false; + } + + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return false; + } + + @Override + public boolean isTransformableSize(String sourceMimetype, long sourceSize, + String targetMimetype, TransformationOptions options) + { + return false; + } + + @Override + public String getComments(boolean available) + { + return ""; + } + + @Override + public long getMaxSourceSizeKBytes(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return 0; + } + + @Override + public boolean isExplicitTransformation(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return false; + } + + @Override + public long getTransformationTime() + { + return 0; + } + + @Override + public long getTransformationTime(String sourceMimetype, String targetMimetype) + { + return 0; + } + + @Override + public void transform(ContentReader reader, ContentWriter writer) throws ContentIOException + { + } + + @Override + public void transform(ContentReader reader, ContentWriter writer, + Map options) throws ContentIOException + { + } + + @Override + public void transform(ContentReader reader, ContentWriter contentWriter, + TransformationOptions options) throws ContentIOException + { + } +}; diff --git a/source/java/org/alfresco/repo/content/transform/TransformerSelectorImpl.java b/source/java/org/alfresco/repo/content/transform/TransformerSelectorImpl.java index 86f714b8be..420bb9fe04 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerSelectorImpl.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerSelectorImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -20,10 +20,7 @@ package org.alfresco.repo.content.transform; import java.util.ArrayList; import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; import java.util.List; -import java.util.Map; import org.alfresco.service.cmr.repository.TransformationOptions; @@ -65,6 +62,8 @@ public class TransformerSelectorImpl implements TransformerSelector { // TODO cache results for reuse. This was a heavy operation in the past and still is. + // TODO cache results of last few successful transforms as we tend to repeat some of them as part of compound transforms. + List transformers = contentTransformerRegistry.getTransformers(); List possibleTransformers = findTransformers(transformers, sourceMimetype, sourceSize, targetMimetype, options); return sortTransformers(possibleTransformers); @@ -110,16 +109,18 @@ public class TransformerSelectorImpl implements TransformerSelector { private final ContentTransformer transformer; private final int priority; - private long averageTime = -1; + private final long averageTime; + private final long count; TransformerSortData(ContentTransformer transformer, String sourceMimetype, String targetMimetype, int priority) { this.transformer = transformer; this.priority = priority; - TransformerStatistics stats = transformerConfig.getStatistics(transformer, sourceMimetype, targetMimetype); - long threashold = transformerConfig.getThresholdCount(transformer, sourceMimetype, targetMimetype); - averageTime = (stats.getCount() < threashold) ? 0 : stats.getAverageTime(); + TransformerStatistics stats = transformerConfig.getStatistics(transformer, sourceMimetype, targetMimetype, true); + int threashold = transformerConfig.getThresholdCount(transformer, sourceMimetype, targetMimetype); + count = stats.getCount(); + averageTime = (count < threashold) ? 0 : stats.getAverageTime(); } @Override @@ -141,14 +142,22 @@ public class TransformerSelectorImpl implements TransformerSelector @Override public int compareTo(TransformerSortData that) { - int relativePriority = priority - that.priority; - if (relativePriority != 0) + int relativeInt = priority - that.priority; + if (relativeInt != 0) { - return relativePriority; + return relativeInt; } - long relativeTime = averageTime - that.averageTime; - return relativeTime > 0L ? 1 : relativeTime < 0L ? -1 : 0; + long relativeLong = averageTime - that.averageTime; + relativeInt = relativeLong > 0L ? 1 : relativeLong < 0L ? -1 : 0; + if (relativeInt != 0) + { + return relativeInt; + } + + relativeLong = count - that.count; + relativeInt = relativeLong > 0L ? 1 : relativeLong < 0L ? -1 : 0; + return relativeInt; } } } diff --git a/source/java/org/alfresco/repo/content/transform/TransformerSelectorImplTest.java b/source/java/org/alfresco/repo/content/transform/TransformerSelectorImplTest.java new file mode 100644 index 0000000000..40b649fa39 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/TransformerSelectorImplTest.java @@ -0,0 +1,318 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.content.transform; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** + * Test class for TransformerSelectorImpl. + * + * @author Alan Davis + */ +public class TransformerSelectorImplTest +{ + private static final String PNG = "image/png"; + + private static final String PDF = "application/pdf"; + + @Mock + private TransformerConfig transformerConfig; + + @Mock + private ContentTransformerRegistry contentTransformerRegistry; + + @Mock + private TransformationOptions options; + + @Mock + private DummyContentTransformer transformer1; + + @Mock + private DummyContentTransformer transformer2; + + @Mock + private DummyContentTransformer transformer3; + + @Mock + private DummyContentTransformer transformer4; + + private List allTransformers; + + private TransformerSelectorImpl selector; + + @Before + public void setUp() throws Exception + { + MockitoAnnotations.initMocks(this); + + selector = new TransformerSelectorImpl(); + selector.setTransformerConfig(transformerConfig); + selector.setContentTransformerRegistry(contentTransformerRegistry); + + when(transformer1.getName()).thenReturn("transformer.1"); + when(transformer2.getName()).thenReturn("transformer.2"); + when(transformer3.getName()).thenReturn("transformer.3"); + when(transformer4.getName()).thenReturn("transformer.4"); + + allTransformers = new ArrayList(); + when(contentTransformerRegistry.getTransformers()).thenReturn(allTransformers); + } + + private void mockTransformer(DummyContentTransformer transformer, int priority, String sourceMimetype, String targetMimetype) + { + when(transformerConfig.getPriority(transformer, sourceMimetype, targetMimetype)).thenReturn(priority); + allTransformers.add(transformer); + } + + private void mockTransformer(DummyContentTransformer transformer, int priority, String sourceMimetype, String targetMimetype, + int count, int averageTime, int threshold) + { + mockTransformer(transformer, priority, sourceMimetype, targetMimetype); + when(transformer.isTransformable(sourceMimetype, -1, targetMimetype, options)).thenReturn(true); + when(transformerConfig.getStatistics(transformer, sourceMimetype, targetMimetype, true)).thenReturn(new DummyTransformerStatistics(count, averageTime)); + when(transformerConfig.getThresholdCount(transformer, sourceMimetype, targetMimetype)).thenReturn(threshold); + + } + + private void assertTransformers(String context, List expected, + List actual) + { + String expectedNames = getNames(expected); + String actualNames = getNames(actual); + assertEquals(context, expectedNames, actualNames); + } + + private String getNames(List transformers) + { + StringBuilder sb = new StringBuilder(); + + for (ContentTransformer transformer: transformers) + { + if (sb.length() > 0) + { + sb.append(' '); + } + sb.append(transformer.getName()); + } + return sb.toString(); + } + + private void runMultipleSelections(int[][] data) throws Exception + { + // Run a sequence of selections up to the thresholds and beyond + List expected = null; + for (int i=0; i actual = selector.selectTransformers(PDF, -1, PNG, options); + assertTransformers(i+" multiple", expected, actual); + } + } + + @Test + public void firstRunUnderThresholdTest() + { + // 1 and 4 can do PDF->PNG, same priority (100) and neither have not been run + mockTransformer(transformer1, 100, PDF, PNG, 0, 0, 3); + mockTransformer(transformer2, 100, PDF, PNG); + mockTransformer(transformer3, 100, PDF, PNG); + mockTransformer(transformer4, 100, PDF, PNG, 0, 0, 3); + + List expected = Arrays.asList(new ContentTransformer[] {transformer1, transformer4}); + List actual = selector.selectTransformers(PDF, -1, PNG, options); + + assertTransformers("", expected, actual); + } + + @Test + public void secondRunUnderThresholdOneRunTest() + { + // 1 and 4 can do PDF->PNG, same priority (100) and but 1 has been run + // Should continue to round robin to 2 + mockTransformer(transformer1, 100, PDF, PNG, 1, 23, 3); + mockTransformer(transformer2, 100, PDF, PNG); + mockTransformer(transformer3, 100, PDF, PNG); + mockTransformer(transformer4, 100, PDF, PNG, 0, 0, 3); + + List expected = Arrays.asList(new ContentTransformer[] {transformer4, transformer1}); + List actual = selector.selectTransformers(PDF, -1, PNG, options); + + assertTransformers("", expected, actual); + } + + @Test + public void thirdRunUnderThresholdTwoRunTest() + { + // 1 and 4 can do PDF->PNG, same priority (100) and both have been run, but still under threshold + // Note: If the threshold was not taken into account 4 would be selected + // Should continue to round robin + mockTransformer(transformer1, 100, PDF, PNG, 1, 23, 3); + mockTransformer(transformer2, 100, PDF, PNG); + mockTransformer(transformer3, 100, PDF, PNG); + mockTransformer(transformer4, 100, PDF, PNG, 1, 16, 3); + + List expected = Arrays.asList(new ContentTransformer[] {transformer1, transformer4}); + List actual = selector.selectTransformers(PDF, -1, PNG, options); + + assertTransformers("", expected, actual); + } + + @Test + public void multipleTestSameThreshold() throws Exception + { + int[][] data = new int[][] + { + { 0, 0, 3, 0, 0, 3, 1}, // same as firstRunUnderThresholdTest + { 1, 23, 3, 0, 0, 3, 4}, // same as secondRunUnderThresholdTest + { 1, 23, 3, 1, 16, 3, 1}, // same as thirdRunUnderThresholdTwoRunTest + { 2, 25, 3, 1, 16, 3, 4}, + { 2, 25, 3, 2, 27, 3, 1}, + { 3, 24, 3, 2, 27, 3, 4}, + { 3, 24, 3, 3, 27, 3, 1}, + + // Threshold past for both transformers - 1 was faster + { 4, 25, 3, 3, 27, 3, 1}, + { 5, 22, 3, 3, 27, 3, 1}, + { 6, 21, 3, 3, 27, 3, 1}, + + // 1 is slower than 4 now, so it switches + { 7, 28, 3, 3, 27, 3, 4}, + { 7, 28, 3, 4, 26, 3, 4}, + }; + + runMultipleSelections(data); + } + + @Test + public void multipleTestDifferentThreshold() throws Exception + { + int[][] data = new int[][] + { + { 0, 0, 2, 0, 0, 4, 1}, + { 1, 23, 2, 0, 0, 4, 4}, + { 1, 23, 2, 1, 16, 4, 1}, + + // Threshold past for 1 but not 4 + { 2, 25, 2, 1, 16, 4, 4}, + { 2, 25, 2, 2, 27, 4, 4}, + { 2, 25, 2, 3, 22, 4, 4}, + + // Threshold past for both transformers + { 2, 25, 2, 4, 22, 4, 4}, + { 2, 25, 2, 5, 29, 4, 1}, + { 3, 28, 2, 5, 29, 4, 1}, + + // 1 is slower than 4 now, so it switches + { 4, 30, 2, 5, 29, 4, 4}, + }; + + runMultipleSelections(data); + } + + @Test + public void priorityTest() + { + // 1 and 4 can do PDF->PNG, but 4 is higher priority, even though 1 appears faster + mockTransformer(transformer1, 100, PDF, PNG, 10, 22, 3); + mockTransformer(transformer2, 100, PDF, PNG); + mockTransformer(transformer3, 100, PDF, PNG); + mockTransformer(transformer4, 50, PDF, PNG, 10, 44, 3); + + List expected = Arrays.asList(new ContentTransformer[] {transformer4, transformer1}); + List actual = selector.selectTransformers(PDF, -1, PNG, options); + + assertTransformers("", expected, actual); + } + + @Test + public void priorityAndTimeTest() + { + // 1, 2 and 4 can do PDF->PNG, but 4 is higher priority, even though 1 and 2 appear faster + mockTransformer(transformer1, 100, PDF, PNG, 10, 22, 3); + mockTransformer(transformer2, 100, PDF, PNG, 10, 23, 3); + mockTransformer(transformer3, 100, PDF, PNG); + mockTransformer(transformer4, 50, PDF, PNG, 10, 44, 3); + + List expected = Arrays.asList(new ContentTransformer[] {transformer4, transformer1, transformer2}); + List actual = selector.selectTransformers(PDF, -1, PNG, options); + + assertTransformers("", expected, actual); + } + + @Test + public void priorityAndThresholdTest() + { + // 1, 2 and 4 can do PDF->PNG, but 4 is higher priority, even though 1 and 2 have not reached the threshold + mockTransformer(transformer1, 100, PDF, PNG, 0, 0, 3); + mockTransformer(transformer2, 100, PDF, PNG, 0, 0, 3); + mockTransformer(transformer3, 100, PDF, PNG); + mockTransformer(transformer4, 50, PDF, PNG, 10, 44, 3); + + List expected = Arrays.asList(new ContentTransformer[] {transformer4, transformer1, transformer2}); + List actual = selector.selectTransformers(PDF, -1, PNG, options); + + assertTransformers("", expected, actual); + } +} + +class DummyTransformerStatistics extends TransformerStatisticsImpl +{ + private final long count; + private final long averageTime; + + public DummyTransformerStatistics(long count, long averageTime) + { + super(null, null, null, null, null, 0L, 0L, 0L); + this.count = count; + this.averageTime = averageTime; + } + + @Override + public long getCount() + { + return count; + } + + @Override + public long getAverageTime() + { + return averageTime; + } +} diff --git a/source/java/org/alfresco/repo/content/transform/TransformerStatistics.java b/source/java/org/alfresco/repo/content/transform/TransformerStatistics.java index a5d06f2719..afcf10b51d 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerStatistics.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerStatistics.java @@ -86,5 +86,5 @@ public interface TransformerStatistics /** * Adds 1 to the error count of this TransformationData and its parents. */ - public void recordError(); + public void recordError(long transformationTime); } \ No newline at end of file diff --git a/source/java/org/alfresco/repo/content/transform/TransformerStatisticsImpl.java b/source/java/org/alfresco/repo/content/transform/TransformerStatisticsImpl.java index 1ccd185f5b..120729bae9 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerStatisticsImpl.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerStatisticsImpl.java @@ -21,7 +21,6 @@ package org.alfresco.repo.content.transform; import static org.alfresco.repo.content.transform.TransformerConfig.ANY; import org.alfresco.service.cmr.repository.MimetypeService; -import org.alfresco.service.cmr.repository.TransformationOptionLimits; /** @@ -29,7 +28,6 @@ import org.alfresco.service.cmr.repository.TransformationOptionLimits; * * @author Alan Davis */ -// TODO These values should be made visible via JMX. public class TransformerStatisticsImpl implements TransformerStatistics { private final MimetypeService mimetypeService; @@ -97,19 +95,19 @@ public class TransformerStatisticsImpl implements TransformerStatistics } @Override - public synchronized void recordError() + public synchronized void recordError(long transformationTime) { if (errorCount < Long.MAX_VALUE) { errorCount++; } - if (errorTime > 0) - { - recordTime(errorTime); - } + + // Error time is only recorded for transformer, source and target combinations + recordTime((parent == null || transformer == null || errorTime <= 0 ? transformationTime : errorTime)); + if (parent != null) { - parent.recordError(); + parent.recordError(transformationTime); } } @@ -153,144 +151,4 @@ public class TransformerStatisticsImpl implements TransformerStatistics { return TransformerConfig.ANY.equals(sourceMimetype) && TransformerConfig.ANY.equals(targetMimetype); } - - - - - - - //////////////////////////////////////// TODO Split into summary class /////////////////////////////////// - - - - - -// private enum Property -// { -// priority(true) -// { -// String getValue(TransformerData bean) -// { -// return Integer.toString(bean.getPriority()); -// } -// void setValue(TransformerData bean, String value) -// { -// bean.setPriority(Integer.valueOf(value)); -// } -// }, -// -// averageTime(false) -// { -// String getValue(TransformerData bean) -// { -// return Integer.toString(bean.getPriority()); -// } -// }, -// -// count(false) -// { -// String getValue(TransformerData bean) -// { -// return Integer.toString(bean.getPriority()); -// } -// }, -// -// errors(false) -// { -// String getValue(TransformerData bean) -// { -// return Integer.toString(bean.getPriority()); -// } -// }; -// -// private final boolean updatable; -// -// Property(boolean updatable) -// { -// this.updatable = updatable; -// } -// -// abstract String getValue(TransformerData bean); -// -// void setValue(TransformerData bean, String value) -// { -// } -// -// public boolean isUpdatable() -// { -// return updatable; -// } -// -// public static Set getNames() -// { -// Set names = new HashSet(); -// for (Property property: Property.class.getEnumConstants()) -// { -// names.add(property.name()); -// } -// return names; -// } -// }; -// -// public List getId() -// { -// List id = super.getId(); -// -// id.add(transformerName); -// -// if (TransformerConfig.ANY.equals(sourceExt)) -// { -// id.add(sourceExt); -// } -// -// if (TransformerConfig.ANY.equals(targetExt)) -// { -// id.add(targetExt); -// } -// -// return id; -// } -// -// public boolean isUpdateable(String name) -// { -// return Enum.valueOf(Property.class, name).isUpdatable(); -// } -// -// @Override -// protected PropertyBackedBeanState createInitialState() throws IOException -// { -// return new PropertyBackedBeanState() -// { -// -// @Override -// public Set getPropertyNames() -// { -// return Property.getNames(); -// } -// -// @Override -// public String getProperty(String name) -// { -// return Enum.valueOf(Property.class, name).getValue(TransformerDataImpl.this); -// } -// -// @Override -// public void setProperty(String name, String value) -// { -// Enum.valueOf(Property.class, name).setValue(TransformerDataImpl.this, value); -// } -// -// @Override -// public void start() -// { -// ; -// } -// -// @Override -// public void stop() -// { -// ; -// } -// }; -// } } diff --git a/source/java/org/alfresco/repo/content/transform/magick/AbstractImageMagickContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/magick/AbstractImageMagickContentTransformerWorker.java index 0cfe660c55..040aff8214 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/AbstractImageMagickContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/magick/AbstractImageMagickContentTransformerWorker.java @@ -212,6 +212,24 @@ public abstract class AbstractImageMagickContentTransformerWorker extends Conten } } + @Override + public String getComments(boolean available) + { + StringBuilder sb = new StringBuilder(); + sb.append("# Supports transformations between mimetypes starting with \"image/\", but not\n"); + sb.append("# \""); + sb.append(MimetypeMap.MIMETYPE_IMAGE_RGB); + sb.append("\", \""); + sb.append(MimetypeMap.MIMETYPE_IMAGE_SVG); + sb.append("\" or \""); + sb.append(MimetypeMap.MIMETYPE_IMG_DWG); + sb.append("\".\n"); + sb.append("# eps as source or target.\n"); + sb.append("# pdf or ai to png.\n"); + sb.append("# tiff to pdf.\n"); + return sb.toString(); + } + /** * @see #transformInternal(File, File) */ diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java index 70013c9add..d6264be7f7 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java @@ -306,12 +306,14 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont private boolean isSingleSourcePageRangeRequired(String sourceMimetype, String targetMimetype) { // Need a page source if we're transforming from PDF or TIFF to an image other than TIFF + // or from PSD return ((sourceMimetype.equals(MimetypeMap.MIMETYPE_PDF) || sourceMimetype.equals(MimetypeMap.MIMETYPE_IMAGE_TIFF)) && ((!targetMimetype.equals(MimetypeMap.MIMETYPE_IMAGE_TIFF) && targetMimetype.contains(MIMETYPE_IMAGE_PREFIX)) || targetMimetype.equals(MimetypeMap.MIMETYPE_APPLICATION_PHOTOSHOP) || - targetMimetype.equals(MimetypeMap.MIMETYPE_APPLICATION_EPS))); + targetMimetype.equals(MimetypeMap.MIMETYPE_APPLICATION_EPS)) || + sourceMimetype.equals(MimetypeMap.MIMETYPE_APPLICATION_PHOTOSHOP)); } /** diff --git a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java index 7fa6649229..659f21fa38 100644 --- a/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java +++ b/source/java/org/alfresco/repo/domain/activities/ActivityFeedDAO.java @@ -57,5 +57,5 @@ public interface ActivityFeedDAO extends ActivitiesDAO public List selectSiteFeedEntries(String siteUserId, String format, int maxFeedItems) throws SQLException; - public PagingResults selectPagedUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest) throws SQLException; + public PagingResults selectPagedUserFeedEntries(String feedUserId, String networkId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest) throws SQLException; } diff --git a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java index d8dcd93287..141a8dc5a6 100644 --- a/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/activities/ibatis/ActivityFeedDAOImpl.java @@ -21,6 +21,8 @@ package org.alfresco.repo.domain.activities.ibatis; import java.sql.SQLException; import java.util.ArrayList; import java.util.Date; +import java.util.Iterator; +import java.util.LinkedList; import java.util.List; import org.alfresco.error.AlfrescoRuntimeException; @@ -31,13 +33,28 @@ import org.alfresco.query.PagingResults; import org.alfresco.repo.domain.activities.ActivityFeedDAO; import org.alfresco.repo.domain.activities.ActivityFeedEntity; import org.alfresco.repo.domain.activities.ActivityFeedQueryEntity; +import org.alfresco.repo.tenant.TenantService; import org.alfresco.util.Pair; import org.apache.ibatis.session.RowBounds; public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFeedDAO { - @Override - public long insertFeedEntry(ActivityFeedEntity activityFeed) throws SQLException + private static final int DEFAULT_FETCH_BATCH_SIZE = 150; + + private TenantService tenantService; + private int fetchBatchSize = DEFAULT_FETCH_BATCH_SIZE; + + public void setTenantService(TenantService tenantService) + { + this.tenantService = tenantService; + } + + public void setFetchBatchSize(int fetchBatchSize) + { + this.fetchBatchSize = fetchBatchSize; + } + + public long insertFeedEntry(ActivityFeedEntity activityFeed) throws SQLException { // ALF-17455 temporary assertion that the format is "json" // TODO remove the summary format completely. @@ -45,7 +62,7 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe { throw new AlfrescoRuntimeException("Obsolete summary format specified - only json expected"); } - + template.insert("alfresco.activities.insert.insert_activity_feed", activityFeed); Long id = activityFeed.getId(); return (id != null ? id : -1); @@ -121,8 +138,70 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe { return (List)template.selectList("alfresco.activities.select_activity_user_feeds_greater_than_max", maxFeedSize); } + + public Long countUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, int maxFeedSize) throws SQLException + { + ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); + params.setFeedUserId(feedUserId); + params.setActivitySummaryFormat(format); + + if (minFeedId > -1) + { + params.setMinId(minFeedId); + } + + if (siteId != null) + { + if (excludeThisUser && excludeOtherUsers) + { + return Long.valueOf(0); + } + if ((!excludeThisUser) && (!excludeOtherUsers)) + { + // no excludes => everyone => where feed user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_and_site", params); + } + else if ((excludeThisUser) && (!excludeOtherUsers)) + { + // exclude feed user => others => where feed user is me and post user is not me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_others_and_site", params); + } + else if ((excludeOtherUsers) && (!excludeThisUser)) + { + // exclude others => me => where feed user is me and post user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_me_and_site", params); + } + } + else + { + // all sites + + if (excludeThisUser && excludeOtherUsers) + { + // effectively NOOP - return empty feed + return Long.valueOf(0); + } + if (!excludeThisUser && !excludeOtherUsers) + { + // no excludes => everyone => where feed user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser", params); + } + else if (excludeThisUser) + { + // exclude feed user => others => where feed user is me and post user is not me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_others", params); + } + else if (excludeOtherUsers) + { + // exclude others => me => where feed user is me and post user is me + return (Long)template.selectOne("alfresco.activities.count_activity_feed_for_feeduser_me", params); + } + } + + // belts-and-braces + throw new AlfrescoRuntimeException("Unexpected: invalid arguments"); + } - @SuppressWarnings("unchecked") @Override public List selectSiteFeedsToClean(int maxFeedSize) throws SQLException { @@ -238,8 +317,76 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe }; } + /* + * Get a paged list of activities, filtering out those activities that do not belong to the network "networkId". + */ @SuppressWarnings("unchecked") - public PagingResults selectPagedUserFeedEntries(String feedUserId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest) throws SQLException + private List filterByNetwork(String networkId, String siteId, String sql, ActivityFeedQueryEntity params, PagingRequest pagingRequest) + { + int expectedSkipCount = pagingRequest.getSkipCount(); + // +1 to calculate hasMoreItems + int expectedMaxItems = (pagingRequest.getMaxItems() == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? pagingRequest.getMaxItems() : pagingRequest.getMaxItems() + 1); + + int skipCount = 0; + int maxItems = fetchBatchSize; + + List ret = new LinkedList(); + + int numMatchingItems = 0; + int numAddedItems = 0; + boolean skipping = true; + + List feedEntries = null; + + // fetch activities in batches of size "maxItems" + // iterate through them, filtering out any that don't match the networkId + do + { + RowBounds rowBounds = new RowBounds(skipCount, maxItems); + + feedEntries = (List)template.selectList(sql, params, rowBounds); + Iterator feedEntriesIt = feedEntries.iterator(); + + while(feedEntriesIt.hasNext() && numAddedItems < expectedMaxItems) + { + ActivityFeedEntity activityFeedEntry = feedEntriesIt.next(); + + if(siteId == null) + { + // note: pending requirements for THOR-224, for now assume all activities are within context of site and filter by current tenant + if(!networkId.equals(tenantService.getDomain(activityFeedEntry.getSiteNetwork()))) + { + continue; + } + } + + numMatchingItems++; + + if(skipping) + { + if(numMatchingItems > expectedSkipCount) + { + skipping = false; + } + else + { + continue; + } + } + + ret.add(activityFeedEntry); + + numAddedItems++; + } + + skipCount += maxItems; + } + while(feedEntries != null && feedEntries.size() > 0 && numAddedItems < expectedMaxItems); + + return ret; + } + + public PagingResults selectPagedUserFeedEntries(String feedUserId, String networkId, String format, String siteId, boolean excludeThisUser, boolean excludeOtherUsers, long minFeedId, PagingRequest pagingRequest) throws SQLException { ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); params.setFeedUserId(feedUserId); @@ -250,13 +397,6 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe params.setMinId(minFeedId); } - int skipCount = pagingRequest.getSkipCount(); - int maxItems = pagingRequest.getMaxItems(); - - RowBounds rowBounds = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? - new RowBounds(skipCount, RowBounds.NO_ROW_LIMIT) : - new RowBounds(skipCount, maxItems + 1)); // +1 to check for more items - if (siteId != null) { // given site @@ -270,17 +410,17 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe if ((!excludeThisUser) && (!excludeOtherUsers)) { // no excludes => everyone => where feed user is me - return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_and_site", params, rowBounds)); + return getPagingResults(pagingRequest, filterByNetwork(networkId, siteId, "alfresco.activities.select_activity_feed_for_feeduser_and_site", params, pagingRequest)); } else if ((excludeThisUser) && (!excludeOtherUsers)) { // exclude feed user => others => where feed user is me and post user is not me - return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_others_and_site", params, rowBounds)); + return getPagingResults(pagingRequest, filterByNetwork(networkId, siteId, "alfresco.activities.select_activity_feed_for_feeduser_others_and_site", params, pagingRequest)); } else if ((excludeOtherUsers) && (!excludeThisUser)) { // exclude others => me => where feed user is me and post user is me - return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_me_and_site", params, rowBounds)); + return getPagingResults(pagingRequest, filterByNetwork(networkId, siteId, "alfresco.activities.select_activity_feed_for_feeduser_me_and_site", params, pagingRequest)); } } else @@ -295,17 +435,17 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe if (!excludeThisUser && !excludeOtherUsers) { // no excludes => everyone => where feed user is me - return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser", params, rowBounds)); + return getPagingResults(pagingRequest, filterByNetwork(networkId, siteId, "alfresco.activities.select_activity_feed_for_feeduser", params, pagingRequest)); } else if (excludeThisUser) { // exclude feed user => others => where feed user is me and post user is not me - return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_others", params, rowBounds)); + return getPagingResults(pagingRequest, filterByNetwork(networkId, siteId, "alfresco.activities.select_activity_feed_for_feeduser_others", params, pagingRequest)); } else if (excludeOtherUsers) { // exclude others => me => where feed user is me and post user is me - return getPagingResults(pagingRequest, (List)template.selectList("alfresco.activities.select_activity_feed_for_feeduser_me", params, rowBounds)); + return getPagingResults(pagingRequest, filterByNetwork(networkId, siteId, "alfresco.activities.select_activity_feed_for_feeduser_me", params, pagingRequest)); } } @@ -313,7 +453,6 @@ public class ActivityFeedDAOImpl extends ActivitiesDAOImpl implements ActivityFe throw new AlfrescoRuntimeException("Unexpected: invalid arguments"); } - @SuppressWarnings("unchecked") public Long countSiteFeedEntries(String siteId, String format, int maxFeedSize) throws SQLException { ActivityFeedQueryEntity params = new ActivityFeedQueryEntity(); diff --git a/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java b/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java index ed21d4a4e6..4eb8d0548b 100644 --- a/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/locks/AbstractLockDAOImpl.java @@ -58,6 +58,7 @@ public abstract class AbstractLockDAOImpl implements LockDAO this.qnameDAO = qnameDAO; } + @Override public void getLock(QName lockQName, String lockToken, long timeToLive) { String qnameNamespaceUri = lockQName.getNamespaceURI(); @@ -162,21 +163,33 @@ public abstract class AbstractLockDAOImpl implements LockDAO // Done } + @Override public void refreshLock(QName lockQName, String lockToken, long timeToLive) { - updateLocks(lockQName, lockToken, lockToken, timeToLive); + updateLocks(lockQName, lockToken, lockToken, timeToLive, false); } - public void releaseLock(QName lockQName, String lockToken) + @Override + public boolean releaseLock(QName lockQName, String lockToken, boolean optimistic) { - updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L); + return updateLocks(lockQName, lockToken, LOCK_TOKEN_RELEASED, 0L, optimistic); } /** * Put new values against the given exclusive lock. This works against the related locks as well. - * @throws LockAcquisitionException on failure + * @param optimistic true if a mismatch in the number of locked rows should + * be ignored. + * @return true if the lock was successfully and fully updated + * using the lock token provided + * @throws LockAcquisitionException if the method is pessimistic and the number of rows does not + * correspond to the number expected */ - private void updateLocks(QName lockQName, String lockToken, String newLockToken, long timeToLive) + private boolean updateLocks( + QName lockQName, + String lockToken, + String newLockToken, + long timeToLive, + boolean optimistic) { String qnameNamespaceUri = lockQName.getNamespaceURI(); String qnameLocalName = lockQName.getLocalName(); @@ -213,6 +226,12 @@ public abstract class AbstractLockDAOImpl implements LockDAO // Check if (updateCount != requiredUpdateCount) { + if (optimistic) + { + // We don't mind. Assume success but report that the lock was not removed by us. + return false; + } + // Fall through to error states (pessimistic) if (LOCK_TOKEN_RELEASED.equals(newLockToken)) { throw new LockAcquisitionException( @@ -226,6 +245,11 @@ public abstract class AbstractLockDAOImpl implements LockDAO lockQName, lockToken, new Integer(updateCount), new Integer(requiredUpdateCount)); } } + else + { + // All updated successfully + return true; + } // Done } diff --git a/source/java/org/alfresco/repo/domain/locks/LockDAO.java b/source/java/org/alfresco/repo/domain/locks/LockDAO.java index a85f5e3d66..70b6896ae5 100644 --- a/source/java/org/alfresco/repo/domain/locks/LockDAO.java +++ b/source/java/org/alfresco/repo/domain/locks/LockDAO.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco @@ -14,63 +14,68 @@ * 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 . - */ -package org.alfresco.repo.domain.locks; - -import org.alfresco.repo.lock.LockAcquisitionException; -import org.alfresco.service.namespace.QName; - -/** - * DAO services for alf_lock and related tables - * - * @author Derek Hulley - * @since 3.2 - */ -public interface LockDAO -{ - /** - * Aquire a given exclusive lock, assigning it (and any implicitly shared locks) a - * timeout. All shared locks are implicitly taken as well. - *

- * A lock can be re-taken if it has expired and if the lock token has not changed - * - * @param lockQName the unique name of the lock to acquire - * @param lockToken the potential lock token (max 36 chars) - * @param timeToLive the time (in milliseconds) that the lock must remain - * @return Returns true if the lock was taken, - * otherwise false - * @throws LockAcquisitionException on failure - */ - void getLock(QName lockQName, String lockToken, long timeToLive); - - /** - * Refresh a held lock. This is successful if the lock in question still exists - * and if the lock token has not changed. Lock expiry does not prevent the lock - * from being refreshed. - * - * @param lockQName the unique name of the lock to update - * @param lockToken the lock token for the lock held - * @param timeToLive the new time to live (in milliseconds) - * @return Returns true if the lock was updated, - * otherwise false - * @throws LockAcquisitionException on failure - */ - void refreshLock(QName lockQName, String lockToken, long timeToLive); - - /** - * Release a lock. The lock token must still apply and all the shared and exclusive - * locks need to still be present. Lock expiration does not prevent this operation - * from succeeding. - *

- * Note: Failure to release a lock due to a exception condition is dealt with by - * passing the exception out. - * - * @param lockQName the unique name of the lock to release - * @param lockToken the current lock token - * @return Returns true if all the required locks were - * (still) held under the lock token and were - * valid at the time of release, otherwise false - */ - void releaseLock(QName lockQName, String lockToken); -} + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.locks; + +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.service.namespace.QName; + +/** + * DAO services for alf_lock and related tables + * + * @author Derek Hulley + * @since 3.2 + */ +public interface LockDAO +{ + /** + * Aquire a given exclusive lock, assigning it (and any implicitly shared locks) a + * timeout. All shared locks are implicitly taken as well. + *

+ * A lock can be re-taken if it has expired and if the lock token has not changed + * + * @param lockQName the unique name of the lock to acquire + * @param lockToken the potential lock token (max 36 chars) + * @param timeToLive the time (in milliseconds) that the lock must remain + * @return Returns true if the lock was taken, + * otherwise false + * @throws LockAcquisitionException on failure + */ + void getLock(QName lockQName, String lockToken, long timeToLive); + + /** + * Refresh a held lock. This is successful if the lock in question still exists + * and if the lock token has not changed. Lock expiry does not prevent the lock + * from being refreshed. + * + * @param lockQName the unique name of the lock to update + * @param lockToken the lock token for the lock held + * @param timeToLive the new time to live (in milliseconds) + * @return Returns true if the lock was updated, + * otherwise false + * @throws LockAcquisitionException on failure + */ + void refreshLock(QName lockQName, String lockToken, long timeToLive); + + /** + * Release a lock. The lock token must still apply and all the shared and exclusive + * locks need to still be present, unless the method is optimistic, in which case the + * unlock is considered to be a success.
+ * Lock expiration does not prevent this operation from succeeding. + *

+ * Note: Failure to release a lock due to a exception condition is dealt with by + * passing the exception out. + * + * @param lockQName the unique name of the lock to release + * @param lockToken the current lock token + * @param optimistic true if the release attempt is enough even + * if the number of released locks was incorrect. + * @return true if the lock was successfully (and completely) + * released or false if the lock was no longer valid + * and the method was being called optimistically. + * @throws LockAcquisitionException if the number of locks released was incorrect + * and pessimistic release is requested. + */ + boolean releaseLock(QName lockQName, String lockToken, boolean optimistic); +} diff --git a/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java b/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java index 59c6853a61..d4cab9e4f0 100644 --- a/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java +++ b/source/java/org/alfresco/repo/domain/locks/LockDAOTest.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2005-2010 Alfresco Software Limited. * * This file is part of Alfresco @@ -14,424 +14,450 @@ * 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 . - */ -package org.alfresco.repo.domain.locks; - -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.ReentrantLock; - -import junit.framework.TestCase; - -import org.alfresco.repo.lock.LockAcquisitionException; -import org.alfresco.repo.transaction.AlfrescoTransactionSupport; -import org.alfresco.repo.transaction.RetryingTransactionHelper; -import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.namespace.QName; -import org.alfresco.service.transaction.TransactionService; -import org.alfresco.util.ApplicationContextHelper; -import org.springframework.context.ApplicationContext; - -/** - * @see LockDAO - * - * @author Derek Hulley - * @since 3.2 - */ -public class LockDAOTest extends TestCase -{ - public static final String NAMESPACE = "http://www.alfresco.org/test/LockDAOTest"; - - private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); - - private TransactionService transactionService; - private RetryingTransactionHelper txnHelper; - private LockDAO lockDAO; - // Lock names for the tests - private QName lockA; - private QName lockAA; - private QName lockAAA; - private QName lockAAB; - private QName lockAAC; - private QName lockAB; - private QName lockABA; - private QName lockABB; - private QName lockABC; - - @Override - public void setUp() throws Exception - { - ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); - transactionService = serviceRegistry.getTransactionService(); - txnHelper = transactionService.getRetryingTransactionHelper(); - txnHelper.setMinRetryWaitMs(10); - txnHelper.setRetryWaitIncrementMs(10); - txnHelper.setMaxRetryWaitMs(50); - - lockDAO = (LockDAO) ctx.getBean("lockDAO"); - // Get the test name - String testName = getName(); - // Build lock names for the test - lockA = QName.createQName(NAMESPACE, "a-" + testName); - lockAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName); - lockAAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".a-" + testName); - lockAAB = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".b-" + testName); - lockAAC = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".c-" + testName); - lockAB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName); - lockABA = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".a-" + testName); - lockABB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".b-" + testName); - lockABC = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".c-" + testName); - } - - private String lock(final QName lockName, final long timeToLive, boolean expectSuccess) - { - try - { - String token = lock(lockName, timeToLive); - if (!expectSuccess) - { - fail("Expected lock " + lockName + " to have been denied"); - } - return token; - } - catch (LockAcquisitionException e) - { - if (expectSuccess) - { - // oops - throw new RuntimeException("Expected to get lock " + lockName + " with TTL of " + timeToLive, e); - } - else - { - return null; - } - } - } - /** - * Do the lock in a new transaction - * @return Returns the lock token or null if it didn't work - * @throws LockAcquisitionException on failure - */ - private String lock(final QName lockName, final long timeToLive) - { - RetryingTransactionCallback callback = new RetryingTransactionCallback() - { - public String execute() throws Throwable - { - String txnId = AlfrescoTransactionSupport.getTransactionId(); - lockDAO.getLock(lockName, txnId, timeToLive); - return txnId; - } - }; - return txnHelper.doInTransaction(callback); - } - - private void refresh(final QName lockName, final String lockToken, final long timeToLive, boolean expectSuccess) - { - RetryingTransactionCallback callback = new RetryingTransactionCallback() - { - public Boolean execute() throws Throwable - { - lockDAO.refreshLock(lockName, lockToken, timeToLive); - return Boolean.TRUE; - } - }; - try - { - txnHelper.doInTransaction(callback); - if (!expectSuccess) - { - fail("Expected to have failed to refresh lock " + lockName); - } - } - catch (LockAcquisitionException e) - { - if (expectSuccess) - { - throw new RuntimeException("Expected to have refreshed lock " + lockName, e); - } - } - } - - private void release(final QName lockName, final String lockToken, boolean expectSuccess) - { - RetryingTransactionCallback callback = new RetryingTransactionCallback() - { - public Boolean execute() throws Throwable - { - lockDAO.releaseLock(lockName, lockToken); - return Boolean.TRUE; - } - }; - try - { - txnHelper.doInTransaction(callback); - if (!expectSuccess) - { - fail("Expected to have failed to release lock " + lockName); - } - } - catch (LockAcquisitionException e) - { - if (expectSuccess) - { - throw new RuntimeException("Expected to have released lock " + lockName, e); - } - } - } - - public void testGetLockBasic() throws Exception - { - lock(lockAAA, 500L, true); - } - - /** - * Ensure that the lock tables and queries scale - */ - public void testLockTableScaling() throws Exception - { - int count = 500; - long before = System.currentTimeMillis(); - for (int i = 1; i <= count; i++) - { - QName lockName = QName.createQName(lockAAA.getNamespaceURI(), lockAAA.getLocalName() + "-" + i); - lock(lockName, 500L, true); - if (i % 100 == 0) - { - long after = System.currentTimeMillis(); - System.out.println("Creation of " + i + " locks took " + (after-before)/1000 + "s"); - } - } - } - - public void testGetLockFailureBasic() throws Exception - { - lock(lockAAA, 500L, true); - lock(lockAAA, 0L, false); - } - - public void testSharedLocks() throws Exception - { - lock(lockAAA, 500L, true); - lock(lockAAB, 500L, true); - lock(lockAAC, 500L, true); - lock(lockABA, 500L, true); - lock(lockABB, 500L, true); - lock(lockABC, 500L, true); - } - - public void testExclusiveLockBlockedByShared() throws Exception - { - lock(lockAAA, 5000L, true); - lock(lockAA, 5000L, false); - lock(lockAB, 5000L, true); - lock(lockA, 5000L, false); - lock(lockABA, 5000L, false); - } - - public void testReleaseLockBasic() throws Exception - { - String token = lock(lockAAA, 500000L, true); - release(lockAAA, token, true); - token = lock(lockAAA, 0L, true); - } - - public void testSharedLockAndRelease() throws Exception - { - String tokenAAA = lock(lockAAA, 5000L, true); - String tokenAAB = lock(lockAAB, 5000L, true); - String tokenAAC = lock(lockAAC, 5000L, true); - String tokenABA = lock(lockABA, 5000L, true); - String tokenABB = lock(lockABB, 5000L, true); - String tokenABC = lock(lockABC, 5000L, true); - // Can't lock shared resources - lock(lockAA, 0L, false); - lock(lockAB, 0L, false); - lock(lockA, 0L, false); - // Release a lock and check again - release(lockAAA, tokenAAA, true); - lock(lockAA, 0L, false); - lock(lockAB, 0L, false); - lock(lockA, 0L, false); - // Release a lock and check again - release(lockAAB, tokenAAB, true); - lock(lockAA, 0L, false); - lock(lockAB, 0L, false); - lock(lockA, 0L, false); - // Release a lock and check again - release(lockAAC, tokenAAC, true); - String tokenAA = lock(lockAA, 5000L, true); // This should be open now - lock(lockAB, 0L, false); - lock(lockA, 0L, false); - // Release a lock and check again - release(lockABA, tokenABA, true); - lock(lockAB, 0L, false); - lock(lockA, 0L, false); - // Release a lock and check again - release(lockABB, tokenABB, true); - lock(lockAB, 0L, false); - lock(lockA, 0L, false); - // Release a lock and check again - release(lockABC, tokenABC, true); - String tokenAB = lock(lockAB, 5000L, true); - lock(lockA, 0L, false); - // Release AA and AB - release(lockAA, tokenAA, true); - release(lockAB, tokenAB, true); - String tokenA = lock(lockA, 5000L, true); - // ... and release - release(lockA, tokenA, true); - } - - public synchronized void testLockExpiry() throws Exception - { - lock(lockAAA, 50L, true); - this.wait(100L); - lock(lockAA, 50L, true); - this.wait(100L); - lock(lockA, 100L, true); - } - - /** - * Check that locks grabbed away due to expiry cannot be released - * @throws Exception - */ - public synchronized void testLockExpiryAndRelease() throws Exception - { - String tokenAAA = lock(lockAAA, 500L, true); - release(lockAAA, tokenAAA, true); - tokenAAA = lock(lockAAA, 50L, true); // Make sure we can re-acquire the lock - this.wait(100L); // Wait for expiry - String grabbedTokenAAAA = lock(lockAAA, 50L, true); // Grabbed lock over the expiry - release(lockAAA, tokenAAA, false); // Can't release any more - this.wait(100L); // Wait for expiry - release(lockAAA, grabbedTokenAAAA, true); // Proof that expiry, on it's own, doesn't prevent release - } - - public synchronized void testLockRefresh() throws Exception - { - String tokenAAA = lock(lockAAA, 1000L, true); - // Loop, refreshing and testing - for (int i = 0; i < 40; i++) - { - wait(50L); - // It will have expired, but refresh it anyway - refresh(lockAAA, tokenAAA, 1000L, true); - // Check that it is still holding - lock(lockAAA, 0L, false); - } - } - - /** - * Uses a thread lock to ensure that the lock DAO only allows locks through one at a time. - */ - public synchronized void xtestConcurrentLockAcquisition() throws Exception - { - ReentrantLock threadLock = new ReentrantLock(); - GetLockThread[] threads = new GetLockThread[5]; - for (int i = 0; i < threads.length; i++) - { - threads[i] = new GetLockThread(threadLock); - threads[i].start(); - } - // Wait a bit and see if any encountered errors - boolean allDone = false; - waitLoop: - for (int waitLoop = 0; waitLoop < 500; waitLoop++) - { - wait(1000L); - for (int i = 0; i < threads.length; i++) - { - if (!threads[i].isDone()) - { - continue waitLoop; - } - } - // All the threads are done - allDone = true; - break; - } - // Check that all the threads got a turn - if (!allDone) - { - fail("Not all threads managed to acquire the lock"); - } - // Get errors - StringBuilder errors = new StringBuilder(512); - for (int i = 0; i < threads.length; i++) - { - if (threads[i].error != null) - { - errors.append("\nThread ").append(i).append(" error: ").append(threads[i].error); - } - } - if (errors.toString().length() > 0) - { - fail(errors.toString()); - } - } - - /** - * Checks that the lock via the DAO forces a serialization - */ - private class GetLockThread extends Thread - { - private final ReentrantLock threadLock; - private boolean done; - private String error; - private GetLockThread(ReentrantLock threadLock) - { - this.threadLock = threadLock; - this.done = false; - this.error = null; - setDaemon(true); - } - @Override - public synchronized void run() - { - boolean gotLock = false; - try - { - String tokenAAA = null; - while (true) - { - try - { - tokenAAA = lock(lockAAA, 100000L); // Lock for a long time - // Success - break; - } - catch (LockAcquisitionException e) - { - // OK. Keep trying. - } - try { wait(20L); } catch (InterruptedException e) {} - } - gotLock = threadLock.tryLock(0, TimeUnit.MILLISECONDS); - if (!gotLock) - { - error = "Got lock via DAO but not via thread lock"; - return; - } - release(lockAAA, tokenAAA, true); - } - catch (Throwable e) - { - error = e.getMessage(); - } - finally - { - done = true; - if (gotLock) - { - threadLock.unlock(); - } - } - } - public synchronized boolean isDone() - { - return done; - } - } -} + * along with Alfresco. If not, see . + */ +package org.alfresco.repo.domain.locks; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.ReentrantLock; + +import junit.framework.TestCase; + +import org.alfresco.repo.lock.LockAcquisitionException; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.ApplicationContextHelper; +import org.springframework.context.ApplicationContext; + +/** + * @see LockDAO + * + * @author Derek Hulley + * @since 3.2 + */ +public class LockDAOTest extends TestCase +{ + public static final String NAMESPACE = "http://www.alfresco.org/test/LockDAOTest"; + + private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private TransactionService transactionService; + private RetryingTransactionHelper txnHelper; + private LockDAO lockDAO; + // Lock names for the tests + private QName lockA; + private QName lockAA; + private QName lockAAA; + private QName lockAAB; + private QName lockAAC; + private QName lockAB; + private QName lockABA; + private QName lockABB; + private QName lockABC; + + @Override + public void setUp() throws Exception + { + ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY); + transactionService = serviceRegistry.getTransactionService(); + txnHelper = transactionService.getRetryingTransactionHelper(); + txnHelper.setMinRetryWaitMs(10); + txnHelper.setRetryWaitIncrementMs(10); + txnHelper.setMaxRetryWaitMs(50); + + lockDAO = (LockDAO) ctx.getBean("lockDAO"); + // Get the test name + String testName = getName(); + // Build lock names for the test + lockA = QName.createQName(NAMESPACE, "a-" + testName); + lockAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName); + lockAAA = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".a-" + testName); + lockAAB = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".b-" + testName); + lockAAC = QName.createQName(NAMESPACE, "a-" + testName + ".a-" + testName + ".c-" + testName); + lockAB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName); + lockABA = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".a-" + testName); + lockABB = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".b-" + testName); + lockABC = QName.createQName(NAMESPACE, "a-" + testName + ".b-" + testName + ".c-" + testName); + } + + private String lock(final QName lockName, final long timeToLive, boolean expectSuccess) + { + try + { + String token = lock(lockName, timeToLive); + if (!expectSuccess) + { + fail("Expected lock " + lockName + " to have been denied"); + } + return token; + } + catch (LockAcquisitionException e) + { + if (expectSuccess) + { + // oops + throw new RuntimeException("Expected to get lock " + lockName + " with TTL of " + timeToLive, e); + } + else + { + return null; + } + } + } + /** + * Do the lock in a new transaction + * @return Returns the lock token or null if it didn't work + * @throws LockAcquisitionException on failure + */ + private String lock(final QName lockName, final long timeToLive) + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public String execute() throws Throwable + { + String txnId = AlfrescoTransactionSupport.getTransactionId(); + lockDAO.getLock(lockName, txnId, timeToLive); + return txnId; + } + }; + return txnHelper.doInTransaction(callback); + } + + private void refresh(final QName lockName, final String lockToken, final long timeToLive, boolean expectSuccess) + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Boolean execute() throws Throwable + { + lockDAO.refreshLock(lockName, lockToken, timeToLive); + return Boolean.TRUE; + } + }; + try + { + txnHelper.doInTransaction(callback); + if (!expectSuccess) + { + fail("Expected to have failed to refresh lock " + lockName); + } + } + catch (LockAcquisitionException e) + { + if (expectSuccess) + { + throw new RuntimeException("Expected to have refreshed lock " + lockName, e); + } + } + } + + private void release(final QName lockName, final String lockToken, boolean expectSuccess) + { + RetryingTransactionCallback callback = new RetryingTransactionCallback() + { + public Boolean execute() throws Throwable + { + lockDAO.releaseLock(lockName, lockToken, false); + return Boolean.TRUE; + } + }; + try + { + txnHelper.doInTransaction(callback); + if (!expectSuccess) + { + fail("Expected to have failed to release lock " + lockName); + } + } + catch (LockAcquisitionException e) + { + if (expectSuccess) + { + throw new RuntimeException("Expected to have released lock " + lockName, e); + } + } + } + + public void testGetLockBasic() throws Exception + { + lock(lockAAA, 500L, true); + } + + /** + * Ensure that the lock tables and queries scale + */ + public void testLockTableScaling() throws Exception + { + int count = 500; + long before = System.currentTimeMillis(); + for (int i = 1; i <= count; i++) + { + QName lockName = QName.createQName(lockAAA.getNamespaceURI(), lockAAA.getLocalName() + "-" + i); + lock(lockName, 500L, true); + if (i % 100 == 0) + { + long after = System.currentTimeMillis(); + System.out.println("Creation of " + i + " locks took " + (after-before)/1000 + "s"); + } + } + } + + public void testGetLockFailureBasic() throws Exception + { + lock(lockAAA, 500L, true); + lock(lockAAA, 0L, false); + } + + public void testSharedLocks() throws Exception + { + lock(lockAAA, 500L, true); + lock(lockAAB, 500L, true); + lock(lockAAC, 500L, true); + lock(lockABA, 500L, true); + lock(lockABB, 500L, true); + lock(lockABC, 500L, true); + } + + public void testExclusiveLockBlockedByShared() throws Exception + { + lock(lockAAA, 5000L, true); + lock(lockAA, 5000L, false); + lock(lockAB, 5000L, true); + lock(lockA, 5000L, false); + lock(lockABA, 5000L, false); + } + + public void testReleaseLockBasic() throws Exception + { + String token = lock(lockAAA, 500000L, true); + release(lockAAA, token, true); + token = lock(lockAAA, 0L, true); + } + + public void testReleaseLockRepeated() throws Exception + { + String token = lock(lockAAA, 500000L, true); + release(lockAAA, token, true); + release(lockAAA, token, false); + try + { + lockDAO.releaseLock(lockAAA, token, false); + fail("Pessimistic lock release should have failed."); + } + catch (LockAcquisitionException e) + { + // Expected + } + try + { + boolean released = lockDAO.releaseLock(lockAAA, token, true); + // Expected + assertFalse("Release should have been negative.", released); + } + catch (LockAcquisitionException e) + { + fail("Optimistic lock release should have succeeded."); + } + } + + public void testSharedLockAndRelease() throws Exception + { + String tokenAAA = lock(lockAAA, 5000L, true); + String tokenAAB = lock(lockAAB, 5000L, true); + String tokenAAC = lock(lockAAC, 5000L, true); + String tokenABA = lock(lockABA, 5000L, true); + String tokenABB = lock(lockABB, 5000L, true); + String tokenABC = lock(lockABC, 5000L, true); + // Can't lock shared resources + lock(lockAA, 0L, false); + lock(lockAB, 0L, false); + lock(lockA, 0L, false); + // Release a lock and check again + release(lockAAA, tokenAAA, true); + lock(lockAA, 0L, false); + lock(lockAB, 0L, false); + lock(lockA, 0L, false); + // Release a lock and check again + release(lockAAB, tokenAAB, true); + lock(lockAA, 0L, false); + lock(lockAB, 0L, false); + lock(lockA, 0L, false); + // Release a lock and check again + release(lockAAC, tokenAAC, true); + String tokenAA = lock(lockAA, 5000L, true); // This should be open now + lock(lockAB, 0L, false); + lock(lockA, 0L, false); + // Release a lock and check again + release(lockABA, tokenABA, true); + lock(lockAB, 0L, false); + lock(lockA, 0L, false); + // Release a lock and check again + release(lockABB, tokenABB, true); + lock(lockAB, 0L, false); + lock(lockA, 0L, false); + // Release a lock and check again + release(lockABC, tokenABC, true); + String tokenAB = lock(lockAB, 5000L, true); + lock(lockA, 0L, false); + // Release AA and AB + release(lockAA, tokenAA, true); + release(lockAB, tokenAB, true); + String tokenA = lock(lockA, 5000L, true); + // ... and release + release(lockA, tokenA, true); + } + + public synchronized void testLockExpiry() throws Exception + { + lock(lockAAA, 50L, true); + this.wait(100L); + lock(lockAA, 50L, true); + this.wait(100L); + lock(lockA, 100L, true); + } + + /** + * Check that locks grabbed away due to expiry cannot be released + * @throws Exception + */ + public synchronized void testLockExpiryAndRelease() throws Exception + { + String tokenAAA = lock(lockAAA, 500L, true); + release(lockAAA, tokenAAA, true); + tokenAAA = lock(lockAAA, 50L, true); // Make sure we can re-acquire the lock + this.wait(100L); // Wait for expiry + String grabbedTokenAAAA = lock(lockAAA, 50L, true); // Grabbed lock over the expiry + release(lockAAA, tokenAAA, false); // Can't release any more + this.wait(100L); // Wait for expiry + release(lockAAA, grabbedTokenAAAA, true); // Proof that expiry, on it's own, doesn't prevent release + } + + public synchronized void testLockRefresh() throws Exception + { + String tokenAAA = lock(lockAAA, 1000L, true); + // Loop, refreshing and testing + for (int i = 0; i < 40; i++) + { + wait(50L); + // It will have expired, but refresh it anyway + refresh(lockAAA, tokenAAA, 1000L, true); + // Check that it is still holding + lock(lockAAA, 0L, false); + } + } + + /** + * Uses a thread lock to ensure that the lock DAO only allows locks through one at a time. + */ + public synchronized void xtestConcurrentLockAcquisition() throws Exception + { + ReentrantLock threadLock = new ReentrantLock(); + GetLockThread[] threads = new GetLockThread[5]; + for (int i = 0; i < threads.length; i++) + { + threads[i] = new GetLockThread(threadLock); + threads[i].start(); + } + // Wait a bit and see if any encountered errors + boolean allDone = false; + waitLoop: + for (int waitLoop = 0; waitLoop < 500; waitLoop++) + { + wait(1000L); + for (int i = 0; i < threads.length; i++) + { + if (!threads[i].isDone()) + { + continue waitLoop; + } + } + // All the threads are done + allDone = true; + break; + } + // Check that all the threads got a turn + if (!allDone) + { + fail("Not all threads managed to acquire the lock"); + } + // Get errors + StringBuilder errors = new StringBuilder(512); + for (int i = 0; i < threads.length; i++) + { + if (threads[i].error != null) + { + errors.append("\nThread ").append(i).append(" error: ").append(threads[i].error); + } + } + if (errors.toString().length() > 0) + { + fail(errors.toString()); + } + } + + /** + * Checks that the lock via the DAO forces a serialization + */ + private class GetLockThread extends Thread + { + private final ReentrantLock threadLock; + private boolean done; + private String error; + private GetLockThread(ReentrantLock threadLock) + { + this.threadLock = threadLock; + this.done = false; + this.error = null; + setDaemon(true); + } + @Override + public synchronized void run() + { + boolean gotLock = false; + try + { + String tokenAAA = null; + while (true) + { + try + { + tokenAAA = lock(lockAAA, 100000L); // Lock for a long time + // Success + break; + } + catch (LockAcquisitionException e) + { + // OK. Keep trying. + } + try { wait(20L); } catch (InterruptedException e) {} + } + gotLock = threadLock.tryLock(0, TimeUnit.MILLISECONDS); + if (!gotLock) + { + error = "Got lock via DAO but not via thread lock"; + return; + } + release(lockAAA, tokenAAA, true); + } + catch (Throwable e) + { + error = e.getMessage(); + } + finally + { + done = true; + if (gotLock) + { + threadLock.unlock(); + } + } + } + public synchronized boolean isDone() + { + return done; + } + } +} diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index 271fe2f5a9..c2b58f982f 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -138,6 +138,8 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO private LocaleDAO localeDAO; private UsageDAO usageDAO; private NodeIndexer nodeIndexer; + + private int cachingThreshold = 10; /** * Cache for the Store root nodes by StoreRef:
@@ -225,6 +227,11 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { this.dictionaryService = dictionaryService; } + + public void setCachingThreshold(int cachingThreshold) + { + this.cachingThreshold = cachingThreshold; + } /** * @param policyBehaviourFilter the service to determine the behaviour for cm:auditable and @@ -1022,9 +1029,18 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // The cache says that the node is not there or is deleted. // We double check by going to the DB Node dbNode = selectNodeByNodeRef(nodeRef); - if (dbNode == null || dbNode.getDeleted(qnameDAO)) + if (dbNode == null) { - // The DB agrees. This is an invalid noderef. + // The DB agrees. This is an invalid noderef. Why are you trying to use it? + return null; + } + else if (dbNode.getDeleted(qnameDAO)) + { + // We may have reached this deleted node via an invalid association; trigger a post transaction prune of + // any associations that point to this deleted one + pruneDanglingAssocs(dbNode.getId()); + + // The DB agrees. This is a deleted noderef. return null; } else @@ -1067,7 +1083,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO public boolean handle(Pair childAssocPair, Pair parentNodePair, Pair childNodePair) { - bindFixAssocAndCollectLostAndFound(childNodePair, "childNodeWithDeletedParent", childAssocPair.getFirst(), childAssocPair.getSecond().isPrimary()); + bindFixAssocAndCollectLostAndFound(childNodePair, "childNodeWithDeletedParent", childAssocPair.getFirst(), childAssocPair.getSecond().isPrimary() && exists(childAssocPair.getFirst())); return true; } @@ -1115,9 +1131,18 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // The cache says that the node is not there or is deleted. // We double check by going to the DB Node dbNode = selectNodeById(nodeId); - if (dbNode == null || dbNode.getDeleted(qnameDAO)) + if (dbNode == null) { - // The DB agrees. This is an invalid noderef. + // The DB agrees. This is an invalid noderef. Why are you trying to use it? + return null; + } + else if (dbNode.getDeleted(qnameDAO)) + { + // We may have reached this deleted node via an invalid association; trigger a post transaction prune of + // any associations that point to this deleted one + pruneDanglingAssocs(dbNode.getId()); + + // The DB agrees. This is a deleted noderef. return null; } else @@ -3735,6 +3760,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO { @Override public void afterRollback() + { + afterCommit(); + } + + @Override + public void afterCommit() { if (transactionService.getAllowWrite()) { @@ -4213,7 +4244,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // We have already validated on loading that we have a list in sync with the child node, so if the list is still // empty we have an integrity problem - if (value.getPrimaryParentAssoc() == null && !value.isStoreRoot()) + if (value.getPrimaryParentAssoc() == null && !node.getDeleted(qnameDAO) && !value.isStoreRoot()) { Pair currentNodePair = node.getNodePair(); // We have a corrupt repository - non-root node has a missing parent ?! @@ -4291,11 +4322,12 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO // Now check if we are seeing the correct version of the node if (assocs.isEmpty()) { - // No results. Currently Alfresco has very few parentless nodes (root nodes) - // and the lack of parent associations will be cached, anyway. - // But to match earlier fixes of ALF-12393, we do a double-check of the node's details + // No results. + // Nodes without parents are root nodes or deleted nodes. The latter will not normally + // be accessed here but it is possible. + // To match earlier fixes of ALF-12393, we do a double-check of the node's details. NodeEntity nodeCheckFromDb = selectNodeById(nodeId); - if (nodeCheckFromDb == null || nodeCheckFromDb.getDeleted(qnameDAO) || !nodeCheckFromDb.getNodeVersionKey().equals(nodeVersionKey)) + if (nodeCheckFromDb == null || !nodeCheckFromDb.getNodeVersionKey().equals(nodeVersionKey)) { // The node is gone or has moved on in version invalidateNodeCaches(nodeId); @@ -4452,7 +4484,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO } } - /** + /** * {@inheritDoc} *

* Loads properties, aspects, parent associations and the ID-noderef cache. @@ -4475,7 +4507,7 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO * no results. To avoid unnecessary checking when the cache is PROBABLY cold, we * examine the ratio of hits/misses at regular intervals. */ - if (nodeRefs.size() < 10) + if (nodeRefs.size() < cachingThreshold) { // We only cache where the number of results is potentially // a problem for the N+1 loading that might result. diff --git a/source/java/org/alfresco/repo/domain/node/NodeEntity.java b/source/java/org/alfresco/repo/domain/node/NodeEntity.java index 5fab75606a..1d8611bd3f 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeEntity.java +++ b/source/java/org/alfresco/repo/domain/node/NodeEntity.java @@ -180,7 +180,7 @@ public class NodeEntity implements Node, PermissionCheckValue { NodeRef nodeRef = new NodeRef(store.getStoreRef(), uuid); boolean deleted = getDeleted(qnameDAO); - return new NodeRef.Status(nodeRef, transaction.getChangeTxnId(), transaction.getId(), deleted); + return new NodeRef.Status(id, nodeRef, transaction.getChangeTxnId(), transaction.getId(), deleted); } @Override diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java index 5b8de39c0c..1e3b42c9c0 100644 --- a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrap.java @@ -159,6 +159,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean private static final String DEBUG_SCHEMA_COMP_NO_REF_FILE = "system.schema_comp.debug.no_ref_file"; private static final String INFO_SCHEMA_COMP_ALL_OK = "system.schema_comp.info.all_ok"; private static final String WARN_SCHEMA_COMP_PROBLEMS_FOUND = "system.schema_comp.warn.problems_found"; + private static final String WARN_SCHEMA_COMP_PROBLEMS_FOUND_NO_FILE = "system.schema_comp.warn.problems_found_no_file"; private static final String DEBUG_SCHEMA_COMP_TIME_TAKEN = "system.schema_comp.debug.time_taken"; public static final int DEFAULT_LOCK_RETRY_COUNT = 24; @@ -166,14 +167,10 @@ public class SchemaBootstrap extends AbstractLifecycleBean public static final int DEFAULT_MAX_STRING_LENGTH = 1024; - - - private static volatile int maxStringLength = DEFAULT_MAX_STRING_LENGTH; private Dialect dialect; private ResourcePatternResolver rpr = new PathMatchingResourcePatternResolver(this.getClass().getClassLoader()); - /** * @see PropertyValue#DEFAULT_MAX_STRING_LENGTH @@ -250,9 +247,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean private List preCreateScriptUrls; private List postCreateScriptUrls; private List schemaReferenceUrls; - private List validateUpdateScriptPatches; private List preUpdateScriptPatches; private List postUpdateScriptPatches; + private List updateActivitiScriptPatches; private int schemaUpdateLockRetryCount = DEFAULT_LOCK_RETRY_COUNT; private int schemaUpdateLockRetryWaitSeconds = DEFAULT_LOCK_RETRY_WAIT_SECONDS; private int maximumStringLength; @@ -264,9 +261,9 @@ public class SchemaBootstrap extends AbstractLifecycleBean { preCreateScriptUrls = new ArrayList(1); postCreateScriptUrls = new ArrayList(1); - validateUpdateScriptPatches = new ArrayList(4); preUpdateScriptPatches = new ArrayList(4); postUpdateScriptPatches = new ArrayList(4); + updateActivitiScriptPatches = new ArrayList(4); maximumStringLength = -1; globalProperties = new Properties(); } @@ -322,31 +319,6 @@ public class SchemaBootstrap extends AbstractLifecycleBean this.stopAfterSchemaBootstrap = stopAfterSchemaBootstrap; } - /** - * Set the scripts that must be executed before the schema has been created. - * - * @param postCreateScriptUrls file URLs - * - * @see #PLACEHOLDER_DIALECT - */ - public void setPreCreateScriptUrls(List preUpdateScriptUrls) - { - this.preCreateScriptUrls = preUpdateScriptUrls; - } - - /** - * Set the scripts that must be executed after the schema has been created. - * - * @param postCreateScriptUrls file URLs - * - * @see #PLACEHOLDER_DIALECT - */ - public void setPostCreateScriptUrls(List postUpdateScriptUrls) - { - this.postCreateScriptUrls = postUpdateScriptUrls; - } - - /** * Specifies the schema reference files that will be used to validate the repository * schema whenever changes have been made. The database dialect placeholder will be @@ -360,41 +332,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean { this.schemaReferenceUrls = schemaReferenceUrls; } - - /** - * Set the schema script patches that must have been applied. These will not be - * applied to the database. These can be used where the script cannot be - * applied automatically or where a particular upgrade path is no longer supported. - * For example, at version 3.0, the upgrade scripts for version 1.4 may be considered - * unsupported - this doesn't prevent the manual application of the scripts, though. - * - * @param scriptPatches a list of schema patches to check - */ - public void setValidateUpdateScriptPatches(List scriptPatches) - { - this.validateUpdateScriptPatches = scriptPatches; - } - - /** - * Set the schema script patches that may be applied prior to the auto-update process. - * - * @param scriptPatches a list of schema patches to check - */ - public void setPreUpdateScriptPatches(List scriptPatches) - { - this.preUpdateScriptPatches = scriptPatches; - } - - /** - * Set the schema script patches that may be applied after the auto-update process. - * - * @param postUpdateScriptPatches a list of schema patches to check - */ - public void setPostUpdateScriptPatches(List scriptPatches) - { - this.postUpdateScriptPatches = scriptPatches; - } - + /** * Set the number times that the DB must be checked for the presence of the table * indicating that a schema change is in progress. @@ -476,6 +414,78 @@ public class SchemaBootstrap extends AbstractLifecycleBean return (SessionFactory) localSessionFactory.getObject(); } + /** + * Register a new script for execution when creating a clean schema. The order of registration + * determines the order of execution. + * + * @param preCreateScriptUrl the script URL, possibly containing the ${db.script.dialect} placeholder + */ + public void addPreCreateScriptUrl(String preCreateScriptUrl) + { + if (logger.isDebugEnabled()) + { + logger.debug("Registered create script URL (pre-Hibernate): " + preCreateScriptUrl); + } + this.preCreateScriptUrls.add(preCreateScriptUrl); + } + + /** + * Register a new script for execution after the Hibernate schema creation phase. The order of registration + * determines the order of execution. + * + * @param postCreateScriptUrl the script URL, possibly containing the ${db.script.dialect} placeholder + */ + public void addPostCreateScriptUrl(String postUpdateScriptUrl) + { + if (logger.isDebugEnabled()) + { + logger.debug("Registered create script URL (post-Hibernate): " + postUpdateScriptUrl); + } + this.postCreateScriptUrls.add(postUpdateScriptUrl); + } + + /** + * Register a new SQL-based patch for consideration against the instance (before Hibernate execution) + * + * @param scriptPatch the patch that will be examined for execution + */ + public void addPreUpdateScriptPatch(SchemaUpgradeScriptPatch scriptPatch) + { + if (logger.isDebugEnabled()) + { + logger.debug("Registered script patch (pre-Hibernate): " + scriptPatch.getId()); + } + this.preUpdateScriptPatches.add(scriptPatch); + } + + /** + * Register a new SQL-based patch for consideration against the instance (after Hibernate execution) + * + * @param scriptPatch the patch that will be examined for execution + */ + public void addPostUpdateScriptPatch(SchemaUpgradeScriptPatch scriptPatch) + { + if (logger.isDebugEnabled()) + { + logger.debug("Registered script patch (post-Hibernate): " + scriptPatch.getId()); + } + this.postUpdateScriptPatches.add(scriptPatch); + } + + /** + * Register a new SQL-based patch for consideration against the Activiti instance + * + * @param scriptPatch the patch that will be examined for execution + */ + public void addUpdateActivitiScriptPatch(SchemaUpgradeScriptPatch scriptPatch) + { + if (logger.isDebugEnabled()) + { + logger.debug("Registered Activiti script patch: " + scriptPatch.getId()); + } + this.updateActivitiScriptPatches.add(scriptPatch); + } + private static class NoSchemaException extends Exception { private static final long serialVersionUID = 5574280159910824660L; @@ -603,6 +613,40 @@ public class SchemaBootstrap extends AbstractLifecycleBean } } + + /** + * Check whether Activiti tables already created in db. + * + * @param cfg The Hibernate config + * @param connection a valid database connection + * @return true if Activiti tables already created in schema, otherwise false + */ + private boolean checkActivitiTablesExists(Configuration cfg, Connection connection) + { + Statement stmt = null; + try + { + stmt = connection.createStatement(); + stmt.executeQuery("select count(id_) from act_ru_task"); + return true; + } + catch (SQLException e) + { + return false; + } + finally + { + try + { + if (stmt != null) + { + stmt.close(); + } + } + catch (Throwable e) {} + } + } + /** * @return Returns the name of the applied patch table, or null if the table doesn't exist */ @@ -785,12 +829,20 @@ public class SchemaBootstrap extends AbstractLifecycleBean String dialectStr = dialect.getClass().getSimpleName(); // Initialise Activiti DB, using an unclosable connection - if(create) + if(!checkActivitiTablesExists(cfg, connection)) { // Activiti DB updates are performed as patches in alfresco, only give // control to activiti when creating new one. initialiseActivitiDBSchema(new UnclosableConnection(connection)); } + else + { + // Execute any auto-update scripts for Activiti tables + checkSchemaPatchScripts(cfg, connection, updateActivitiScriptPatches, true); + + // verify that all Activiti patches have been applied correctly + checkSchemaPatchScripts(cfg, connection, updateActivitiScriptPatches, false); + } if (create) { @@ -841,8 +893,6 @@ public class SchemaBootstrap extends AbstractLifecycleBean } else { - // Check for scripts that must have been run - checkSchemaPatchScripts(cfg, connection, validateUpdateScriptPatches, false); // Execute any pre-auto-update scripts checkSchemaPatchScripts(cfg, connection, preUpdateScriptPatches, true); @@ -1090,7 +1140,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean if (executedStatements == null) { // Validate the schema, pre-upgrade - validateSchema("Alfresco-{0}-Validation-Pre-Upgrade-{1}-"); + validateSchema("Alfresco-{0}-Validation-Pre-Upgrade-{1}-", null); dumpSchema("pre-upgrade"); @@ -1492,7 +1542,6 @@ public class SchemaBootstrap extends AbstractLifecycleBean @Override public synchronized void onBootstrap(ApplicationEvent event) { - // from Thor if (event != null) { // Use the application context to load resources @@ -1579,7 +1628,6 @@ public class SchemaBootstrap extends AbstractLifecycleBean if (! createdSchema) { // verify that all patches have been applied correctly - checkSchemaPatchScripts(cfg, connection, validateUpdateScriptPatches, false); // check scripts checkSchemaPatchScripts(cfg, connection, preUpdateScriptPatches, false); // check scripts checkSchemaPatchScripts(cfg, connection, postUpdateScriptPatches, false); // check scripts } @@ -1595,7 +1643,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean if (executedStatements != null) { // Validate the schema, post-upgrade - validateSchema("Alfresco-{0}-Validation-Post-Upgrade-{1}-"); + validateSchema("Alfresco-{0}-Validation-Post-Upgrade-{1}-", null); // 4.0+ schema dump dumpSchema("post-upgrade"); @@ -1666,7 +1714,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean * @param outputFileNameTemplate * @return the number of potential problems found. */ - public synchronized int validateSchema(String outputFileNameTemplate) + public synchronized int validateSchema(String outputFileNameTemplate, PrintWriter out) { int totalProblems = 0; @@ -1684,7 +1732,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean else { // Validate schema against each reference file - int problems = validateSchema(referenceResource, outputFileNameTemplate); + int problems = validateSchema(referenceResource, outputFileNameTemplate, out); totalProblems += problems; } } @@ -1693,11 +1741,11 @@ public class SchemaBootstrap extends AbstractLifecycleBean return totalProblems; } - private int validateSchema(Resource referenceResource, String outputFileNameTemplate) + private int validateSchema(Resource referenceResource, String outputFileNameTemplate, PrintWriter out) { try { - return attemptValidateSchema(referenceResource, outputFileNameTemplate); + return attemptValidateSchema(referenceResource, outputFileNameTemplate, out); } catch (Throwable e) { @@ -1709,7 +1757,7 @@ public class SchemaBootstrap extends AbstractLifecycleBean } } - private int attemptValidateSchema(Resource referenceResource, String outputFileNameTemplate) + private int attemptValidateSchema(Resource referenceResource, String outputFileNameTemplate, PrintWriter out) { Date startTime = new Date(); @@ -1745,22 +1793,30 @@ public class SchemaBootstrap extends AbstractLifecycleBean dialect.getClass().getSimpleName(), reference.getDbPrefix() }; - String outputFileName = MessageFormat.format(outputFileNameTemplate, outputFileNameParams); - - File outputFile = TempFileProvider.createTempFile(outputFileName, ".txt"); - - PrintWriter pw = null; - try + PrintWriter pw; + File outputFile = null; + if (out == null) { - pw = new PrintWriter(outputFile, SchemaComparator.CHAR_SET); + String outputFileName = MessageFormat.format(outputFileNameTemplate, outputFileNameParams); + + outputFile = TempFileProvider.createTempFile(outputFileName, ".txt"); + + try + { + pw = new PrintWriter(outputFile, SchemaComparator.CHAR_SET); + } + catch (FileNotFoundException error) + { + throw new RuntimeException("Unable to open file for writing: " + outputFile); + } + catch (UnsupportedEncodingException error) + { + throw new RuntimeException("Unsupported char set: " + SchemaComparator.CHAR_SET, error); + } } - catch (FileNotFoundException error) + else { - throw new RuntimeException("Unable to open file for writing: " + outputFile); - } - catch (UnsupportedEncodingException error) - { - throw new RuntimeException("Unsupported char set: " + SchemaComparator.CHAR_SET, error); + pw = out; } // Populate the file with details of the comparison's results. @@ -1779,7 +1835,14 @@ public class SchemaBootstrap extends AbstractLifecycleBean else { int numProblems = results.size(); - LogUtil.warn(logger, WARN_SCHEMA_COMP_PROBLEMS_FOUND, numProblems, outputFile); + if (outputFile == null) + { + LogUtil.warn(logger, WARN_SCHEMA_COMP_PROBLEMS_FOUND_NO_FILE, numProblems); + } + else + { + LogUtil.warn(logger, WARN_SCHEMA_COMP_PROBLEMS_FOUND, numProblems, outputFile); + } } Date endTime = new Date(); long durationMillis = endTime.getTime() - startTime.getTime(); diff --git a/source/java/org/alfresco/repo/domain/schema/SchemaBootstrapRegistration.java b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrapRegistration.java new file mode 100644 index 0000000000..7c2611c6be --- /dev/null +++ b/source/java/org/alfresco/repo/domain/schema/SchemaBootstrapRegistration.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.domain.schema; + +import java.util.Collections; +import java.util.List; + +import org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch; +import org.alfresco.util.PropertyCheck; + +/** + * Registers a list of create scripts. + * + * @author Derek Hulley + * @since 4.2 + */ +public class SchemaBootstrapRegistration +{ + private SchemaBootstrap schemaBootstrap; + private List preCreateScriptUrls; + private List postCreateScriptUrls; + private List preUpdateScriptPatches; + private List postUpdateScriptPatches; + private List updateActivitiScriptPatches; + + public SchemaBootstrapRegistration() + { + this.preCreateScriptUrls = Collections.emptyList(); + this.postCreateScriptUrls = Collections.emptyList(); + this.preUpdateScriptPatches = Collections.emptyList(); + this.postUpdateScriptPatches = Collections.emptyList(); + this.updateActivitiScriptPatches = Collections.emptyList(); + } + + /** + * @param schemaBootstrap the component with which to register the URLs + */ + public void setSchemaBootstrap(SchemaBootstrap schemaBootstrap) + { + this.schemaBootstrap = schemaBootstrap; + } + + /** + * @param preCreateScriptUrls a list of schema create URLs that will be registered in order. + * + * @see SchemaBootstrap#addPreCreateScriptUrl(String) + */ + public void setPreCreateScriptUrls(List preCreateScriptUrls) + { + this.preCreateScriptUrls = preCreateScriptUrls; + } + + /** + * @param postCreateScriptUrls a list of schema create URLs that will be registered in order. + * + * @see SchemaBootstrap#addPostCreateScriptUrl(String) + */ + public void setPostCreateScriptUrls(List preCreateScriptUrls) + { + this.postCreateScriptUrls = preCreateScriptUrls; + } + + /** + * @param updateActivitiScriptPatches a list of schema upgade script patches for Activiti tables to execute + * + * @see SchemaBootstrap#addUpdateActivitiScriptPatch(org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch) + */ + public void setUpdateActivitiScriptPatches(List updateActivitiScriptPatches) + { + this.updateActivitiScriptPatches = updateActivitiScriptPatches; + } + + /** + * @param preUpdateScriptPatches a list of schema upgade script patches to execute before Hibernate patching + * + * @see SchemaBootstrap#addPreUpdateScriptPatch(org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch) + */ + public void setPreUpdateScriptPatches(List preUpdateScriptPatches) + { + this.preUpdateScriptPatches = preUpdateScriptPatches; + } + + /** + * @param postUpdateScriptPatches a list of schema upgade script patches to execute after Hibernate patching + * + * @see SchemaBootstrap#addPostUpdateScriptPatch(org.alfresco.repo.admin.patch.impl.SchemaUpgradeScriptPatch) + */ + public void setPostUpdateScriptPatches(List postUpdateScriptPatches) + { + this.postUpdateScriptPatches = postUpdateScriptPatches; + } + + /** + * Registers all the necessary scripts and patches with the {@link SchemaBootstrap}. + */ + public void register() + { + PropertyCheck.mandatory(this, "schemaBootstrap", schemaBootstrap); + PropertyCheck.mandatory(this, "preCreateScriptUrls", preCreateScriptUrls); + PropertyCheck.mandatory(this, "postCreateScriptUrls", postCreateScriptUrls); + PropertyCheck.mandatory(this, "preUpdateScriptPatches", preUpdateScriptPatches); + PropertyCheck.mandatory(this, "postUpdateScriptPatches", postUpdateScriptPatches); + PropertyCheck.mandatory(this, "updateActivitiScriptPatches", updateActivitiScriptPatches); + + for (String preCreateScriptUrl : preCreateScriptUrls) + { + schemaBootstrap.addPreCreateScriptUrl(preCreateScriptUrl); + } + for (String postCreateScriptUrl : postCreateScriptUrls) + { + schemaBootstrap.addPostCreateScriptUrl(postCreateScriptUrl); + } + for (SchemaUpgradeScriptPatch preUpdateScriptPatch : preUpdateScriptPatches) + { + schemaBootstrap.addPreUpdateScriptPatch(preUpdateScriptPatch); + } + for (SchemaUpgradeScriptPatch postUpdateScriptPatch : postUpdateScriptPatches) + { + schemaBootstrap.addPostUpdateScriptPatch(postUpdateScriptPatch); + } + for (SchemaUpgradeScriptPatch updateActivitiScriptPatch : updateActivitiScriptPatches) + { + schemaBootstrap.addUpdateActivitiScriptPatch(updateActivitiScriptPatch); + } + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/favourites/FavouritesServiceImpl.java b/source/java/org/alfresco/repo/favourites/FavouritesServiceImpl.java new file mode 100644 index 0000000000..c93442b864 --- /dev/null +++ b/source/java/org/alfresco/repo/favourites/FavouritesServiceImpl.java @@ -0,0 +1,924 @@ +package org.alfresco.repo.favourites; + +import java.io.Serializable; +import java.text.Collator; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.StringTokenizer; +import java.util.TreeMap; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.query.PageDetails; +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.favourites.PersonFavourite.PersonFavouriteKey; +import org.alfresco.repo.policy.ClassPolicy; +import org.alfresco.repo.policy.ClassPolicyDelegate; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.security.authentication.AuthenticationContext; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.site.SiteModel; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.favourites.FavouritesService; +import org.alfresco.service.cmr.preference.PreferenceService; +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.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.site.SiteInfo; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO8601DateFormat; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * Favourites service implementation that uses the PreferencesService for persisting favourites. + * + * Unfortunately, we are tied to the PreferencesService and to the preference names and data structure because Share uses the PreferenceService directly. + * + * @author steveglover + */ +public class FavouritesServiceImpl implements FavouritesService, InitializingBean +{ + private static final Log logger = LogFactory.getLog(FavouritesServiceImpl.class); + + private Map prefKeys; + + private PreferenceService preferenceService; + private NodeService nodeService; + private DictionaryService dictionaryService; + private SiteService siteService; + private PolicyComponent policyComponent; + private PermissionService permissionService; + private PersonService personService; + + /** Authentication Service */ + private AuthenticationContext authenticationContext; + + private ClassPolicyDelegate onAddFavouriteDelegate; + private ClassPolicyDelegate onRemoveFavouriteDelegate; + + private Collator collator = Collator.getInstance(); + + public interface OnAddFavouritePolicy extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onAddfavourite"); + + /** + * Called after a node has been favourited + * + * @param userName the username of the person who favourited the node + * @param nodeRef the node which was favourited + */ + public void onAddFavourite(String userName, NodeRef nodeRef); + } + + public interface OnRemoveFavouritePolicy extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "onRemovefavourite"); + + /** + * Called after a favourite has been removed + * + * @param userName the username of the person who favourited the node + * @param nodeRef the node that was un-favourited + */ + public void onRemoveFavourite(String userName, NodeRef nodeRef); + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setAuthenticationContext(AuthenticationContext authenticationContext) + { + this.authenticationContext = authenticationContext; + } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + public void setSiteService(SiteService siteService) + { + this.siteService = siteService; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + public void setPreferenceService(PreferenceService preferenceService) + { + this.preferenceService = preferenceService; + } + + private static class PrefKeys + { + private String sharePrefKey; + private String alfrescoPrefKey; + + public PrefKeys(String sharePrefKey, String alfrescoPrefKey) + { + super(); + this.sharePrefKey = sharePrefKey; + this.alfrescoPrefKey = alfrescoPrefKey; + } + + public String getSharePrefKey() + { + return sharePrefKey; + } + + public String getAlfrescoPrefKey() + { + return alfrescoPrefKey; + } + } + + @Override + public void afterPropertiesSet() throws Exception + { + this.prefKeys = new HashMap(); + this.prefKeys.put(Type.SITE, new PrefKeys("org.alfresco.share.sites.favourites.", "org.alfresco.ext.sites.favourites.")); + this.prefKeys.put(Type.FILE, new PrefKeys("org.alfresco.share.documents.favourites", "org.alfresco.ext.documents.favourites.")); + this.prefKeys.put(Type.FOLDER, new PrefKeys("org.alfresco.share.folders.favourites", "org.alfresco.ext.folders.favourites.")); + } + + public void init() + { + this.onAddFavouriteDelegate = policyComponent.registerClassPolicy(OnAddFavouritePolicy.class); + this.onRemoveFavouriteDelegate = policyComponent.registerClassPolicy(OnRemoveFavouritePolicy.class); + } + + private PrefKeys getPrefKeys(Type type) + { + PrefKeys prefKey = prefKeys.get(type); + return prefKey; + } + + private boolean removeFavouriteSite(String userName, NodeRef nodeRef) + { + PrefKeys prefKeys = getPrefKeys(Type.SITE); + boolean exists = false; + + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey()); + sitePrefKeyBuilder.append(siteInfo.getShortName()); + String sitePrefKey = sitePrefKeyBuilder.toString(); + + String siteFavouritedKey = siteFavouritedKey(siteInfo); + + exists = preferenceService.getPreference(userName, siteFavouritedKey) != null; + preferenceService.clearPreferences(userName, sitePrefKey); + } + else + { + throw new IllegalArgumentException("NodeRef " + nodeRef + " is not a site"); + } + + return exists; + } + + private String siteFavouritedKey(SiteInfo siteInfo) + { + PrefKeys prefKeys = getPrefKeys(Type.SITE); + + StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getSharePrefKey()); + sitePrefKeyBuilder.append(siteInfo.getShortName()); + String sitePrefKey = sitePrefKeyBuilder.toString(); + + String favouritedKey = sitePrefKey; + return favouritedKey; + } + + private String siteCreatedAtKey(SiteInfo siteInfo) + { + PrefKeys prefKeys = getPrefKeys(Type.SITE); + + StringBuilder sitePrefKeyBuilder = new StringBuilder(prefKeys.getAlfrescoPrefKey()); + sitePrefKeyBuilder.append(siteInfo.getShortName()); + String sitePrefKey = sitePrefKeyBuilder.toString(); + + StringBuilder createdAtKeyBuilder = new StringBuilder(sitePrefKey); + createdAtKeyBuilder.append(".createdAt"); + String createdAtKey = createdAtKeyBuilder.toString(); + return createdAtKey; + } + + private Comparator getComparator(final List> sortProps) + { + Comparator comparator = new Comparator() + { + @Override + public int compare(PersonFavouriteKey o1, PersonFavouriteKey o2) + { + int ret = 0; + for(Pair sort : sortProps) + { + FavouritesService.SortFields field = sort.getFirst(); + Boolean ascending = sort.getSecond(); + if(field.equals(FavouritesService.SortFields.username)) + { + if(ascending) + { + ret = collator.compare(o1.getUserName(), o2.getUserName()); + } + else + { + ret = o2.getUserName().compareTo(o1.getUserName()); + } + + if(ret != 0) + { + break; + } + } + else if(field.equals(FavouritesService.SortFields.type)) + { + if(ascending) + { + ret = o1.getType().compareTo(o2.getType()); + } + else + { + ret = o2.getType().compareTo(o1.getType()); + } + + if(ret != 0) + { + break; + } + } + else if(field.equals(FavouritesService.SortFields.createdAt)) + { + if(ascending) + { + if(o1.getCreatedAt() != null && o2.getCreatedAt() != null) + { + ret = o1.getCreatedAt().compareTo(o2.getCreatedAt()); + } + } + else + { + if(o1.getCreatedAt() != null && o2.getCreatedAt() != null) + { + ret = o2.getCreatedAt().compareTo(o1.getCreatedAt()); + } + } + + if(ret != 0) + { + break; + } + } + else if(field.equals(FavouritesService.SortFields.title)) + { + if(ascending) + { + if(o1.getTitle() != null && o2.getTitle() != null) + { + ret = collator.compare(o1.getTitle(), o2.getTitle()); + } + } + else + { + if(o1.getTitle() != null && o2.getTitle() != null) + { + ret = collator.compare(o2.getTitle(), o1.getTitle()); + } + } + + if(ret != 0) + { + break; + } + } + } + + if(ret == 0) + { + // some favourites may not have a createdAt value, rendering this comparator less selective. + // If the favourites are still regarded as the same, differentiate on nodeRef. + ret = o1.getNodeRef().toString().compareTo(o2.getNodeRef().toString()); + } + + return ret; + } + }; + return comparator; + } + + private PersonFavourite addFavouriteSite(String userName, NodeRef nodeRef) + { + PersonFavourite favourite = null; + + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + favourite = getFavouriteSite(userName, siteInfo); + if(favourite == null) + { + Map preferences = new HashMap(1); + + String siteFavouritedKey = siteFavouritedKey(siteInfo); + preferences.put(siteFavouritedKey, Boolean.TRUE); + + // ISO8601 string format: PreferenceService works with strings only for dates it seems + String siteCreatedAtKey = siteCreatedAtKey(siteInfo); + Date createdAt = new Date(); + String createdAtStr = ISO8601DateFormat.format(createdAt); + preferences.put(siteCreatedAtKey, createdAtStr); + + preferenceService.setPreferences(userName, preferences); + + favourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteInfo.getTitle(), createdAt); + + QName nodeClass = nodeService.getType(nodeRef); + OnAddFavouritePolicy policy = onAddFavouriteDelegate.get(nodeRef, nodeClass); + policy.onAddFavourite(userName, nodeRef); + } + } + else + { + // shouldn't happen, getType recognizes it as a site or subtype + logger.warn("Unable to get site for " + nodeRef); + } + + return favourite; + } + + private PersonFavourite getFavouriteSite(String userName, SiteInfo siteInfo) + { + PersonFavourite favourite = null; + + String siteFavouritedKey = siteFavouritedKey(siteInfo); + String siteCreatedAtKey = siteCreatedAtKey(siteInfo); + + Boolean isFavourited = false; + Serializable s = preferenceService.getPreference(userName, siteFavouritedKey); + if(s != null) + { + if(s instanceof String) + { + isFavourited = Boolean.valueOf((String)s); + } + else if(s instanceof Boolean) + { + isFavourited = (Boolean)s; + } + else + { + throw new AlfrescoRuntimeException("Unexpected favourites preference value"); + } + } + + if(isFavourited) + { + String createdAtStr = (String)preferenceService.getPreference(userName, siteCreatedAtKey); + Date createdAt = (createdAtStr == null ? null : ISO8601DateFormat.parse(createdAtStr)); + + favourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteInfo.getTitle(), createdAt); + } + + return favourite; + } + + private boolean isFavouriteSite(String userName, NodeRef nodeRef) + { + Boolean isFavourited = Boolean.FALSE; + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + String favouritedPrefKey = siteFavouritedKey(siteInfo); + Serializable value = preferenceService.getPreference(userName, favouritedPrefKey); + + if(value != null) + { + if(value instanceof String) + { + isFavourited = Boolean.valueOf((String)value); + } + else if(value instanceof String) + { + isFavourited = (Boolean)value; + } + else + { + throw new AlfrescoRuntimeException("Unexpected favourites preference value"); + } + } + } + else + { + throw new IllegalArgumentException("NodeRef " + nodeRef + " is not a site"); + } + + return isFavourited.booleanValue(); + } + + private void updateFavouriteNodes(String userName, Type type, Map favouriteNodes) + { + PrefKeys prefKeys = getPrefKeys(type); + + Map preferences = new HashMap(1); + + StringBuilder values = new StringBuilder(); + for(PersonFavourite node : favouriteNodes.values()) + { + NodeRef nodeRef = node.getNodeRef(); + + values.append(nodeRef.toString()); + values.append(","); + + // ISO8601 string format: PreferenceService works with strings only for dates it seems + StringBuilder sb = new StringBuilder(prefKeys.getAlfrescoPrefKey()); + sb.append(nodeRef.toString()); + sb.append(".createdAt"); + String createdAtKey = sb.toString(); + Date createdAt = node.getCreatedAt(); + if(createdAt != null) + { + String createdAtStr = ISO8601DateFormat.format(createdAt); + preferences.put(createdAtKey, createdAtStr); + } + } + + if(values.length() > 1) + { + values.delete(values.length() - 1, values.length()); + } + + preferences.put(prefKeys.getSharePrefKey(), values.toString()); + preferenceService.setPreferences(userName, preferences); + } + + private Map getFavouriteNodes(String userName, Type type) + { + PrefKeys prefKeys = getPrefKeys(type); + Map favouriteNodes = Collections.emptyMap(); + Map prefs = preferenceService.getPreferences(userName, prefKeys.getSharePrefKey()); + String nodes = (String)prefs.get(prefKeys.getSharePrefKey()); + if(nodes != null) + { + favouriteNodes = extractFavouriteNodes(userName, type, nodes); + } + else + { + favouriteNodes = new HashMap(); + } + + return favouriteNodes; + } + + /* + * Extract favourite nodes of the given type from the comma-separated list in "nodes". + */ + private Map extractFavouriteNodes(String userName, Type type, String nodes) + { + PrefKeys prefKeys = getPrefKeys(type); + Map favouriteNodes = new HashMap(); + + StringTokenizer st = new StringTokenizer(nodes, ","); + while(st.hasMoreTokens()) + { + String nodeRefStr = st.nextToken(); + nodeRefStr = nodeRefStr.trim(); + if(!NodeRef.isNodeRef((String)nodeRefStr)) + { + continue; + } + + NodeRef nodeRef = new NodeRef((String)nodeRefStr); + + if(!nodeService.exists(nodeRef)) + { + continue; + } + + if(permissionService.hasPermission(nodeRef, PermissionService.READ_PROPERTIES) == AccessStatus.DENIED) + { + continue; + } + + // get createdAt for this favourited node + // use ISO8601 + StringBuilder builder = new StringBuilder(prefKeys.getAlfrescoPrefKey()); + builder.append(nodeRef.toString()); + builder.append(".createdAt"); + String prefKey = builder.toString(); + String createdAtStr = (String)preferenceService.getPreference(userName, prefKey); + Date createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr): null); + + String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + + PersonFavourite personFavourite = new PersonFavourite(userName, nodeRef, type, name, createdAt); + PersonFavouriteKey key = personFavourite.getKey(); + favouriteNodes.put(key, personFavourite); + } + + return favouriteNodes; + } + + private void extractFavouriteSite(String userName, Type type, Map sortedFavouriteNodes, Map preferences, String key) + { + // preference value indicates whether the site has been favourited + Serializable pref = preferences.get(key); + Boolean isFavourite = (Boolean)pref; + if(isFavourite) + { + PrefKeys sitePrefKeys = getPrefKeys(Type.SITE); + int length = sitePrefKeys.getSharePrefKey().length(); + String siteId = key.substring(length); + + try + { + SiteInfo siteInfo = siteService.getSite(siteId); + if(siteInfo != null) + { + StringBuilder builder = new StringBuilder(sitePrefKeys.getAlfrescoPrefKey()); + builder.append(siteId); + builder.append(".createdAt"); + String createdAtPrefKey = builder.toString(); + String createdAtStr = (String)preferences.get(createdAtPrefKey); + Date createdAt = null; + if(createdAtStr != null) + { + createdAt = (createdAtStr != null ? ISO8601DateFormat.parse(createdAtStr): null); + } + PersonFavourite personFavourite = new PersonFavourite(userName, siteInfo.getNodeRef(), Type.SITE, siteInfo.getTitle(), createdAt); + sortedFavouriteNodes.put(personFavourite.getKey(), personFavourite); + } + } + catch(AccessDeniedException ex) + { + // the user no longer has access to this site, skip over the favourite + // TODO remove the favourite preference + return; + } + } + } + + private PersonFavourite getFavouriteDocumentOrFolder(String userName, Type type, NodeRef nodeRef) + { + PersonFavourite favourite = null; + + PrefKeys prefKeys = getPrefKeys(type); + Map preferences = preferenceService.getPreferences(userName, prefKeys.getSharePrefKey()); + String nodes = (String)preferences.get(prefKeys.getSharePrefKey()); + if(nodes != null) + { + Map favouriteNodes = extractFavouriteNodes(userName, type, nodes); + String title = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_TITLE); + PersonFavouriteKey key = new PersonFavouriteKey(userName, title, type, nodeRef); + favourite = favouriteNodes.get(key); + } + return favourite; + } + + private PersonFavourite getPersonFavourite(String userName, Type type, NodeRef nodeRef) + { + PersonFavourite ret = null; + if(type.equals(Type.SITE)) + { + SiteInfo siteInfo = siteService.getSite(nodeRef); + if(siteInfo != null) + { + ret = getFavouriteSite(userName, siteInfo); + } + else + { + // shouldn't happen, getType recognizes it as a site or subtype + logger.warn("Unable to get site for " + nodeRef); + } + } + else if(type.equals(Type.FILE)) + { + ret = getFavouriteDocumentOrFolder(userName, type, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + ret = getFavouriteDocumentOrFolder(userName, type, nodeRef); + } + else + { + // shouldn't happen + throw new AlfrescoRuntimeException("Unexpected favourite type"); + } + + return ret; + } + + private PersonFavourite addFavouriteDocumentOrFolder(String userName, Type type, NodeRef nodeRef) + { + Map personFavourites = getFavouriteNodes(userName, type); + PersonFavourite personFavourite = getPersonFavourite(userName, type, nodeRef); + if(personFavourite == null) + { + Date createdAt = new Date(); + String name = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_NAME); + personFavourite = new PersonFavourite(userName, nodeRef, type, name, createdAt); + personFavourites.put(personFavourite.getKey(), personFavourite); + updateFavouriteNodes(userName, type, personFavourites); + + QName nodeClass = nodeService.getType(nodeRef); + OnAddFavouritePolicy policy = onAddFavouriteDelegate.get(nodeRef, nodeClass); + policy.onAddFavourite(userName, nodeRef); + } + + return personFavourite; + } + + private boolean isFavouriteNode(String userName, Type type, NodeRef nodeRef) + { + Map personFavourites = getFavouriteNodes(userName, type); + PersonFavouriteKey personFavouriteKey = new PersonFavouriteKey(userName, null, type, nodeRef); + boolean isFavourite = personFavourites.containsKey(personFavouriteKey); + return isFavourite; + } + + public Type getType(NodeRef nodeRef) + { + Type favouriteType = null; + + QName type = nodeService.getType(nodeRef); + boolean isSite = dictionaryService.isSubClass(type, SiteModel.TYPE_SITE); + if(isSite) + { + favouriteType = Type.SITE; + } + else + { + boolean isContainer = (dictionaryService.isSubClass(type, ContentModel.TYPE_FOLDER) && + !dictionaryService.isSubClass(type, ContentModel.TYPE_SYSTEM_FOLDER)); + if(isContainer) + { + favouriteType = Type.FOLDER; + } + else + { + boolean isFile = dictionaryService.isSubClass(type, ContentModel.TYPE_CONTENT); + if(isFile) + { + favouriteType = Type.FILE; + } + } + } + + return favouriteType; + } + + private boolean removeFavouriteNode(String userName, Type type, NodeRef nodeRef) + { + boolean exists = false; + + Map personFavourites = getFavouriteNodes(userName, type); + + PersonFavouriteKey personFavouriteKey = new PersonFavouriteKey(userName, null, type, nodeRef); + PersonFavourite personFavourite = personFavourites.remove(personFavouriteKey); + exists = personFavourite != null; + updateFavouriteNodes(userName, type, personFavourites); + + QName nodeClass = nodeService.getType(nodeRef); + OnRemoveFavouritePolicy policy = onRemoveFavouriteDelegate.get(nodeRef, nodeClass); + policy.onRemoveFavourite(userName, nodeRef); + + return exists; + } + + @Override + public PersonFavourite addFavourite(String userName, NodeRef nodeRef) + { + PersonFavourite personFavourite = null; + + Type type = getType(nodeRef); + if(type == null) + { + throw new IllegalArgumentException("Cannot favourite this node"); + } + else if(type.equals(Type.FILE)) + { + personFavourite = addFavouriteDocumentOrFolder(userName, Type.FILE, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + personFavourite = addFavouriteDocumentOrFolder(userName, Type.FOLDER, nodeRef); + } + else if(type.equals(Type.SITE)) + { + personFavourite = addFavouriteSite(userName, nodeRef); + } + else + { + throw new IllegalArgumentException("Cannot favourite this node"); + } + + return personFavourite; + } + + @Override + public boolean removeFavourite(String userName, NodeRef nodeRef) + { + boolean exists = false; + + Type type = getType(nodeRef); + if(type == null) + { + throw new IllegalArgumentException("Cannot un-favourite this node"); + } + else if(type.equals(Type.FILE)) + { + exists = removeFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + exists = removeFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.SITE)) + { + exists = removeFavouriteSite(userName, nodeRef); + } + else + { + throw new IllegalArgumentException("Cannot un-favourite this node"); + } + + return exists; + } + + @Override + public boolean isFavourite(String userName, NodeRef nodeRef) + { + boolean isFavourite = false; + + Type type = getType(nodeRef); + if(type == null) + { + throw new IllegalArgumentException("Unsupported node type"); + } + else if(type.equals(Type.FILE)) + { + isFavourite = isFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.FOLDER)) + { + isFavourite = isFavouriteNode(userName, type, nodeRef); + } + else if(type.equals(Type.SITE)) + { + isFavourite = isFavouriteSite(userName, nodeRef); + } + else + { + throw new IllegalArgumentException("Unsupported node type"); + } + + return isFavourite; + } + + @Override + public PagingResults getPagedFavourites(String userName, Set types, + List> sortProps, PagingRequest pagingRequest) + { + // Get the user node reference + final NodeRef personNodeRef = personService.getPerson(userName); + if(personNodeRef == null) + { + throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because he/she does not exist."); + } + + boolean includeFiles = types.contains(Type.FILE); + boolean includeFolders = types.contains(Type.FOLDER); + boolean includeSites = types.contains(Type.SITE); + + String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); + if (authenticationContext.isSystemUserName(currentUserName) == true || + permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED || + userName.equals(currentUserName) == true) + { + // we may have more than one favourite that is considered the same w.r.t. the PersonFavourite comparator + final Map sortedFavouriteNodes = new TreeMap(getComparator(sortProps)); + + PrefKeys sitePrefKeys = getPrefKeys(Type.SITE); + PrefKeys documentsPrefKeys = getPrefKeys(Type.FILE); + PrefKeys foldersPrefKeys = getPrefKeys(Type.FOLDER); + + Map preferences = preferenceService.getPreferences(userName); + for(String key : preferences.keySet()) + { + if(includeFiles && key.startsWith(documentsPrefKeys.sharePrefKey)) + { + String nodes = (String)preferences.get(key); + if(nodes != null) + { + sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FILE, nodes)); + } + } + else if(includeFolders && key.startsWith(foldersPrefKeys.sharePrefKey)) + { + String nodes = (String)preferences.get(key); + if(nodes != null) + { + sortedFavouriteNodes.putAll(extractFavouriteNodes(userName, Type.FOLDER, nodes)); + } + } + else if(includeSites && key.startsWith(sitePrefKeys.getSharePrefKey()) && !key.endsWith(".createdAt")) + { + // key is either of the form org.alfresco.share.sites.favourites..favourited or + // org.alfresco.share.sites.favourites. + extractFavouriteSite(userName, Type.SITE, sortedFavouriteNodes, preferences, key); + } + } + + int totalSize = sortedFavouriteNodes.size(); + final PageDetails pageDetails = PageDetails.getPageDetails(pagingRequest, totalSize); + + final List page = new ArrayList(pageDetails.getPageSize()); + Iterator it = sortedFavouriteNodes.values().iterator(); + for(int counter = 0; counter < pageDetails.getEnd() && it.hasNext(); counter++) + { + PersonFavourite favouriteNode = it.next(); + + if(counter < pageDetails.getSkipCount()) + { + continue; + } + + if(counter > pageDetails.getEnd() - 1) + { + break; + } + + page.add(favouriteNode); + } + + return new PagingResults() + { + @Override + public List getPage() + { + return page; + } + + @Override + public boolean hasMoreItems() + { + return pageDetails.hasMoreItems(); + } + + @Override + public Pair getTotalResultCount() + { + Integer total = Integer.valueOf(sortedFavouriteNodes.size()); + return new Pair(total, total); + } + + @Override + public String getQueryExecutionId() + { + return null; + } + }; + } + else + { + // The current user does not have sufficient permissions to update the preferences for this user + throw new AccessDeniedException("The current user " + currentUserName + " does not have sufficient permissions to get the favourites of the user " + userName); + } + } + + public PersonFavourite getFavourite(String userName, NodeRef nodeRef) + { + Type type = getType(nodeRef); + return getPersonFavourite(userName, type, nodeRef); + } +} diff --git a/source/java/org/alfresco/repo/favourites/PersonFavourite.java b/source/java/org/alfresco/repo/favourites/PersonFavourite.java new file mode 100644 index 0000000000..278132104f --- /dev/null +++ b/source/java/org/alfresco/repo/favourites/PersonFavourite.java @@ -0,0 +1,252 @@ +package org.alfresco.repo.favourites; + +import java.util.Date; + +import org.alfresco.service.cmr.favourites.FavouritesService.Type; +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * Representation of a user's favourite site, document, folder. + * + * @author steveglover + * + */ +public class PersonFavourite +{ + private String userName; + private String title; + private Type type; // using a type rather then subclassing to make sorting of PersonFavourites easier TODO + private Date createdAt; + private NodeRef nodeRef; + + public static class PersonFavouriteKey + { + private String userName; + private Type type; + private String title; + private NodeRef nodeRef; + private Date createdAt; + + public PersonFavouriteKey(String userName, String title, Type type, NodeRef nodeRef) + { + super(); + this.userName = userName; + this.type = type; + this.nodeRef = nodeRef; + } + + public PersonFavouriteKey(String userName, String title, Type type, NodeRef nodeRef, Date createdAt) + { + super(); + this.userName = userName; + this.type = type; + this.nodeRef = nodeRef; + this.title = title; + this.createdAt = createdAt; + } + + public String getTitle() + { + return title; + } + + public String getUserName() + { + return userName; + } + + public Type getType() + { + return type; + } + + public Date getCreatedAt() + { + return createdAt; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + + ((nodeRef == null) ? 0 : nodeRef.hashCode()); + result = prime * result + + ((userName == null) ? 0 : userName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PersonFavouriteKey other = (PersonFavouriteKey) obj; + if (nodeRef == null) + { + if (other.nodeRef != null) + return false; + } else if (!nodeRef.equals(other.nodeRef)) + return false; + if (userName == null) + { + if (other.userName != null) + return false; + } else if (!userName.equals(other.userName)) + return false; + return true; + } + + @Override + public String toString() + { + return "PersonFavouriteKey [userName=" + userName + ", nodeRef=" + + nodeRef + "]"; + } + } + + /* + * Used for comparisons + */ + PersonFavourite(String userName, NodeRef nodeRef, Type type) + { + super(); + if(userName == null) + { + throw new IllegalArgumentException("Must provide a userName"); + } + if(nodeRef == null) + { + throw new IllegalArgumentException("Must provide a nodeRef"); + } + if(type == null) + { + throw new IllegalArgumentException("Must provide a type"); + } + this.userName = userName; + this.nodeRef = nodeRef; + this.type = type; + } + + public PersonFavourite(String userName, NodeRef nodeRef, Type type, String title, Date createdAt) + { + super(); + if(userName == null) + { + throw new IllegalArgumentException("Must provide a userName"); + } + if(nodeRef == null) + { + throw new IllegalArgumentException("Must provide a nodeRef"); + } + if(type == null) + { + throw new IllegalArgumentException("Must provide a type"); + } + if(title == null) + { + throw new IllegalArgumentException("Must provide a title"); + } + // re-instate if Share can persist createdAt for favourites +// if(createdAt == null) +// { +// throw new IllegalArgumentException("Must provide a createdAt"); +// } + this.userName = userName; + this.nodeRef = nodeRef; + this.type = type; + this.title = title; + this.createdAt = createdAt; + } + +// PersonFavourite(String userName, NodeRef nodeRef, Type type, String title, Date createdAt, boolean exists) +// { +// this(userName, nodeRef, type, title, createdAt); +// this.exists = exists; +// } + + public PersonFavouriteKey getKey() + { + PersonFavouriteKey key = new PersonFavouriteKey(getUserName(), getTitle(), getType(), getNodeRef(), getCreatedAt()); + return key; + } + + public String getTitle() + { + return title; + } + + public String getUserName() + { + return userName; + } + + public Type getType() + { + return type; + } + + public NodeRef getNodeRef() + { + return nodeRef; + } + + public Date getCreatedAt() + { + return createdAt; + } + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((nodeRef == null) ? 0 : nodeRef.hashCode()); + result = prime * result + + ((userName == null) ? 0 : userName.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + PersonFavourite other = (PersonFavourite) obj; + if (nodeRef == null) + { + if (other.nodeRef != null) + return false; + } else if (!nodeRef.equals(other.nodeRef)) + return false; + if (userName == null) + { + if (other.userName != null) + return false; + } else if (!userName.equals(other.userName)) + return false; + return true; + } + + @Override + public String toString() + { + return "PersonFavourite [userName=" + userName + ", name=" + title + + ", type=" + type + ", createdAt=" + createdAt + ", nodeRef=" + + nodeRef + "]"; + } +} diff --git a/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java b/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java index 73004ce6a1..573e6dca27 100644 --- a/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java +++ b/source/java/org/alfresco/repo/forms/processor/workflow/AbstractWorkflowFormProcessor.java @@ -31,6 +31,7 @@ import org.alfresco.repo.forms.processor.FormCreationData; import org.alfresco.repo.forms.processor.node.ContentModelFormProcessor; import org.alfresco.repo.forms.processor.node.ContentModelItemData; import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.service.cmr.workflow.WorkflowService; import org.alfresco.util.ParameterCheck; @@ -103,6 +104,10 @@ public abstract class AbstractWorkflowFormProcessor exten ParameterCheck.mandatory("item", item); return getTypedItemForDecodedId(item.getId()); } + catch (AccessDeniedException ade) + { + throw ade; + } catch (Exception e) { throw new FormNotFoundException(item, e); diff --git a/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java b/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java index 0f7826318a..593953ece4 100755 --- a/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java +++ b/source/java/org/alfresco/repo/googledocs/GoogleDocsServiceImpl.java @@ -1,5 +1,5 @@ /* -* Copyright (C) 2005-2010 Alfresco Software Limited. +* Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -718,7 +718,7 @@ public class GoogleDocsServiceImpl extends TransactionListenerAdapter } else { - throw new AlfrescoRuntimeException("Unsuported document type: " + docType); + throw new AlfrescoRuntimeException("Unsupported document type: " + docType); } // Log the download URI diff --git a/source/java/org/alfresco/repo/importer/ImporterComponent.java b/source/java/org/alfresco/repo/importer/ImporterComponent.java index 88a7edb45f..3d7053d5cd 100644 --- a/source/java/org/alfresco/repo/importer/ImporterComponent.java +++ b/source/java/org/alfresco/repo/importer/ImporterComponent.java @@ -627,7 +627,7 @@ public class ImporterComponent implements ImporterService } // check whether the node should be hidden - hiddenAspect.checkHidden(nodeRef, false); + hiddenAspect.checkHidden(nodeRef, false, false); // import content, if applicable for (Map.Entry property : context.getProperties().entrySet()) diff --git a/source/java/org/alfresco/repo/invitation/InvitationImpl.java b/source/java/org/alfresco/repo/invitation/InvitationImpl.java index 3e0b571c9d..0544240f20 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationImpl.java @@ -18,6 +18,8 @@ */ package org.alfresco.repo.invitation; +import java.io.Serializable; +import java.util.Date; import java.util.Map; import org.alfresco.service.cmr.invitation.Invitation; @@ -31,6 +33,8 @@ import org.alfresco.service.cmr.invitation.Invitation.ResourceType; public static final String RESOURCE_NAME_KEY = "resourceName"; public static final String RESOURCE_TYPE_KEY = "resourceType"; public static final String ROLE_KEY = "role"; + public static final String CREATED_AT = "createdAt"; + public static final String MODIFIED_AT = "modifiedAt"; /** * Unique reference for this invitation @@ -56,15 +60,21 @@ import org.alfresco.service.cmr.invitation.Invitation.ResourceType; * Who is this invitation for */ private final String inviteeUserName; + + private final Date createdAt; + + private final Date modifiedAt; - public InvitationImpl(Map props) + public InvitationImpl(Map props) { - this.inviteId = props.get(ID_KEY); - this.inviteeUserName = props.get(INVITEE_KEY); - this.resourceName = props.get(RESOURCE_NAME_KEY); - this.roleName = props.get(ROLE_KEY); - String type = props.get(RESOURCE_TYPE_KEY); + this.inviteId = (String)props.get(ID_KEY); + this.inviteeUserName = (String)props.get(INVITEE_KEY); + this.resourceName = (String)props.get(RESOURCE_NAME_KEY); + this.roleName = (String)props.get(ROLE_KEY); + String type = (String)props.get(RESOURCE_TYPE_KEY); this.resourceType = type==null ? ResourceType.WEB_SITE : ResourceType.valueOf(type); + this.createdAt = (Date)props.get(CREATED_AT); + this.modifiedAt = (Date)props.get(MODIFIED_AT); } /** @@ -75,8 +85,18 @@ import org.alfresco.service.cmr.invitation.Invitation.ResourceType; { return resourceType; } - - public String getInviteId() + + public Date getCreatedAt() + { + return createdAt; + } + + public Date getModifiedAt() + { + return modifiedAt; + } + + public String getInviteId() { return inviteId; } diff --git a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java index da75ecd8a9..e56feab3da 100644 --- a/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java +++ b/source/java/org/alfresco/repo/invitation/InvitationServiceImpl.java @@ -50,11 +50,13 @@ import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.invitation.Invitation; +import org.alfresco.service.cmr.invitation.Invitation.ResourceType; import org.alfresco.service.cmr.invitation.InvitationException; import org.alfresco.service.cmr.invitation.InvitationExceptionForbidden; import org.alfresco.service.cmr.invitation.InvitationExceptionNotFound; import org.alfresco.service.cmr.invitation.InvitationExceptionUserError; import org.alfresco.service.cmr.invitation.InvitationSearchCriteria; +import org.alfresco.service.cmr.invitation.InvitationSearchCriteria.InvitationType; import org.alfresco.service.cmr.invitation.InvitationService; import org.alfresco.service.cmr.invitation.ModeratedInvitation; import org.alfresco.service.cmr.invitation.NominatedInvitation; @@ -398,7 +400,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli public Invitation approve(String invitationId, String reason) { WorkflowTask startTask = getStartTask(invitationId); - ModeratedInvitation invitation = getModeratedInvitation(startTask); + ModeratedInvitation invitation = getModeratedInvitation(invitationId); if(invitation == null) { String msg = "State error, can only call approve on a Moderated invitation."; @@ -439,7 +441,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli private Invitation rejectModeratedInvitation(WorkflowTask startTask, String reason) { - ModeratedInvitation invitation = getModeratedInvitation(startTask); + ModeratedInvitation invitation = getModeratedInvitation(startTask.getPath().getId()); // Check rejecter is a site manager and throw and exception if not String rejecterUserName = this.authenticationService.getCurrentUserName(); checkManagerRole(rejecterUserName, invitation.getResourceType(), invitation.getResourceName()); @@ -490,7 +492,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli private Invitation cancelModeratedInvitation(WorkflowTask startTask) { - ModeratedInvitation invitation = getModeratedInvitation(startTask); + ModeratedInvitation invitation = getModeratedInvitation(startTask.getPath().getId()); String currentUserName = this.authenticationService.getCurrentUserName(); if (!AuthenticationUtil.isRunAsUserTheSystemUser()) { @@ -549,7 +551,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli Invitation invitation = getNominatedInvitation(startTask); if(invitation == null) { - invitation = getModeratedInvitation(startTask); + invitation = getModeratedInvitation(startTask.getPath().getId()); } return invitation; } @@ -581,15 +583,71 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli return result; } + + private WorkflowTask getModeratedInvitationReviewTask(String inviteeId, String siteShortName) + { + WorkflowTask reviewTask = null; - private ModeratedInvitation getModeratedInvitation(WorkflowTask startTask) + // Is there an outstanding site invite request for the invitee? + InvitationSearchCriteriaImpl criteria = new InvitationSearchCriteriaImpl(); + criteria.setInvitationType(InvitationType.MODERATED); + criteria.setInvitee(inviteeId); + criteria.setResourceName(siteShortName); + criteria.setResourceType(ResourceType.WEB_SITE); + + // should be at most 1 invite + List invitationIds = searchInvitationsForIds(criteria, 1); + if(invitationIds.size() == 1) + { + reviewTask = getModeratedInvitationReviewTask(invitationIds.get(0)); + } + + return reviewTask; +// List invitations = searchInvitation(criteria); +// if(invitations.size() > 1) +// { +// throw new AlfrescoRuntimeException("There should be only one outstanding site invitation"); +// } +// return (invitations.size() == 0 ? null : (ModeratedInvitation)invitations.get(0)); + } + + private WorkflowTask getModeratedInvitationReviewTask(String invitationId) + { + WorkflowTask reviewTask = null; + + // since the invitation may have been updated e.g. invitee comments (and therefore modified date) + // we need to get the properties from the review task (which should be the only active + // task) + List tasks = workflowService.getTasksForWorkflowPath(invitationId); + for(WorkflowTask task : tasks) + { + if(taskTypeMatches(task, WorkflowModelModeratedInvitation.WF_ACTIVITI_REVIEW_TASK)) + { + reviewTask = task; + break; + } + } + + return reviewTask; + } + + private ModeratedInvitation getModeratedInvitation(String invitationId) + { + WorkflowTask reviewTask = getModeratedInvitationReviewTask(invitationId); + ModeratedInvitation invitation = getModeratedInvitation(invitationId, reviewTask); + return invitation; + } + + private ModeratedInvitation getModeratedInvitation(String invitationId, WorkflowTask reviewTask) { ModeratedInvitation invitation = null; - if (taskTypeMatches(startTask, WorkflowModelModeratedInvitation.WF_START_TASK)) + + if(reviewTask != null) { - String invitationId = startTask.getPath().getInstance().getId(); - invitation = new ModeratedInvitationImpl(invitationId, startTask.getProperties()); + Map properties = reviewTask.getProperties(); + invitation = new ModeratedInvitationImpl(invitationId, properties); } + return invitation; } @@ -643,6 +701,15 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli crit.setInvitee(invitee); return searchInvitation(crit); } + + public List listPendingInvitationsForInvitee(String invitee, Invitation.ResourceType resourceType) + { + InvitationSearchCriteriaImpl crit = new InvitationSearchCriteriaImpl(); + crit.setInvitationType(InvitationSearchCriteria.InvitationType.ALL); + crit.setInvitee(invitee); + crit.setResourceType(resourceType); + return searchInvitation(crit); + } /** * list Invitations for a specific resource @@ -1321,6 +1388,38 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli return (NominatedInvitation) startWorkflow(wfDefinition, workflowProps); } + @Override + public ModeratedInvitation updateModeratedInvitation(String inviteeId, String siteShortName, String inviteeComments) + { + ModeratedInvitation ret = null; + + // find and update the review task with the new property values + WorkflowTask reviewTask = getModeratedInvitationReviewTask(inviteeId, siteShortName); + if(reviewTask == null) + { + Object objs[] = { siteShortName, inviteeId }; + throw new InvitationExceptionNotFound("invitation.error.not_found_by_invitee", objs); + } + else + { + String invitationId = reviewTask.getPath().getInstance().getId(); + + if(inviteeComments != null) + { + // update the properties on the review task + Map properties = new HashMap(); + properties.put(WorkflowModelModeratedInvitation.WF_PROP_INVITEE_COMMENTS, inviteeComments); + Date time = new Date(); + properties.put(WorkflowModelModeratedInvitation.WF_PROP_MODIFIED_AT, time); + reviewTask = workflowService.updateTask(reviewTask.getId(), properties, null, null); + } + + ret = getModeratedInvitation(invitationId, reviewTask); + } + + return ret; + } + private Invitation startWorkflow(WorkflowDefinition wfDefinition, Map workflowProps) { NodeRef wfPackage = workflowService.createPackage(null); @@ -1354,6 +1453,7 @@ public class InvitationServiceImpl implements InvitationService, NodeServicePoli logger.debug("Failed - caught error during Invite workflow transition: " + err.getMessage()); throw err; } + Invitation invitation = getInvitation(startTask); return invitation; } diff --git a/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java b/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java index 3e3dcd38b4..0e0c502d7c 100644 --- a/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java +++ b/source/java/org/alfresco/repo/invitation/ModeratedInvitationImpl.java @@ -21,13 +21,16 @@ package org.alfresco.repo.invitation; import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_COMMENTS; import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_ROLE; import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_INVITEE_USER_NAME; +import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_MODIFIED_AT; import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_NAME; import static org.alfresco.repo.invitation.WorkflowModelModeratedInvitation.WF_PROP_RESOURCE_TYPE; import java.io.Serializable; +import java.util.Date; import java.util.HashMap; import java.util.Map; +import org.alfresco.model.ContentModel; import org.alfresco.service.cmr.invitation.ModeratedInvitation; import org.alfresco.service.namespace.QName; @@ -41,20 +44,32 @@ import org.alfresco.service.namespace.QName; private final String inviteeComments; + public static ModeratedInvitation getModeratedInvitationImpl(ModeratedInvitation moderatedInvitation) + { + ModeratedInvitation copy = null; + String inviteId = moderatedInvitation.getInviteId(); + Map props = new HashMap(); + props.put(WF_PROP_INVITEE_COMMENTS, moderatedInvitation.getInviteeComments()); + copy = new ModeratedInvitationImpl(inviteId, props); + return copy; + } + public ModeratedInvitationImpl(String inviteId, Map props) { super(getConstructorProps(inviteId, props)); inviteeComments = (String)props.get(WF_PROP_INVITEE_COMMENTS); } - private static Map getConstructorProps(String inviteId, Map props) + private static Map getConstructorProps(String inviteId, Map props) { - Map parentProps = new HashMap(); + Map parentProps = new HashMap(); parentProps.put(ID_KEY, inviteId); parentProps.put(INVITEE_KEY, (String) props.get(WF_PROP_INVITEE_USER_NAME)); parentProps.put(ROLE_KEY,(String)props.get(WF_PROP_INVITEE_ROLE)); parentProps.put(RESOURCE_NAME_KEY,(String)props.get(WF_PROP_RESOURCE_NAME)); parentProps.put(RESOURCE_TYPE_KEY,(String)props.get(WF_PROP_RESOURCE_TYPE)); + parentProps.put(CREATED_AT,(Date)props.get(ContentModel.PROP_CREATED)); + parentProps.put(MODIFIED_AT,(Date)props.get(WF_PROP_MODIFIED_AT)); return parentProps; } diff --git a/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java b/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java index faf0c936af..7474889ae2 100644 --- a/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java +++ b/source/java/org/alfresco/repo/invitation/NominatedInvitationImpl.java @@ -79,9 +79,9 @@ import org.alfresco.service.namespace.QName; this.sentInviteDate =inviteDate; } - private static Map getConstructorProps(String inviteId, Map props) + private static Map getConstructorProps(String inviteId, Map props) { - Map parentProps = new HashMap(); + Map parentProps = new HashMap(); parentProps.put(ID_KEY, inviteId); parentProps.put(INVITEE_KEY, (String) props.get(WF_PROP_INVITEE_USER_NAME)); parentProps.put(ROLE_KEY,(String)props.get(WF_PROP_INVITEE_ROLE)); diff --git a/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java b/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java index e0023ac5d4..1be6fee061 100644 --- a/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java +++ b/source/java/org/alfresco/repo/invitation/WorkflowModelModeratedInvitation.java @@ -59,7 +59,8 @@ public interface WorkflowModelModeratedInvitation public static final QName WF_PROP_RESOURCE_TYPE= QName.createQName(NAMESPACE_URI, "resourceType"); public static final QName WF_PROP_REVIEW_COMMENTS= QName.createQName(NAMESPACE_URI, "reviewComments"); public static final QName WF_PROP_REVIEWER= QName.createQName(NAMESPACE_URI, "reviewer"); - + public static final QName WF_PROP_MODIFIED_AT= QName.createQName(NAMESPACE_URI, "modifiedAt"); + // workflow execution context variable names public static final String wfVarInviteeUserName = "imwf_inviteeUserName"; public static final String wfVarInviteeRole = "imwf_inviteeRole"; diff --git a/source/java/org/alfresco/repo/jscript/People.java b/source/java/org/alfresco/repo/jscript/People.java index 09c4bffa70..50bddcf3d4 100644 --- a/source/java/org/alfresco/repo/jscript/People.java +++ b/source/java/org/alfresco/repo/jscript/People.java @@ -34,6 +34,7 @@ import org.alfresco.repo.security.authentication.AuthenticationException; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.UserNameGenerator; import org.alfresco.repo.security.authority.AuthorityDAO; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.person.PersonServiceImpl; import org.alfresco.repo.security.sync.UserRegistrySynchronizer; import org.alfresco.repo.tenant.TenantDomainMismatchException; @@ -43,6 +44,7 @@ import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.PermissionEvaluationMode; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; @@ -687,6 +689,8 @@ public class People extends BaseScopableProcessorExtension implements Initializi SearchParameters params = new SearchParameters(); params.addQueryTemplate("_PERSON", "|%firstName OR |%lastName OR |%userName"); params.setDefaultFieldName("_PERSON"); + params.setExcludeTenantFilter(getExcludeTenantFilter()); + params.setPermissionEvaluation(getPermissionEvaluationMode()); StringBuilder query = new StringBuilder(256); @@ -769,7 +773,8 @@ public class People extends BaseScopableProcessorExtension implements Initializi query.append(term.substring(0, propIndex+1)) .append('"') .append(term.substring(propIndex+1)) - .append('"'); + .append('"') + .append(' '); propertySearch = true; } @@ -854,12 +859,20 @@ public class People extends BaseScopableProcessorExtension implements Initializi return personRefs; } - private List getSortedPeopleObjects(List peopleRefs, final String sortBy, boolean sortAsc) + private List getSortedPeopleObjects(List peopleRefs, final String sortBy, Boolean sortAsc) { + if(sortBy == null) + { + return peopleRefs; + } + + + //make copy of peopleRefs because it can be unmodifiable list. + List sortedPeopleRefs = new ArrayList(peopleRefs); final Collator col = Collator.getInstance(I18NUtil.getLocale()); final NodeService nodeService = services.getNodeService(); - final int orderMultiplicator = sortAsc ? 1 : -1; - Collections.sort(peopleRefs, new Comparator() + final int orderMultiplicator = ((sortAsc == null) || sortAsc) ? 1 : -1; + Collections.sort(sortedPeopleRefs, new Comparator() { @Override public int compare(NodeRef n1, NodeRef n2) @@ -918,7 +931,7 @@ public class People extends BaseScopableProcessorExtension implements Initializi }); - return peopleRefs; + return sortedPeopleRefs; } /** @@ -929,8 +942,18 @@ public class People extends BaseScopableProcessorExtension implements Initializi */ public ScriptNode getPerson(final String username) { + NodeRef personRef = null; + ParameterCheck.mandatoryString("Username", username); - final NodeRef personRef = personService.getPersonOrNull(username); + try + { + personRef = personService.getPersonOrNull(username); + } + catch(AccessDeniedException e) + { + // ok, just return null to indicate not found + } + return personRef == null ? null : new ScriptNode(personRef, services, getScope()); } @@ -1247,4 +1270,14 @@ public class People extends BaseScopableProcessorExtension implements Initializi return members != null ? members : new Object[0]; } + + public boolean getExcludeTenantFilter() + { + return false; + } + + public PermissionEvaluationMode getPermissionEvaluationMode() + { + return PermissionEvaluationMode.EAGER; + } } diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index 5486dc7aa1..5bc23c7741 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -505,7 +505,8 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider }, AuthenticationUtil.getSystemUserName()); // final node must be accessible to the user via the usual ACL permission checks - if (result != null && AccessStatus.ALLOWED != services.getPermissionService().hasPermission(result, PermissionService.READ_PROPERTIES)) + if (result != null + && services.getPublicServiceAccessService().hasAccess("NodeService", "getProperties", result) != AccessStatus.ALLOWED) { result = null; } diff --git a/source/java/org/alfresco/repo/lock/JobLockService.java b/source/java/org/alfresco/repo/lock/JobLockService.java index 2f264ec21f..fbeba9e3e1 100644 --- a/source/java/org/alfresco/repo/lock/JobLockService.java +++ b/source/java/org/alfresco/repo/lock/JobLockService.java @@ -169,12 +169,14 @@ public interface JobLockService void refreshLock(String lockToken, QName lockQName, long timeToLive, JobLockRefreshCallback callback); /** - * Release the lock using a valid lock token. + * Release the lock using a valid lock token. The lock can have expired or even been taken + * by another processes (i.e. the lock token will no longer be valid); none of this will + * prevent the method from succeeding. * * @param lockToken the lock token returned when the lock was acquired * @param lockQName the name of the previously-acquired lock */ - void releaseLock(String lockToken, QName lockQName); + boolean releaseLock(String lockToken, QName lockQName); /** * Interface for implementations that need a timed callback in order to refresh the lock. @@ -200,7 +202,7 @@ public interface JobLockService /** * Timed callback from the service to determine if the lock is still required. *

- * IMPORTANT: Do not block calls to this method for any reason and do perform any + * IMPORTANT: Do not block calls to this method for any reason and do not perform any * non-trivial determination of state i.e. have the answer to this * method immediately available at all times. Failure to observe this * will lead to warnings and lock termination. diff --git a/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java b/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java index c06a9de1e5..390a92b042 100644 --- a/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java +++ b/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java @@ -397,7 +397,7 @@ public class JobLockServiceImpl implements JobLockService // indication that the isActive implementation is performing complex state determination, // which is specifically referenced in the API doc. throw new RuntimeException( - "isActive check took " + timeWastedMs + " to return, which is too long."); + "isActive check took " + timeWastedMs + "ms to return, which is too long."); } return isActive; } @@ -419,17 +419,16 @@ public class JobLockServiceImpl implements JobLockService /** * {@inheritDoc} */ - public void releaseLock(final String lockToken, final QName lockQName) + public boolean releaseLock(final String lockToken, final QName lockQName) { - RetryingTransactionCallback releaseCallback = new RetryingTransactionCallback() + RetryingTransactionCallback releaseCallback = new RetryingTransactionCallback() { - public Void execute() throws Throwable + public Boolean execute() throws Throwable { - lockDAO.releaseLock(lockQName, lockToken); - return null; + return lockDAO.releaseLock(lockQName, lockToken, true); } }; - retryingTransactionHelper.doInTransaction(releaseCallback, false, true); + return retryingTransactionHelper.doInTransaction(releaseCallback, false, true); } /** @@ -567,7 +566,7 @@ public class JobLockServiceImpl implements JobLockService // Any one of the them could fail for (QName lockQName : heldLocks) { - lockDAO.releaseLock(lockQName, txnId); + lockDAO.releaseLock(lockQName, txnId, false); } return null; } @@ -599,7 +598,7 @@ public class JobLockServiceImpl implements JobLockService { public Object execute() throws Throwable { - lockDAO.releaseLock(lockQName, txnId); + lockDAO.releaseLock(lockQName, txnId, false); return null; } }; diff --git a/source/java/org/alfresco/repo/lock/JobLockServiceTest.java b/source/java/org/alfresco/repo/lock/JobLockServiceTest.java index dc4e00aed6..e263425155 100644 --- a/source/java/org/alfresco/repo/lock/JobLockServiceTest.java +++ b/source/java/org/alfresco/repo/lock/JobLockServiceTest.java @@ -40,6 +40,7 @@ import org.springframework.context.ApplicationContext; * @author Derek Hulley * @since 3.2 */ +@SuppressWarnings("unused") public class JobLockServiceTest extends TestCase { public static final String NAMESPACE = "http://www.alfresco.org/test/JobLockServiceTest"; @@ -376,7 +377,7 @@ public class JobLockServiceTest extends TestCase // The first refresh will occur in 500ms wait(1000L); // Should NOT get a callback saying that the lock has been released - assertFalse("DID NOT expect lockReleased to have been called", released[0] > 0); + assertTrue("Lock should be optimistically released", released[0] > 0); try { jobLockService.getLock(lockQName, lockTTL); diff --git a/source/java/org/alfresco/repo/management/subsystems/test/SubsystemsTest.java b/source/java/org/alfresco/repo/management/subsystems/test/SubsystemsTest.java deleted file mode 100644 index 9a06ea9929..0000000000 --- a/source/java/org/alfresco/repo/management/subsystems/test/SubsystemsTest.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (C) 2005-2010 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * 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 . - */ -package org.alfresco.repo.management.subsystems.test; - -import org.alfresco.repo.management.subsystems.ApplicationContextFactory; -import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; -import org.alfresco.util.ApplicationContextHelper; -import org.alfresco.util.BaseSpringTest; -import org.apache.cxf.endpoint.ServerRegistryImpl; -import org.springframework.context.ConfigurableApplicationContext; - -/** - * Ensures the following features of subsystems are working: - *

    - *
  • Subsystem default properties - *
  • Global property overrides (via alfresco-global.properties) - *
  • Subsystem instance specific property overrides (via extension classpath) - *
  • Subsystem instance specific Spring overrides (via extension classpath) - *
  • Composite property defaults - *
  • Composite property instance overrides - *
- * - * @see ChildApplicationContextFactory - * @author dward - */ -public class SubsystemsTest extends BaseSpringTest -{ - - /* - * (non-Javadoc) - * @see org.alfresco.util.BaseSpringTest#getConfigLocations() - */ - @Override - protected String[] getConfigLocations() - { - return new String[] - { - ApplicationContextHelper.CONFIG_LOCATIONS[0], "classpath:subsystem-test-context.xml" - }; - } - - /** - * Test subsystems. - * - * @throws Exception - * the exception - */ - public void testSubsystems() throws Exception - { - ApplicationContextFactory subsystem = (ApplicationContextFactory) getApplicationContext().getBean( - "testsubsystem"); - ConfigurableApplicationContext childContext = (ConfigurableApplicationContext) subsystem - .getApplicationContext(); - assertTrue("Subsystem not started", childContext.isActive()); - TestService testService = (TestService) childContext.getBean("testService"); - // Make sure subsystem defaults work - assertEquals("Subsystem Default1", testService.getSimpleProp1()); - // Make sure global property overrides work - assertEquals(true, testService.getSimpleProp2().booleanValue()); - // Make sure extension classpath property overrides work - assertEquals("Instance Override3", testService.getSimpleProp3()); - // Make sure extension classpath Spring overrides work - assertEquals("An extra bean I changed", childContext.getBean("anotherBean")); - // Make sure composite properties and their defaults work - TestBean[] testBeans = testService.getTestBeans(); - assertNotNull("Composite property not set", testBeans); - assertEquals(3, testBeans.length); - assertEquals("inst1", testBeans[0].getId()); - assertEquals(false, testBeans[0].isBoolProperty()); - assertEquals(123456789123456789L, testBeans[0].getLongProperty()); - assertEquals("Global Default", testBeans[0].getAnotherStringProperty()); - assertEquals("inst2", testBeans[1].getId()); - assertEquals(true, testBeans[1].isBoolProperty()); - assertEquals(123456789123456789L, testBeans[1].getLongProperty()); - assertEquals("Global Default", testBeans[1].getAnotherStringProperty()); - assertEquals("inst3", testBeans[2].getId()); - assertEquals(false, testBeans[2].isBoolProperty()); - assertEquals(123456789123456789L, testBeans[2].getLongProperty()); - assertEquals("Global Instance Default", testBeans[2].getAnotherStringProperty()); - } - - public void testALF6058() throws Exception - { - ServerRegistryImpl serverRegistry = getApplicationContext().getBean(ServerRegistryImpl.class); - ApplicationContextFactory subsystem = (ApplicationContextFactory) getApplicationContext().getBean("testsubsystem"); - int beforeStop = serverRegistry.getServers().size(); - subsystem.stop(); - //Make sure CXF doesn't remove its endpoints after subsystem stops - assertEquals(beforeStop, serverRegistry.getServers().size()); - subsystem.start(); - - } - -} diff --git a/source/java/org/alfresco/repo/model/ModelTestSuite.java b/source/java/org/alfresco/repo/model/ModelTestSuite.java index acf282e382..dec0461b94 100644 --- a/source/java/org/alfresco/repo/model/ModelTestSuite.java +++ b/source/java/org/alfresco/repo/model/ModelTestSuite.java @@ -20,48 +20,37 @@ package org.alfresco.repo.model; import org.alfresco.repo.model.filefolder.FileFolderDuplicateChildTest; import org.alfresco.repo.model.filefolder.FileFolderServiceImplTest; +import org.alfresco.repo.model.filefolder.HiddenAspectTest; import org.alfresco.repo.model.ml.tools.ContentFilterLanguagesMapTest; import org.alfresco.repo.model.ml.tools.EditionServiceImplTest; import org.alfresco.repo.model.ml.tools.EmptyTranslationAspectTest; import org.alfresco.repo.model.ml.tools.MLContainerTypeTest; import org.alfresco.repo.model.ml.tools.MultilingualContentServiceImplTest; import org.alfresco.repo.model.ml.tools.MultilingualDocumentAspectTest; -import org.alfresco.util.ApplicationContextHelper; - -import junit.framework.Test; -import junit.framework.TestSuite; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; /** * Model test suite */ -public class ModelTestSuite extends TestSuite +@RunWith(Suite.class) +@SuiteClasses({ + ContentFilterLanguagesMapTest.class, + EmptyTranslationAspectTest.class, + MLContainerTypeTest.class, + MultilingualContentServiceImplTest.class, + MultilingualDocumentAspectTest.class, + EditionServiceImplTest.class, + + HiddenAspectTest.class, + + // Add the file folder tests + // These need to come afterwards, as they insert extra + // interceptors which would otherwise confuse things + FileFolderServiceImplTest.class, + FileFolderDuplicateChildTest.class, +}) +public class ModelTestSuite { - /** - * Creates the test suite - * - * @return the test suite - */ - public static Test suite() - { - TestSuite suite = new TestSuite(); - - // Ensure that the default context is available - ApplicationContextHelper.getApplicationContext(); - - // Add the multilingual tests - suite.addTestSuite( ContentFilterLanguagesMapTest.class ); - suite.addTestSuite( EmptyTranslationAspectTest.class ); - suite.addTestSuite( MLContainerTypeTest.class ); - suite.addTestSuite( MultilingualContentServiceImplTest.class ); - suite.addTestSuite( MultilingualDocumentAspectTest.class ); - suite.addTestSuite( EditionServiceImplTest.class ); - - // Add the file folder tests - // These need to come afterwards, as they insert extra - // interceptors which would otherwise confuse things - suite.addTestSuite( FileFolderServiceImplTest.class ); - suite.addTestSuite( FileFolderDuplicateChildTest.class ); - - return suite; - } } diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java index 075cc59a13..12e89bbdf3 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImpl.java @@ -1622,7 +1622,7 @@ public class FileFolderServiceImpl implements FileFolderService } else { - hiddenAspect.hideNode(nodeRef, mask); + hiddenAspect.hideNode(nodeRef, mask, true, true, false); } } diff --git a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java index 1ff7e34488..c809609ba4 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/FileFolderServiceImplTest.java @@ -1431,10 +1431,10 @@ public class FileFolderServiceImplTest extends TestCase fileFolderService.create(nodeRef1, "visiblechild" + i, ContentModel.TYPE_CONTENT).getNodeRef(); } - // switch to a client that should not see the hidden files - saveClient = FileFilterMode.setClient(Client.cmis); - PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE); - pagingRequest.setRequestTotalCountMax(10000); // need this so that total count is set + // switch to a client that should not see the hidden files + saveClient = FileFilterMode.setClient(Client.script); + PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE); + pagingRequest.setRequestTotalCountMax(10000); // need this so that total count is set PagingResults results = fileFolderService.list(nodeRef1, true, true, null, null, pagingRequest); Pair totalResultCount = results.getTotalResultCount(); diff --git a/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java b/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java index a2ccebb6b3..1b498eef16 100644 --- a/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java +++ b/source/java/org/alfresco/repo/model/filefolder/FilenameFilteringInterceptor.java @@ -239,10 +239,10 @@ public class FilenameFilteringInterceptor implements MethodInterceptor ret = runAsSystem(invocation); FileInfoImpl fileInfo = (FileInfoImpl)ret; permissionService.setPermission(fileInfo.getNodeRef(), PermissionService.ALL_AUTHORITIES, PermissionService.FULL_CONTROL, true); - + // it's always marked temporary and hidden checkTemporaryAspect(true, fileInfo); - hiddenAspect.hideNode(fileInfo, getSystemFileVisibilityMask()); + hiddenAspect.hideNode(fileInfo, getSystemFileVisibilityMask(), false, false, false); } else { @@ -255,14 +255,18 @@ public class FilenameFilteringInterceptor implements MethodInterceptor { // it's on a system path, check whether temporary, hidden and noindex aspects need to be applied checkTemporaryAspect(true, fileInfo); - hiddenAspect.hideNode(fileInfo, getSystemFileVisibilityMask()); + hiddenAspect.hideNode(fileInfo, getSystemFileVisibilityMask(), false, false, false); } else { // check whether it's a temporary or hidden file FileInfo sourceInfo = (FileInfo)ret; checkTemporaryAspect(isNameOfTmporaryObject(filename, sourceInfo.getNodeRef()), sourceInfo); - hiddenAspect.checkHidden(fileInfo, false); + boolean isHidden = hiddenAspect.checkHidden(fileInfo, false, false); + if(isHidden && fileInfo instanceof FileInfoImpl) + { + ((FileInfoImpl)fileInfo).setHidden(true); + } } } } @@ -293,7 +297,7 @@ public class FilenameFilteringInterceptor implements MethodInterceptor if(getMode() == Mode.ENHANCED) { - hiddenAspect.checkHidden(sourceNodeRef, true); + hiddenAspect.checkHidden(sourceNodeRef, true, true); } } else if (methodName.startsWith("copy")) @@ -312,7 +316,11 @@ public class FilenameFilteringInterceptor implements MethodInterceptor checkTemporaryAspect(isNameOfTmporaryObject(filename, fileInfo.getNodeRef()), fileInfo); if(getMode() == Mode.ENHANCED) { - hiddenAspect.checkHidden(fileInfo, true); + boolean isHidden = hiddenAspect.checkHidden(fileInfo, true, true); + if(isHidden && fileInfo instanceof FileInfoImpl) + { + ((FileInfoImpl)fileInfo).setHidden(true); + } } /* * TODO should these two calls be before the proceed? However its the same problem as create @@ -343,7 +351,11 @@ public class FilenameFilteringInterceptor implements MethodInterceptor if(getMode() == Mode.ENHANCED) { - hiddenAspect.checkHidden(sourceNodeRef, true); + boolean isHidden = hiddenAspect.checkHidden(sourceNodeRef, true, true); + if(isHidden && ret instanceof FileInfoImpl) + { + ((FileInfoImpl)ret).setHidden(true); + } } return ret; diff --git a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java index 88e2680cf8..f3d1f2c6a5 100644 --- a/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java +++ b/source/java/org/alfresco/repo/model/filefolder/HiddenAspect.java @@ -32,9 +32,11 @@ import java.util.regex.Pattern; import org.alfresco.model.ContentModel; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; +import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileFolderServiceType; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; @@ -46,6 +48,7 @@ import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.namespace.QName; +import org.alfresco.util.FileFilterMode; import org.alfresco.util.FileFilterMode.Client; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -62,6 +65,16 @@ import org.apache.commons.logging.LogFactory; public class HiddenAspect { private static Log logger = LogFactory.getLog(HiddenAspect.class); + + public static Set HIDDEN_PROPERTIES = new HashSet(); + + static + { + HIDDEN_PROPERTIES.add(ContentModel.PROP_CLIENT_CONTROLLED); + HIDDEN_PROPERTIES.add(ContentModel.PROP_CASCADE_HIDDEN); + HIDDEN_PROPERTIES.add(ContentModel.PROP_CASCADE_INDEX_CONTROL); + HIDDEN_PROPERTIES.add(ContentModel.PROP_VISIBILITY_MASK); + } public static enum Visibility { @@ -113,10 +126,16 @@ public class HiddenAspect private NodeService nodeService; private FileFolderService fileFolderService; private SearchService searchService; + private PolicyComponent policyComponent; public HiddenAspect() { } + + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } public void setNodeService(NodeService nodeService) { @@ -141,6 +160,10 @@ public class HiddenAspect } } + public void init() + { + } + public List getPatterns() { return filters; @@ -218,7 +241,7 @@ public class HiddenAspect public void unhideExplicit(NodeRef nodeRef) { nodeService.setProperty(nodeRef, ContentModel.PROP_HIDDEN_FLAG, false); - checkHidden(nodeRef, true); + checkHidden(nodeRef, true, false); } private void addHiddenAspect(NodeRef nodeRef, int visibilityMask, boolean explicit) @@ -234,15 +257,44 @@ public class HiddenAspect } } - private void removeHiddenAspect(NodeRef nodeRef) + private void addHiddenAspect(NodeRef nodeRef, int visibilityMask, boolean cascadeHiddenAspect, boolean cascadeIndexControlAspect, boolean clientControlled) + { + Map props = new HashMap(1); + props.put(ContentModel.PROP_VISIBILITY_MASK, visibilityMask); + props.put(ContentModel.PROP_CASCADE_HIDDEN, cascadeHiddenAspect); + props.put(ContentModel.PROP_CASCADE_INDEX_CONTROL, cascadeIndexControlAspect); + props.put(ContentModel.PROP_CLIENT_CONTROLLED, clientControlled); + nodeService.addAspect(nodeRef, ContentModel.ASPECT_HIDDEN, props); + + if (logger.isDebugEnabled()) + { + logger.debug("Applied hidden marker: " + nodeRef); + } + } + + private void addHiddenAspect(NodeRef nodeRef, HiddenFileInfo filter) + { + Map props = new HashMap(1); + props.put(ContentModel.PROP_VISIBILITY_MASK, filter.getVisibilityMask()); + props.put(ContentModel.PROP_CASCADE_HIDDEN, filter.cascadeHiddenAspect()); + props.put(ContentModel.PROP_CASCADE_INDEX_CONTROL, filter.cascadeIndexControlAspect()); + props.put(ContentModel.PROP_CLIENT_CONTROLLED, filter.isClientControlled()); + nodeService.addAspect(nodeRef, ContentModel.ASPECT_HIDDEN, props); + + if (logger.isDebugEnabled()) + { + logger.debug("Applied hidden marker: " + nodeRef); + } + } + + public void removeHiddenAspect(NodeRef nodeRef) { // Remove the aspect nodeService.removeAspect(nodeRef, ContentModel.ASPECT_HIDDEN); - if (logger.isDebugEnabled()) { logger.debug("Removed hidden marker: " + nodeRef); - } + } } private Visibility getVisibility(Integer mask, Client client) @@ -281,7 +333,7 @@ public class HiddenAspect return matched; } - private boolean hasHiddenAspect(NodeRef nodeRef) + public boolean hasHiddenAspect(NodeRef nodeRef) { return nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN); } @@ -291,60 +343,141 @@ public class HiddenAspect return nodeService.hasAspect(nodeRef, ContentModel.ASPECT_INDEX_CONTROL); } - private void applyHidden(NodeRef nodeRef, HiddenFileInfo filter, int visibilityMask) +// private void applyHidden(NodeRef nodeRef, HiddenFileInfo filter, int visibilityMask) +// { +// if(!filter.cascadeHiddenAspect() && !filter.cascadeIndexControlAspect()) +// { +// return; +// } +// +// PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE, null); +// PagingResults results = fileFolderService.list(nodeRef, true, true, null, null, pagingRequest); +// List files = results.getPage(); +// +// // apply the hidden aspect to all folders and folders and then recursively to all sub-folders, unless the sub-folder +// // already has the hidden aspect applied (it may have been applied for a different pattern). +// for(FileInfo file : files) +// { +// NodeRef childNodeRef = file.getNodeRef(); +// if(filter.cascadeHiddenAspect() && !hasHiddenAspect(childNodeRef)) +// { +// addHiddenAspect(childNodeRef, visibilityMask, false); +// } +// +// if(filter.cascadeIndexControlAspect() && !hasIndexControlAspect(childNodeRef)) +// { +// addIndexControlAspect(childNodeRef); +// } +// +// if(file.isFolder()) +// { +// applyHidden(file.getNodeRef(), filter, visibilityMask); +// } +// } +// } + + private void applyHidden(FileInfo fileInfo, HiddenFileInfo filter) { - if(!filter.cascadeHiddenAspect() && !filter.cascadeIndexControlAspect()) - { - return; - } + NodeRef nodeRef = fileInfo.getNodeRef(); - PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE, null); - PagingResults results = fileFolderService.list(nodeRef, true, true, null, null, pagingRequest); - List files = results.getPage(); - - // apply the hidden aspect to all folders and folders and then recursively to all sub-folders, unless the sub-folder - // already has the hidden aspect applied (it may have been applied for a different pattern). - for(FileInfo file : files) + if(!hasHiddenAspect(nodeRef)) { - NodeRef childNodeRef = file.getNodeRef(); - if(filter.cascadeHiddenAspect() && !hasHiddenAspect(childNodeRef)) - { - addHiddenAspect(childNodeRef, visibilityMask, false); - } - - if(filter.cascadeIndexControlAspect() && !hasIndexControlAspect(childNodeRef)) - { - addIndexControlAspect(childNodeRef); - } + // the file matches a pattern, apply the hidden and aspect control aspects + addHiddenAspect(nodeRef, filter); + } + else + { + nodeService.setProperty(nodeRef, ContentModel.PROP_VISIBILITY_MASK, filter.getVisibilityMask()); + nodeService.setProperty(nodeRef, ContentModel.PROP_CASCADE_HIDDEN, filter.cascadeHiddenAspect()); + nodeService.setProperty(nodeRef, ContentModel.PROP_CASCADE_INDEX_CONTROL, filter.cascadeIndexControlAspect()); + } - if(file.isFolder()) + if(!hasIndexControlAspect(nodeRef)) + { + addIndexControlAspect(nodeRef); + } + + if(fileInfo.isFolder() && (filter.cascadeHiddenAspect() || filter.cascadeIndexControlAspect())) + { + PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE, null); + PagingResults results = fileFolderService.list(nodeRef, true, true, null, null, pagingRequest); + List files = results.getPage(); + + // apply the hidden aspect to all folders and folders and then recursively to all sub-folders, unless the sub-folder + // already has the hidden aspect applied (it may have been applied for a different pattern). + for(FileInfo file : files) { - applyHidden(file.getNodeRef(), filter, visibilityMask); + applyHidden(file, filter); + } + } + } + + private void applyHidden(NodeRef nodeRef, HiddenFileInfo filter, boolean checkChildren) + { + if(!hasHiddenAspect(nodeRef)) + { + addHiddenAspect(nodeRef, filter); + } + else + { + nodeService.setProperty(nodeRef, ContentModel.PROP_VISIBILITY_MASK, filter.getVisibilityMask()); + nodeService.setProperty(nodeRef, ContentModel.PROP_CASCADE_HIDDEN, filter.cascadeHiddenAspect()); + nodeService.setProperty(nodeRef, ContentModel.PROP_CASCADE_INDEX_CONTROL, filter.cascadeIndexControlAspect()); + } + + if(!hasIndexControlAspect(nodeRef)) + { + addIndexControlAspect(nodeRef); + } + + QName typeQName = nodeService.getType(nodeRef); + FileFolderServiceType type = fileFolderService.getType(typeQName); + boolean isFolder = type.equals(FileFolderServiceType.FOLDER) || type.equals(FileFolderServiceType.SYSTEM_FOLDER); + if(isFolder && checkChildren && (filter.cascadeHiddenAspect() || filter.cascadeIndexControlAspect())) + { + PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE, null); + PagingResults results = fileFolderService.list(nodeRef, true, true, null, null, pagingRequest); + List files = results.getPage(); + + // apply the hidden aspect to all folders and folders and then recursively to all sub-folders, unless the sub-folder + // already has the hidden aspect applied (it may have been applied for a different pattern). + for(FileInfo file : files) + { + applyHidden(file, filter); } } } - private void removeHidden(NodeRef nodeRef) + public void removeHidden(NodeRef nodeRef) { - PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE, null); - PagingResults results = fileFolderService.list(nodeRef, true, true, null, null, pagingRequest); - List files = results.getPage(); - - for(FileInfo file : files) - { - String name = (String)nodeService.getProperty(file.getNodeRef(), ContentModel.PROP_NAME); - // remove hidden aspect only if it doesn't match a hidden pattern - if(isHidden(name) == null) - { - removeHiddenAspect(file.getNodeRef()); - removeIndexControlAspect(file.getNodeRef()); - - if(file.isFolder()) - { - removeHidden(file.getNodeRef()); - } - } - } + Client saveClient = FileFilterMode.getClient(); + FileFilterMode.setClient(Client.admin); + try + { + PagingRequest pagingRequest = new PagingRequest(0, Integer.MAX_VALUE, null); + PagingResults results = fileFolderService.list(nodeRef, true, true, null, null, pagingRequest); + List files = results.getPage(); + + for(FileInfo file : files) + { + String name = (String)nodeService.getProperty(file.getNodeRef(), ContentModel.PROP_NAME); + // remove hidden aspect only if it doesn't match a hidden pattern + if(isHidden(name) == null) + { + removeHiddenAspect(file.getNodeRef()); + removeIndexControlAspect(file.getNodeRef()); + + if(file.isFolder()) + { + removeHidden(file.getNodeRef()); + } + } + } + } + finally + { + FileFilterMode.setClient(saveClient); + } } private HiddenFileInfo findMatch(NodeRef nodeRef) @@ -446,9 +579,9 @@ public class HiddenAspect * @param fileInfo * @return */ - public void hideNode(NodeRef nodeRef) + public void hideNode(NodeRef nodeRef, boolean cascadeHiddenAspect, boolean cascadeIndexControlAspect, boolean clientControlled) { - addHiddenAspect(nodeRef, 0, false); + addHiddenAspect(nodeRef, 0, cascadeHiddenAspect, cascadeIndexControlAspect, clientControlled); addIndexControlAspect(nodeRef); } @@ -479,9 +612,9 @@ public class HiddenAspect * @param nodeRef the node to hide * @param clientVisibilityMask */ - public void hideNode(NodeRef nodeRef, int clientVisibilityMask) + public void hideNode(NodeRef nodeRef, int clientVisibilityMask, boolean cascadeHiddenAspect, boolean cascadeIndexControlAspect, boolean clientControlled) { - addHiddenAspect(nodeRef, clientVisibilityMask, true); + addHiddenAspect(nodeRef, clientVisibilityMask, cascadeHiddenAspect, cascadeIndexControlAspect, clientControlled); addIndexControlAspect(nodeRef); } @@ -502,32 +635,25 @@ public class HiddenAspect { if(!hasHiddenAspect(nodeRef)) { - hideNode(nodeRef, filter.getVisibilityMask()); + hideNode(nodeRef, filter.getVisibilityMask(), true, true, false); } } } } /** - * Checks whether the file should be hidden based upon whether its name maches a set of filters and applies the hidden - * and not indexed aspects if so. - *

- * Can optionally remove the hidden and index control aspects if the name of a node no longer matches the filter. + * Checks whether the file should be hidden and applies the hidden and not indexed aspects if so. * * @param fileInfo * @param both if true, will check if the node should not be hidden and remove hidden and index control - * aspects if they are present + * aspects if they are present * @return */ - public HiddenFileInfo checkHidden(FileInfoImpl fileInfo, boolean both) + public boolean checkHidden(FileInfo fileInfo, boolean both, boolean checkChildren) { NodeRef nodeRef = fileInfo.getNodeRef(); - HiddenFileInfo hiddenFileInfo = checkHidden(nodeRef, both); - if(hiddenFileInfo != null) - { - fileInfo.setHidden(true); - } - return hiddenFileInfo; + boolean ret = checkHidden(nodeRef, both, checkChildren); + return ret; } /** @@ -538,15 +664,88 @@ public class HiddenAspect * @param fileInfo, file to make hidden * @param visibilityMask */ - public void hideNode(FileInfoImpl fileInfo, int visibilityMask) + public void hideNode(FileInfoImpl fileInfo, int visibilityMask, boolean cascadeHiddenAspect, boolean cascadeIndexControlAspect, boolean clientControlled) { - hideNode(fileInfo.getNodeRef(), visibilityMask); + hideNode(fileInfo.getNodeRef(), visibilityMask, cascadeHiddenAspect, cascadeIndexControlAspect, clientControlled); fileInfo.setHidden(true); } + private HiddenFileInfo isParentHidden(NodeRef nodeRef) + { + HiddenFileInfo info = null; + + ChildAssociationRef childAssocRef = nodeService.getPrimaryParent(nodeRef); + if(childAssocRef != null) + { + NodeRef primaryParentNodeRef = childAssocRef.getParentRef(); + if(primaryParentNodeRef != null) + { + boolean isParentHidden = hasHiddenAspect(primaryParentNodeRef); + if(isParentHidden) + { + final Integer visibilityMask = (Integer)nodeService.getProperty(primaryParentNodeRef, ContentModel.PROP_VISIBILITY_MASK); + final Boolean cascadeHidden = (Boolean)nodeService.getProperty(primaryParentNodeRef, ContentModel.PROP_CASCADE_HIDDEN); + final Boolean cascadeIndexControl = (Boolean)nodeService.getProperty(primaryParentNodeRef, ContentModel.PROP_CASCADE_HIDDEN); + final Boolean clientControlled = (Boolean)nodeService.getProperty(primaryParentNodeRef, ContentModel.PROP_CLIENT_CONTROLLED); + + + info = new HiddenFileInfo() + { + @Override + public boolean isHidden(String path) + { + // not checking by path but by hidden aspect, not used in this use case anyway + return false; + } + + @Override + public int getVisibilityMask() + { + // default is hidden to all clients if not specified + return visibilityMask != null ? visibilityMask.intValue() : 0; + } + + @Override + public boolean isClientControlled() + { + return clientControlled != null ? clientControlled.booleanValue() : false; + } + + @Override + public String getFilter() + { + return null; + } + + @Override + public boolean cascadeIndexControlAspect() + { + return cascadeIndexControl != null ? cascadeIndexControl.booleanValue() : false; + } + + @Override + public boolean cascadeHiddenAspect() + { + return cascadeHidden != null ? cascadeHidden.booleanValue() : false; + } + }; + } + } + } + + return info; + } + + public boolean isClientControlled(NodeRef nodeRef) + { + Boolean clientControlled = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_CLIENT_CONTROLLED); + return clientControlled != null && clientControlled.booleanValue(); + } + /** - * Checks whether the file should be hidden based upon whether its name maches a set of filters and applies the hidden - * and not indexed aspects if so. + * Checks whether the file should be hidden and applies the hidden and not indexed aspects to it + * and its children (if cascadeHidden == true). The visibility mask property will determine visibility for specific + * clients. *

* Can optionally remove the hidden and index control aspects if the name of a node no longer matches the filter. * @@ -554,10 +753,10 @@ public class HiddenAspect * @param both if true, will check both if the node should not be hidden and remove hidden and index control * aspects if they are present, and if the node should be hidden and add hidden and index control * aspects if they are not present. - * @return HiddenFileInfo + * @return true if the node is hidden, irrespective of the clientVisibility property value. */ - public HiddenFileInfo checkHidden(NodeRef nodeRef, boolean both) - { + public boolean checkHidden(NodeRef nodeRef, boolean both, boolean checkChildren) + { if(nodeService.hasAspect(nodeRef, ContentModel.ASPECT_HIDDEN)) { Boolean isHiddenFlag = (Boolean)nodeService.getProperty(nodeRef, ContentModel.PROP_HIDDEN_FLAG); @@ -565,45 +764,79 @@ public class HiddenAspect { logger.debug("node has hidden flag set"); // node has hidden flag - we are not going to change anything. - return null; + return true; } } - - HiddenFileInfo filter = findMatch(nodeRef); - if(filter != null) - { - int visibilityMask = filter.getVisibilityMask(); + + boolean isHidden = false; - if(!hasHiddenAspect(nodeRef)) + if(hasHiddenAspect(nodeRef) && isClientControlled(nodeRef)) + { + // node is already hidden and client controlled -> hidden + isHidden = true; + } + else + { + HiddenFileInfo info = isParentHidden(nodeRef); + if(info != null && info.cascadeHiddenAspect()) { - // the file matches a pattern, apply the hidden and aspect control aspects - addHiddenAspect(nodeRef, visibilityMask, false); - } - - if(!hasIndexControlAspect(nodeRef)) - { - addIndexControlAspect(nodeRef); - } + // Parent has hidden aspect and cascade == true, so apply hidden aspect to children + isHidden = true; + if(!hasHiddenAspect(nodeRef)) + { + addHiddenAspect(nodeRef, info); + } - applyHidden(nodeRef, filter, visibilityMask); - } - else if(both) - { - // the file does not match the pattern, ensure that the hidden and index control aspects are not present - if(hasHiddenAspect(nodeRef)) - { - removeHiddenAspect(nodeRef); + if(!hasIndexControlAspect(nodeRef)) + { + addIndexControlAspect(nodeRef); + } + applyHidden(nodeRef, info, checkChildren); } + else + { + // apply the "old" behaviour: try to match the node path against one of the registered hidden file patterns. + info = findMatch(nodeRef); + if(info != null) + { + isHidden = true; + if(!hasHiddenAspect(nodeRef)) + { + addHiddenAspect(nodeRef, info); + } + else + { + nodeService.setProperty(nodeRef, ContentModel.PROP_VISIBILITY_MASK, info.getVisibilityMask()); + nodeService.setProperty(nodeRef, ContentModel.PROP_CASCADE_HIDDEN, info.cascadeHiddenAspect()); + nodeService.setProperty(nodeRef, ContentModel.PROP_CASCADE_INDEX_CONTROL, info.cascadeIndexControlAspect()); + } - if(hasIndexControlAspect(nodeRef)) - { - removeIndexControlAspect(nodeRef); - } + if(!hasIndexControlAspect(nodeRef)) + { + addIndexControlAspect(nodeRef); + } - removeHidden(nodeRef); + applyHidden(nodeRef, info, checkChildren); + } + else if(both) + { + // the file does not match the pattern, ensure that the hidden and index control aspects are not present + if(hasHiddenAspect(nodeRef)) + { + removeHiddenAspect(nodeRef); + } + + if(hasIndexControlAspect(nodeRef)) + { + removeIndexControlAspect(nodeRef); + } + + removeHidden(nodeRef); + } + } } - return filter; + return isHidden; } /** @@ -625,7 +858,11 @@ public class HiddenAspect Integer visibilityMask = (Integer)nodeService.getProperty(nodeRef, ContentModel.PROP_VISIBILITY_MASK); if (visibilityMask != null) { - if(visibilityMask.intValue() == 0) + if(client != null && client.equals(Client.admin)) + { + ret = Visibility.Visible; + } + else if(visibilityMask.intValue() == 0) { ret = Visibility.NotVisible; } @@ -746,5 +983,11 @@ public class HiddenAspect { return cascadeIndexControlAspect; } + + @Override + public boolean isClientControlled() + { + return false; + } } } diff --git a/source/java/org/alfresco/repo/model/filefolder/HiddenAspectTest.java b/source/java/org/alfresco/repo/model/filefolder/HiddenAspectTest.java index bfeae3a550..93c4a41f6e 100644 --- a/source/java/org/alfresco/repo/model/filefolder/HiddenAspectTest.java +++ b/source/java/org/alfresco/repo/model/filefolder/HiddenAspectTest.java @@ -5,10 +5,13 @@ import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Locale; +import java.util.Map; import javax.transaction.NotSupportedException; import javax.transaction.Status; @@ -31,6 +34,9 @@ import org.alfresco.service.cmr.model.FileExistsException; import org.alfresco.service.cmr.model.FileFolderService; import org.alfresco.service.cmr.model.FileInfo; import org.alfresco.service.cmr.model.FileNotFoundException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; @@ -38,6 +44,7 @@ import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchService; import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -67,6 +74,7 @@ public class HiddenAspectTest private NodeService nodeService; private FileFolderService fileFolderService; private MutableAuthenticationService authenticationService; + private PermissionService permissionService; private UserTransaction txn; private SearchService searchService; @@ -79,9 +87,11 @@ public class HiddenAspectTest private NodeRef rootNodeRef; private NodeRef topNodeRef; + private boolean imapEnabled; + private final String MAILBOX_NAME_A = "mailbox_a"; private final String MAILBOX_NAME_B = ".mailbox_a"; - private String anotherUserName; + private String username; private AlfrescoImapUser user; @Before @@ -98,19 +108,41 @@ public class HiddenAspectTest imapService = serviceRegistry.getImapService(); personService = serviceRegistry.getPersonService(); searchService = serviceRegistry.getSearchService(); + permissionService = serviceRegistry.getPermissionService(); + imapEnabled = serviceRegistry.getImapService().getImapServerEnabled(); + nodeDAO = (NodeDAO)ctx.getBean("nodeDAO"); // start the transaction txn = transactionService.getUserTransaction(); txn.begin(); + + username = "user" + System.currentTimeMillis(); + + PropertyMap testUser = new PropertyMap(); + testUser.put(ContentModel.PROP_USERNAME, username); + testUser.put(ContentModel.PROP_FIRSTNAME, username); + testUser.put(ContentModel.PROP_LASTNAME, username); + testUser.put(ContentModel.PROP_EMAIL, username + "@alfresco.com"); + testUser.put(ContentModel.PROP_JOBTITLE, "jobTitle"); // authenticate - AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); + + personService.createPerson(testUser); + + // create the ACEGI Authentication instance for the new user + authenticationService.createAuthentication(username, username.toCharArray()); + + user = new AlfrescoImapUser(username + "@alfresco.com", username, username); // create a test store storeRef = nodeService .createStore(StoreRef.PROTOCOL_WORKSPACE, getName() + System.currentTimeMillis()); rootNodeRef = nodeService.getRootNode(storeRef); + permissionService.setPermission(rootNodeRef, username, PermissionService.CREATE_CHILDREN, true); + + AuthenticationUtil.setFullyAuthenticatedUser(username); topNodeRef = nodeService.createNode( rootNodeRef, @@ -118,21 +150,7 @@ public class HiddenAspectTest QName.createQName(NamespaceService.ALFRESCO_URI, "working root"), ContentModel.TYPE_FOLDER).getChildRef(); - anotherUserName = "user" + System.currentTimeMillis(); - - PropertyMap testUser = new PropertyMap(); - testUser.put(ContentModel.PROP_USERNAME, anotherUserName); - testUser.put(ContentModel.PROP_FIRSTNAME, anotherUserName); - testUser.put(ContentModel.PROP_LASTNAME, anotherUserName); - testUser.put(ContentModel.PROP_EMAIL, anotherUserName + "@alfresco.com"); - testUser.put(ContentModel.PROP_JOBTITLE, "jobTitle"); - - personService.createPerson(testUser); - - // create the ACEGI Authentication instance for the new user - authenticationService.createAuthentication(anotherUserName, anotherUserName.toCharArray()); - - user = new AlfrescoImapUser(anotherUserName + "@alfresco.com", anotherUserName, anotherUserName); + AuthenticationUtil.setAdminUserAsFullyAuthenticatedUser(); } private String getName() @@ -147,7 +165,7 @@ public class HiddenAspectTest { if (txn.getStatus() != Status.STATUS_ROLLEDBACK && txn.getStatus() != Status.STATUS_COMMITTED) { - txn.rollback(); + txn.commit(); } } catch (Throwable e) @@ -223,7 +241,10 @@ public class HiddenAspectTest assertEquals("", 0, results.length()); for(Client client : hiddenAspect.getClients()) { - assertEquals(Visibility.NotVisible, hiddenAspect.getVisibility(client, node)); + if(!client.equals(Client.admin)) + { + assertEquals(Visibility.NotVisible, hiddenAspect.getVisibility(client, node)); + } } // .DS_Store is a system path and so is visible in nfs and webdav, as a hidden file in cifs and hidden to all other clients @@ -242,7 +263,7 @@ public class HiddenAspectTest { assertEquals("Should be visible for client " + client, Visibility.Visible, hiddenAspect.getVisibility(client, node)); } - else + else if(client != Client.admin) { assertEquals("Should not be visible for client " + client, Visibility.NotVisible, hiddenAspect.getVisibility(client, node)); } @@ -256,7 +277,10 @@ public class HiddenAspectTest assertEquals("", 0, results.length()); for(Client client : hiddenAspect.getClients()) { - assertEquals("Client " + client.toString(), Visibility.NotVisible, hiddenAspect.getVisibility(client, node)); + if(client != Client.admin) + { + assertEquals("Client " + client.toString(), Visibility.NotVisible, hiddenAspect.getVisibility(client, node)); + } } } finally @@ -265,25 +289,180 @@ public class HiddenAspectTest } } + @Test + public void testClientControlled() throws Exception + { + // test that a client controlled hidden node is not subject to hidden file patterns + { + // node does not match a hidden file pattern + String nodeName = GUID.generate(); + String hiddenNodeName = nodeName; + Map properties = new HashMap(11); + properties.put(ContentModel.PROP_NAME, hiddenNodeName); + + // create the node + QName assocQName = QName.createQName( + NamespaceService.CONTENT_MODEL_1_0_URI, + QName.createValidLocalName(hiddenNodeName)); + ChildAssociationRef assocRef = null; + try + { + assocRef = nodeService.createNode( + topNodeRef, + ContentModel.ASSOC_CONTAINS, + assocQName, + ContentModel.TYPE_FOLDER, + properties); + } + catch (DuplicateChildNodeNameException e) + { + throw new FileExistsException(topNodeRef, hiddenNodeName); + } + + NodeRef parent = assocRef.getChildRef(); + NodeRef child = null; + NodeRef child1 = null; + + Client saveClient = FileFilterMode.setClient(Client.cmis); + try + { + hiddenAspect.hideNode(parent, true, true, true); + + child = fileFolderService.create(parent, "folder11", ContentModel.TYPE_FOLDER).getNodeRef(); + child1 = fileFolderService.create(child, "folder21", ContentModel.TYPE_FOLDER).getNodeRef(); + assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); + assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); + assertTrue(nodeService.hasAspect(child1, ContentModel.ASPECT_HIDDEN)); + assertTrue(nodeService.hasAspect(child1, ContentModel.ASPECT_INDEX_CONTROL)); + + // renaming from a hidden file pattern to a non-hidden file pattern should leave the node as hidden + // because it is client-controlled. + fileFolderService.rename(parent, nodeName); + + assertTrue(nodeService.hasAspect(parent, ContentModel.ASPECT_HIDDEN)); + assertTrue(nodeService.hasAspect(parent, ContentModel.ASPECT_INDEX_CONTROL)); + assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); + assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); + assertTrue(nodeService.hasAspect(child1, ContentModel.ASPECT_HIDDEN)); + assertTrue(nodeService.hasAspect(child1, ContentModel.ASPECT_INDEX_CONTROL)); + } + finally + { + FileFilterMode.setClient(saveClient); + } + + List children = fileFolderService.list(parent); + assertEquals(0, children.size()); + + saveClient = FileFilterMode.setClient(Client.script); + try + { + children = fileFolderService.list(parent); + } + finally + { + FileFilterMode.setClient(saveClient); + } + assertEquals(0, children.size()); + + saveClient = FileFilterMode.setClient(Client.cmis); + try + { + children = fileFolderService.list(parent); + } + finally + { + FileFilterMode.setClient(saveClient); + } + assertEquals(0, children.size()); + + // remove the client-controlled hidden aspect from the parent and check that it is no longer hidden + saveClient = FileFilterMode.setClient(Client.cmis); + try + { + nodeService.removeAspect(parent, ContentModel.ASPECT_HIDDEN); + + assertFalse(nodeService.hasAspect(parent, ContentModel.ASPECT_HIDDEN)); + } + finally + { + FileFilterMode.setClient(saveClient); + } + } + + // test that a cascading hidden pattern defined in model-specific-services-content.xml results in hidden files + // (node is not client-controlled hidden) + { + NodeRef parent = null; + NodeRef child = null; + NodeRef child1 = null; + + Client saveClient = FileFilterMode.setClient(Client.cmis); + try + { + parent = fileFolderService.create(topNodeRef, "." + GUID.generate(), ContentModel.TYPE_FOLDER).getNodeRef(); + child = fileFolderService.create(parent, "folder11", ContentModel.TYPE_FOLDER).getNodeRef(); + child1 = fileFolderService.create(child, "folder21", ContentModel.TYPE_FOLDER).getNodeRef(); + } + finally + { + FileFilterMode.setClient(saveClient); + } + + assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); + assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); + assertTrue(nodeService.hasAspect(child1, ContentModel.ASPECT_HIDDEN)); + assertTrue(nodeService.hasAspect(child1, ContentModel.ASPECT_INDEX_CONTROL)); + + List children = fileFolderService.list(parent); + assertEquals(0, children.size()); + + saveClient = FileFilterMode.setClient(Client.script); + try + { + children = fileFolderService.list(parent); + } + finally + { + FileFilterMode.setClient(saveClient); + } + assertEquals(0, children.size()); + + saveClient = FileFilterMode.setClient(Client.cmis); + try + { + children = fileFolderService.list(parent); + } + finally + { + FileFilterMode.setClient(saveClient); + } + assertEquals(0, children.size()); + } + } + @Test public void testImap() { - FileFilterMode.setClient(Client.webdav); - - try - { - // Test that hidden files don't apply to imap service - imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); - imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); - assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_A)); - assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_B)); - assertEquals("Can't rename mailbox", 0, numMailboxes(user, MAILBOX_NAME_A)); - assertEquals("Can't rename mailbox", 1, numMailboxes(user, MAILBOX_NAME_B)); - } - finally - { - FileFilterMode.clearClient(); - } + if(imapEnabled) + { + FileFilterMode.setClient(Client.webdav); + + try + { + // Test that hidden files don't apply to imap service + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); + imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); + assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_A)); + assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_B)); + assertEquals("Can't rename mailbox", 0, numMailboxes(user, MAILBOX_NAME_A)); + assertEquals("Can't rename mailbox", 1, numMailboxes(user, MAILBOX_NAME_B)); + } + finally + { + FileFilterMode.clearClient(); + } + } } @Test @@ -366,7 +545,7 @@ public class HiddenAspectTest { fail(); } - + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); assertFalse(nodeService.hasAspect(node11, ContentModel.ASPECT_HIDDEN)); @@ -381,7 +560,7 @@ public class HiddenAspectTest assertTrue(nodeService.hasAspect(node31, ContentModel.ASPECT_INDEX_CONTROL)); assertTrue(nodeService.hasAspect(node41, ContentModel.ASPECT_HIDDEN)); assertTrue(nodeService.hasAspect(node41, ContentModel.ASPECT_INDEX_CONTROL)); - + results = searchForName(nodeName); assertEquals("", 1, results.length()); } @@ -394,129 +573,133 @@ public class HiddenAspectTest @Test public void testHiddenFilesBasicClient() { - FileFilterMode.setClient(Client.imap); - - try - { - // check temporary file - NodeRef parent = fileFolderService.create(topNodeRef, "New Folder", ContentModel.TYPE_FOLDER).getNodeRef(); - NodeRef child = fileFolderService.create(parent, "file.tmp", ContentModel.TYPE_CONTENT).getNodeRef(); - assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_TEMPORARY)); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); - ResultSet results = searchForName("file.tmp"); - assertEquals("", 1, results.length()); - List children = fileFolderService.list(parent); - assertEquals(1, children.size()); - - // check hidden files - should not be hidden for a basic client - parent = fileFolderService.create(topNodeRef, ".TemporaryItems", ContentModel.TYPE_FOLDER).getNodeRef(); - child = fileFolderService.create(parent, "inTemporaryItems", ContentModel.TYPE_FOLDER).getNodeRef(); - assertFalse(nodeService.hasAspect(parent, ContentModel.ASPECT_TEMPORARY)); - assertFalse(nodeService.hasAspect(parent, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(parent, ContentModel.ASPECT_INDEX_CONTROL)); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_TEMPORARY)); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); - results = searchForName(".TemporaryItems"); - assertEquals("", 1, results.length()); - children = fileFolderService.list(parent); - assertEquals(1, children.size()); - - parent = fileFolderService.create(topNodeRef, "Folder 2", ContentModel.TYPE_FOLDER).getNodeRef(); - child = fileFolderService.create(parent, "Thumbs.db", ContentModel.TYPE_CONTENT).getNodeRef(); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_TEMPORARY)); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); - results = searchForName("Thumbs.db"); - assertEquals("", 1, results.length()); - children = fileFolderService.list(parent); - assertEquals(1, children.size()); - - NodeRef node = fileFolderService.create(topNodeRef, "surf-config", ContentModel.TYPE_FOLDER).getNodeRef(); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); - results = searchForName("surf-config"); - assertEquals("", 1, results.length()); - - node = fileFolderService.create(topNodeRef, ".DS_Store", ContentModel.TYPE_CONTENT).getNodeRef(); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); - results = searchForName(".DS_Store"); - assertEquals("", 1, results.length()); - for(Client client : hiddenAspect.getClients()) - { - assertEquals("Should be visible for client " + client, Visibility.Visible, hiddenAspect.getVisibility(client, node)); - } - - node = fileFolderService.create(topNodeRef, "._resourceFork", ContentModel.TYPE_FOLDER).getNodeRef(); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); - results = searchForName("._resourceFork"); - assertEquals("", 1, results.length()); - - children = fileFolderService.list(parent); - assertEquals(1, children.size()); - - - String nodeName = "Node" + System.currentTimeMillis(); - node = fileFolderService.create(topNodeRef, nodeName, ContentModel.TYPE_CONTENT).getNodeRef(); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); - results = searchForName(nodeName); - assertEquals("", 1, results.length()); - try - { - fileFolderService.rename(node, "." + nodeName); - } - catch (FileExistsException e) - { - fail(); - } - catch (FileNotFoundException e) - { - fail(); - } - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); - - results = searchForName(nodeName); - assertEquals("", 1, results.length()); - - results = searchForName("." + nodeName); - assertEquals("", 1, results.length()); - - try - { - fileFolderService.rename(node, nodeName); - } - catch (FileExistsException e) - { - fail(); - } - catch (FileNotFoundException e) - { - fail(); - } - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); - assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); - - results = searchForName("." + nodeName); - assertEquals("", 1, results.length()); - - imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); - imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); - assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_A)); - assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_B)); - assertEquals("Can't rename mailbox", 0, numMailboxes(user, MAILBOX_NAME_A)); - assertEquals("Can't rename mailbox", 1, numMailboxes(user, MAILBOX_NAME_B)); - } - finally - { - FileFilterMode.clearClient(); - } + if(imapEnabled) + { + FileFilterMode.setClient(Client.imap); + + try + { + // check temporary file + NodeRef parent = fileFolderService.create(topNodeRef, "New Folder", ContentModel.TYPE_FOLDER).getNodeRef(); + NodeRef child = fileFolderService.create(parent, "file.tmp", ContentModel.TYPE_CONTENT).getNodeRef(); + assertTrue(nodeService.hasAspect(child, ContentModel.ASPECT_TEMPORARY)); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); + ResultSet results = searchForName("file.tmp"); + assertEquals("", 1, results.length()); + List children = fileFolderService.list(parent); + assertEquals(1, children.size()); + + // check hidden files - should not be hidden for a basic client + parent = fileFolderService.create(topNodeRef, ".TemporaryItems", ContentModel.TYPE_FOLDER).getNodeRef(); + child = fileFolderService.create(parent, "inTemporaryItems", ContentModel.TYPE_FOLDER).getNodeRef(); + assertFalse(nodeService.hasAspect(parent, ContentModel.ASPECT_TEMPORARY)); + assertFalse(nodeService.hasAspect(parent, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(parent, ContentModel.ASPECT_INDEX_CONTROL)); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_TEMPORARY)); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); + results = searchForName(".TemporaryItems"); + assertEquals("", 1, results.length()); + children = fileFolderService.list(parent); + assertEquals(1, children.size()); + + parent = fileFolderService.create(topNodeRef, "Folder 2", ContentModel.TYPE_FOLDER).getNodeRef(); + child = fileFolderService.create(parent, "Thumbs.db", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_TEMPORARY)); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(child, ContentModel.ASPECT_INDEX_CONTROL)); + results = searchForName("Thumbs.db"); + assertEquals("", 1, results.length()); + children = fileFolderService.list(parent); + assertEquals(1, children.size()); + + NodeRef node = fileFolderService.create(topNodeRef, "surf-config", ContentModel.TYPE_FOLDER).getNodeRef(); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); + results = searchForName("surf-config"); + assertEquals("", 1, results.length()); + + node = fileFolderService.create(topNodeRef, ".DS_Store", ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); + results = searchForName(".DS_Store"); + assertEquals("", 1, results.length()); + for(Client client : hiddenAspect.getClients()) + { + assertEquals("Should be visible for client " + client, Visibility.Visible, hiddenAspect.getVisibility(client, node)); + } + + node = fileFolderService.create(topNodeRef, "._resourceFork", ContentModel.TYPE_FOLDER).getNodeRef(); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); + results = searchForName("._resourceFork"); + assertEquals("", 1, results.length()); + + children = fileFolderService.list(parent); + assertEquals(1, children.size()); + + + String nodeName = "Node" + System.currentTimeMillis(); + node = fileFolderService.create(topNodeRef, nodeName, ContentModel.TYPE_CONTENT).getNodeRef(); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); + results = searchForName(nodeName); + assertEquals("", 1, results.length()); + try + { + fileFolderService.rename(node, "." + nodeName); + } + catch (FileExistsException e) + { + fail(); + } + catch (FileNotFoundException e) + { + fail(); + } + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); + + results = searchForName(nodeName); + assertEquals("", 1, results.length()); + + results = searchForName("." + nodeName); + assertEquals("", 1, results.length()); + + try + { + fileFolderService.rename(node, nodeName); + } + catch (FileExistsException e) + { + fail(); + } + catch (FileNotFoundException e) + { + fail(); + } + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_HIDDEN)); + assertFalse(nodeService.hasAspect(node, ContentModel.ASPECT_INDEX_CONTROL)); + + results = searchForName("." + nodeName); + assertEquals("", 1, results.length()); + + imapService.getOrCreateMailbox(user, MAILBOX_NAME_A, false, true); + imapService.renameMailbox(user, MAILBOX_NAME_A, MAILBOX_NAME_B); + assertFalse("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_A)); + assertTrue("Can't rename mailbox", checkMailbox(user, MAILBOX_NAME_B)); + assertEquals("Can't rename mailbox", 0, numMailboxes(user, MAILBOX_NAME_A)); + assertEquals("Can't rename mailbox", 1, numMailboxes(user, MAILBOX_NAME_B)); + } + finally + { + FileFilterMode.clearClient(); + } + } } + @SuppressWarnings("unused") @Test public void testCheckHidden() throws Exception { @@ -578,7 +761,7 @@ public class HiddenAspectTest // call checkHidden to maks sure it does not make a mistake - hiddenAspect.checkHidden(childA, true); + hiddenAspect.checkHidden(childA, true, false); // hiddenAspect.checkHidden(childB, true); { diff --git a/source/java/org/alfresco/repo/model/filefolder/HiddenFileInfo.java b/source/java/org/alfresco/repo/model/filefolder/HiddenFileInfo.java index 7ae1c93440..88fd35656c 100644 --- a/source/java/org/alfresco/repo/model/filefolder/HiddenFileInfo.java +++ b/source/java/org/alfresco/repo/model/filefolder/HiddenFileInfo.java @@ -10,6 +10,7 @@ public interface HiddenFileInfo { public boolean cascadeHiddenAspect(); public boolean cascadeIndexControlAspect(); + public boolean isClientControlled(); public int getVisibilityMask(); public String getFilter(); public boolean isHidden(String path); diff --git a/source/java/org/alfresco/repo/node/NodeServiceTest.java b/source/java/org/alfresco/repo/node/NodeServiceTest.java index ee28ac6769..8c8badc43c 100644 --- a/source/java/org/alfresco/repo/node/NodeServiceTest.java +++ b/source/java/org/alfresco/repo/node/NodeServiceTest.java @@ -23,6 +23,7 @@ import static org.mockito.Mockito.verify; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; @@ -64,6 +65,7 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.DuplicateChildNodeNameException; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; import org.alfresco.service.cmr.repository.MLText; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeRef.Status; @@ -81,6 +83,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.hibernate.dialect.Dialect; import org.springframework.context.ApplicationContext; +import org.springframework.dao.ConcurrencyFailureException; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -1284,6 +1287,159 @@ public class NodeServiceTest extends TestCase fail("We allowed orphaned nodes or nodes with deleted parents."); } + /** + * Test for MNT-8494 - we should be able to recover when indexing encounters a node with deleted ancestors + */ + public void testLinkToDeletedNodeRecovery() throws Throwable + { + QNameDAO qnameDAO = (QNameDAO) ctx.getBean("qnameDAO"); + Long deletedTypeQNameId = qnameDAO.getOrCreateQName(ContentModel.TYPE_DELETED).getFirst(); + // First find any broken links to start with + final NodeEntity params = new NodeEntity(); + params.setId(0L); + params.setTypeQNameId(deletedTypeQNameId); + + List nodesWithDeletedParents = getChildNodesWithDeletedParentNode(params, 0); + List deletedChildren = getDeletedChildren(params, 0); + List nodesWithNoParents = getChildNodesWithNoParentNode(params, 0); + + logger.debug("Found child nodes with deleted parent node (before): " + nodesWithDeletedParents); + + final NodeRef[] nodeRefs = new NodeRef[10]; + final NodeRef workspaceRootNodeRef = nodeService.getRootNode(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE); + buildNodeHierarchy(workspaceRootNodeRef, nodeRefs); + + int cnt = 5; + final List childNodeRefs = new ArrayList(cnt); + + final NodeDAO nodeDAO = (NodeDAO) ctx.getBean("nodeDAO"); + + for (int i = 0; i < cnt; i++) + { + // create some pseudo- thumnails + String randomName = getName() + "-" + System.nanoTime(); + QName randomQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, randomName); + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, randomName); + + // Choose a random parent node from the hierarchy + int random = new Random().nextInt(10); + NodeRef parentNodeRef = nodeRefs[random]; + + NodeRef childNodeRef = nodeService.createNode(parentNodeRef, ContentModel.ASSOC_CONTAINS, randomQName, + ContentModel.TYPE_THUMBNAIL, props).getChildRef(); + + childNodeRefs.add(childNodeRef); + } + + // forcefully delete the root, a random connecting one, and a random leaf + // We'll need to disable indexing to do this or the transaction will be thrown out + nodeIndexer.setDisabled(true); + try + { + txnService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + Long nodeId = (Long) nodeService.getProperty(nodeRefs[0], ContentModel.PROP_NODE_DBID); + nodeDAO.updateNode(nodeId, ContentModel.TYPE_DELETED, null); + nodeDAO.removeNodeAspects(nodeId); + nodeDAO.removeNodeProperties(nodeId, nodeDAO.getNodeProperties(nodeId).keySet()); + nodeId = (Long) nodeService.getProperty(nodeRefs[2], ContentModel.PROP_NODE_DBID); + nodeDAO.updateNode(nodeId, ContentModel.TYPE_DELETED, null); + nodeDAO.removeNodeAspects(nodeId); + nodeDAO.removeNodeProperties(nodeId, nodeDAO.getNodeProperties(nodeId).keySet()); + nodeId = (Long) nodeService.getProperty(childNodeRefs.get(childNodeRefs.size() - 1), + ContentModel.PROP_NODE_DBID); + nodeDAO.updateNode(nodeId, ContentModel.TYPE_DELETED, null); + nodeDAO.removeNodeAspects(nodeId); + nodeDAO.removeNodeProperties(nodeId, nodeDAO.getNodeProperties(nodeId).keySet()); + return null; + } + }); + } + finally + { + nodeIndexer.setDisabled(false); + } + + // Now need to identify the problem nodes + final List childNodeIds = getChildNodesWithDeletedParentNode(params, nodesWithDeletedParents.size()); + assertFalse(childNodeIds.isEmpty()); + logger.debug("Found child nodes with deleted parent node (after): " + childNodeIds); + + // Now visit the nodes in reverse order and do indexing-like things + List allNodeRefs = new ArrayList(nodeRefs.length + childNodeRefs.size()); + allNodeRefs.addAll(Arrays.asList(nodeRefs)); + allNodeRefs.addAll(childNodeRefs); + Collections.reverse(allNodeRefs); + for (final NodeRef nodeRef : allNodeRefs) + { + txnService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + if (nodeService.exists(nodeRef)) + { + try + { + for (ChildAssociationRef parentRef : nodeService.getParentAssocs(nodeRef)) + { + nodeService.getPath(parentRef.getParentRef()); + } + nodeService.getPath(nodeRef); // ignore return + } + catch (InvalidNodeRefException e) + { + throw new ConcurrencyFailureException("Deleted node - should be healed on retry", e); + } + } + return null; + } + }); + } + + // Let's fix up the deleted child nodes indexing might not spot, but hierarchy traversal (e.g. getChildAssocs) + // might + for (final NodeRef nodeRef : allNodeRefs) + { + txnService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() + { + @Override + public Void execute() throws Throwable + { + nodeDAO.getNodePair(nodeRef); + return null; + } + }); + } + + // Check again + List nodeIds = getDeletedChildren(params, deletedChildren.size()); + assertTrue("The following deleted nodes still have parents: " + nodeIds, nodeIds.isEmpty()); + nodeIds = getChildNodesWithDeletedParentNode(params, nodesWithDeletedParents.size()); + assertTrue("The following child nodes have deleted parent nodes: " + nodeIds, nodeIds.isEmpty()); + nodeIds = getChildNodesWithNoParentNode(params, nodesWithNoParents.size()); + assertTrue("The following child nodes have no parent node: " + nodeIds, nodeIds.isEmpty()); + + // check lost_found ... + List lostAndFoundNodeRefs = getLostAndFoundNodes(); + assertFalse(lostAndFoundNodeRefs.isEmpty()); + + List lostAndFoundNodeIds = new ArrayList(lostAndFoundNodeRefs.size()); + for (NodeRef nodeRef : lostAndFoundNodeRefs) + { + lostAndFoundNodeIds.add(nodeDAO.getNodePair(nodeRef).getFirst()); + } + + for (Long childNodeId : childNodeIds) + { + assertTrue("Not found: "+childNodeId, lostAndFoundNodeIds.contains(childNodeId) || !nodeDAO.exists(childNodeId)); + } + } + /** * Pending repeatable test - force issue ALF-ALF-13066 (non-root node with no parent) */ @@ -1392,7 +1548,7 @@ public class NodeServiceTest extends TestCase for (Long childNodeId : childNodeIds) { - assertTrue("Not found: "+childNodeId, lostAndFoundNodeIds.contains(childNodeId)); + assertTrue("Not found: "+childNodeId, lostAndFoundNodeIds.contains(childNodeId) || !nodeDAO.exists(childNodeId)); } } @@ -1416,6 +1572,16 @@ public class NodeServiceTest extends TestCase Integer.MAX_VALUE); } + private List getDeletedChildren(NodeEntity params, int idsToSkip) + { + return cannedQueryDAO.executeQuery( + "alfresco.query.test", + "select_NodeServiceTest_testLinkToDeletedNodeRecovery_GetDeletedChildrenCannedQuery", + params, + idsToSkip, + Integer.MAX_VALUE); + } + private List getLostAndFoundNodes() { Set childNodeTypeQNames = new HashSet(1); diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java index 428cfadad5..0eb04c2aa9 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImpl.java @@ -1605,7 +1605,7 @@ public class DbNodeServiceImpl extends AbstractNodeServiceImpl * If any of the values are null, a marker object is put in to mimic nulls. They will be turned back into * a real nulls when the properties are requested again. * - * @see Node#getProperties() + * @see Node#getProperties(boolean) */ public void setProperties(NodeRef nodeRef, Map properties) throws InvalidNodeRefException { diff --git a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java index 039a9529d2..3c453a54b5 100644 --- a/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java +++ b/source/java/org/alfresco/repo/node/db/DbNodeServiceImplTest.java @@ -19,6 +19,7 @@ package org.alfresco.repo.node.db; import java.io.Serializable; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; @@ -29,6 +30,7 @@ import javax.transaction.UserTransaction; import org.alfresco.model.ContentModel; import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.node.NodeDAO.ChildAssocRefQueryCallback; import org.alfresco.repo.domain.node.Transaction; import org.alfresco.repo.node.BaseNodeServiceTest; import org.alfresco.repo.node.cleanup.NodeCleanupRegistry; @@ -43,6 +45,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.namespace.QName; import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.service.transaction.TransactionService; +import org.alfresco.util.Pair; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -356,6 +359,13 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest assertNotNull(newStatus); // check assertEquals("Change didn't update status", currentTxnId, newStatus.getChangeTxnId()); + + // Make sure we can pre-load the node i.e. nodes in all state need to be pre-loadable + // See CLOUD-1807 + Long nodeId = newStatus.getDbId(); + nodeDAO.getParentAssocs(nodeId, null, null, null, new DummyChildAssocRefQueryCallback()); + nodeDAO.cacheNodesById(Collections.singletonList(nodeId)); + txn.commit(); } catch (Throwable e) @@ -364,6 +374,38 @@ public class DbNodeServiceImplTest extends BaseNodeServiceTest throw e; } } + + /** + * Dummy implementation that does nothing with the results + * @author Derek Hulley + * @since 4.2 + */ + public static class DummyChildAssocRefQueryCallback implements ChildAssocRefQueryCallback + { + @Override + public boolean preLoadNodes() + { + return true; + } + @Override + public boolean orderResults() + { + return false; + } + + @Override + public boolean handle( + Pair childAssocPair, + Pair parentNodePair, Pair childNodePair) + { + return true; + } + + @Override + public void done() + { + } + } public void testMLTextValues() throws Exception { diff --git a/source/java/org/alfresco/repo/policy/MTPolicyComponentTest.java b/source/java/org/alfresco/repo/policy/MTPolicyComponentTest.java new file mode 100644 index 0000000000..467fa4ae93 --- /dev/null +++ b/source/java/org/alfresco/repo/policy/MTPolicyComponentTest.java @@ -0,0 +1,410 @@ +/* + * Copyright (C) 2013-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.policy; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.List; +import java.util.Map; + +import junit.framework.TestCase; + +import org.alfresco.repo.cache.NullCache; +import org.alfresco.repo.dictionary.CompiledModel; +import org.alfresco.repo.dictionary.DictionaryBootstrap; +import org.alfresco.repo.dictionary.DictionaryComponent; +import org.alfresco.repo.dictionary.DictionaryDAOImpl; +import org.alfresco.repo.dictionary.NamespaceDAOImpl; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.QName; + +import static org.mockito.Matchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +/** + * Policy Component Tests + * + * Multi-tennant + * + * @author mrogers + */ +public class MTPolicyComponentTest extends TestCase +{ + private static final String TEST_MODEL = "org/alfresco/repo/policy/policycomponenttest_model.xml"; + private static final String TEST_NAMESPACE = "http://www.alfresco.org/test/policycomponenttest/1.0"; + private static QName BASE_TYPE = QName.createQName(TEST_NAMESPACE, "base"); + private static QName BASE_PROP_A = QName.createQName(TEST_NAMESPACE, "base_a"); + private static QName BASE_ASSOC_A = QName.createQName(TEST_NAMESPACE, "base_assoc_a"); + private static QName FILE_TYPE = QName.createQName(TEST_NAMESPACE, "file"); + private static QName FILE_PROP_B = QName.createQName(TEST_NAMESPACE, "file_b"); + private static QName FOLDER_TYPE = QName.createQName(TEST_NAMESPACE, "folder"); + private static QName FOLDER_PROP_D = QName.createQName(TEST_NAMESPACE, "folder_d"); + private static QName TEST_ASPECT = QName.createQName(TEST_NAMESPACE, "aspect"); + private static QName ASPECT_PROP_A = QName.createQName(TEST_NAMESPACE, "aspect_a"); + private static QName INVALID_TYPE = QName.createQName(TEST_NAMESPACE, "classdoesnotexist"); + + private PolicyComponent policyComponent = null; + + + static final String BASE_PROTOCOL = "baseProtocol"; + static final String BASE_IDENTIFIER = "baseIdentifier"; + static final String BASE_ID = "baseId"; + + @Override + protected void setUp() throws Exception + { + TenantService mockTenantService = mock(TenantService.class); + when(mockTenantService.isEnabled()).thenReturn(true); + when(mockTenantService.getCurrentUserDomain()).thenReturn("test.com"); + when(mockTenantService.getDomainUser(any(String.class), any(String.class))).thenReturn("System"); + when(mockTenantService.getBaseName(any(NodeRef.class))).thenReturn(new NodeRef(BASE_PROTOCOL, BASE_IDENTIFIER, BASE_ID)); + when(mockTenantService.getBaseName(any(StoreRef.class))).thenReturn(new StoreRef(BASE_PROTOCOL, BASE_IDENTIFIER)); + + + NamespaceDAOImpl namespaceDAO = new NamespaceDAOImpl(); + namespaceDAO.setTenantService(mockTenantService); + initNamespaceCaches(namespaceDAO); + DictionaryDAOImpl dictionaryDAO = new DictionaryDAOImpl(namespaceDAO); + dictionaryDAO.setTenantService(mockTenantService); + initDictionaryCaches(dictionaryDAO); + + DictionaryBootstrap bootstrap = new DictionaryBootstrap(); + List bootstrapModels = new ArrayList(); + bootstrapModels.add("alfresco/model/dictionaryModel.xml"); + bootstrapModels.add("org/alfresco/repo/policy/policycomponenttest_model.xml"); + bootstrapModels.add(TEST_MODEL); + bootstrap.setModels(bootstrapModels); + bootstrap.setDictionaryDAO(dictionaryDAO); + bootstrap.setTenantService(mockTenantService); + bootstrap.bootstrap(); + + DictionaryComponent dictionary = new DictionaryComponent(); + dictionary.setDictionaryDAO(dictionaryDAO); + + // Instantiate Policy Component + PolicyComponentImpl x = new PolicyComponentImpl(dictionary); + x.setTenantService(mockTenantService); + policyComponent = x; + } + + @SuppressWarnings("unchecked") + private void initDictionaryCaches(DictionaryDAOImpl dictionaryDAO) + { + // note: unit tested here with null cache + dictionaryDAO.setDictionaryRegistryCache(new NullCache()); + } + + @SuppressWarnings("unchecked") + private void initNamespaceCaches(NamespaceDAOImpl namespaceDAO) + { + // note: unit tested here with null cache + namespaceDAO.setNamespaceRegistryCache(new NullCache()); + } + + public void testJavaBehaviour() + { + Behaviour validBehaviour = new JavaBehaviour(this, "validClassTest"); + TestClassPolicy policy = validBehaviour.getInterface(TestClassPolicy.class); + assertNotNull(policy); + NodeRef nodeRef = new NodeRef("workspace", "SpacesStore", "123"); + Date date = new Date(); + StoreRef storeRef = new StoreRef("workspace", "SpacesStore"); + TestClassPolicyResult result = policy.test("argument", nodeRef, date, storeRef); + assertEquals("", "ValidTest: argument", result.getString()); + assertEquals("", nodeRef, result.getNodeRef()); + } + + + @SuppressWarnings("unchecked") + public void testRegisterDefinitions() + { + try + { + @SuppressWarnings("unused") ClassPolicyDelegate delegate = policyComponent.registerClassPolicy(InvalidMetaDataPolicy.class); + fail("Failed to catch hidden metadata"); + } + catch(PolicyException e) + { + } + + try + { + @SuppressWarnings("unused") ClassPolicyDelegate delegate = policyComponent.registerClassPolicy(NoMethodPolicy.class); + fail("Failed to catch no methods defined in policy"); + } + catch(PolicyException e) + { + } + + try + { + @SuppressWarnings("unused") ClassPolicyDelegate delegate = policyComponent.registerClassPolicy(MultiMethodPolicy.class); + fail("Failed to catch multiple methods defined in policy"); + } + catch(PolicyException e) + { + } + + QName policyName = QName.createQName(TEST_NAMESPACE, "test"); + boolean isRegistered = policyComponent.isRegisteredPolicy(PolicyType.Class, policyName); + assertFalse(isRegistered); + ClassPolicyDelegate delegate = policyComponent.registerClassPolicy(TestClassPolicy.class); + assertNotNull(delegate); + isRegistered = policyComponent.isRegisteredPolicy(PolicyType.Class, policyName); + assertTrue(isRegistered); + PolicyDefinition definition = policyComponent.getRegisteredPolicy(PolicyType.Class, policyName); + assertNotNull(definition); + assertEquals(policyName, definition.getName()); + assertEquals(PolicyType.Class, definition.getType()); + assertEquals(TestClassPolicy.class, definition.getPolicyInterface()); + } + + + public void testBindBehaviour() + { + QName policyName = QName.createQName(TEST_NAMESPACE, "test"); + Behaviour validBehaviour = new JavaBehaviour(this, "validClassTest"); + + // Test null policy + try + { + policyComponent.bindClassBehaviour(null, FILE_TYPE, validBehaviour); + fail("Failed to catch null policy whilst binding behaviour"); + } + catch(IllegalArgumentException e) {} + + // Test null Class Reference + try + { + policyComponent.bindClassBehaviour(policyName, null, validBehaviour); + fail("Failed to catch null class reference whilst binding behaviour"); + } + catch(IllegalArgumentException e) {} + + // Test invalid Class Reference + try + { + policyComponent.bindClassBehaviour(policyName, INVALID_TYPE, validBehaviour); + fail("Failed to catch invalid class reference whilst binding behaviour"); + } + catch(IllegalArgumentException e) {} + + // Test null Behaviour + try + { + policyComponent.bindClassBehaviour(policyName, FILE_TYPE, null); + fail("Failed to catch null behaviour whilst binding behaviour"); + } + catch(IllegalArgumentException e) {} + + // Test invalid behaviour (for registered policy) + Behaviour invalidBehaviour = new JavaBehaviour(this, "methoddoesnotexist"); + policyComponent.registerClassPolicy(TestClassPolicy.class); + try + { + policyComponent.bindClassBehaviour(policyName, FILE_TYPE, invalidBehaviour); + fail("Failed to catch invalid behaviour whilst binding behaviour"); + } + catch(PolicyException e) {} + + // Test valid behaviour (for registered policy) + try + { + BehaviourDefinition definition = policyComponent.bindClassBehaviour(policyName, FILE_TYPE, validBehaviour); + assertNotNull(definition); + assertEquals(policyName, definition.getPolicy()); + assertEquals(FILE_TYPE, definition.getBinding().getClassQName()); + } + catch(PolicyException e) + { + fail("Policy exception thrown for valid behaviour" + e.toString()); + } + } + + + public void testClassDelegate() + { + // Register Policy + ClassPolicyDelegate delegate = policyComponent.registerClassPolicy(TestClassPolicy.class); + + // Bind Class Behaviour + QName policyName = QName.createQName(TEST_NAMESPACE, "test"); + Behaviour fileBehaviour = new JavaBehaviour(this, "fileTest"); + policyComponent.bindClassBehaviour(policyName, FILE_TYPE, fileBehaviour); + + NodeRef nodeRef = new NodeRef("workspace", "SpacesStore", "123"); + // base node ref gets set by mocked mt service + NodeRef baseNodeRef = new NodeRef(BASE_PROTOCOL, BASE_IDENTIFIER, BASE_ID); + + Date date = new Date(); + StoreRef storeRef = new StoreRef("workspace", "SpacesStore"); + + // Test NOOP Policy delegate + Collection basePolicies = delegate.getList(BASE_TYPE); + assertNotNull(basePolicies); + assertEquals(0, basePolicies.size()); + TestClassPolicy basePolicy = delegate.get(BASE_TYPE); + assertNotNull(basePolicy); + TestClassPolicyResult baseResult = basePolicy.test("womble", nodeRef, date, storeRef); + // we don't expect a result from the NO-OP handler + assertNull("noop handler unexpectedly returned a result", baseResult); + + // Test single Policy delegate + Collection filePolicies = delegate.getList(FILE_TYPE); + assertNotNull(filePolicies); + assertEquals(1, filePolicies.size()); + TestClassPolicy filePolicy = delegate.get(FILE_TYPE); + assertNotNull(filePolicy); + TestClassPolicyResult fileResult = filePolicy.test("womble", nodeRef, date, storeRef); + assertEquals("argument type of NodeRef not replaced by base node ref", fileResult.getNodeRef(), baseNodeRef); + + + // Bind Service Behaviour + Behaviour serviceBehaviour = new JavaBehaviour(this, "serviceTest"); + policyComponent.bindClassBehaviour(policyName, this, serviceBehaviour); + + // Test multi Policy delegate + Collection file2Policies = delegate.getList(FILE_TYPE); + assertNotNull(file2Policies); + assertEquals(2, file2Policies.size()); + TestClassPolicy filePolicy2 = delegate.get(FILE_TYPE); + assertNotNull(filePolicy2); + TestClassPolicyResult fileResult2 = filePolicy2.test("womble", nodeRef, date, storeRef); + assertEquals("argument type of NodeRef not replaced by base node ref", fileResult2.getNodeRef(), baseNodeRef); + + // Test multiple class behaviours + Behaviour file2Behaviour = new JavaBehaviour(this, "fileTest2"); + policyComponent.bindClassBehaviour(policyName, FILE_TYPE, file2Behaviour); + Collection file3Policies = delegate.getList(FILE_TYPE); + assertNotNull(file3Policies); + assertEquals(3, file3Policies.size()); + TestClassPolicy filePolicy3 = delegate.get(FILE_TYPE); + assertNotNull(filePolicy3); + TestClassPolicyResult fileResult3 = filePolicy3.test("womble", nodeRef, date, storeRef); + assertEquals("argument type of NodeRef not replaced by base node ref", fileResult3.getNodeRef(), baseNodeRef); + } + + // + // The following interfaces represents policies + // + + public class TestClassPolicyResult + { + String s; // Not affected by MT + NodeRef nodeRef; + StoreRef storeRef; + ChildAssociationRef childAssociationRef; + Date date; // Not affected by MT + String getString() + { + return s; + } + NodeRef getNodeRef() + { + return nodeRef; + } + } + + public interface TestClassPolicy extends ClassPolicy + { + static String NAMESPACE = TEST_NAMESPACE; + public TestClassPolicyResult test(String argument, NodeRef nodeRef, Date date, StoreRef storeRef); + } + + public interface TestPropertyPolicy extends PropertyPolicy + { + static String NAMESPACE = TEST_NAMESPACE; + public String test(String argument); + } + + public interface TestAssociationPolicy extends AssociationPolicy + { + static String NAMESPACE = TEST_NAMESPACE; + public String test(String argument); + } + + public interface InvalidMetaDataPolicy extends ClassPolicy + { + static int NAMESPACE = 0; + public String test(String nodeRef); + } + + public interface NoMethodPolicy extends ClassPolicy + { + } + + public interface MultiMethodPolicy extends ClassPolicy + { + public void a(); + public void b(); + } + + + // + // The following methods represent Java Behaviours + // + + public TestClassPolicyResult validClassTest(String argument, NodeRef nodeRef, Date date, StoreRef storeRef) + { + TestClassPolicyResult result = new TestClassPolicyResult(); + result.s = "ValidTest: " + argument; + result.nodeRef = nodeRef; + result.date = date; + result.storeRef = storeRef; + return result; + } + + public TestClassPolicyResult fileTest(String argument, NodeRef nodeRef, Date date, StoreRef storeRef) + { + TestClassPolicyResult result = new TestClassPolicyResult(); + result.s = "ValidTest: " + argument; + result.nodeRef = nodeRef; + result.date = date; + result.storeRef = storeRef; + return result; + } + + public TestClassPolicyResult fileTest2(String argument, NodeRef nodeRef, Date date, StoreRef storeRef) + { + TestClassPolicyResult result = new TestClassPolicyResult(); + result.s = "ValidTest: " + argument; + result.nodeRef = nodeRef; + result.date = date; + result.storeRef = storeRef; + return result; + } + + + public TestClassPolicyResult serviceTest(String argument, NodeRef nodeRef, Date date, StoreRef storeRef) + { + TestClassPolicyResult result = new TestClassPolicyResult(); + result.s = "ValidTest: " + argument; + result.nodeRef = nodeRef; + result.date = date; + result.storeRef = storeRef; + return result; + } + +} diff --git a/source/java/org/alfresco/repo/policy/PolicyComponentTest.java b/source/java/org/alfresco/repo/policy/PolicyComponentTest.java index 454a007ac5..3ecaf01825 100644 --- a/source/java/org/alfresco/repo/policy/PolicyComponentTest.java +++ b/source/java/org/alfresco/repo/policy/PolicyComponentTest.java @@ -235,7 +235,7 @@ public class PolicyComponentTest extends TestCase assertEquals(1, filePolicies.size()); TestClassPolicy filePolicy = delegate.get(FILE_TYPE); assertNotNull(filePolicy); - assertEquals(filePolicies.iterator().next(), filePolicy); +// assertEquals(filePolicies.iterator().next(), filePolicy); // Bind Service Behaviour Behaviour serviceBehaviour = new JavaBehaviour(this, "serviceTest"); @@ -376,7 +376,6 @@ public class PolicyComponentTest extends TestCase assertEquals(1, filePolicies.size()); TestPropertyPolicy filePolicy = delegate.get(FILE_TYPE, FILE_PROP_B); assertNotNull(filePolicy); - assertEquals(filePolicies.iterator().next(), filePolicy); // Bind Service Behaviour Behaviour serviceBehaviour = new JavaBehaviour(this, "serviceTest"); diff --git a/source/java/org/alfresco/repo/policy/PolicyFactory.java b/source/java/org/alfresco/repo/policy/PolicyFactory.java index 388b0c1942..71e818a874 100644 --- a/source/java/org/alfresco/repo/policy/PolicyFactory.java +++ b/source/java/org/alfresco/repo/policy/PolicyFactory.java @@ -163,7 +163,9 @@ import org.alfresco.service.cmr.repository.StoreRef; { if (policyList.size() == 1) { - return policyList.iterator().next(); + P policy = (policyList.iterator()).next(); + return (P)Proxy.newProxyInstance(policyClass.getClassLoader(), + new Class[]{policyClass}, new SingleHandler

(policy)); } else if (policyList.size() == 0) { @@ -207,6 +209,70 @@ import org.alfresco.service.cmr.repository.StoreRef; } } + /** + * @author mrogers + * + * @param

+ */ + private static class SingleHandler

implements InvocationHandler + { + private P policyInterface; + + /** + * Construct + * + * @param policyInterfaces the collection of policy implementations + */ + public SingleHandler(P policyInterface) + { + this.policyInterface = policyInterface; + } + + /* (non-Javadoc) + * @see java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[]) + */ + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable + { + if ((tenantService != null) && (tenantService.isEnabled()) && (args != null)) + { + // Convert each of the arguments to the spoofed (no tenant prefix) reference + convertMTArgs(args); + } + + // Handle PolicyList level methods + if (method.getDeclaringClass().equals(PolicyList.class)) + { + return method.invoke(this, args); + } + + // Handle Object level methods + if (method.getName().equals("toString")) + { + return toString() + ": wrapped " + 1 + " policy"; + } + else if (method.getName().equals("hashCode")) + { + return hashCode(); + } + else if (method.getName().equals("equals")) + { + return equals(args[0]); + } + + // Invoke each wrapped policy in turn + try + { + Object result = null; + result = method.invoke(policyInterface, args); + return result; + } + catch (InvocationTargetException e) + { + throw e.getTargetException(); + } + } + } + /** * Multi-policy Invocation Handler. @@ -237,38 +303,7 @@ import org.alfresco.service.cmr.repository.StoreRef; if ((tenantService != null) && (tenantService.isEnabled()) && (args != null)) { // Convert each of the arguments to the spoofed (no tenant prefix) reference - for (int i = 0; i < args.length; i++) - { - Object arg = args[i]; - Object newArg = arg; - if (arg == null) - { - // No conversion possible - } - if (arg instanceof StoreRef) - { - StoreRef ref = (StoreRef) arg; - newArg = tenantService.getBaseName(ref); - } - else if (arg instanceof NodeRef) - { - NodeRef ref = (NodeRef) arg; - newArg = tenantService.getBaseName(ref); - } - else if (arg instanceof ChildAssociationRef) - { - ChildAssociationRef ref = (ChildAssociationRef) arg; - newArg = tenantService.getBaseName(ref); - } - else if (arg instanceof AssociationRef) - { - AssociationRef ref = (AssociationRef) arg; - newArg = tenantService.getBaseName(ref); - } - - // Substitute the new value - args[i] = newArg; - } + convertMTArgs(args); } // Handle PolicyList level methods @@ -316,4 +351,47 @@ import org.alfresco.service.cmr.repository.StoreRef; } } + /** + * Convert each of the arguments to the spoofed (no tenant prefix) reference. + * + * Converts arguments of Type NodeRef to base name etc. + * + * @param args list of non final arguments - the arguments are updated in place + */ + protected static void convertMTArgs (Object[] args) + { + // Convert each of the arguments to the spoofed (no tenant prefix) reference + for (int i = 0; i < args.length; i++) + { + Object arg = args[i]; + Object newArg = arg; + if (arg == null) + { + // No conversion possible + } + if (arg instanceof StoreRef) + { + StoreRef ref = (StoreRef) arg; + newArg = tenantService.getBaseName(ref); + } + else if (arg instanceof NodeRef) + { + NodeRef ref = (NodeRef) arg; + newArg = tenantService.getBaseName(ref); + } + else if (arg instanceof ChildAssociationRef) + { + ChildAssociationRef ref = (ChildAssociationRef) arg; + newArg = tenantService.getBaseName(ref); + } + else if (arg instanceof AssociationRef) + { + AssociationRef ref = (AssociationRef) arg; + newArg = tenantService.getBaseName(ref); + } + + // Substitute the new value + args[i] = newArg; + } + } } diff --git a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java index c7b36721e1..e4afa26b2c 100644 --- a/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java +++ b/source/java/org/alfresco/repo/preference/PreferenceServiceImpl.java @@ -19,16 +19,14 @@ package org.alfresco.repo.preference; import java.io.Serializable; -import java.text.Collator; import java.util.ArrayList; -import java.util.Comparator; -import java.util.HashMap; +import java.util.Date; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; -import java.util.TreeSet; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; @@ -50,8 +48,7 @@ import org.alfresco.service.cmr.security.AccessStatus; import org.alfresco.service.cmr.security.AuthorityService; import org.alfresco.service.cmr.security.PermissionService; import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.service.cmr.site.SiteInfo; -import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.util.ISO8601DateFormat; import org.alfresco.util.Pair; import org.json.JSONException; import org.json.JSONObject; @@ -63,18 +60,24 @@ import org.json.JSONObject; */ public class PreferenceServiceImpl implements PreferenceService { - private static final String FAVOURITE_SITES_PREFIX = "org.alfresco.share.sites.favourites."; - private static final int FAVOURITE_SITES_PREFIX_LENGTH = FAVOURITE_SITES_PREFIX.length(); - + + private static final String SHARE_SITES_PREFERENCE_KEY = "org.alfresco.share.sites.favourites."; + private static final int SHARE_SITES_PREFERENCE_KEY_LEN = SHARE_SITES_PREFERENCE_KEY.length(); + private static final String EXT_SITES_PREFERENCE_KEY = "org.alfresco.ext.sites.favourites."; + + /** Node service */ private NodeService nodeService; private ContentService contentService; private PersonService personService; - private SiteService siteService; private PermissionService permissionService; private AuthenticationContext authenticationContext; - private AuthorityService authorityService; + /** + * Set the node service + * + * @param nodeService the node service + */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; @@ -84,12 +87,12 @@ public class PreferenceServiceImpl implements PreferenceService { this.contentService = contentService; } - - public void setSiteService(SiteService siteService) - { - this.siteService = siteService; - } - + + /** + * Set the person service + * + * @param personService the person service + */ public void setPersonService(PersonService personService) { this.personService = personService; @@ -118,59 +121,6 @@ public class PreferenceServiceImpl implements PreferenceService { return getPreferences(userName, null); } - - /** - * @see org.alfresco.repo.person.PersonService#getPreferences(java.lang.String, java.lang.String) - * java.lang.String) - */ - @SuppressWarnings("unchecked") - public Map getPreferences(String userName, String preferenceFilter) - { - Map preferences = new TreeMap(); - - try - { - JSONObject jsonPrefs = getPreferencesObject(userName); - if(jsonPrefs != null) - { - // Build hash from preferences stored in the repository - Iterator keys = jsonPrefs.keys(); - while (keys.hasNext()) - { - String key = (String)keys.next(); - - if (preferenceFilter == null || - preferenceFilter.length() == 0 || - matchPreferenceNames(key, preferenceFilter) == true) - { - preferences.put(key, (Serializable)jsonPrefs.get(key)); - } - } - } - } - catch (JSONException exception) - { - throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because there was an error pasing the JSON data.", exception); - } - - return preferences; - } - - private PageDetails getPageDetails(PagingRequest pagingRequest, int totalSize) - { - int skipCount = pagingRequest.getSkipCount(); - int maxItems = pagingRequest.getMaxItems(); - int end = maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? totalSize : skipCount + maxItems; - int pageSize = (maxItems == CannedQueryPageDetails.DEFAULT_PAGE_SIZE ? totalSize : maxItems); - if(pageSize > totalSize - skipCount) - { - pageSize = totalSize - skipCount; - } - - boolean hasMoreItems = end < totalSize; - - return new PageDetails(pageSize, hasMoreItems, skipCount, maxItems, end); - } private JSONObject getPreferencesObject(String userName) throws JSONException { @@ -185,10 +135,10 @@ public class PreferenceServiceImpl implements PreferenceService } String currentUserName = AuthenticationUtil.getFullyAuthenticatedUser(); - if (userName.equals(currentUserName) || - personService.getUserIdentifier(userName).equals(personService.getUserIdentifier(currentUserName)) || - authenticationContext.isSystemUserName(currentUserName) || - authorityService.isAdminAuthority(currentUserName)) + boolean isSystem = AuthenticationUtil.isRunAsUserTheSystemUser() || authenticationContext.isSystemUserName(currentUserName); + if (isSystem || userName.equals(currentUserName) + || personService.getUserIdentifier(userName).equals(personService.getUserIdentifier(currentUserName)) + || authorityService.isAdminAuthority(currentUserName)) { // Check for preferences aspect if (this.nodeService.hasAspect(personNodeRef, ContentModel.ASPECT_PREFERENCES) == true) @@ -216,7 +166,6 @@ public class PreferenceServiceImpl implements PreferenceService public Serializable getPreference(String userName, String preferenceName) { String preferenceValue = null; - try { JSONObject jsonPrefs = getPreferencesObject(userName); @@ -235,6 +184,80 @@ public class PreferenceServiceImpl implements PreferenceService return preferenceValue; } + + /** + * @see org.alfresco.repo.person.PersonService#getPreferences(java.lang.String, java.lang.String) + */ + @SuppressWarnings({ "unchecked" }) + public Map getPreferences(String userName, String preferenceFilter) + { + Map preferences = new TreeMap(); + + try + { + Set siteIds = new HashSet(); + + JSONObject jsonPrefs = getPreferencesObject(userName); + if(jsonPrefs != null) + { + // Build hash from preferences stored in the repository + Iterator keys = jsonPrefs.keys(); + while (keys.hasNext()) + { + String key = (String)keys.next(); + Serializable value = (Serializable)jsonPrefs.get(key); + + if(key.startsWith(SHARE_SITES_PREFERENCE_KEY)) + { + // CLOUD-1518: convert site preferences on the fly + // convert keys as follows: + // ..favourited -> . + // ..createdAt -> ..createdAt + if(key.endsWith(".favourited")) + { + int idx = key.indexOf(".favourited"); + String siteId = key.substring(SHARE_SITES_PREFERENCE_KEY_LEN, idx); + StringBuilder sb = new StringBuilder(SHARE_SITES_PREFERENCE_KEY); + sb.append(siteId); + siteIds.add(siteId); + key = sb.toString(); + } + + else if(key.endsWith(".createdAt")) + { + int idx = key.indexOf(".createdAt"); + String siteId = key.substring(SHARE_SITES_PREFERENCE_KEY_LEN, idx); + StringBuilder sb = new StringBuilder(EXT_SITES_PREFERENCE_KEY); + sb.append(siteId); + sb.append(".createdAt"); + siteIds.add(siteId); + key = sb.toString(); + } + else if(preferences.containsKey(key)) + { + // Ensure that the values of the following form (the only other important form in this case) does not + // override those on the lhs from above: + // . + continue; + } + } + + if (preferenceFilter == null || + preferenceFilter.length() == 0 || + matchPreferenceNames(key, preferenceFilter)) + { + preferences.put(key, value); + } + } + } + } + catch (JSONException exception) + { + throw new AlfrescoRuntimeException("Can not get preferences for " + userName + " because there was an error pasing the JSON data.", exception); + } + + return preferences; + } public PagingResults> getPagedPreferences(String userName, String preferenceFilter, PagingRequest pagingRequest) { @@ -312,6 +335,11 @@ public class PreferenceServiceImpl implements PreferenceService matchTo = matchTo.replace(".", "+"); String[] matchToArr = matchTo.split("\\+"); + if(matchToArr.length > nameArr.length) + { + return false; + } + int index = 0; for (String matchToElement : matchToArr) { @@ -368,7 +396,40 @@ public class PreferenceServiceImpl implements PreferenceService // Update with the new preference values for (Map.Entry entry : preferences.entrySet()) { - jsonPrefs.put(entry.getKey(), entry.getValue()); + String key = entry.getKey(); + + // CLOUD-1518: remove extraneous site preferences, if present + if(key.startsWith(SHARE_SITES_PREFERENCE_KEY)) + { + // remove any extraneous keys, if present + String siteId = key.substring(SHARE_SITES_PREFERENCE_KEY_LEN); + + StringBuilder sb = new StringBuilder(SHARE_SITES_PREFERENCE_KEY); + sb.append(siteId); + sb.append(".favourited"); + String testKey = sb.toString(); + if(jsonPrefs.has(testKey)) + { + jsonPrefs.remove(testKey); + } + + sb = new StringBuilder(SHARE_SITES_PREFERENCE_KEY); + sb.append(siteId); + sb.append(".createdAt"); + testKey = sb.toString(); + if(jsonPrefs.has(testKey)) + { + jsonPrefs.remove(testKey); + } + } + + Serializable value = entry.getValue(); + if(value != null && value.equals("CURRENT_DATE")) + { + Date date = new Date(); + value = ISO8601DateFormat.format(date); + } + jsonPrefs.put(key, value); } // Save the updated preferences @@ -443,6 +504,7 @@ public class PreferenceServiceImpl implements PreferenceService // Remove the prefs that match the filter List removeKeys = new ArrayList(10); + @SuppressWarnings("unchecked") Iterator keys = jsonPrefs.keys(); while (keys.hasNext()) { @@ -504,6 +566,7 @@ public class PreferenceServiceImpl implements PreferenceService authenticationContext.isSystemUserName(currentUserName) || permissionService.hasPermission(personNodeRef, PermissionService.WRITE) == AccessStatus.ALLOWED); } + public static class PageDetails { private boolean hasMoreItems = false; @@ -547,127 +610,4 @@ public class PreferenceServiceImpl implements PreferenceService return pageSize; } } - - /** - * @see org.alfresco.service.cmr.site.SiteService#isFavouriteSite(java.lang.String, java.lang.String) - */ - public boolean isFavouriteSite(String userName, String siteShortName) - { - StringBuilder prefKey = new StringBuilder(FAVOURITE_SITES_PREFIX); - prefKey.append(siteShortName); - - String value = (String)getPreference(userName, prefKey.toString()); - return (value == null ? false : value.equalsIgnoreCase("true")); - } - - /** - * @see org.alfresco.service.cmr.preference.PreferenceService#addFavouriteSite(java.lang.String, java.lang.String) - */ - public void addFavouriteSite(String userName, String siteShortName) - { - StringBuilder prefKey = new StringBuilder(FAVOURITE_SITES_PREFIX); - prefKey.append(siteShortName); - - Map preferences = new HashMap(1); - preferences.put(prefKey.toString(), Boolean.TRUE); - setPreferences(userName, preferences); - } - - /** - * @see org.alfresco.service.cmr.preference.PreferenceService#removeFavouriteSite(java.lang.String, java.lang.String) - */ - public void removeFavouriteSite(String userName, String siteShortName) - { - StringBuilder prefKey = new StringBuilder(FAVOURITE_SITES_PREFIX); - prefKey.append(siteShortName); - - clearPreferences(userName, prefKey.toString()); - } - - /** - * @see org.alfresco.service.cmr.site.SiteService#getFavouriteSites(java.lang.String, org.alfresco.query.PagingRequest) - */ - public PagingResults getFavouriteSites(String userName, PagingRequest pagingRequest) - { - final Collator collator = Collator.getInstance(); - - final Set sortedFavouriteSites = new TreeSet(new Comparator() - { - @Override - public int compare(SiteInfo o1, SiteInfo o2) - { - return collator.compare(o1.getTitle(), o2.getTitle()); - } - }); - - Map prefs = getPreferences(userName, FAVOURITE_SITES_PREFIX); - for(String key : prefs.keySet()) - { - boolean isFavourite = false; - Serializable s = prefs.get(key); - if(s instanceof Boolean) - { - isFavourite = (Boolean)s; - } - if(isFavourite) - { - String siteShortName = key.substring(FAVOURITE_SITES_PREFIX_LENGTH); - SiteInfo siteInfo = siteService.getSite(siteShortName); - if(siteInfo != null) - { - sortedFavouriteSites.add(siteInfo); - } - } - } - - int totalSize = sortedFavouriteSites.size(); - final PageDetails pageDetails = getPageDetails(pagingRequest, totalSize); - - final List page = new ArrayList(pageDetails.getPageSize()); - Iterator it = sortedFavouriteSites.iterator(); - for(int counter = 0; counter < pageDetails.getEnd() && it.hasNext(); counter++) - { - SiteInfo favouriteSite = it.next(); - - if(counter < pageDetails.getSkipCount()) - { - continue; - } - - if(counter > pageDetails.getEnd() - 1) - { - break; - } - - page.add(favouriteSite); - } - - return new PagingResults() - { - @Override - public List getPage() - { - return page; - } - - @Override - public boolean hasMoreItems() - { - return pageDetails.hasMoreItems(); - } - - @Override - public Pair getTotalResultCount() - { - Integer total = Integer.valueOf(sortedFavouriteSites.size()); - return new Pair(total, total); - } - - @Override - public String getQueryExecutionId() - { - return null; - } - }; - } } diff --git a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java index 2c4cea98e0..ef5d274a20 100644 --- a/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java +++ b/source/java/org/alfresco/repo/remoteconnector/RemoteConnectorServiceImpl.java @@ -257,6 +257,8 @@ public class RemoteConnectorServiceImpl implements RemoteConnectorService if (status == Status.STATUS_FORBIDDEN || status == Status.STATUS_UNAUTHORIZED) { + // TODO Forbidden may need to be handled differently. + // TODO Need to get error message into the AuthenticationException throw new AuthenticationException(statusText); } diff --git a/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java index b721f964ec..f137de004c 100644 --- a/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java +++ b/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -23,6 +23,7 @@ import java.util.Collection; import org.alfresco.repo.action.ParameterDefinitionImpl; import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.repo.content.transform.TransformerConfig; import org.alfresco.repo.content.transform.TransformerDebug; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -80,6 +81,11 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend */ public static final String PARAM_PAGE_LIMIT = TransformationOptionLimits.OPT_PAGE_LIMIT; + /** + * This optional {@link String} parameter specifies the type (or use) of the rendition. + */ + public static final String PARAM_USE = TransformerConfig.USE.replaceAll("\\.", ""); + /* Error messages */ private static final String TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN = "Transformer for '%s' source mime type and '%s' target mime type was not found. Operation can't be performed"; private static final String NOT_TRANSFORMABLE_MESSAGE_PATTERN = "Content not transformable for '%s' source mime type and '%s' target mime type. Operation can't be performed"; @@ -199,6 +205,12 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend options.setPageLimit(pageLimit); } + String use = context.getCheckedParam(PARAM_USE, String.class); + if (use != null) + { + options.setUse(use); + } + if (getSourceOptionsSerializers() != null) { for (TransformationSourceOptionsSerializer sourceSerializer : getSourceOptionsSerializers()) @@ -233,6 +245,8 @@ public abstract class AbstractTransformationRenderingEngine extends AbstractRend getParamDisplayLabel(PARAM_MAX_PAGES))); paramList.add(new ParameterDefinitionImpl(PARAM_PAGE_LIMIT, DataTypeDefinition.INT, false, getParamDisplayLabel(PARAM_PAGE_LIMIT))); + paramList.add(new ParameterDefinitionImpl(PARAM_USE, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_USE))); return paramList; } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java index 65b43b8d2c..1912a6c0de 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/ADMLuceneIndexerImpl.java @@ -1461,11 +1461,12 @@ public class ADMLuceneIndexerImpl extends AbstractLuceneIndexerImpl imp { // get the transformer TransformationOptions options = new TransformationOptions(); + options.setUse("index"); options.setSourceNodeRef(nodeRef); transformerDebug.pushAvailable(reader.getContentUrl(), sourceMimetype, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); long sourceSize = reader.getSize(); List transformers = contentService.getActiveTransformers(sourceMimetype, sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); - transformerDebug.availableTransformers(transformers, sourceSize, "ADMLuceneIndexer"); + transformerDebug.availableTransformers(transformers, sourceSize, options, "ADMLuceneIndexer"); if (transformers.isEmpty()) { diff --git a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java index c51e5352fe..baa0b06dbd 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/AVMLuceneIndexerImpl.java @@ -1,5 +1,5 @@ /** - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -1143,11 +1143,12 @@ public class AVMLuceneIndexerImpl extends AbstractLuceneIndexerImpl impl { // get the transformer TransformationOptions options = new TransformationOptions(); + options.setUse("index"); options.setSourceNodeRef(banana); // TODO check it is OK to use this noderef transformerDebug.pushAvailable(reader.getContentUrl(), reader.getMimetype(), MimetypeMap.MIMETYPE_TEXT_PLAIN, options); long sourceSize = reader.getSize(); List transformers = contentService.getActiveTransformers(reader.getMimetype(), sourceSize, MimetypeMap.MIMETYPE_TEXT_PLAIN, options); - transformerDebug.availableTransformers(transformers, sourceSize, "AVMLuceneIndexer"); + transformerDebug.availableTransformers(transformers, sourceSize, options, "AVMLuceneIndexer"); if (transformers.isEmpty()) { diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 1c7efc5b36..7939e2c1bf 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; +import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Locale; @@ -43,6 +44,7 @@ import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.StoreRef; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.PermissionEvaluationMode; import org.alfresco.service.cmr.search.ResultSet; import org.alfresco.service.cmr.search.SearchParameters; import org.alfresco.service.cmr.search.SearchParameters.FieldFacet; @@ -295,9 +297,15 @@ public class SolrQueryHTTPClient implements BeanFactoryAware } url.append(sortBuffer); - url.append("&fq=").append(encoder.encode("{!afts}AUTHORITY_FILTER_FROM_JSON", "UTF-8")); + if(searchParameters.getPermissionEvaluation() != PermissionEvaluationMode.NONE) + { + url.append("&fq=").append(encoder.encode("{!afts}AUTHORITY_FILTER_FROM_JSON", "UTF-8")); + } - url.append("&fq=").append(encoder.encode("{!afts}TENANT_FILTER_FROM_JSON", "UTF-8")); + if(searchParameters.getExcludeTenantFilter() == false) + { + url.append("&fq=").append(encoder.encode("{!afts}TENANT_FILTER_FROM_JSON", "UTF-8")); + } if(searchParameters.getFieldFacets().size() > 0) { diff --git a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java index 55edbb515b..680bd4473c 100644 --- a/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java +++ b/source/java/org/alfresco/repo/security/authentication/AuthenticationServiceImpl.java @@ -61,10 +61,17 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp { try { + String tenant = getPrevalidationTenantDomain(); // clear context - to avoid MT concurrency issue (causing domain mismatch) - see also 'validate' below - //clearCurrentSecurityContext(); + clearCurrentSecurityContext(); preAuthenticationCheck(userName); - authenticationComponent.authenticate(userName, password); + authenticationComponent.authenticate(userName, password); + if (tenant == null) + { + Pair userTenant = AuthenticationUtil.getUserTenant(userName); + tenant = userTenant.getSecond(); + } + TenantContextHolder.setTenantDomain(tenant); } catch(AuthenticationException ae) { @@ -111,7 +118,7 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp String currentUser = null; try { - String tenant = TenantContextHolder.getTenantDomain(); + String tenant = getPrevalidationTenantDomain(); // clear context - to avoid MT concurrency issue (causing domain mismatch) - see also 'authenticate' above clearCurrentSecurityContext(); @@ -123,7 +130,6 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp Pair userTenant = AuthenticationUtil.getUserTenant(currentUser); tenant = userTenant.getSecond(); } - TenantContextHolder.setTenantDomain(tenant); } catch (AuthenticationException ae) @@ -132,6 +138,18 @@ public class AuthenticationServiceImpl extends AbstractAuthenticationService imp throw ae; } } + + /** + * This method is called from the {@#validate(String)} method. If this method returns null then + * the user's tenant will be obtained from the username. This is generally correct in the case where the user can be + * associated with just one tenant. + * Override this method in order to force the selection of a different tenant (for whatever reason). + * @return + */ + protected String getPrevalidationTenantDomain() + { + return null; + } public String getCurrentTicket() throws AuthenticationException { diff --git a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java index 828f9a7346..9966fdeeaf 100644 --- a/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java +++ b/source/java/org/alfresco/repo/security/authentication/RepositoryAuthenticationDao.java @@ -34,6 +34,7 @@ import net.sf.acegisecurity.providers.encoding.PasswordEncoder; import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.cache.TransactionalCache; import org.alfresco.repo.node.NodeServicePolicies.BeforeDeleteNodePolicy; import org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy; import org.alfresco.repo.policy.JavaBehaviour; @@ -747,10 +748,10 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In nodeService.setProperty(userNode, ContentModel.PROP_USER_USERNAME, uidAfter); nodeService.moveNode(userNode, nodeService.getPrimaryParent(userNode).getParentRef(), ContentModel.ASSOC_CHILDREN, QName.createQName(ContentModel.USER_MODEL_URI, uidAfter)); - authenticationCache.remove(uidBefore); + removeAuthenticationFromCache(uidBefore); } } - authenticationCache.remove(uidAfter); + removeAuthenticationFromCache(uidAfter); } public void onUpdateUserProperties(NodeRef nodeRef, Map before, Map after) @@ -758,7 +759,7 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In String uidBefore = DefaultTypeConverter.INSTANCE.convert(String.class, before.get(ContentModel.PROP_USER_USERNAME)); if (uidBefore != null) { - authenticationCache.remove(uidBefore); + removeAuthenticationFromCache(uidBefore); } } @@ -768,10 +769,25 @@ public class RepositoryAuthenticationDao implements MutableAuthenticationDao, In String userName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_USER_USERNAME); if (userName != null) { - authenticationCache.remove(userName); + removeAuthenticationFromCache(userName); } } + /** + * Remove from the cache and lock the value for the transaction + * @param key + * @param lock + */ + private void removeAuthenticationFromCache(String key) + { + authenticationCache.remove(key); + if (authenticationCache instanceof TransactionalCache) + { + TransactionalCache authenticationCacheTxn = (TransactionalCache) authenticationCache; + authenticationCacheTxn.lockValue(key); + } + } + static class CacheEntry { public NodeRef nodeRef; diff --git a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java index af7a890bea..920c294c16 100644 --- a/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java +++ b/source/java/org/alfresco/repo/security/authority/AuthorityDAOImpl.java @@ -43,6 +43,7 @@ import org.alfresco.query.PagingResults; import org.alfresco.repo.cache.RefreshableCacheEvent; import org.alfresco.repo.cache.RefreshableCacheListener; import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.cache.TransactionalCache; import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.JavaBehaviour; @@ -89,8 +90,6 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor { private static Log logger = LogFactory.getLog(AuthorityDAOImpl.class); - private static String DELETING_AUTHORITY_SET_RESOURCE = "DeletingAuthoritySetResource"; - private static String PARENTS_OF_DELETING_CHILDREN_SET_RESOURCE = "ParentsOfDeletingChildrenSetResource"; private static final NodeRef NULL_NODEREF = new NodeRef("null", "null", "null"); @@ -367,7 +366,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor zoneAuthorityCache.remove(new Pair(currentUserDomain, authorityZone)); } zoneAuthorityCache.remove(new Pair(currentUserDomain, null)); - removeParentsFromChildAuthorityCache(nodeRef); + removeParentsFromChildAuthorityCache(nodeRef, false); authorityLookupCache.remove(cacheKey(name)); userAuthorityCache.clear(); authorityBridgeTableCache.refresh(); @@ -746,7 +745,9 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor } } - + /** + * Explicitly use the bridge table to list authorities. + */ private void listAuthoritiesByBridgeTable(Set authorities, String name) { BridgeTable bridgeTable = authorityBridgeTableCache.get(); @@ -754,6 +755,9 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor AuthorityType type = AuthorityType.getAuthorityType(name); switch(type) { + case WILDCARD: + // Dual use of the enum means that this value should not be received + logger.warn("Found an authority with type '" + AuthorityType.WILDCARD + "': " + name); case ADMIN: case GUEST: case USER: @@ -780,9 +784,9 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor } } + @Override public Set getContainingAuthorities(AuthorityType type, String name, boolean immediate) { - // Optimize for the case where we want all the authorities that a user belongs to if (!immediate && AuthorityType.getAuthorityType(name) == AuthorityType.USER) { @@ -799,10 +803,8 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor { listAuthorities(null, name, authorities, true, true); } - if(AlfrescoTransactionSupport.getTransactionId() != null && !TransactionalResourceHelper.getSet(DELETING_AUTHORITY_SET_RESOURCE).contains(name)) - { - userAuthorityCache.put(name, Collections.unmodifiableSet(authorities)); - } + // Add the set back to the cache. If the value is locked then nothing will happen. + userAuthorityCache.put(name, Collections.unmodifiableSet(authorities)); } // If we wanted the unfiltered set we are done if (type == null) @@ -962,7 +964,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor } } - private void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type) + protected void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type) { if (type == null || AuthorityType.getAuthorityType(authorityName).equals(type)) { @@ -970,7 +972,7 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor } } - private void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type, Pattern pattern) + protected void addAuthorityNameIfMatches(Set authorities, String authorityName, AuthorityType type, Pattern pattern) { if (type == null || AuthorityType.getAuthorityType(authorityName).equals(type)) { @@ -1170,8 +1172,22 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor return false; } - private void removeParentsFromChildAuthorityCache(NodeRef nodeRef) + /** + * Remove entries for the parents of the given node. + * + * @param lock true if the cache modifications need to be locked + * i.e. if the caller is handling a beforeXYZ callback. + */ + private void removeParentsFromChildAuthorityCache(NodeRef nodeRef, boolean lock) { + // Get the transactional version of the cache if we need locking + TransactionalCache, List>> childAuthorityCacheTxn = null; + if (lock && childAuthorityCache instanceof TransactionalCache) + { + childAuthorityCacheTxn = (TransactionalCache, List>>) childAuthorityCache; + } + + // Iterate over all relevant parents of the given node for (ChildAssociationRef car: nodeService.getParentAssocs(nodeRef)) { NodeRef parentRef = car.getParentRef(); @@ -1179,6 +1195,10 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor { TransactionalResourceHelper.getSet(PARENTS_OF_DELETING_CHILDREN_SET_RESOURCE).add(parentRef); childAuthorityCache.remove(parentRef); + if (childAuthorityCacheTxn != null) + { + childAuthorityCacheTxn.lockValue(parentRef); + } } } } @@ -1456,13 +1476,25 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor return authorities; } - // Listen out for person removals so that we can clear cached authorities + /** + * Listen out for person removals so that we can clear cached authorities. + */ public void beforeDeleteNode(NodeRef nodeRef) { String authorityName = getAuthorityName(nodeRef); - TransactionalResourceHelper.getSet(DELETING_AUTHORITY_SET_RESOURCE).add(authorityName); userAuthorityCache.remove(authorityName); - removeParentsFromChildAuthorityCache(nodeRef); + if (userAuthorityCache instanceof TransactionalCache) + { + /* + * We lock the removal for the duration of the transaction as the node has not + * yet been deleted, leaving scope for some other code to come along and add the + * value back before the deletion can actually take place. + */ + TransactionalCache> userAuthorityCacheTxn = (TransactionalCache>) userAuthorityCache; + userAuthorityCacheTxn.lockValue(authorityName); + } + // Remove cache elements for the parents, ensuring that we lock because the data still exists + removeParentsFromChildAuthorityCache(nodeRef, true); } public void onUpdateProperties(NodeRef nodeRef, Map before, Map after) @@ -1516,7 +1548,8 @@ public class AuthorityDAOImpl implements AuthorityDAO, NodeServicePolicies.Befor { userAuthorityCache.remove(authBefore); } - removeParentsFromChildAuthorityCache(nodeRef); + // Remove cache entires for the parents. No need to lock because the data has already been updated. + removeParentsFromChildAuthorityCache(nodeRef, false); } else { diff --git a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java index db502a7dea..1f223a4bda 100644 --- a/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java +++ b/source/java/org/alfresco/repo/security/person/PersonServiceImpl.java @@ -38,6 +38,7 @@ import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.repo.action.executer.MailActionExecuter; import org.alfresco.repo.cache.SimpleCache; +import org.alfresco.repo.cache.TransactionalCache; import org.alfresco.repo.domain.permissions.AclDAO; import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.node.NodeServicePolicies.BeforeCreateNodePolicy; @@ -55,6 +56,7 @@ import org.alfresco.repo.tenant.TenantDomainMismatchException; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.repo.transaction.AlfrescoTransactionSupport.TxnReadState; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; import org.alfresco.repo.transaction.TransactionListenerAdapter; import org.alfresco.repo.transaction.TransactionalResourceHelper; @@ -109,8 +111,6 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { private static Log logger = LogFactory.getLog(PersonServiceImpl.class); - private static String DELETING_PERSON_SET_RESOURCE = "DeletingPersonSetResource"; - private static final String CANNED_QUERY_PEOPLE_LIST = "getPeopleCannedQueryFactory"; private static final String DELETE = "DELETE"; @@ -579,7 +579,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per if (addToCache) { // Don't bother caching unless we get a result that doesn't need duplicate processing - putToCache(searchUserName, allRefs); + putToCache(searchUserName, allRefs, false); } } return returnRef; @@ -980,7 +980,7 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } } - removeFromCache(userName); + removeFromCache(userName, false); return personRef; } @@ -1720,7 +1720,9 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per if (getPeopleContainer().equals(childAssocRef.getParentRef())) { - removeFromCache(userName); + // The value is stale. However, we have already made the data change and + // therefore do not need to lock the removal from further changes. + removeFromCache(userName, false); } permissionsManager.setPermissions(personRef, userName, userName); @@ -1779,7 +1781,12 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per parentRef = parentAssocRef.getParentRef(); if (getPeopleContainer().equals(parentRef)) { - removeFromCache(userName); + // Remove the cache entry. + // Note that the associated node has not been deleted and is therefore still + // visible to any other code that attempts to see it. We therefore need to + // prevent the value from being added back before the node is actually + // deleted. + removeFromCache(userName, true); } } } @@ -1807,21 +1814,35 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per { return this.personCache.get(userName.toLowerCase()); } - - private void putToCache(String userName, Set refs) + + /** + * Put a value into the {@link #setPersonCache(SimpleCache) personCache}, optionally + * locking the value against any changes. + */ + private void putToCache(String userName, Set refs, boolean lock) { String key = userName.toLowerCase(); - if (AlfrescoTransactionSupport.getTransactionId() != null && !TransactionalResourceHelper.getSet(DELETING_PERSON_SET_RESOURCE).contains(key)) + this.personCache.put(key, refs); + if (lock && personCache instanceof TransactionalCache) { - this.personCache.put(key, refs); + TransactionalCache> personCacheTxn = (TransactionalCache>) personCache; + personCacheTxn.lockValue(key); } } - private void removeFromCache(String userName) + /** + * Remove a value from the {@link #setPersonCache(SimpleCache) personCache}, optionally + * locking the value against any changes. + */ + private void removeFromCache(String userName, boolean lock) { String key = userName.toLowerCase(); - TransactionalResourceHelper.getSet(DELETING_PERSON_SET_RESOURCE).add(key); - this.personCache.remove(key); + personCache.remove(key); + if (lock && personCache instanceof TransactionalCache) + { + TransactionalCache> personCacheTxn = (TransactionalCache>) personCache; + personCacheTxn.lockValue(key); + } } /** @@ -1932,7 +1953,11 @@ public class PersonServiceImpl extends TransactionListenerAdapter implements Per } // Fix cache - removeFromCache(uidBefore); + // We are going to be pessimistic here. Even though the properties have changed and + // should always be seen correctly by other policy listeners, we are not entirely sure + // that there won't be some sort of corruption i.e. the behaviour was pessimistic before + // this change so I'm leaving it that way. + removeFromCache(uidBefore, true); } else { diff --git a/source/java/org/alfresco/repo/site/SiteMembersCannedQuery.java b/source/java/org/alfresco/repo/site/SiteMembersCannedQuery.java index 0798eb672d..b4bd3414fd 100644 --- a/source/java/org/alfresco/repo/site/SiteMembersCannedQuery.java +++ b/source/java/org/alfresco/repo/site/SiteMembersCannedQuery.java @@ -113,7 +113,7 @@ public class SiteMembersCannedQuery extends AbstractCannedQuery lastName = (String)nodeService.getProperty(nodeRef, ContentModel.PROP_LASTNAME); } - SiteMembership siteMember = new SiteMembership(siteShortName, siteInfo.getTitle(), authority, firstName, lastName, SiteRole.valueOf(permission)); + SiteMembership siteMember = new SiteMembership(siteInfo, authority, firstName, lastName, SiteRole.valueOf(permission)); siteMembers.add(siteMember); } @@ -221,8 +221,10 @@ public class SiteMembersCannedQuery extends AbstractCannedQuery { String personId1 = o1.getPersonId(); String personId2 = o2.getPersonId(); - String shortName1 = o1.getSiteShortName(); - String shortName2 = o2.getSiteShortName(); + SiteInfo siteInfo1 = o1.getSiteInfo(); + SiteInfo siteInfo2 = o2.getSiteInfo(); + String shortName1 = siteInfo1.getShortName(); + String shortName2 = siteInfo2.getShortName(); String firstName1 = o1.getFirstName(); String firstName2 = o2.getFirstName(); String lastName1 = o1.getLastName(); diff --git a/source/java/org/alfresco/repo/site/SiteMembership.java b/source/java/org/alfresco/repo/site/SiteMembership.java index 7e4efbea32..cd5bbaab6c 100644 --- a/source/java/org/alfresco/repo/site/SiteMembership.java +++ b/source/java/org/alfresco/repo/site/SiteMembership.java @@ -18,6 +18,7 @@ */ package org.alfresco.repo.site; +import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.service.cmr.site.SiteRole; /** @@ -28,17 +29,16 @@ import org.alfresco.service.cmr.site.SiteRole; */ public class SiteMembership { - private String siteShortName; - private String siteTitle; + private SiteInfo siteInfo; private String personId; private String firstName; private String lastName; private SiteRole role; - public SiteMembership(String siteShortName, String siteTitle, String personId, String firstName, String lastName, SiteRole role) + public SiteMembership(SiteInfo siteInfo, String personId, String firstName, String lastName, SiteRole role) { super(); - if(siteShortName == null) + if(siteInfo == null) { throw new java.lang.IllegalArgumentException(); } @@ -58,18 +58,17 @@ public class SiteMembership { throw new java.lang.IllegalArgumentException(); } - this.siteShortName = siteShortName; - this.siteTitle = siteTitle; + this.siteInfo = siteInfo; this.personId = personId; this.firstName = firstName; this.lastName = lastName; this.role = role; } - public SiteMembership(String siteShortName, String siteTitle, String personId, SiteRole role) + public SiteMembership(SiteInfo siteInfo, String personId, SiteRole role) { super(); - if(siteShortName == null) + if(siteInfo == null) { throw new java.lang.IllegalArgumentException(); } @@ -82,20 +81,14 @@ public class SiteMembership throw new java.lang.IllegalArgumentException(); } - this.siteShortName = siteShortName; + this.siteInfo = siteInfo; this.personId = personId; - this.siteTitle = siteTitle; this.role = role; } - public String getSiteShortName() + public SiteInfo getSiteInfo() { - return siteShortName; - } - - public String getSiteTitle() - { - return siteTitle; + return siteInfo; } public String getPersonId() @@ -127,7 +120,7 @@ public class SiteMembership + ((personId == null) ? 0 : personId.hashCode()); result = prime * result + ((role == null) ? 0 : role.hashCode()); result = prime * result - + ((siteShortName == null) ? 0 : siteShortName.hashCode()); + + ((getSiteInfo() == null) ? 0 : getSiteInfo().hashCode()); return result; } @@ -148,10 +141,10 @@ public class SiteMembership return false; if (role != other.role) return false; - if (siteShortName == null) { - if (other.siteShortName != null) + if (getSiteInfo() == null) { + if (other.getSiteInfo() != null) return false; - } else if (!siteShortName.equals(other.siteShortName)) + } else if (!getSiteInfo().equals(other.getSiteInfo())) return false; return true; } @@ -159,7 +152,7 @@ public class SiteMembership @Override public String toString() { - return "SiteMembership [siteShortName=" + siteShortName + return "SiteMembership [siteInfo=" + getSiteInfo() + ", personId=" + personId + ", firstName=" + firstName + ", lastName=" + lastName + ", role=" + role + "]"; } diff --git a/source/java/org/alfresco/repo/site/SiteServiceImpl.java b/source/java/org/alfresco/repo/site/SiteServiceImpl.java index aa87b9f98b..1c3f4d2fc6 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImpl.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImpl.java @@ -1345,6 +1345,15 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic } } + /** + * @see org.alfresco.service.cmr.site.SiteService#hasSite(java.lang.String) + */ + @Override + public boolean hasSite(String shortName) + { + return (getSiteNodeRef(shortName, false) != null); + } + /** * @see org.alfresco.service.cmr.site.SiteService#updateSite(org.alfresco.service.cmr.site.SiteInfo) */ @@ -1777,17 +1786,17 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic protected Map listMembersImpl(String shortName, String nameFilter, String roleFilter, int size, boolean collapseGroups) { - Map members = new HashMap(32); - List list = listMembersInfoImpl(shortName, nameFilter, roleFilter, size, collapseGroups); + Map members = new HashMap(list.size()); + for (SiteMemberInfo info : list) members.put(info.getMemberName(), info.getMemberRole()); return members; } - private List listMembersInfoImpl(String shortName, String nameFilter, + protected List listMembersInfoImpl(String shortName, String nameFilter, String roleFilter, int size, boolean collapseGroups) { NodeRef siteNodeRef = getSiteNodeRef(shortName); @@ -2801,6 +2810,17 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic } } + public int countAuthoritiesWithRole(String shortName, String role) + { + // Check that we are not about to remove the last site manager + String group = getSiteRoleGroup(shortName, role, true); + Set siteUsers = this.authorityService.getContainedAuthorities( + AuthorityType.USER, group, true); + Set siteGroups = this.authorityService.getContainedAuthorities( + AuthorityType.GROUP, group, true); + return siteUsers.size() + siteGroups.size(); + } + /** * Helper to check that we are not removing the last Site Manager from a site * @@ -2813,19 +2833,11 @@ public class SiteServiceImpl extends AbstractLifecycleBean implements SiteServic // Check that we are not about to remove the last site manager if (SiteModel.SITE_MANAGER.equals(role) == true) { - String mgrGroup = getSiteRoleGroup(shortName, SITE_MANAGER, true); - Set siteUserMangers = this.authorityService.getContainedAuthorities( - AuthorityType.USER, mgrGroup, true); - if (siteUserMangers.size() <= 1) + int siteAuthorities = countAuthoritiesWithRole(shortName, SiteModel.SITE_MANAGER); + if (siteAuthorities <= 1) { - Set siteGroupManagers = this.authorityService.getContainedAuthorities( - AuthorityType.GROUP, mgrGroup, true); - - if (siteUserMangers.size() + siteGroupManagers.size() == 1) - { - throw new SiteServiceException(MSG_DO_NOT_CHANGE_MGR, new Object[] {authorityName}); - } - } + throw new SiteServiceException(MSG_DO_NOT_CHANGE_MGR, new Object[] {authorityName}); + } } } diff --git a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java index b891796f27..037b8273e1 100644 --- a/source/java/org/alfresco/repo/site/SiteServiceImplTest.java +++ b/source/java/org/alfresco/repo/site/SiteServiceImplTest.java @@ -231,8 +231,22 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest { // Expected } - - + } + + public void testHasSite() throws Exception + { + this.authenticationComponent.setCurrentUser(USER_ONE); + // Create a Public site + createSite("publicsite1", "doclib", SiteVisibility.PUBLIC); + // Create a Private site + createSite("privatesite1", "doclib", SiteVisibility.PRIVATE); + + // ensure USER_TWO has correct visibility - can "get" public site but not a private one, can "has" exist check both + this.authenticationComponent.setCurrentUser(USER_TWO); + assertTrue(this.siteService.getSite("publicsite1") != null); + assertTrue(this.siteService.getSite("privatesite1") == null); // should not be visible to get() + assertTrue(this.siteService.hasSite("publicsite1")); + assertTrue(this.siteService.hasSite("privatesite1")); // should be visible to has() exist check } /** @@ -242,7 +256,6 @@ public class SiteServiceImplTest extends BaseAlfrescoSpringTest */ public void testETHREEOH_2133() throws Exception { - // Test for duplicate site error with a private site this.siteService.createSite(TEST_SITE_PRESET, "wibble", TEST_TITLE, TEST_DESCRIPTION, SiteVisibility.PRIVATE); diff --git a/source/java/org/alfresco/repo/site/SitesCannedQuery.java b/source/java/org/alfresco/repo/site/SitesCannedQuery.java index 5588c4d9b7..da5a38aa17 100644 --- a/source/java/org/alfresco/repo/site/SitesCannedQuery.java +++ b/source/java/org/alfresco/repo/site/SitesCannedQuery.java @@ -106,7 +106,10 @@ public class SitesCannedQuery extends AbstractCannedQuery if(siteInfo != null) { String role = siteService.getMembersRole(siteName, userName); - siteMembers.add(new SiteMembership(siteName, siteInfo.getTitle(), authority, SiteRole.valueOf(role))); + if(role != null) + { + siteMembers.add(new SiteMembership(siteInfo, authority, SiteRole.valueOf(role))); + } } } @@ -208,16 +211,18 @@ public class SitesCannedQuery extends AbstractCannedQuery { String personId1 = o1.getPersonId(); String personId2 = o2.getPersonId(); - String shortName1 = o1.getSiteShortName(); - String shortName2 = o2.getSiteShortName(); + SiteInfo siteInfo1 = o1.getSiteInfo(); + SiteInfo siteInfo2 = o2.getSiteInfo(); + String shortName1 = siteInfo1.getShortName(); + String shortName2 = siteInfo2.getShortName(); String firstName1 = o1.getFirstName(); String firstName2 = o2.getFirstName(); String lastName1 = o1.getLastName(); String lastName2 = o2.getLastName(); SiteRole siteRole1 = o1.getRole(); SiteRole siteRole2 = o2.getRole(); - String siteTitle1 = o1.getSiteTitle(); - String siteTitle2 = o2.getSiteTitle(); + String siteTitle1 = siteInfo1.getTitle(); + String siteTitle2 = siteInfo2.getTitle(); int personId = safeCompare(personId1, personId2); int firstName = safeCompare(firstName1, firstName2); diff --git a/source/java/org/alfresco/repo/site/script/ScriptSiteService.java b/source/java/org/alfresco/repo/site/script/ScriptSiteService.java index b9bb7ccb97..78908f9ca4 100644 --- a/source/java/org/alfresco/repo/site/script/ScriptSiteService.java +++ b/source/java/org/alfresco/repo/site/script/ScriptSiteService.java @@ -131,6 +131,17 @@ public class ScriptSiteService extends BaseScopableProcessorExtension return new Site(siteInfo, this.serviceRegistry, this.siteService, getScope()); } + /** + * Site existence check. Allows private site existence to be tested. + * + * @param shortName site short name + * @return true if the site exists, false otherwise. + */ + public boolean hasSite(String shortName) + { + return this.siteService.hasSite(shortName); + } + /** * This method checks if the currently authenticated user has permission to create sites. * diff --git a/source/java/org/alfresco/repo/site/script/Site.java b/source/java/org/alfresco/repo/site/script/Site.java index 86e2d2dac3..a278b1462f 100644 --- a/source/java/org/alfresco/repo/site/script/Site.java +++ b/source/java/org/alfresco/repo/site/script/Site.java @@ -29,9 +29,9 @@ import org.alfresco.repo.invitation.script.ScriptInvitation; import org.alfresco.repo.invitation.script.ScriptInvitationFactory; import org.alfresco.repo.jscript.ContentAwareScriptableQNameMap; import org.alfresco.repo.jscript.ScriptNode; +import org.alfresco.repo.jscript.ScriptNode.NodeValueConverter; import org.alfresco.repo.jscript.ScriptableHashMap; import org.alfresco.repo.jscript.ScriptableQNameMap; -import org.alfresco.repo.jscript.ScriptNode.NodeValueConverter; import org.alfresco.repo.security.authentication.AuthenticationUtil; import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.repo.security.permissions.AccessDeniedException; @@ -40,8 +40,8 @@ import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.dictionary.PropertyDefinition; import org.alfresco.service.cmr.invitation.Invitation; import org.alfresco.service.cmr.invitation.InvitationException; -import org.alfresco.service.cmr.invitation.InvitationService; import org.alfresco.service.cmr.invitation.InvitationSearchCriteria.InvitationType; +import org.alfresco.service.cmr.invitation.InvitationService; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.AccessStatus; @@ -626,43 +626,52 @@ public class Site implements Serializable if (permissions != null && permissions instanceof ScriptableObject) { - // Get the permission service final PermissionService permissionService = this.serviceRegistry.getPermissionService(); - - if (!permissionService.getInheritParentPermissions(nodeRef)) + // ensure the user has permission to Change Permissions + if (permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS).equals(AccessStatus.ALLOWED)) { - // remove existing permissions - permissionService.deletePermissions(nodeRef); - } - - // Assign the correct permissions - ScriptableObject scriptable = (ScriptableObject)permissions; - Object[] propIds = scriptable.getIds(); - for (int i = 0; i < propIds.length; i++) - { - // Work on each key in turn - Object propId = propIds[i]; - - // Only interested in keys that are formed of Strings - if (propId instanceof String) + AuthenticationUtil.runAs(new RunAsWork() { - // Get the value out for the specified key - it must be String - final String key = (String)propId; - final Object value = scriptable.get(key, scriptable); - if (value instanceof String) - { - // Set the permission on the node - permissionService.setPermission(nodeRef, key, (String)value, true); + public Void doWork() throws Exception + { + if (!permissionService.getInheritParentPermissions(nodeRef)) + { + // remove existing permissions + permissionService.deletePermissions(nodeRef); + } + + // Assign the correct permissions + ScriptableObject scriptable = (ScriptableObject)permissions; + Object[] propIds = scriptable.getIds(); + for (int i = 0; i < propIds.length; i++) + { + // Work on each key in turn + Object propId = propIds[i]; + + // Only interested in keys that are formed of Strings + if (propId instanceof String) + { + // Get the value out for the specified key - it must be String + final String key = (String)propId; + final Object value = scriptable.get(key, scriptable); + if (value instanceof String) + { + // Set the permission on the node + permissionService.setPermission(nodeRef, key, (String)value, true); + } + } + } + + // always add the site managers group with SiteManager permission + String managers = siteService.getSiteRoleGroup(getShortName(), SiteModel.SITE_MANAGER); + permissionService.setPermission(nodeRef, managers, SiteModel.SITE_MANAGER, true); + + // now turn off inherit to finalize our permission changes + permissionService.setInheritParentPermissions(nodeRef, false); + return null; } - } + }, AuthenticationUtil.SYSTEM_USER_NAME); } - - // always add the site managers group with SiteManager permission - String managers = this.siteService.getSiteRoleGroup(getShortName(), SiteModel.SITE_MANAGER); - permissionService.setPermission(nodeRef, managers, SiteModel.SITE_MANAGER, true); - - // now turn off inherit to finalize our permission changes - permissionService.setInheritParentPermissions(nodeRef, false); } else { @@ -682,19 +691,27 @@ public class Site implements Serializable { final NodeRef nodeRef = node.getNodeRef(); - PermissionService permissionService = serviceRegistry.getPermissionService(); - try + // ensure the user has permission to Change Permissions + final PermissionService permissionService = serviceRegistry.getPermissionService(); + if (permissionService.hasPermission(nodeRef, PermissionService.CHANGE_PERMISSIONS).equals(AccessStatus.ALLOWED)) { - // Ensure node isn't inheriting permissions from an ancestor before deleting - if (!permissionService.getInheritParentPermissions(nodeRef)) + AuthenticationUtil.runAs(new RunAsWork() { - permissionService.deletePermissions(nodeRef); - permissionService.setInheritParentPermissions(nodeRef, true); - } + public Void doWork() throws Exception + { + // Ensure node isn't inheriting permissions from an ancestor before deleting + if (!permissionService.getInheritParentPermissions(nodeRef)) + { + permissionService.deletePermissions(nodeRef); + permissionService.setInheritParentPermissions(nodeRef, true); + } + return null; + } + }, AuthenticationUtil.SYSTEM_USER_NAME); } - catch (AccessDeniedException e) + else { - throw new AlfrescoRuntimeException("You do not have the authority to update permissions on this node.", e); + throw new AlfrescoRuntimeException("You do not have the authority to update permissions on this node."); } } diff --git a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java index 66266dcfeb..c5a96ba48e 100644 --- a/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java +++ b/source/java/org/alfresco/repo/tenant/MultiTServiceImpl.java @@ -615,6 +615,42 @@ public class MultiTServiceImpl implements TenantService return DEFAULT_DOMAIN; // default domain - non-tenant user } + /** + * Get the primary domain for the given user, if a tenant for that domain exists. + * + * For user names of the form "user@tenantdomain", the tenant domain the part of the string + * after the @ symbol. A check is then made to see if tenant with that domain name exists. + * If it does, then the identified domain is returned. If no tenant exists then null is + * returned. + * + * If the username does not end with a domain, as described above, then the default domain is + * returned. + */ + @Override public String getPrimaryDomain(String username) + { + String result = null; + // can be null (e.g. for System user / during app ctx init) + if (username != null) + { + int idx = username.lastIndexOf(SEPARATOR); + if ((idx > 0) && (idx < (username.length()-1))) + { + String tenantDomain = getTenantDomain(username.substring(idx+1)); + + if (getTenant(tenantDomain) != null) + { + result = tenantDomain; + } + } + else + { + result = DEFAULT_DOMAIN; + } + } + + return result; // default domain - non-tenant user + } + /* (non-Javadoc) * @see org.alfresco.repo.tenant.TenantUserService#getCurrentUserDomain() */ diff --git a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java index db2eadef3e..923d72c65a 100644 --- a/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java +++ b/source/java/org/alfresco/repo/thumbnail/CreateThumbnailActionExecuter.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -34,6 +34,7 @@ import org.alfresco.service.cmr.repository.ContentData; import org.alfresco.service.cmr.repository.ContentServiceTransientException; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.QName; import org.apache.commons.logging.Log; @@ -176,7 +177,8 @@ public class CreateThumbnailActionExecuter extends ActionExecuterAbstractBase // Create the thumbnail try { - this.thumbnailService.createThumbnail(actionedUponNodeRef, contentProperty, details.getMimetype(), details.getTransformationOptions(), thumbnailName, null); + TransformationOptions options = details.getTransformationOptions(); + this.thumbnailService.createThumbnail(actionedUponNodeRef, contentProperty, details.getMimetype(), options, thumbnailName, null); } catch (ContentServiceTransientException cste) { diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java index 2248f3ad60..3d49434e2d 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailDefinition.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -87,6 +87,7 @@ public class ThumbnailDefinition { this(mimetype, options); this.name= thumbnailName; + options.setUse(thumbnailName); } /** @@ -101,6 +102,7 @@ public class ThumbnailDefinition { this(mimetype, options, thumbnailName); this.placeHolderResourcePath = placeHolderResourcePath; + options.setUse(thumbnailName); } /** diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index 3673b65a5f..9ef960dcc6 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -145,7 +145,7 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi { throw new ThumbnailException("When adding a thumbnail details object make sure the name is set."); } - + td.getTransformationOptions().setUse(thumbnailName); this.thumbnailDefinitions.put(thumbnailName, td); } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java index 6502fb6e0c..894f03e5a5 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -153,6 +153,8 @@ public class ThumbnailRenditionConvertor putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_MAX_PAGES, transformationOptions.getMaxPages(), parameters); putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_PAGE_LIMIT, transformationOptions.getPageLimit(), parameters); + putParameterIfNotNull(AbstractTransformationRenderingEngine.PARAM_USE, transformationOptions.getUse(), parameters); + if (transformationOptions instanceof SWFTransformationOptions) { SWFTransformationOptions swfTransformationOptions = (SWFTransformationOptions)transformationOptions; diff --git a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java index 132b9cac92..1773a7c7a3 100644 --- a/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java +++ b/source/java/org/alfresco/repo/transaction/RetryingTransactionHelper.java @@ -125,6 +125,7 @@ public class RetryingTransactionHelper * * @return List of enterprise exception classes or empty list if not available. */ + @SuppressWarnings("unchecked") private static List> enterpriseRetryExceptions() { List> retryExceptions = null; diff --git a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java index a3ed9671df..999e14322c 100644 --- a/source/java/org/alfresco/repo/version/Version2ServiceImpl.java +++ b/source/java/org/alfresco/repo/version/Version2ServiceImpl.java @@ -33,10 +33,14 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.model.ContentModel; import org.alfresco.repo.policy.PolicyScope; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.version.VersionRevertCallback.RevertAspectAction; +import org.alfresco.repo.version.VersionRevertCallback.RevertAssocAction; import org.alfresco.repo.version.common.VersionHistoryImpl; import org.alfresco.repo.version.common.VersionImpl; import org.alfresco.repo.version.common.VersionUtil; import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; +import org.alfresco.service.cmr.dictionary.AspectDefinition; +import org.alfresco.service.cmr.dictionary.TypeDefinition; import org.alfresco.service.cmr.repository.AspectMissingException; import org.alfresco.service.cmr.repository.AssociationRef; import org.alfresco.service.cmr.repository.ChildAssociationRef; @@ -1080,6 +1084,11 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe */ public void revert(NodeRef nodeRef, Version version, boolean deep) { + if(logger.isDebugEnabled()) + { + logger.debug("revert nodeRef:" + nodeRef); + } + if (useDeprecatedV1) { super.revert(nodeRef, version, deep); @@ -1100,37 +1109,95 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe // Turn off any auto-version policy behaviours this.policyBehaviourFilter.disableBehaviour(nodeRef, ContentModel.ASPECT_VERSIONABLE); try - { + { + // The current (old) values + Map oldProps = this.nodeService.getProperties(nodeRef); + Set oldAspectQNames = this.nodeService.getAspects(nodeRef); + QName oldNodeTypeQName = nodeService.getType(nodeRef); // Store the current version label String currentVersionLabel = (String)this.nodeService.getProperty(nodeRef, ContentModel.PROP_VERSION_LABEL); - + + // The frozen (which will become new) values // Get the node that represents the frozen state NodeRef versionNodeRef = version.getFrozenStateNodeRef(); - - // Revert the property values - Map props = this.nodeService.getProperties(versionNodeRef); - VersionUtil.convertFrozenToOriginalProps(props); + Map newProps = this.nodeService.getProperties(versionNodeRef); + VersionUtil.convertFrozenToOriginalProps(newProps); + Set newAspectQNames = this.nodeService.getAspects(versionNodeRef); - this.nodeService.setProperties(nodeRef, props); - - // Apply/remove the aspects as required - Set aspects = new HashSet(this.nodeService.getAspects(nodeRef)); - for (QName versionAspect : this.nodeService.getAspects(versionNodeRef)) + // RevertDetails - given to policy behaviours + VersionRevertDetailsImpl revertDetails = new VersionRevertDetailsImpl(); + revertDetails.setNodeRef(nodeRef); + revertDetails.setNodeType(oldNodeTypeQName); + + // Do we want to maintain any existing property values? + Collection propsToLeaveAlone = new ArrayList(); + Collection assocsToLeaveAlone = new ArrayList(); + + TypeDefinition typeDef = dictionaryService.getType(oldNodeTypeQName); + if(typeDef != null) { - if (aspects.contains(versionAspect) == false) - { - this.nodeService.addAspect(nodeRef, versionAspect, null); - } - else - { - aspects.remove(versionAspect); - } + for(QName assocName : typeDef.getAssociations().keySet()) + { + if(getRevertAssocAction(oldNodeTypeQName, assocName, revertDetails) == RevertAssocAction.IGNORE) + { + assocsToLeaveAlone.add(assocName); + } + } } - for (QName aspect : aspects) + + for (QName aspect : oldAspectQNames) + { + AspectDefinition aspectDef = dictionaryService.getAspect(aspect); + if(aspectDef != null) + { + if (getRevertAspectAction(aspect, revertDetails) == RevertAspectAction.IGNORE) + { + propsToLeaveAlone.addAll(aspectDef.getProperties().keySet()); + } + for(QName assocName : aspectDef.getAssociations().keySet()) + { + if(getRevertAssocAction(aspect, assocName, revertDetails) == RevertAssocAction.IGNORE) + { + assocsToLeaveAlone.addAll(aspectDef.getAssociations().keySet()); + } + } + } + } + + for(QName prop : propsToLeaveAlone) + { + if(oldProps.containsKey(prop)) + { + newProps.put(prop, oldProps.get(prop)); + } + } + + this.nodeService.setProperties(nodeRef, newProps); + + Set aspectsToRemove = new HashSet(oldAspectQNames); + aspectsToRemove.removeAll(newAspectQNames); + + Set aspectsToAdd = new HashSet(newAspectQNames); + aspectsToAdd.removeAll(oldAspectQNames); + + // add aspects that are not on the current node + for (QName aspect : aspectsToAdd) + { + if (getRevertAspectAction(aspect, revertDetails) != RevertAspectAction.IGNORE) + { + this.nodeService.addAspect(nodeRef, aspect, null); + } + } + + // remove aspects that are not on the frozen node + for (QName aspect : aspectsToRemove) { - this.nodeService.removeAspect(nodeRef, aspect); + if (getRevertAspectAction(aspect, revertDetails) != RevertAspectAction.IGNORE) + { + this.nodeService.removeAspect(nodeRef, aspect); + } } - + // Re-add the versionable aspect to the reverted node if (this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_VERSIONABLE) == false) { @@ -1187,24 +1254,34 @@ public class Version2ServiceImpl extends VersionServiceImpl implements VersionSe else { children.remove(versionedChild); - } + } } for (ChildAssociationRef ref : children) { - this.nodeService.removeChild(nodeRef, ref.getChildRef()); + if (!assocsToLeaveAlone.contains(ref.getTypeQName())) + { + this.nodeService.removeChild(nodeRef, ref.getChildRef()); + } } // Add/remove the target associations for (AssociationRef assocRef : this.nodeService.getTargetAssocs(nodeRef, RegexQNamePattern.MATCH_ALL)) { - this.nodeService.removeAssociation(assocRef.getSourceRef(), assocRef.getTargetRef(), assocRef.getTypeQName()); + if (!assocsToLeaveAlone.contains(assocRef.getTypeQName())) + { + this.nodeService.removeAssociation(assocRef.getSourceRef(), assocRef.getTargetRef(), assocRef.getTypeQName()); + } } for (AssociationRef versionedAssoc : this.nodeService.getTargetAssocs(versionNodeRef, RegexQNamePattern.MATCH_ALL)) { - if (this.nodeService.exists(versionedAssoc.getTargetRef()) == true) - { - this.nodeService.createAssociation(nodeRef, versionedAssoc.getTargetRef(), versionedAssoc.getTypeQName()); - } + if (!assocsToLeaveAlone.contains(versionedAssoc.getTypeQName())) + { + + if (this.nodeService.exists(versionedAssoc.getTargetRef()) == true) + { + this.nodeService.createAssociation(nodeRef, versionedAssoc.getTargetRef(), versionedAssoc.getTypeQName()); + } + } // else // Since the target of the assoc no longer exists we can't recreate the assoc diff --git a/source/java/org/alfresco/repo/version/VersionRevertCallback.java b/source/java/org/alfresco/repo/version/VersionRevertCallback.java new file mode 100644 index 0000000000..dedf676fdb --- /dev/null +++ b/source/java/org/alfresco/repo/version/VersionRevertCallback.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2013-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.version; + +import org.alfresco.repo.copy.CopyBehaviourCallback.AssocCopySourceAction; +import org.alfresco.repo.copy.CopyBehaviourCallback.CopyAssociationDetails; +import org.alfresco.service.namespace.QName; + +/** + * A callback to modify version revert behaviour associated with a given type or aspect. This + * callback is called per type and per aspect. + * + * @since 4.2 + * @author mrogers + * + */ +public interface VersionRevertCallback +{ + /** + * + */ + public enum RevertAspectAction implements Comparable + { + /** + * Revert this aspect, if it does not exist on the target version then it will be removed. + */ + REVERT, + + /** + * Ignore the aspect, do not remove it or its properties. + */ + IGNORE, + } + + /** + * How should the specified aspect be reverted? + * + * @param aspectName, the name of the aspect to revert + * @param details, details of the aspect to revert + * + */ + public RevertAspectAction getRevertAspectAction(QName aspectName, VersionRevertDetails details); + + /** + * + */ + public enum RevertAssocAction implements Comparable + { + /** + * Revert this assoc, if it does not exist on the target version then it will be removed. + */ + REVERT, + + /** + * Ignore the assoc, do not remove it or add it. + */ + IGNORE, + } + + /** + * How should the specified assoc be reverted? + * + * @param assocName, the name of the assoc to revert + * @param details, details of the node to revert + * + */ + public RevertAssocAction getRevertAssocAction(QName assocName, VersionRevertDetails details); + + + + + +} diff --git a/source/java/org/alfresco/repo/version/VersionRevertDetails.java b/source/java/org/alfresco/repo/version/VersionRevertDetails.java new file mode 100644 index 0000000000..9fa19085db --- /dev/null +++ b/source/java/org/alfresco/repo/version/VersionRevertDetails.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.version; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +public interface VersionRevertDetails +{ + /** + * Node to revert + * @return the noderef of the node to revert + */ + NodeRef getNodeRef(); + + /** + * Type of node that is being reverted + * @return the type of the node that is being reverted + */ + public QName getNodeType(); + +} diff --git a/source/java/org/alfresco/repo/version/VersionRevertDetailsImpl.java b/source/java/org/alfresco/repo/version/VersionRevertDetailsImpl.java new file mode 100644 index 0000000000..d5539c3b4a --- /dev/null +++ b/source/java/org/alfresco/repo/version/VersionRevertDetailsImpl.java @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2013-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.repo.version; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * Implementation of VersionRevertDetails + * @author mrogers + * @since 4.2 + */ +/*package*/class VersionRevertDetailsImpl implements VersionRevertDetails +{ + private NodeRef nodeRef; + private QName nodeType; + + + Map revertedProperties; + + public void setNodeRef(NodeRef nodeRef) + { + this.nodeRef = nodeRef; + } + + @Override + public NodeRef getNodeRef() + { + return nodeRef; + } + + public void setNodeType(QName nodeType) { + this.nodeType = nodeType; + } + + @Override + public QName getNodeType() { + return nodeType; + } + +} diff --git a/source/java/org/alfresco/repo/version/VersionServicePolicies.java b/source/java/org/alfresco/repo/version/VersionServicePolicies.java index 3b08247734..c2fe4978fe 100644 --- a/source/java/org/alfresco/repo/version/VersionServicePolicies.java +++ b/source/java/org/alfresco/repo/version/VersionServicePolicies.java @@ -94,6 +94,26 @@ public interface VersionServicePolicies PolicyScope nodeDetails); } + /** + * + * Called for all types and aspects before reverting a node. + * + * @param classRef the type or aspect qualified name + * @param revertDetails the details of the impending revert + * @return Return the callback that will be used to modify the revert behaviour for this + * type or aspect. Return null to assume the default. + * + * + * @since V4.2 + */ + public interface OnRevertVersionPolicy extends ClassPolicy + { + public static final QName QNAME = QName.createQName(NamespaceService.ALFRESCO_URI, "getRevertVersionCallback"); + + VersionRevertCallback getRevertVersionCallback(QName classRef, VersionRevertDetails copyDetails); + } + + /** * Calculate version lable policy interface */ diff --git a/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java b/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java index 4e736950ca..3387f7e7f2 100644 --- a/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java +++ b/source/java/org/alfresco/repo/version/common/AbstractVersionServiceImpl.java @@ -19,6 +19,7 @@ package org.alfresco.repo.version.common; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; import java.util.Map; import java.util.Set; @@ -26,11 +27,16 @@ import java.util.Set; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.PolicyComponent; import org.alfresco.repo.policy.PolicyScope; +import org.alfresco.repo.version.VersionRevertCallback; +import org.alfresco.repo.version.VersionRevertCallback.RevertAspectAction; +import org.alfresco.repo.version.VersionRevertCallback.RevertAssocAction; +import org.alfresco.repo.version.VersionRevertDetails; import org.alfresco.repo.version.VersionServicePolicies; import org.alfresco.repo.version.VersionServicePolicies.AfterCreateVersionPolicy; import org.alfresco.repo.version.VersionServicePolicies.BeforeCreateVersionPolicy; import org.alfresco.repo.version.VersionServicePolicies.CalculateVersionLabelPolicy; import org.alfresco.repo.version.VersionServicePolicies.OnCreateVersionPolicy; +import org.alfresco.repo.version.VersionServicePolicies.OnRevertVersionPolicy; import org.alfresco.repo.version.common.versionlabel.SerialVersionLabelPolicy; import org.alfresco.service.cmr.dictionary.DictionaryService; import org.alfresco.service.cmr.repository.NodeRef; @@ -69,6 +75,7 @@ public abstract class AbstractVersionServiceImpl private ClassPolicyDelegate afterCreateVersionDelegate; private ClassPolicyDelegate onCreateVersionDelegate; private ClassPolicyDelegate calculateVersionLabelDelegate; + private ClassPolicyDelegate onRevertVersionDelegate; /** * Sets the general node service @@ -109,7 +116,8 @@ public abstract class AbstractVersionServiceImpl this.beforeCreateVersionDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.BeforeCreateVersionPolicy.class); this.afterCreateVersionDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.AfterCreateVersionPolicy.class); this.onCreateVersionDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.OnCreateVersionPolicy.class); - this.calculateVersionLabelDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.CalculateVersionLabelPolicy.class); + this.calculateVersionLabelDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.CalculateVersionLabelPolicy.class); + this.onRevertVersionDelegate = this.policyComponent.registerClassPolicy(VersionServicePolicies.OnRevertVersionPolicy.class); } /** @@ -164,6 +172,59 @@ public abstract class AbstractVersionServiceImpl } } + + /** + * How should revert deal with this aspect? + * @param aspectName + * @param revertDetails + * @return the action to be taken + */ + protected RevertAspectAction getRevertAspectAction(QName aspectName, VersionRevertDetails revertDetails) + { + // first check for a policy for the node type + Collection policies = onRevertVersionDelegate.getList(aspectName); + for(OnRevertVersionPolicy policy : policies) + { + VersionRevertCallback cb = policy.getRevertVersionCallback(aspectName, revertDetails); + if(cb != null) + { + RevertAspectAction action = cb.getRevertAspectAction(aspectName, revertDetails); + if(action != null && action == RevertAspectAction.IGNORE) + { + // ignore always wins + return action; + } + } + } + return RevertAspectAction.REVERT; + } + + /** + * How should revert deal with this association + * @param className + * @param assocName + * @param revertDetails + * @return the action to be taken + */ + protected RevertAssocAction getRevertAssocAction(QName className, QName assocName, VersionRevertDetails revertDetails) + { + Collection policies = onRevertVersionDelegate.getList(className); + for(OnRevertVersionPolicy policy : policies) + { + VersionRevertCallback cb = policy.getRevertVersionCallback(className, revertDetails); + if(cb != null) + { + RevertAssocAction action = cb.getRevertAssocAction(assocName, revertDetails); + if(action != null && action == RevertAssocAction.IGNORE) + { + // ignore always wins + return action; + } + } + } + return RevertAssocAction.REVERT; + } + /** * Invokes the on create version policy behaviour for a given type diff --git a/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java index b90b842e8c..1ea49adc7e 100644 --- a/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java +++ b/source/java/org/alfresco/repo/workflow/AbstractWorkflowServiceIntegrationTest.java @@ -33,6 +33,7 @@ import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.model.Repository; import org.alfresco.repo.security.authentication.AuthenticationComponent; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; import org.alfresco.repo.security.person.TestGroupManager; import org.alfresco.repo.security.person.TestPersonManager; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; @@ -74,6 +75,7 @@ public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringT protected final static String USER1 = "WFUser1" + GUID.generate(); protected final static String USER2 = "WFUser2" + GUID.generate(); protected final static String USER3 = "WFUser3" + GUID.generate(); + protected final static String USER4 = "WFUser4" + GUID.generate(); protected final static String GROUP = "WFGroup" + GUID.generate(); protected final static String SUB_GROUP = "WFSubGroup" + GUID.generate(); protected final static QName customStringProp = QName.createQName(NamespaceService.WORKFLOW_MODEL_1_0_URI, "customStringProp"); @@ -876,6 +878,98 @@ public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringT assertEquals(1, tasks.size()); } + public void testActionVsPermissions() + { + // Start adhoc Workflow + WorkflowDefinition workflowDef = deployDefinition(getAdhocDefinitionPath()); + assertNotNull(workflowDef); + + String workflowInstanceId = startAdhocWorkflow(workflowDef, USER2); + + // End start task to progress workflow + WorkflowTask startTask = null; + try + { + personManager.setUser(USER4); + startTask = workflowService.getStartTask(workflowInstanceId); + fail(); + } + catch (AccessDeniedException e) + { + personManager.setUser(USER2); + } + + startTask = workflowService.getStartTask(workflowInstanceId); + final String startTaskId = startTask.getId(); + try + { + personManager.setUser(USER4); + workflowService.endTask(startTaskId, null); + fail(); + } + catch (AccessDeniedException e) + { + personManager.setUser(USER2); + } + + workflowService.endTask(startTaskId, null); + + WorkflowTask theTask = getNextTaskForWorkflow(workflowInstanceId); + + // Set some custom properties on the task + HashMap params = new HashMap(); + params.put(customStringProp, "stringValue"); + try + { + personManager.setUser(USER4); + workflowService.updateTask(theTask.getId(), params, null, null); + fail(); + } + catch (AccessDeniedException e) + { + personManager.setUser(USER2); + } + workflowService.updateTask(theTask.getId(), params, null, null); + + // Test all query features for running tasks + checkTaskQueryInProgress(workflowInstanceId, theTask, workflowInstanceId); + + // Test all query features for the start-task + checkTaskQueryStartTaskCompleted(workflowInstanceId, startTask); + + // Finish the task adhoc-task + try + { + personManager.setUser(USER4); + workflowService.endTask(theTask.getId(), null); + fail(); + } + catch (AccessDeniedException e) + { + personManager.setUser(USER2); + } + workflowService.endTask(theTask.getId(), null); + + // Test all query features for completed tasks + checkTaskQueryTaskCompleted(workflowInstanceId, theTask, startTask); + + // Finally end the workflow and check the querying isActive == false + WorkflowTask lastTask = getNextTaskForWorkflow(workflowInstanceId); + try + { + personManager.setUser(USER4); + workflowService.endTask(lastTask.getId(), null); + fail(); + } + catch (AccessDeniedException e) + { + personManager.setUser(USER2); + } + workflowService.endTask(lastTask.getId(), null); + + checkQueryTasksInactiveWorkflow(workflowInstanceId); + } + public void checkCompletedWorkflows(String defId, String... expectedIds) { List workflows = workflowService.getCompletedWorkflows(defId); @@ -1234,6 +1328,7 @@ public abstract class AbstractWorkflowServiceIntegrationTest extends BaseSpringT personManager.createPerson(USER1); personManager.createPerson(USER2); personManager.createPerson(USER3); + personManager.createPerson(USER4); // create test groups groupManager.addGroupToParent(GROUP, SUB_GROUP); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java index 92ee9a05c0..f1fb103e43 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowObjectFactory.java @@ -234,7 +234,8 @@ public class WorkflowObjectFactory String processKey = getProcessKey(workflowDefinitionName) + ".task." + name; TypeDefinition metadata = taskDef.getMetadata(); String title = getLabel(processKey, TITLE_LABEL, metadata.getTitle(dictionaryService), defaultTitle, name); - String description = getLabel(processKey, DESC_LABEL, metadata.getDescription(dictionaryService), defaultDescription, title); + defaultDescription = (defaultDescription !=null && defaultDescription.trim().length() == 0) ? null : defaultDescription; + String description = getLabel(processKey, DESC_LABEL, defaultDescription, metadata.getDescription(dictionaryService), title); return new WorkflowTask(actualId, taskDef, name, title, description, state, path, properties); diff --git a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java index 7e273f7404..5a347c4f8b 100644 --- a/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java +++ b/source/java/org/alfresco/repo/workflow/WorkflowServiceImpl.java @@ -983,6 +983,12 @@ public class WorkflowServiceImpl implements WorkflowService task = getTaskById(task.getId()); // Refresh the task. } + // if task is null just return false + if (task == null) + { + return false; + } + // if the task is complete it is not editable if (task.getState() == WorkflowTaskState.COMPLETED) { @@ -1026,6 +1032,12 @@ public class WorkflowServiceImpl implements WorkflowService task = getTaskById(task.getId()); // Refresh the task. } + // if task is null just return false + if (task == null) + { + return false; + } + // if the task is complete it is not reassignable if (task.getState() == WorkflowTaskState.COMPLETED) { @@ -1080,6 +1092,12 @@ public class WorkflowServiceImpl implements WorkflowService task = getTaskById(task.getId()); // Refresh the task. } + // if task is null just return false + if (task == null) + { + return false; + } + // if the task is complete it is not claimable if (task.getState() == WorkflowTaskState.COMPLETED) { @@ -1112,6 +1130,12 @@ public class WorkflowServiceImpl implements WorkflowService { task = getTaskById(task.getId()); // Refresh the task. } + // if task is null just return false + if (task == null) + { + return false; + } + // if the task is complete it is not releasable if (task.getState() == WorkflowTaskState.COMPLETED) { diff --git a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTimerExecutionTest.java b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTimerExecutionTest.java index b5ad2e13cf..6a3c7fe272 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/ActivitiTimerExecutionTest.java +++ b/source/java/org/alfresco/repo/workflow/activiti/ActivitiTimerExecutionTest.java @@ -1,329 +1,299 @@ -///* -// * Copyright (C) 2005-2011 Alfresco Software Limited. -// * -// * This file is part of Alfresco -// * -// * 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 . -// */ -// -//package org.alfresco.repo.workflow.activiti; -// -//import java.io.InputStream; -//import java.io.Serializable; -//import java.util.HashMap; -//import java.util.List; -//import java.util.Map; -// -//import org.activiti.engine.ProcessEngine; -//import org.activiti.engine.impl.persistence.entity.TimerEntity; -//import org.activiti.engine.repository.ProcessDefinition; -//import org.activiti.engine.runtime.Job; -//import org.alfresco.model.ContentModel; -//import org.alfresco.repo.content.MimetypeMap; -//import org.alfresco.repo.security.authentication.AuthenticationComponent; -//import org.alfresco.repo.security.authentication.AuthenticationUtil; -//import org.alfresco.repo.security.person.TestPersonManager; -//import org.alfresco.repo.transaction.RetryingTransactionHelper; -//import org.alfresco.repo.workflow.BPMEngineRegistry; -//import org.alfresco.service.ServiceRegistry; -//import org.alfresco.service.cmr.repository.NodeRef; -//import org.alfresco.service.cmr.repository.NodeService; -//import org.alfresco.service.cmr.security.MutableAuthenticationService; -//import org.alfresco.service.cmr.security.PersonService; -//import org.alfresco.service.cmr.workflow.WorkflowDefinition; -//import org.alfresco.service.cmr.workflow.WorkflowDeployment; -//import org.alfresco.service.cmr.workflow.WorkflowInstance; -//import org.alfresco.service.cmr.workflow.WorkflowPath; -//import org.alfresco.service.cmr.workflow.WorkflowService; -//import org.alfresco.service.cmr.workflow.WorkflowTask; -//import org.alfresco.service.namespace.QName; -//import org.alfresco.util.BaseSpringTest; -//import org.alfresco.util.GUID; -// -///** -// * Test to verify timer execution autentication and transaction behaviour. -// * -// * @author Frederik Heremans -// * @since 3.4.e -// */ -//public class ActivitiTimerExecutionTest extends BaseSpringTest -//{ -// -// private static final String USER1 = "User1" + GUID.generate(); -// -// private RetryingTransactionHelper transactionHelper; -// -// private WorkflowService workflowService; -// -// private AuthenticationComponent authenticationComponent; -// -// private NodeService nodeService; -// -// private ProcessEngine activitiProcessEngine; -// -// private TestPersonManager personManager; -// -// @SuppressWarnings("deprecation") -// public void testTimerExecutionAuthentication() throws Exception -// { -// this.setComplete(); -// this.endTransaction(); -// -// try -// { -// WorkflowInstance taskAssigneeWorkflowInstance = transactionHelper -// .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() -// { -// public WorkflowInstance execute() throws Throwable -// { -// // Create test person -// personManager.createPerson(USER1); -// -// WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml"); -// -// // Start the test timer transaction process, with 'error' = false, expecting a timer job -// // to be executed without an error, with task timer is assigned to assigned to USER1 -// Map params = new HashMap(); -// params.put(QName.createQName("error"), Boolean.FALSE); -// params.put(QName.createQName("theTaskAssignee"), USER1); -// -// WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); -// // End start-task -// workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); -// -// return path.getInstance(); -// } -// }); -// -// final String definitionId = taskAssigneeWorkflowInstance.getDefinition().getId(); -// WorkflowInstance unassignedWorkflowInstance = transactionHelper -// .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() -// { -// public WorkflowInstance execute() throws Throwable -// { -// // Start the test timer transaction process, with 'error' = false, expecting a timer job -// // to be executed without an error, with task timer is unassigned -// Map params = new HashMap(); -// params.put(QName.createQName("error"), Boolean.FALSE); -// params.put(QName.createQName("theTaskAssignee"), null); -// -// WorkflowPath path = workflowService.startWorkflow(definitionId, params); -// // End start-task -// workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); -// -// return path.getInstance(); -// } -// }); -// -// // No timers should be available after a while they should have been executed, otherwise test fails -// waitForTimersToBeExecuted(taskAssigneeWorkflowInstance.getId()); -// waitForTimersToBeExecuted(unassignedWorkflowInstance.getId()); -// -// // Test assigned task -// WorkflowPath path = workflowService.getWorkflowPaths(taskAssigneeWorkflowInstance.getId()).get(0); -// -// // Check if job executed without exception, process should be waiting in "waitTask" -// List tasks = workflowService.getTasksForWorkflowPath(path.getId()); -// assertNotNull(tasks); -// assertEquals(1, tasks.size()); -// assertEquals("waitTask", tasks.get(0).getDefinition().getNode().getName()); -// -// // Check if timer was executed as task assignee, was set while executing timer -// Map pathProps = workflowService.getPathProperties(path.getId()); -// assertEquals(USER1, pathProps.get(QName.createQName("timerExecutedAs"))); -// -// // Test unassigned task, should be executed as admin-user -// path = workflowService.getWorkflowPaths(unassignedWorkflowInstance.getId()).get(0); -// -// // Check if job did executed without exception, process should be waiting in "waitTask" -// tasks = workflowService.getTasksForWorkflowPath(path.getId()); -// assertNotNull(tasks); -// assertEquals(1, tasks.size()); -// assertEquals("waitTask", tasks.get(0).getDefinition().getNode().getName()); -// -// // Check if timer was executed as system -// pathProps = workflowService.getPathProperties(path.getId()); -// assertEquals(AuthenticationUtil.getSystemUserName(), pathProps.get(QName.createQName("timerExecutedAs"))); -// } -// finally -// { -// cleanUp(); -// -// } -// } -// -// @SuppressWarnings("deprecation") -// public void testTimerExecutionTransactionRollback() throws Exception -// { -// this.setComplete(); -// this.endTransaction(); -// -// try -// { -// WorkflowInstance workflowInstance = transactionHelper -// .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() -// { -// public WorkflowInstance execute() throws Throwable -// { -// // Create test person -// personManager.createPerson(USER1); -// -// WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml"); -// -// // Start the test timer transaction process, with 'error' = false, expecting a timer job -// // to be executed without an error, with task timer is assigned to assigned to USER1 -// Map params = new HashMap(); -// params.put(QName.createQName("error"), Boolean.TRUE); -// params.put(QName.createQName("theTaskAssignee"), USER1); -// -// WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); -// // End start-task -// workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); -// -// return path.getInstance(); -// } -// }); -// -// String processInstanceId = BPMEngineRegistry.getLocalId(workflowInstance.getId()); -// -// // Check the timer, should have "error" set in it -// TimerEntity timer = (TimerEntity) activitiProcessEngine.getManagementService() -// .createJobQuery().timers() -// .processInstanceId(processInstanceId).singleResult(); -// -// int numberOfRetries = 5; -// for (int i = 0; i < numberOfRetries; i++) -// { -// if (timer.getExceptionMessage() != null && timer.getRetries() == 0) -// { -// break; -// } -// Thread.sleep(1000); -// timer = (TimerEntity) activitiProcessEngine.getManagementService() -// .createJobQuery().timers() -// .processInstanceId(processInstanceId).singleResult(); -// } -// assertNotNull("Job should have exception message set", timer.getExceptionMessage()); -// assertEquals(0, timer.getRetries()); -// -// // Check if exception is the one we deliberately caused -// String fullExceptionStacktrace = activitiProcessEngine.getManagementService().getJobExceptionStacktrace(timer.getId()); -// assertTrue(fullExceptionStacktrace.contains("Activiti engine rocks!")); -// -// // Check if alfresco-changes that were performed are rolled back -// NodeRef personNode = personManager.get(USER1); -// NodeRef userHomeNode = (NodeRef)nodeService.getProperty(personNode, ContentModel.PROP_HOMEFOLDER); -// -// String homeFolderName = (String) nodeService.getProperty(userHomeNode, ContentModel.PROP_NAME); -// assertNotSame("User home changed", homeFolderName); -// } -// finally -// { -// cleanUp(); -// } -// } -// -// /** -// * Delete the deployment, cascading all related processes/history -// */ -// private void cleanUp() -// { -// transactionHelper .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() -// { -// public Void execute() throws Throwable -// { -// try -// { -// personManager.clearPeople(); -// } -// finally -// { -// // Make sure process-definition is still deleted, even when clearing people fails. -// ProcessDefinition procDef = activitiProcessEngine.getRepositoryService() -// .createProcessDefinitionQuery() -// .processDefinitionKey("testTimerTransaction") -// .singleResult(); -// -// if (procDef != null) -// { -// activitiProcessEngine.getRepositoryService().deleteDeployment(procDef.getDeploymentId(), true); -// } -// } -// return null; -// } -// }); -// } -// -// @SuppressWarnings("deprecation") -// @Override -// protected void onSetUpInTransaction() throws Exception -// { -// ServiceRegistry registry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); -// this.workflowService = registry.getWorkflowService(); -// this.authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); -// this.nodeService = registry.getNodeService(); -// -// this.transactionHelper = (RetryingTransactionHelper) this.applicationContext -// .getBean("retryingTransactionHelper"); -// -// this.activitiProcessEngine = (ProcessEngine) this.applicationContext.getBean("activitiProcessEngine"); -// -// MutableAuthenticationService authenticationService = registry.getAuthenticationService(); -// PersonService personService = registry.getPersonService(); -// -// this.personManager = new TestPersonManager(authenticationService, personService, nodeService); -// -// authenticationComponent.setSystemUserAsCurrentUser(); -// } -// -// private void waitForTimersToBeExecuted(String workflowInstanceId) throws Exception -// { -// String processInstanceId = BPMEngineRegistry.getLocalId(workflowInstanceId); -// // Job-executor should finish the job, no timers should be available for WF -// List timers = activitiProcessEngine.getManagementService().createJobQuery() -// .timers() -// .processInstanceId(processInstanceId) -// .list(); -// -// int numberOfRetries = 5; -// for (int i=0; i< numberOfRetries; i++) -// { -// if (timers.size() == 0) -// { -// break; -// } -// Thread.sleep(1000); -// timers = activitiProcessEngine.getManagementService().createJobQuery() -// .timers() -// .processInstanceId(processInstanceId) -// .list(); -// } -// } -// -// protected WorkflowDefinition deployDefinition(String resource) -// { -// InputStream input = getInputStream(resource); -// WorkflowDeployment deployment = workflowService.deployDefinition(ActivitiConstants.ENGINE_ID, input, MimetypeMap.MIMETYPE_XML); -// WorkflowDefinition definition = deployment.getDefinition(); -// return definition; -// } -// -// private InputStream getInputStream(String resource) -// { -// ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); -// InputStream input= classLoader.getResourceAsStream(resource); -// return input; -// } -// -//} +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ + +package org.alfresco.repo.workflow.activiti; + +import java.io.InputStream; +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.activiti.engine.ProcessEngine; +import org.activiti.engine.impl.persistence.entity.TimerEntity; +import org.activiti.engine.repository.ProcessDefinition; +import org.activiti.engine.runtime.Job; +import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.security.authentication.AuthenticationComponent; +import org.alfresco.repo.security.person.TestPersonManager; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.workflow.BPMEngineRegistry; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.workflow.WorkflowDefinition; +import org.alfresco.service.cmr.workflow.WorkflowDeployment; +import org.alfresco.service.cmr.workflow.WorkflowInstance; +import org.alfresco.service.cmr.workflow.WorkflowPath; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.cmr.workflow.WorkflowTask; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseSpringTest; +import org.alfresco.util.GUID; + +/** + * Test to verify timer execution autentication and transaction behaviour. + * + * @author Frederik Heremans + * @since 3.4.e + */ +public class ActivitiTimerExecutionTest extends BaseSpringTest +{ + + private static final String USER1 = "User1" + GUID.generate(); + + private RetryingTransactionHelper transactionHelper; + + private WorkflowService workflowService; + + private AuthenticationComponent authenticationComponent; + + private NodeService nodeService; + + private ProcessEngine activitiProcessEngine; + + private TestPersonManager personManager; + + @SuppressWarnings("deprecation") + public void testTimerExecutionAuthentication() throws Exception + { + this.setComplete(); + this.endTransaction(); + + try + { + WorkflowInstance taskAssigneeWorkflowInstance = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public WorkflowInstance execute() throws Throwable + { + // Create test person + personManager.createPerson(USER1); + + WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml"); + + // Start the test timer transaction process, with 'error' = false, expecting a timer job + // to be executed without an error, with task timer is assigned to assigned to USER1 + Map params = new HashMap(); + params.put(QName.createQName("error"), Boolean.FALSE); + params.put(QName.createQName("theTaskAssignee"), USER1); + + WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); + // End start-task + workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); + + return path.getInstance(); + } + }); + + // No timers should be available after a while they should have been executed, otherwise test fails + waitForTimersToBeExecuted(taskAssigneeWorkflowInstance.getId()); + + // Test assigned task + WorkflowPath path = workflowService.getWorkflowPaths(taskAssigneeWorkflowInstance.getId()).get(0); + + // Check if job executed without exception, process should be waiting in "waitTask" + List tasks = workflowService.getTasksForWorkflowPath(path.getId()); + assertNotNull(tasks); + assertEquals(1, tasks.size()); + assertEquals("waitTask", tasks.get(0).getDefinition().getNode().getName()); + + // Check if timer was executed as task assignee, was set while executing timer + Map pathProps = workflowService.getPathProperties(path.getId()); + assertEquals(USER1, pathProps.get(QName.createQName("timerExecutedAs"))); + } + finally + { + cleanUp(); + + } + } + + @SuppressWarnings("deprecation") + public void testTimerExecutionTransactionRollback() throws Exception + { + this.setComplete(); + this.endTransaction(); + + try + { + WorkflowInstance workflowInstance = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public WorkflowInstance execute() throws Throwable + { + // Create test person + personManager.createPerson(USER1); + + WorkflowDefinition definition = deployDefinition("activiti/testTimerTransaction.bpmn20.xml"); + + // Start the test timer transaction process, with 'error' = false, expecting a timer job + // to be executed without an error, with task timer is assigned to assigned to USER1 + Map params = new HashMap(); + params.put(QName.createQName("error"), Boolean.TRUE); + params.put(QName.createQName("theTaskAssignee"), USER1); + + WorkflowPath path = workflowService.startWorkflow(definition.getId(), params); + // End start-task + workflowService.endTask(workflowService.getStartTask(path.getInstance().getId()).getId(), null); + + return path.getInstance(); + } + }); + + String processInstanceId = BPMEngineRegistry.getLocalId(workflowInstance.getId()); + + // Check the timer, should have "error" set in it + TimerEntity timer = (TimerEntity) activitiProcessEngine.getManagementService() + .createJobQuery().timers() + .processInstanceId(processInstanceId).singleResult(); + + int numberOfRetries = 5; + for (int i = 0; i < numberOfRetries; i++) + { + if (timer.getExceptionMessage() != null && timer.getRetries() == 0) + { + break; + } + Thread.sleep(1000); + timer = (TimerEntity) activitiProcessEngine.getManagementService() + .createJobQuery().timers() + .processInstanceId(processInstanceId).singleResult(); + } + assertNotNull("Job should have exception message set", timer.getExceptionMessage()); + assertEquals(0, timer.getRetries()); + + // Check if exception is the one we deliberately caused + String fullExceptionStacktrace = activitiProcessEngine.getManagementService().getJobExceptionStacktrace(timer.getId()); + assertTrue(fullExceptionStacktrace.contains("Activiti engine rocks!")); + + // Check if alfresco-changes that were performed are rolled back + NodeRef personNode = personManager.get(USER1); + NodeRef userHomeNode = (NodeRef)nodeService.getProperty(personNode, ContentModel.PROP_HOMEFOLDER); + + String homeFolderName = (String) nodeService.getProperty(userHomeNode, ContentModel.PROP_NAME); + assertNotSame("User home changed", homeFolderName); + } + finally + { + cleanUp(); + } + } + + /** + * Delete the deployment, cascading all related processes/history + */ + private void cleanUp() + { + transactionHelper .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + try + { + personManager.clearPeople(); + } + finally + { + // Make sure process-definition is still deleted, even when clearing people fails. + ProcessDefinition procDef = activitiProcessEngine.getRepositoryService() + .createProcessDefinitionQuery() + .processDefinitionKey("testTimerTransaction") + .latestVersion() + .singleResult(); + + if (procDef != null) + { + activitiProcessEngine.getRepositoryService().deleteDeployment(procDef.getDeploymentId(), true); + } + } + return null; + } + }); + } + + @SuppressWarnings("deprecation") + @Override + protected void onSetUpInTransaction() throws Exception + { + ServiceRegistry registry = (ServiceRegistry) applicationContext.getBean(ServiceRegistry.SERVICE_REGISTRY); + this.workflowService = registry.getWorkflowService(); + this.authenticationComponent = (AuthenticationComponent) applicationContext.getBean("authenticationComponent"); + this.nodeService = registry.getNodeService(); + + this.transactionHelper = (RetryingTransactionHelper) this.applicationContext + .getBean("retryingTransactionHelper"); + + this.activitiProcessEngine = (ProcessEngine) this.applicationContext.getBean("activitiProcessEngine"); + + MutableAuthenticationService authenticationService = registry.getAuthenticationService(); + PersonService personService = registry.getPersonService(); + + this.personManager = new TestPersonManager(authenticationService, personService, nodeService); + + authenticationComponent.setSystemUserAsCurrentUser(); + } + + private void waitForTimersToBeExecuted(String workflowInstanceId) throws Exception + { + String processInstanceId = BPMEngineRegistry.getLocalId(workflowInstanceId); + // Job-executor should finish the job, no timers should be available for WF + List timers = activitiProcessEngine.getManagementService().createJobQuery() + .timers() + .processInstanceId(processInstanceId) + .list(); + + int numberOfRetries = 5; + for (int i=0; i< numberOfRetries; i++) + { + if (timers.size() == 0) + { + break; + } + Thread.sleep(1000); + timers = activitiProcessEngine.getManagementService().createJobQuery() + .timers() + .processInstanceId(processInstanceId) + .list(); + } + + if(timers.size() > 0) { + fail("There are still timers available for the process: " + processInstanceId); + } + } + + protected WorkflowDefinition deployDefinition(String resource) + { + InputStream input = getInputStream(resource); + WorkflowDeployment deployment = workflowService.deployDefinition(ActivitiConstants.ENGINE_ID, input, MimetypeMap.MIMETYPE_XML); + WorkflowDefinition definition = deployment.getDefinition(); + return definition; + } + + private InputStream getInputStream(String resource) + { + ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); + InputStream input= classLoader.getResourceAsStream(resource); + return input; + } + +} diff --git a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java index be78473756..f492456f28 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AlfrescoProcessEngineConfiguration.java @@ -28,6 +28,7 @@ import org.activiti.engine.impl.jobexecutor.TimerExecuteNestedActivityJobHandler import org.activiti.engine.impl.variable.SerializableType; import org.activiti.engine.impl.variable.VariableType; import org.activiti.spring.SpringProcessEngineConfiguration; +import org.alfresco.repo.transaction.AlfrescoTransactionSupport; import org.alfresco.service.cmr.repository.NodeService; /** @@ -40,6 +41,13 @@ public class AlfrescoProcessEngineConfiguration extends SpringProcessEngineConfi private List customTypes; private NodeService unprotectedNodeService; + public AlfrescoProcessEngineConfiguration() + { + // Make sure the synchornizationAdapter is run before the AlfrescoTransactionSupport (and also before the + // myBatis synchonisation, which unbinds the neccesairy sqlSession used by the JobFailedListener) + this.transactionSynchronizationAdapterOrder = AlfrescoTransactionSupport.SESSION_SYNCHRONIZATION_ORDER - 100; + } + @Override protected void initVariableTypes() { diff --git a/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java index 0633cef8ac..9e37d73940 100644 --- a/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java +++ b/source/java/org/alfresco/repo/workflow/activiti/AuthenticatedTimerJobHandler.java @@ -168,10 +168,13 @@ public class AuthenticatedTimerJobHandler implements JobHandler protected String getInitiator(ActivitiScriptNode initiatorNode) { - NodeRef ref = initiatorNode.getNodeRef(); - if(unprotectedNodeService.exists(ref)) + if(initiatorNode != null) { - return (String) unprotectedNodeService.getProperty(ref, ContentModel.PROP_USERNAME); + NodeRef ref = initiatorNode.getNodeRef(); + if(unprotectedNodeService.exists(ref)) + { + return (String) unprotectedNodeService.getProperty(ref, ContentModel.PROP_USERNAME); + } } return null; } diff --git a/source/java/org/alfresco/service/cmr/favourites/FavouritesService.java b/source/java/org/alfresco/service/cmr/favourites/FavouritesService.java new file mode 100644 index 0000000000..6b59c4bc15 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/favourites/FavouritesService.java @@ -0,0 +1,114 @@ +package org.alfresco.service.cmr.favourites; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.query.PagingRequest; +import org.alfresco.query.PagingResults; +import org.alfresco.repo.favourites.PersonFavourite; +import org.alfresco.service.Auditable; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.util.Pair; + +/** + * A service for managing a person's favourites. + * + * Currently supports favouriting of sites, files and folders. + * + * @author steveglover + * + */ +public interface FavouritesService +{ + /* + * Supported favourite types. + */ + public static enum Type + { + // Note: ordered + FILE, FOLDER, SITE; + + public static Set ALL_FILTER_TYPES; + + static + { + ALL_FILTER_TYPES = new HashSet(); + ALL_FILTER_TYPES.add(FILE); + ALL_FILTER_TYPES.add(FOLDER); + ALL_FILTER_TYPES.add(SITE); + ALL_FILTER_TYPES = Collections.unmodifiableSet(ALL_FILTER_TYPES); + } + }; + + public enum SortFields { username, type, createdAt, title }; + + /* + * Default ordering is (userName ASC, type ASC, createdAt DESC) + */ + @SuppressWarnings("unchecked") + public static List> DEFAULT_SORT_PROPS = Arrays.asList( + new Pair(FavouritesService.SortFields.username, Boolean.TRUE), + new Pair(FavouritesService.SortFields.type, Boolean.TRUE), + new Pair(FavouritesService.SortFields.createdAt, Boolean.FALSE)); + + Type getType(NodeRef nodeRef); + + /** + * Add the entity identified by nodeRef as a favourite for user "userName". + * + * If the nodeRef is already favourited, the favourite entity is returned. No + * information regarding the favourite e.g. createdAt is updated. + * + * @param userName + * @param nodeRef + * @return + */ + @Auditable(parameters = {"userName", "nodeRef"}) + PersonFavourite addFavourite(String userName, NodeRef nodeRef); + + /** + * Is the entity identified by nodeRef a favourite document of user "userName". + * + * @param userName + * @param nodeRef + * @return + */ + @Auditable(parameters = {"userName", "nodeRef"}) + boolean isFavourite(String userName, NodeRef nodeRef); + + /** + * Remove the document identified by nodeRef as a favourite for user "userName". + * + * @param userName + * @param nodeRef + * @return + */ + @Auditable(parameters = {"userName", "nodeRef"}) + boolean removeFavourite(String userName, NodeRef nodeRef); + + /** + * A paged list of favourites for user "userName". + * + * @param userName + * @param types + * @param sortProps + * @param pagingRequest + * @return + */ + @Auditable(parameters = {"userName", "types", "pagingRequest"}) + PagingResults getPagedFavourites(String userName, Set types, + List> sortProps, PagingRequest pagingRequest); + + /** + * Get a specific favourite for user "userName". + * + * @param userName + * @param nodeRef + * @return + */ + @Auditable(parameters = {"userName", "nodeRef"}) + PersonFavourite getFavourite(String userName, NodeRef nodeRef); +} diff --git a/source/java/org/alfresco/service/cmr/invitation/Invitation.java b/source/java/org/alfresco/service/cmr/invitation/Invitation.java index 0d3d249e89..d037e16626 100644 --- a/source/java/org/alfresco/service/cmr/invitation/Invitation.java +++ b/source/java/org/alfresco/service/cmr/invitation/Invitation.java @@ -18,6 +18,8 @@ */ package org.alfresco.service.cmr.invitation; +import java.util.Date; + /** * The invitation request is a command object for who, needs to be added or removed @@ -84,4 +86,8 @@ public interface Invitation * @return the roleName */ public String getRoleName(); + + Date getCreatedAt(); + + Date getModifiedAt(); } diff --git a/source/java/org/alfresco/service/cmr/invitation/InvitationService.java b/source/java/org/alfresco/service/cmr/invitation/InvitationService.java index 89dc4ecf8c..fb1a99f6fb 100644 --- a/source/java/org/alfresco/service/cmr/invitation/InvitationService.java +++ b/source/java/org/alfresco/service/cmr/invitation/InvitationService.java @@ -40,7 +40,7 @@ public interface InvitationService * * @return the names of the workkflows managed by the invitation service. */ - @NotAuditable + @NotAuditable public List getInvitationServiceWorkflowNames(); /** @@ -212,7 +212,17 @@ public interface InvitationService Invitation.ResourceType resourceType, String resourceName, String inviteeRole); - + /** + * Update the invitee comments for an existing moderated invitation + * + * @param inviteeId + * @param siteShortName + * @param inviteeComments + * @return the invitation + */ + @Auditable(parameters = {"inviteeId", "siteShortName", "inviteeComments"}) + ModeratedInvitation updateModeratedInvitation(String inviteeId, String siteShortName, String inviteeComments); + /** * For a Nominated Invitation invitee accepts this invitation * @@ -262,6 +272,9 @@ public interface InvitationService @NotAuditable public List listPendingInvitationsForInvitee(String invitee); + @NotAuditable + public List listPendingInvitationsForInvitee(String invitee, Invitation.ResourceType resourceType); + /** * list Invitations for a specific resource * @param resourceType diff --git a/source/java/org/alfresco/service/cmr/model/FileFolderService.java b/source/java/org/alfresco/service/cmr/model/FileFolderService.java index 6d35db42ab..6471de98b0 100644 --- a/source/java/org/alfresco/service/cmr/model/FileFolderService.java +++ b/source/java/org/alfresco/service/cmr/model/FileFolderService.java @@ -416,7 +416,15 @@ public interface FileFolderService @Auditable(parameters = {"typeQName"}) public FileFolderServiceType getType(QName typeQName); + /** + * @deprecated MNT-8704: WebDAV:Content does not disappear after being deleted + */ + @Deprecated public boolean isHidden(NodeRef nodeRef); + /** + * @deprecated MNT-8704: WebDAV:Content does not disappear after being deleted + */ + @Deprecated public void setHidden(NodeRef nodeRef, boolean isHidden); } diff --git a/source/java/org/alfresco/service/cmr/preference/PreferenceService.java b/source/java/org/alfresco/service/cmr/preference/PreferenceService.java index 61178ca916..e29fba3860 100644 --- a/source/java/org/alfresco/service/cmr/preference/PreferenceService.java +++ b/source/java/org/alfresco/service/cmr/preference/PreferenceService.java @@ -24,7 +24,6 @@ import java.util.Map; import org.alfresco.query.PagingRequest; import org.alfresco.query.PagingResults; import org.alfresco.service.Auditable; -import org.alfresco.service.cmr.site.SiteInfo; import org.alfresco.util.Pair; /** @@ -94,40 +93,4 @@ public interface PreferenceService */ @Auditable(parameters = {"userName", "preferenceFilter"}) void clearPreferences(String userName, String preferenceFilter); - - /** - * Is siteShortName a favourite site of username? - * - * @param userName the user name - * @param siteShortName the site short name - */ - @Auditable(parameters = {"userName", "siteShortName"}) - boolean isFavouriteSite(String userName, String siteShortName); - - /** - * Returns a paged list of favourite sites for the user. - * - * @param userName the user name - * @param pagingRequest paging request - */ - @Auditable(parameters = {"userName", "pagingRequest"}) - PagingResults getFavouriteSites(String userName, PagingRequest pagingRequest); - - /** - * Adds siteShortName as a favourite site for the user. - * - * @param userName the user name - * @param siteShortName the site short name - */ - @Auditable(parameters = {"userName", "siteShortName"}) - void addFavouriteSite(String userName, String siteShortName); - - /** - * Removes siteShortName as a favourite site for the user. - * - * @param userName the user name - * @param siteShortName the site short name - */ - @Auditable(parameters = {"userName", "siteShortName"}) - void removeFavouriteSite(String userName, String siteShortName); } diff --git a/source/java/org/alfresco/service/cmr/repository/AbstractTransformationSourceOptions.java b/source/java/org/alfresco/service/cmr/repository/AbstractTransformationSourceOptions.java index 41b56c04b4..26c78823fc 100644 --- a/source/java/org/alfresco/service/cmr/repository/AbstractTransformationSourceOptions.java +++ b/source/java/org/alfresco/service/cmr/repository/AbstractTransformationSourceOptions.java @@ -32,16 +32,24 @@ public abstract class AbstractTransformationSourceOptions implements Transformat { /** The list of applicable mimetypes */ - private List applicabledMimetypes; + private List applicableMimetypes; + /** + * @deprecated + */ + public List getApplicabledMimetypes() + { + return this.getApplicableMimetypes(); + } + /** * Gets the list of applicable mimetypes * * @return the applicable mimetypes */ - public List getApplicabledMimetypes() + public List getApplicableMimetypes() { - return applicabledMimetypes; + return applicableMimetypes; } /** @@ -49,9 +57,9 @@ public abstract class AbstractTransformationSourceOptions implements Transformat * * @param applicableMimetypes the applicable mimetypes */ - public void setApplicableMimetypes(List applicabledMimetypes) + public void setApplicableMimetypes(List applicableMimetypes) { - this.applicabledMimetypes = applicabledMimetypes; + this.applicableMimetypes = applicableMimetypes; } /** @@ -63,7 +71,7 @@ public abstract class AbstractTransformationSourceOptions implements Transformat */ public boolean isApplicableForMimetype(String mimetype) { - if (mimetype != null && applicabledMimetypes != null) { return applicabledMimetypes.contains(mimetype); } + if (mimetype != null && applicableMimetypes != null) { return applicableMimetypes.contains(mimetype); } return false; } diff --git a/source/java/org/alfresco/service/cmr/repository/ContentService.java b/source/java/org/alfresco/service/cmr/repository/ContentService.java index 14063621c9..b474635882 100644 --- a/source/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/source/java/org/alfresco/service/cmr/repository/ContentService.java @@ -23,7 +23,6 @@ import java.util.Map; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.service.Auditable; -import org.alfresco.service.PublicService; import org.alfresco.service.cmr.dictionary.InvalidTypeException; import org.alfresco.service.namespace.QName; diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimits.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimits.java index 295e7578ba..fa71d98bf6 100644 --- a/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimits.java +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptionLimits.java @@ -18,6 +18,7 @@ */ package org.alfresco.service.cmr.repository; +import java.io.Serializable; import java.util.HashMap; import java.util.Map; @@ -30,8 +31,10 @@ import org.alfresco.util.EqualsHelper; * * @author Alan Davis */ -public class TransformationOptionLimits +public class TransformationOptionLimits implements Serializable { + private static final long serialVersionUID = 1L; + public static final String OPT_TIMEOUT_MS = "timeoutMs"; public static final String OPT_READ_LIMIT_TIME_MS = "readLimitTimeMs"; @@ -72,6 +75,18 @@ public class TransformationOptionLimits } } + /** + * Defaults values that are set in this object into the + * supplied limits. + * @param limits to be set + */ + public void defaultTo(TransformationOptionLimits limits) + { + time.defaultTo(limits.time); + kbytes.defaultTo(limits.kbytes); + pages.defaultTo(limits.pages); + } + // --------------- Time --------------- public TransformationOptionPair getTimePair() { @@ -191,7 +206,12 @@ public class TransformationOptionLimits public String toString() { - return toMap(new HashMap()).toString(); + StringBuilder sb = new StringBuilder("{"); + time.append(sb, OPT_TIMEOUT_MS, OPT_READ_LIMIT_TIME_MS); + kbytes.append(sb, OPT_MAX_SOURCE_SIZE_K_BYTES, OPT_READ_LIMIT_K_BYTES); + pages.append(sb, OPT_MAX_PAGES, OPT_PAGE_LIMIT); + sb.append("}"); + return sb.length() == 2 ? "" : sb.toString(); } /** @@ -217,6 +237,8 @@ public class TransformationOptionLimits { return new TransformationOptionLimits(this, that, lower) { + private static final long serialVersionUID = 1L; + @Override public void setTimeoutMs(long timeoutMs) { diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptionPair.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptionPair.java index ac0b8fc8b8..542f2438ed 100644 --- a/source/java/org/alfresco/service/cmr/repository/TransformationOptionPair.java +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptionPair.java @@ -19,11 +19,10 @@ package org.alfresco.service.cmr.repository; import java.io.IOException; +import java.io.Serializable; import java.util.Map; import org.alfresco.repo.content.transform.TransformerDebug; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; /** * A pair of transformation options that specify @@ -40,8 +39,10 @@ import org.apache.commons.logging.LogFactory; * * @author Alan Davis */ -public class TransformationOptionPair +public class TransformationOptionPair implements Serializable { + private static final long serialVersionUID = 1L; + /** * Action to take place for a given pair of values. */ @@ -82,7 +83,13 @@ public class TransformationOptionPair { throw new IllegalArgumentException(exceptionMessage); } + setMax(max); + } + + private void setMax(long max) + { this.max = max; + this.limit = -1; } public long getLimit() @@ -96,6 +103,12 @@ public class TransformationOptionPair { throw new IllegalArgumentException(exceptionMessage); } + setLimit(limit); + } + + private void setLimit(long limit) + { + this.max = -1; this.limit = limit; } @@ -121,7 +134,29 @@ public class TransformationOptionPair (getLimit() >= 0) ? Action.RETURN_EOF : null; } - + + /** + * Defaults values that are set in this pair into the + * supplied pair. + * @param pair to be set + */ + public void defaultTo(TransformationOptionPair pair) + { + long max = getMax(); + if (max >= 0) + { + pair.setMax(max); + } + else + { + long limit = getLimit(); + if (limit >= 0) + { + pair.setLimit(limit); + } + } + } + public String toString(String max, String limit) { if (getMax() >= 0) @@ -172,6 +207,37 @@ public class TransformationOptionPair return optionsMap; } + public void append(StringBuilder sb, String optMaxKey, String optLimitKey) + { + long max = getMax(); + if (max >= 0) + { + if (sb.length() > 1) + { + sb.append(", "); + } + + sb.append(optMaxKey); + sb.append('='); + sb.append(max); + } + else + { + long limit = getLimit(); + if (limit >= 0) + { + if (sb.length() > 1) + { + sb.append(", "); + } + + sb.append(optLimitKey); + sb.append('='); + sb.append(limit); + } + } + } + public void set(Map optionsMap, String optMaxKey, String optLimitKey, String exceptionMessage) { diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java b/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java index ad426fb32a..26d2570997 100644 --- a/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java +++ b/source/java/org/alfresco/service/cmr/repository/TransformationOptions.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2012 Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -44,6 +44,7 @@ public class TransformationOptions implements Cloneable public static final String OPT_TARGET_NODEREF = "contentWriterNodeRef"; public static final String OPT_TARGET_CONTENT_PROPERTY = "targetContentProperty"; public static final String OPT_INCLUDE_EMBEDDED = "includeEmbedded"; + public static final String OPT_USE = "use"; /** The source node reference */ private NodeRef sourceNodeRef; @@ -60,6 +61,9 @@ public class TransformationOptions implements Cloneable /** The include embedded resources yes/no */ private Boolean includeEmbedded; + /** The use to which the transform will be put. */ + private String use; + /** Time, KBytes and page limits */ private TransformationOptionLimits limits = new TransformationOptionLimits(); @@ -160,6 +164,7 @@ public class TransformationOptions implements Cloneable this.targetNodeRef = (NodeRef)optionsMap.get(OPT_TARGET_NODEREF); this.targetContentProperty = (QName)optionsMap.get(OPT_TARGET_CONTENT_PROPERTY); this.includeEmbedded = (Boolean)optionsMap.get(OPT_INCLUDE_EMBEDDED); + this.use = (String)optionsMap.get(OPT_USE); limits.set(optionsMap); } @@ -271,6 +276,30 @@ public class TransformationOptions implements Cloneable return includeEmbedded; } + /** + * The use to which the transform will be put. + * Initially used to select different transformation limits depending on the + * use: "Index", "Preview"... + * + * @param use to which the transform will be put. + */ + public void setUse(String use) + { + this.use = use; + } + + /** + * The use to which the transform will be put. + * Initially used to select different transformation limits depending on the + * use: "Index", "Preview"... + * + * @return the use - may be null + */ + public String getUse() + { + return use; + } + // --------------- Time --------------- /** @@ -515,6 +544,7 @@ public class TransformationOptions implements Cloneable *

  • {@link #OPT_TARGET_NODEREF}
  • *
  • {@link #OPT_TARGET_CONTENT_PROPERTY}
  • *
  • {@link #OPT_INCLUDE_EMBEDDED}
  • + *
  • {@link #OPT_USE}
  • *
  • {@link TransformationOptionLimits#OPT_TIMEOUT_MS}
  • *
  • {@link TransformationOptionLimits#OPT_READ_LIMIT_TIME_MS}
  • *
  • {@link TransformationOptionLimits#OPT_MAX_SOURCE_SIZE_K_BYTES
  • @@ -534,6 +564,7 @@ public class TransformationOptions implements Cloneable optionsMap.put(OPT_TARGET_NODEREF, targetNodeRef); optionsMap.put(OPT_TARGET_CONTENT_PROPERTY, targetContentProperty); optionsMap.put(OPT_INCLUDE_EMBEDDED, includeEmbedded); + optionsMap.put(OPT_USE, use); limits.toMap(optionsMap); return optionsMap; } @@ -570,4 +601,58 @@ public class TransformationOptions implements Cloneable return Boolean.FALSE; } }; + + @Override + public int hashCode() + { + final int prime = 31; + int result = 1; + result = prime * result + ((this.includeEmbedded == null) ? 0 : this.includeEmbedded.hashCode()); + result = prime * result + ((this.limits == null) ? 0 : this.limits.hashCode()); + result = prime * result + ((this.sourceContentProperty == null) ? 0 : this.sourceContentProperty.hashCode()); + result = prime * result + ((this.sourceNodeRef == null) ? 0 : this.sourceNodeRef.hashCode()); + result = prime * result + ((this.targetContentProperty == null) ? 0 : this.targetContentProperty.hashCode()); + result = prime * result + ((this.targetNodeRef == null) ? 0 : this.targetNodeRef.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) + { + if (this == obj) return true; + if (obj == null) return false; + if (getClass() != obj.getClass()) return false; + TransformationOptions other = (TransformationOptions) obj; + if (this.includeEmbedded == null) + { + if (other.includeEmbedded != null) return false; + } + else if (!this.includeEmbedded.equals(other.includeEmbedded)) return false; + if (this.limits == null) + { + if (other.limits != null) return false; + } + else if (!this.limits.equals(other.limits)) return false; + if (this.sourceContentProperty == null) + { + if (other.sourceContentProperty != null) return false; + } + else if (!this.sourceContentProperty.equals(other.sourceContentProperty)) return false; + if (this.sourceNodeRef == null) + { + if (other.sourceNodeRef != null) return false; + } + else if (!this.sourceNodeRef.equals(other.sourceNodeRef)) return false; + if (this.targetContentProperty == null) + { + if (other.targetContentProperty != null) return false; + } + else if (!this.targetContentProperty.equals(other.targetContentProperty)) return false; + if (this.targetNodeRef == null) + { + if (other.targetNodeRef != null) return false; + } + else if (!this.targetNodeRef.equals(other.targetNodeRef)) return false; + return true; + } } diff --git a/source/java/org/alfresco/service/cmr/repository/TransformationSourceOptions.java b/source/java/org/alfresco/service/cmr/repository/TransformationSourceOptions.java index 40f4e3af05..03de7c4e0d 100644 --- a/source/java/org/alfresco/service/cmr/repository/TransformationSourceOptions.java +++ b/source/java/org/alfresco/service/cmr/repository/TransformationSourceOptions.java @@ -42,8 +42,16 @@ public interface TransformationSourceOptions * Gets the list of applicable mimetypes * * @return the applicable mimetypes + * @deprecated Use {@link #getApplicableMimetypes()} instead. */ public List getApplicabledMimetypes(); + + /** + * Gets the list of applicable mimetypes + * + * @return the applicable mimetypes + */ + public List getApplicableMimetypes(); /** * Gets whether or not these transformation source options apply for the diff --git a/source/java/org/alfresco/service/cmr/site/SiteService.java b/source/java/org/alfresco/service/cmr/site/SiteService.java index 324b2306f6..70e95b4eff 100644 --- a/source/java/org/alfresco/service/cmr/site/SiteService.java +++ b/source/java/org/alfresco/service/cmr/site/SiteService.java @@ -241,6 +241,16 @@ public interface SiteService @NotAuditable String getSiteShortName(NodeRef nodeRef); + /** + * Returns true if the site exists. This allows create scripts to confirm the existence of private sites - they + * would not normally be returned from getSite() if the user does not have permission on the site noderef. + * + * @param shortName the site short name + * @return true if the site exists, false otherwise + */ + @NotAuditable + boolean hasSite(String shortName); + /** * Update the site information. *

    @@ -492,4 +502,7 @@ public interface SiteService @NotAuditable String getMembersRole(String shortName, String authorityName); + + @NotAuditable + int countAuthoritiesWithRole(String shortName, String role); } diff --git a/source/java/org/alfresco/service/cmr/version/VersionService.java b/source/java/org/alfresco/service/cmr/version/VersionService.java index f18f7aafc3..0ab40c6388 100644 --- a/source/java/org/alfresco/service/cmr/version/VersionService.java +++ b/source/java/org/alfresco/service/cmr/version/VersionService.java @@ -171,6 +171,8 @@ public interface VersionService public Version getCurrentVersion(NodeRef nodeRef); /** + * Revert the state of the node to the current version. + *

    * The node reference will be reverted to the current version. *

    * A deep revert will be performed. @@ -183,17 +185,21 @@ public interface VersionService public void revert(NodeRef nodeRef); /** - * The node reference will be reverted to the current version. + * Revert the state of the node to the current version. + *

    + * The node will be reverted to the current version. * * @see VersionService#revert(NodeRef, Version, boolean) * * @param nodeRef the node reference - * @param deep true if a deep revert is to be performed, flase otherwise + * @param deep true if a deep revert is to be performed, false otherwise */ @Auditable(parameters = {"nodeRef", "deep"}) public void revert(NodeRef nodeRef, boolean deep); /** + * Revert the state of the node to the specified version. + *

    * A deep revert will take place by default. * * @see VersionService#revert(NodeRef, Version, boolean) diff --git a/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java b/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java index f3dd88331c..12fa91c45b 100644 --- a/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java +++ b/source/java/org/alfresco/service/cmr/workflow/LazyActivitiWorkflowTask.java @@ -29,7 +29,6 @@ import org.activiti.engine.task.Task; import org.alfresco.model.ContentModel; import org.alfresco.repo.tenant.TenantService; import org.alfresco.repo.workflow.BPMEngineRegistry; -import org.alfresco.repo.workflow.WorkflowConstants; import org.alfresco.repo.workflow.WorkflowModel; import org.alfresco.repo.workflow.activiti.ActivitiConstants; import org.alfresco.repo.workflow.activiti.ActivitiTypeConverter; @@ -180,20 +179,17 @@ public class LazyActivitiWorkflowTask extends WorkflowTask { return getPriority(); } - else if(WorkflowModel.PROP_STATUS.equals(key)) - { - switch(getState()) - { - case COMPLETED: - return WorkflowConstants.TASK_STATUS_COMPLETED; - case IN_PROGRESS: - return WorkflowConstants.TASK_STATUS_IN_PROGRESS; - } - return getState(); - } else if(WorkflowModel.PROP_DESCRIPTION.equals(key)) { - return getDescription(); + // Description-property is based on the task.getDescription(). Revert to the default task(type) description, if missing. + if(task != null) + { + return (task.getDescription() != null && !task.getDescription().isEmpty()) ? task.getDescription() : getDescription(); + } + else + { + return (historicTask.getDescription() != null && !historicTask.getDescription().isEmpty()) ? historicTask.getDescription() : getDescription(); + } } else if(ContentModel.PROP_CREATED.equals(key) || WorkflowModel.PROP_START_DATE.equals(key)) { diff --git a/source/java/org/alfresco/service/cmr/workflow/WorkflowPermissionInterceptor.java b/source/java/org/alfresco/service/cmr/workflow/WorkflowPermissionInterceptor.java new file mode 100644 index 0000000000..393a753c47 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/workflow/WorkflowPermissionInterceptor.java @@ -0,0 +1,210 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.service.cmr.workflow; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.permissions.AccessDeniedException; +import org.alfresco.repo.workflow.WorkflowModel; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.namespace.QName; +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class WorkflowPermissionInterceptor implements MethodInterceptor +{ + private PersonService personService; + private AuthorityService authorityService; + private WorkflowService workflowService; + + @Override + public Object invoke(MethodInvocation invocation) throws Throwable + { + String currentUser = AuthenticationUtil.getRunAsUser(); + // See if we can shortcut (for 'System' and 'admin') + if (currentUser != null && (authorityService.isAdminAuthority(currentUser) || AuthenticationUtil.isRunAsUserTheSystemUser())) + { + return invocation.proceed(); + } + + String methodName = invocation.getMethod().getName(); + + if (methodName.equals("getTaskById") || methodName.equals("getStartTask")) + { + Object result = invocation.proceed(); + WorkflowTask wt = (WorkflowTask) result; + if (isInitiatorOrAssignee(wt, currentUser)) + { + return result; + } + else + { + String taskId = (String) invocation.getArguments()[0]; + throw new AccessDeniedException("Accessing task with id='" + taskId + "' is not allowed for user '" + currentUser + "'"); + } + + } + + if (methodName.equals("updateTask") || methodName.equals("endTask")) + { + String taskId = (String) invocation.getArguments()[0]; + WorkflowTask taskToUpdate = workflowService.getTaskById(taskId); + if (isInitiatorOrAssignee(taskToUpdate, currentUser)) + { + return invocation.proceed(); + } + else + { + throw new AccessDeniedException("Accessing task with id='" + taskId + "' is not allowed for user '" + currentUser + "'"); + } + + } + + if (methodName.equals("getAssignedTasks") || methodName.equals("getPooledTasks") || methodName.equals("getTasksForWorkflowPath") || methodName.equals("getStartTasks") || methodName.equals("queryTasks")) + { + Object result = invocation.proceed(); + List rawList = (List) result; + List resultList = new ArrayList(rawList.size()); + + for (WorkflowTask wt : rawList) + { + if (isInitiatorOrAssignee(wt, currentUser)) + { + resultList.add(wt); + } + } + + return resultList; + } + + return invocation.proceed(); + } + + private boolean isInitiatorOrAssignee(WorkflowTask wt, String userName) + { + if (wt == null) + { + return true; + } + + NodeRef person = personService.getPerson(userName); + Map props = wt.getProperties(); + + String ownerName = (String) props.get(ContentModel.PROP_OWNER); + if (userName != null && userName.equalsIgnoreCase(ownerName)) + { + return true; + } + + List accessUseres = new ArrayList(); + accessUseres.add(getUserGroupRef(props.get(WorkflowModel.ASSOC_ASSIGNEE))); + accessUseres.add(getUserGroupRef(props.get(WorkflowModel.ASSOC_GROUP_ASSIGNEE))); + accessUseres.addAll(getUserGroupRefs(props.get(WorkflowModel.ASSOC_GROUP_ASSIGNEES))); + accessUseres.addAll(getUserGroupRefs(props.get(WorkflowModel.ASSOC_ASSIGNEES))); + accessUseres.addAll(getUserGroupRefs(wt.getProperties().get(WorkflowModel.ASSOC_POOLED_ACTORS))); + accessUseres.add(wt.getPath().getInstance().getInitiator()); + + if (accessUseres.contains(person)) + { + return true; + } + + Set userGroups = authorityService.getAuthoritiesForUser(userName); + for (String groupName : userGroups) + { + NodeRef groupRef = authorityService.getAuthorityNodeRef(groupName); + if (groupRef != null && accessUseres.contains(groupRef)) + { + return true; + } + } + + return false; + } + + private NodeRef getUserGroupRef(Object o) + { + NodeRef result = null; + if (o == null || o instanceof NodeRef) + { + result = (NodeRef) o; + } + else + { + try + { + result = personService.getPerson(o.toString()); + } + catch (Exception e) + { + try + { + result = authorityService.getAuthorityNodeRef(o.toString()); + } + catch (Exception e1) + { + // do nothing + } + } + + } + + return result; + } + + private Collection getUserGroupRefs(Object o) + { + List result = new ArrayList(); + if (o != null && o instanceof Collection) + { + for (Iterator it = ((Collection) o).iterator(); it.hasNext();) + { + result.add(getUserGroupRef(it.next())); + + } + } + + return result; + } + + public void setPersonService(PersonService personService) + { + this.personService = personService; + } + + public void setAuthorityService(AuthorityService authorityService) + { + this.authorityService = authorityService; + } + + public void setWorkflowService(WorkflowService workflowService) + { + this.workflowService = workflowService; + } +} diff --git a/source/java/org/alfresco/util/RuntimeSystemPropertiesSetter.java b/source/java/org/alfresco/util/RuntimeSystemPropertiesSetter.java index d2b2d149a2..a0393bff75 100644 --- a/source/java/org/alfresco/util/RuntimeSystemPropertiesSetter.java +++ b/source/java/org/alfresco/util/RuntimeSystemPropertiesSetter.java @@ -42,7 +42,7 @@ import org.springframework.core.io.support.ResourcePatternResolver; * to work properly. * * @author Jon Cox - * @see #setProperties(Map) + * @see #setProperties(String) */ public class RuntimeSystemPropertiesSetter implements BeanFactoryPostProcessor, ApplicationContextAware, PriorityOrdered { @@ -54,7 +54,7 @@ public class RuntimeSystemPropertiesSetter implements BeanFactoryPostProcessor, private ResourcePatternResolver resolver; /** - * @see #setProperties(Map) + * @see #setProperties(String) */ private Map jvmProperties; diff --git a/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java b/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java index adce5757bf..8a556c6e32 100644 --- a/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java +++ b/source/java/org/alfresco/util/schemacomp/SchemaReferenceFileTest.java @@ -21,11 +21,14 @@ package org.alfresco.util.schemacomp; import static org.junit.Assert.fail; +import java.io.CharArrayWriter; +import java.io.PrintWriter; +import java.io.Writer; + import org.alfresco.repo.domain.schema.SchemaBootstrap; import org.alfresco.util.ApplicationContextHelper; import org.junit.After; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; @@ -65,14 +68,14 @@ public class SchemaReferenceFileTest @Test public void checkReferenceFile() { - String filePrefix = getClass().getSimpleName() + "-{0}-{1}-"; - int numProblems = schemaBootstrap.validateSchema(filePrefix); + Writer buff = new CharArrayWriter(1024); + PrintWriter out = new PrintWriter(buff); + int numProblems = schemaBootstrap.validateSchema(null, out); + out.flush(); if (numProblems > 0) { - fail("Schema check failed with " + numProblems + - " potential problems. Do you need to generate a" + - " new schema reference file? Results are in temp file prefixed with " + filePrefix); + fail(buff.toString()); } } } diff --git a/source/java/org/alfresco/util/test/junitrules/AbstractAlfrescoPersonTest.java b/source/java/org/alfresco/util/test/junitrules/AbstractAlfrescoPersonTest.java new file mode 100644 index 0000000000..874f68addd --- /dev/null +++ b/source/java/org/alfresco/util/test/junitrules/AbstractAlfrescoPersonTest.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ + +package org.alfresco.util.test.junitrules; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.security.PersonService; +import org.junit.BeforeClass; +import org.junit.ClassRule; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; + +/** + * @author Neil Mc Erlean + * @since 4.2 + */ +public abstract class AbstractAlfrescoPersonTest +{ + /** Gets the username of the test user to be created. */ + protected abstract String createTestUserName(); + + /** A hookpoint to allow subclasses to add addition validation. */ + protected void additionalValidations(String username, boolean userExists) + { + // Intentionally empty + } + + // Rule to initialise the default Alfresco spring configuration + @ClassRule public static ApplicationContextInit APP_CONTEXT_INIT = new ApplicationContextInit(); + + // Rule to create a test user + protected final String testUsername = createTestUserName(); + public final AlfrescoPerson testUserRule = new AlfrescoPerson(APP_CONTEXT_INIT, testUsername); + + // A rule to allow individual test methods all to be run as "admin". + public RunAsFullyAuthenticatedRule runAsRule = new RunAsFullyAuthenticatedRule(AuthenticationUtil.getAdminUserName()); + + @Rule public final RuleChain ruleChain = RuleChain.outerRule(runAsRule).around(testUserRule); + + protected static PersonService PERSON_SERVICE; + protected static RetryingTransactionHelper TRANSACTION_HELPER; + + @BeforeClass public static void initStaticData() throws Exception + { + PERSON_SERVICE = APP_CONTEXT_INIT.getApplicationContext().getBean("PersonService", PersonService.class); + TRANSACTION_HELPER = APP_CONTEXT_INIT.getApplicationContext().getBean("retryingTransactionHelper", RetryingTransactionHelper.class); + } + + @Test public void ensureTestUserWasCreated() throws Exception + { + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + validateCmPersonNode(testUsername, true); + additionalValidations(testUsername, true); + + return null; + } + }); + } + + /** Validate that the person is correctly persisted (or not). */ + protected abstract void validateCmPersonNode(String username, boolean exists); + + @Test public void ensureUserIsCleanedUp() throws Throwable + { + // Note that because we need to test that the Rule's 'after' behaviour has worked correctly, we cannot + // use the Rule that has been declared in the normal way - otherwise nothing would be cleaned up until + // after our test method. + // Therefore we have to manually poke the Rule to get it to cleanup during test execution. + // NOTE! This is *not* how a JUnit Rule would normally be used. + + // First manually run the 'after' part of the rule on this class - so that it does not interfere. + this.testUserRule.after(); + + final String testUserForThisMethodOnly = createTestUserName(); + + AlfrescoPerson myTestUser = new AlfrescoPerson(APP_CONTEXT_INIT, testUserForThisMethodOnly); + + // Manually trigger the execution of the 'before' part of the rule. + myTestUser.before(); + + // Now trigger the Rule's cleanup behaviour. + myTestUser.after(); + + // and ensure that the nodes are all gone. + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + validateCmPersonNode(testUserForThisMethodOnly, false); + additionalValidations(testUserForThisMethodOnly, false); + return null; + } + }); + } +} diff --git a/source/java/org/alfresco/util/test/junitrules/AbstractPersonRule.java b/source/java/org/alfresco/util/test/junitrules/AbstractPersonRule.java index c11005c1a9..da24bb128e 100644 --- a/source/java/org/alfresco/util/test/junitrules/AbstractPersonRule.java +++ b/source/java/org/alfresco/util/test/junitrules/AbstractPersonRule.java @@ -1,6 +1,5 @@ /* - * Copyright (C) 2005-2012 - Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,17 +18,8 @@ */ package org.alfresco.util.test.junitrules; -import org.alfresco.model.ContentModel; -import org.alfresco.repo.security.authentication.AuthenticationUtil; -import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.security.MutableAuthenticationService; -import org.alfresco.service.cmr.security.PersonService; -import org.alfresco.util.ParameterCheck; -import org.alfresco.util.PropertyMap; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.junit.rules.ExternalResource; +import org.alfresco.util.test.testusers.TestUserComponent; import org.springframework.context.ApplicationContext; /** @@ -39,20 +29,8 @@ import org.springframework.context.ApplicationContext; * @author Neil Mc Erlean * @since Odin */ -public abstract class AbstractPersonRule extends ExternalResource +public abstract class AbstractPersonRule extends AbstractRule { - private static final Log log = LogFactory.getLog(AbstractPersonRule.class); - - // Fixed defaults for the usual Alfresco cm:person metadata. - protected static final String PASSWORD = "PWD"; - protected static final String FIRST_NAME = "firstName"; - protected static final String LAST_NAME = "lastName"; - protected static final String EMAIL = "email@email.com"; - protected static final String JOB_TITLE = "jobTitle"; - - protected final ApplicationContext appContext; - protected final ApplicationContextInit appContextRule; - /** * Constructs a person rule with the specified spring context, which will be necessary * to actually create and delete the users. @@ -61,10 +39,7 @@ public abstract class AbstractPersonRule extends ExternalResource */ public AbstractPersonRule(ApplicationContext appContext) { - ParameterCheck.mandatory("appContext", appContext); - - this.appContext = appContext; - this.appContextRule = null; + super(appContext); } /** @@ -75,42 +50,7 @@ public abstract class AbstractPersonRule extends ExternalResource */ public AbstractPersonRule(ApplicationContextInit appContextRule) { - ParameterCheck.mandatory("appContextRule", appContextRule); - - this.appContext = null; - this.appContextRule = appContextRule; - } - - /** - * This method retrieves the spring application context given to this rule. - * - * @return the spring application context - * @throws NullPointerException if the application context has not been initialised when requested. - */ - protected ApplicationContext getApplicationContext() - { - ApplicationContext result = null; - - // The app context is either provided explicitly: - if (appContext != null) - { - result = appContext; - } - // or is implicitly accessed via another rule: - else - { - ApplicationContext contextFromRule = appContextRule.getApplicationContext(); - if (contextFromRule != null) - { - result = contextFromRule; - } - else - { - throw new NullPointerException("Cannot retrieve application context from provided rule."); - } - } - - return result; + super(appContextRule); } /** @@ -128,37 +68,9 @@ public abstract class AbstractPersonRule extends ExternalResource final ApplicationContext ctxt = getApplicationContext(); // Extract required service beans - final MutableAuthenticationService authService = (MutableAuthenticationService) ctxt.getBean("authenticationService"); - final PersonService personService = (PersonService) ctxt.getBean("personService"); + final TestUserComponent testUserComponent = (TestUserComponent) ctxt.getBean("testUserComponent"); - // Pre-create a person, if not already created. - if (! authService.authenticationExists(userName)) - { - log.debug("Creating authentication " + userName + "..."); - authService.createAuthentication(userName, PASSWORD.toCharArray()); - } - - NodeRef person; - - if (personService.personExists(userName)) - { - person = personService.getPerson(userName, false); - } - else - { - log.debug("Creating personNode " + userName + "..."); - - PropertyMap ppOne = new PropertyMap(); - ppOne.put(ContentModel.PROP_USERNAME, userName); - ppOne.put(ContentModel.PROP_FIRSTNAME, FIRST_NAME); - ppOne.put(ContentModel.PROP_LASTNAME, LAST_NAME); - ppOne.put(ContentModel.PROP_EMAIL, EMAIL); - ppOne.put(ContentModel.PROP_JOBTITLE, JOB_TITLE); - - person = personService.createPerson(ppOne); - } - - return person; + return testUserComponent.createTestUser(userName); } /** @@ -174,21 +86,8 @@ public abstract class AbstractPersonRule extends ExternalResource final ApplicationContext ctxt = getApplicationContext(); // Extract required service beans - final PersonService personService = (PersonService) ctxt.getBean("personService"); + final TestUserComponent testUserComponent = (TestUserComponent) ctxt.getBean("testUserComponent"); - - // And tear down afterwards. - AuthenticationUtil.runAs(new RunAsWork() - { - @Override public Void doWork() throws Exception - { - if (personService.personExists(userName)) - { - log.debug("Deleting person " + userName + "..."); - personService.deletePerson(userName); - } - return null; - } - }, AuthenticationUtil.getAdminUserName()); + testUserComponent.deleteTestUser(userName); } } diff --git a/source/java/org/alfresco/util/test/junitrules/AbstractRule.java b/source/java/org/alfresco/util/test/junitrules/AbstractRule.java new file mode 100644 index 0000000000..c1ffc1a061 --- /dev/null +++ b/source/java/org/alfresco/util/test/junitrules/AbstractRule.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2005-2012 + * Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.util.test.junitrules; + +import org.alfresco.util.ParameterCheck; +import org.junit.rules.ExternalResource; +import org.springframework.context.ApplicationContext; + +/** + * Abstract junit rule, which provides access to the Spring application context. + * + * An explicit ApplicationContext or an ApplicationContextInit rule can be passed at construction time. + * getApplicationContext will either return the instance passed in, or retrieve one from the rule. + * + * @author Alex Miller + */ +public abstract class AbstractRule extends ExternalResource +{ + + protected final ApplicationContext appContext; + protected final ApplicationContextInit appContextRule; + + /** + * @param appContext for use by sub classes. + */ + protected AbstractRule(ApplicationContext appContext) + { + ParameterCheck.mandatory("appContext", appContext); + + this.appContext = appContext; + this.appContextRule = null; + } + + /** + * @param appContextRule {@link ApplicationContextInit} rule used to provide ApplicationContext to sub classes. + */ + protected AbstractRule(ApplicationContextInit appContextRule) + { + ParameterCheck.mandatory("appContextRule", appContextRule); + + this.appContext = null; + this.appContextRule = appContextRule; + } + + /** + * This method provides the spring application context to subclasses. It either provides the explicit ApplicationContext, passed in + * at construction time, or retrieves it from the {@link ApplicationContextInit} rule, passed in the alternative constructor. + * + * @return the spring application context + * @throws NullPointerException if the application context has not been initialised when requested. + */ + protected ApplicationContext getApplicationContext() { + ApplicationContext result = null; + + // The app context is either provided explicitly: + if (appContext != null) + { + result = appContext; + } + // or is implicitly accessed via another rule: + else + { + ApplicationContext contextFromRule = appContextRule.getApplicationContext(); + if (contextFromRule != null) + { + result = contextFromRule; + } + else + { + throw new NullPointerException("Cannot retrieve application context from provided rule."); + } + } + + return result; + } + +} diff --git a/source/java/org/alfresco/util/test/junitrules/AlfrescoPerson.java b/source/java/org/alfresco/util/test/junitrules/AlfrescoPerson.java index 463aad0bf2..771d91c8f2 100644 --- a/source/java/org/alfresco/util/test/junitrules/AlfrescoPerson.java +++ b/source/java/org/alfresco/util/test/junitrules/AlfrescoPerson.java @@ -1,6 +1,5 @@ /* - * Copyright (C) 2005-2012 - Alfresco Software Limited. + * Copyright (C) 2005-2013 Alfresco Software Limited. * * This file is part of Alfresco * @@ -104,7 +103,7 @@ public class AlfrescoPerson extends AbstractPersonRule this.userName = userName; } - @Override protected void before() throws Throwable + @Override protected void before() { ApplicationContext ctxt = getApplicationContext(); RetryingTransactionHelper transactionHelper = (RetryingTransactionHelper) ctxt.getBean("retryingTransactionHelper"); @@ -144,14 +143,6 @@ public class AlfrescoPerson extends AbstractPersonRule return this.userName; } - /** - * @return the password of the person created by this rule. - */ - public String getPassword() - { - return PASSWORD; - } - /** * Gets the {@link NodeRef person node}. * @return the person node. diff --git a/source/java/org/alfresco/util/test/junitrules/AlfrescoPersonTest.java b/source/java/org/alfresco/util/test/junitrules/AlfrescoPersonTest.java new file mode 100644 index 0000000000..1967602321 --- /dev/null +++ b/source/java/org/alfresco/util/test/junitrules/AlfrescoPersonTest.java @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ + +package org.alfresco.util.test.junitrules; + +import static org.junit.Assert.assertEquals; + +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.util.GUID; + +/** + * Test class for {@link AlfrescoPerson}. + * + * @author Neil Mc Erlean + * @since 4.2 + */ +public class AlfrescoPersonTest extends AbstractAlfrescoPersonTest +{ + @Override protected String createTestUserName() + { + // In Community/Enterprise Alfresco, usernames are "just Strings" - e.g. they need not be email addresses. + return GUID.generate(); + } + + @Override protected void validateCmPersonNode(final String username, final boolean exists) + { + TRANSACTION_HELPER.doInTransaction(new RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + assertEquals("Test person's existence was wrong", exists, PERSON_SERVICE.personExists(username)); + return null; + } + }); + } +} diff --git a/source/java/org/alfresco/util/test/junitrules/AlfrescoTenant.java b/source/java/org/alfresco/util/test/junitrules/AlfrescoTenant.java new file mode 100644 index 0000000000..9a9a79f05b --- /dev/null +++ b/source/java/org/alfresco/util/test/junitrules/AlfrescoTenant.java @@ -0,0 +1,165 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.util.test.junitrules; + +import org.alfresco.repo.tenant.TenantAdminService; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.util.GUID; +import org.springframework.context.ApplicationContext; + +/** + * This JUnit rule can be used to setup and teardown a single Alfresco Tenant for test purposes. + *

    + * Example usage: + *

    + * public class YourTestClass
    + * {
    + *     // Normally we would initialise the spring application context in another rule.
    + *     @ClassRule public static final ApplicationContextInit APP_CONTEXT_RULE = new ApplicationContextInit();
    + *     
    + *     // We pass the rule that creates the spring application context.
    + *     // This rule will give us a tenant with the domain 'testtenant'.
    + *     @Rule public final AlfrescoTenant tenant = new AlfrescoTenant(APP_CONTEXT_RULE, "testtenant");
    + *     
    + *     @Test public void aTestMethod()
    + *     {
    +    	    tenant.runAsSystem(new TenantRunAsWork() {
    +                @Override
    +			    public Void doWork() throws Exception {
    +			       // Do something as the tenant system user.
    +			    }
    +			});
    + *     }
    + * }
    + * 
    + * + * @author Alex Miller + * @see AlfrescoPerson Consider using {@link AlfrescoPerson} instead, which will create tenants as needed when run in a Cloud build. + */ +public class AlfrescoTenant extends AbstractRule +{ + public static final String ADMIN_PASSWORD = "password"; + + private final String tenantName; + + /** + * Constructs the rule with a spring ApplicationContext. + * A GUID-generated tenant name will be used for the test tenant. + * + * @param appContext the spring app context (needed to get at Alfresco services). + */ + public AlfrescoTenant(ApplicationContext appContext) + { + this(appContext, GUID.generate()); + } + + /** + * Constructs the rule with a reference to a {@link ApplicationContextInit rule} which can be used to retrieve the ApplicationContext. + * A GUID-generated tenant name will be used for the test user. + * + * @param appContextRule a rule which can be used to retrieve the spring app context. + */ + public AlfrescoTenant(ApplicationContextInit appContextRule) + { + this(appContextRule, GUID.generate()); + } + + /** + * Constructs the rule with a spring ApplicationContext. + * + * @param appContext the spring app context (needed to get at Alfresco services). + * @param userName the username for the person to be created. + */ + public AlfrescoTenant(ApplicationContext appContext, String tenantName) + { + super(appContext); + this.tenantName = tenantName.toLowerCase(); + } + + /** + * Constructs the rule with a reference to a {@link ApplicationContextInit rule} which can be used to retrieve the ApplicationContext. + * + * @param appContextRule a rule which can be used to retrieve the spring app context. + * @param tenantName the name for the tenant to be created. + */ + public AlfrescoTenant(ApplicationContextInit appContextRule, String tenantName) + { + super(appContextRule); + this.tenantName = tenantName.toLowerCase(); + } + + /** + * Create the tenant. + */ + @Override protected void before() throws Throwable + { + final ApplicationContext appCtx = getApplicationContext(); + RetryingTransactionHelper transactionHelper = appCtx.getBean("retryingTransactionHelper", RetryingTransactionHelper.class); + final TenantAdminService tenantAdminService = appCtx.getBean("tenantAdminService", TenantAdminService.class); + + transactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override public Void execute() throws Throwable + { + tenantAdminService.createTenant(tenantName, ADMIN_PASSWORD.toCharArray()); + return null; + } + + }); + } + + /** + * Remove the tenant + */ + @Override protected void after() + { + final ApplicationContext appCtx = getApplicationContext(); + RetryingTransactionHelper transactionHelper = appCtx.getBean("retryingTransactionHelper", RetryingTransactionHelper.class); + final TenantAdminService tenantAdminService = appCtx.getBean("tenantAdminService", TenantAdminService.class); + + transactionHelper.doInTransaction(new RetryingTransactionCallback() + { + @Override public Void execute() throws Throwable + { + tenantAdminService.deleteTenant(tenantName); + return null; + } + }); + } + + /** + * @return The tenant domain. + */ + public String getTenantDomain() { + return tenantName; + } + + /** + * Do runAsWork as the system user for this tenant. + * + * @param runAsWork The work to be done as the system user of this tenant. + * @return The result of the work + */ + public T runAsSystem(TenantRunAsWork runAsWork) { + return TenantUtil.runAsSystemTenant(runAsWork, tenantName); + } +} diff --git a/source/java/org/alfresco/util/test/junitrules/ApplicationContextInit.java b/source/java/org/alfresco/util/test/junitrules/ApplicationContextInit.java index 1f6a2e6c13..183c4805eb 100644 --- a/source/java/org/alfresco/util/test/junitrules/ApplicationContextInit.java +++ b/source/java/org/alfresco/util/test/junitrules/ApplicationContextInit.java @@ -52,6 +52,8 @@ import org.springframework.context.ApplicationContext; */ public class ApplicationContextInit extends ExternalResource { + public static final String GLOBAL_INTEGRATION_TEST_CONFIG = "classpath:alfresco/test/global-integration-test-context.xml"; + /** * The locations for the application context configurations. */ @@ -67,7 +69,7 @@ public class ApplicationContextInit extends ExternalResource */ public ApplicationContextInit() { - this(new String[0]); + this(ApplicationContextHelper.CONFIG_LOCATIONS); } /** @@ -77,7 +79,16 @@ public class ApplicationContextInit extends ExternalResource */ public ApplicationContextInit(String... configLocations) { - this.configLocations = configLocations; + List requestedConfigs = new ArrayList(); + requestedConfigs.addAll(Arrays.asList(configLocations)); + + // No matter what spring contexts are provided in construction of this object, we always + // add the global test integration config to the end. + // Yes, this will mean that devs cannot override that context file, but it's almost empty anyway. + // We may have to change how this class handles this, but for now: keep it simple, s. + requestedConfigs.add(GLOBAL_INTEGRATION_TEST_CONFIG); + + this.configLocations = requestedConfigs.toArray(new String[0]); } /** @@ -110,20 +121,9 @@ public class ApplicationContextInit extends ExternalResource @Override protected void before() { - // Were any context locations specified in the constructor? - if (configLocations.length > 0) - { - log.debug("Initialising custom Spring Configuration: " + Arrays.asList(configLocations).toString()); - - appContext = ApplicationContextHelper.getApplicationContext(configLocations); - } - // if not, use the Alfresco default - else - { - log.debug("Initialising default Alfresco Spring Configuration"); - - appContext = ApplicationContextHelper.getApplicationContext(); - } + log.debug("Initialising custom Spring Configuration: " + Arrays.asList(configLocations).toString()); + + appContext = ApplicationContextHelper.getApplicationContext(configLocations); } @Override protected void after() diff --git a/source/java/org/alfresco/util/test/junitrules/TenantPerson.java b/source/java/org/alfresco/util/test/junitrules/TenantPerson.java new file mode 100644 index 0000000000..02d0303576 --- /dev/null +++ b/source/java/org/alfresco/util/test/junitrules/TenantPerson.java @@ -0,0 +1,136 @@ +/* + * Copyright (C) 2005-2012 + * Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ +package org.alfresco.util.test.junitrules; + +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.tenant.TenantUtil; +import org.alfresco.repo.tenant.TenantUtil.TenantRunAsWork; +import org.alfresco.util.ParameterCheck; + + +/** + * This JUnit rule can be used to setup and teardown a single Alfresco user, within a tenant, for test purposes. + *

    + * Example usage: + *

    + * public class YourTestClass
    + * {
    + *     // Initialise the spring application context and tenant in a rule chain
    + *     public static final ApplicationContextInit APP_CONTEXT_RULE = new ApplicationContextInit();
    + *     public static final AlfrescoTenant TENANT_RULE = new AlfrescoTenant(APP_CONTEXT_RULE, "testtenant");
    + *     
    + *     @ClassRule public static RuleChain ruleChain = RuleChain.outerRule(APP_CONTEXT_INIT)
    + *         		                                                .around(TENANT);
    + *     
    + *     // We pass the rule that creates the spring application context.
    + *     // This rule will give us a user with username 'AlexM@testtenant'.
    + *     @Rule public final TenantPerson namedPerson = new AlfrescoPerson(APP_CONTEXT_RULE, "AlexM@testTenant", TENANT);
    + *     
    + *     @Test public void aTestMethod()
    + *     {
    + *         	AUSTRALIAN_USER.runAsFullyAuthenticated(new TenantRunAsWork()
    + *          {
    + *          	@Override
    + *          	public Void doWork() throws Exception
    + *          	{
    + *          		// Do something as the tenant user
    + *          	}
    + *          });
    + *      }
    + * }
    + * 
    + * + * @author Alex Miller + */ +public class TenantPerson extends AlfrescoPerson +{ + + private AlfrescoTenant tenant; + + /** + * Constructs the rule with a reference to a {@link ApplicationContextInit rule} which can be used to retrieve the ApplicationContext. + * + * @param appContextRule a rule which can be used to retrieve the spring app context. + * @param userName the username for the person to be created. + * @param tenant the tenant the person should be created under. + */ + public TenantPerson(ApplicationContextInit appContextInit, String userName, AlfrescoTenant tenant) { + super(appContextInit, userName + "@" + tenant.getTenantDomain()); + ParameterCheck.mandatory("tenant", tenant); + this.tenant = tenant; + } + + /** + * Create the user, in the given tenant, using the tenant system user. + */ + @Override protected void before() + { + tenant.runAsSystem(new TenantRunAsWork() { + + @Override + public Void doWork() throws Exception { + TenantPerson.super.before(); + return null; + } + }); + } + + /** + * Remove the user, using the system user for the tenant. + */ + @Override protected void after() + { + tenant.runAsSystem(new TenantRunAsWork() { + + @Override + public Void doWork() throws Exception { + TenantPerson.super.after(); + return null; + } + }); + } + + /** + * Do runAsWork as the fully authenticated user managed by this class, + * + * @param runAsWork + * @return The result of runAsWork + */ + public T runAsFullyAuthenticated(TenantRunAsWork runAsWork) + { + AuthenticationUtil.pushAuthentication(); + AuthenticationUtil.setFullyAuthenticatedUser(getUsername()); + try + { + return TenantUtil.runAsUserTenant(runAsWork, getUsername(), tenant.getTenantDomain()); + } + finally + { + AuthenticationUtil.popAuthentication(); + } + } + + /** + * Get the tenant name of the users tenant. + */ + public String getTenantName() { + return tenant.getTenantDomain(); + } +} diff --git a/source/java/org/alfresco/util/test/testusers/TestUserComponent.java b/source/java/org/alfresco/util/test/testusers/TestUserComponent.java new file mode 100644 index 0000000000..947bcb58d7 --- /dev/null +++ b/source/java/org/alfresco/util/test/testusers/TestUserComponent.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ + +package org.alfresco.util.test.testusers; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * This interface defines a software test component, which is responsible for the creation and deletion + * of Alfresco users - to be used when running integration tests. + * + * @author Neil Mc Erlean + * @since 4.2 + */ +public interface TestUserComponent +{ + /** + * Creates a test user with the specified username. + */ + NodeRef createTestUser(String userName); + + /** + * Deletes the test user with the specified username. + * @param userName + */ + void deleteTestUser(String userName); +} diff --git a/source/java/org/alfresco/util/test/testusers/TestUserComponentImpl.java b/source/java/org/alfresco/util/test/testusers/TestUserComponentImpl.java new file mode 100644 index 0000000000..0b00cd68c5 --- /dev/null +++ b/source/java/org/alfresco/util/test/testusers/TestUserComponentImpl.java @@ -0,0 +1,115 @@ +/* + * Copyright (C) 2005-2013 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * 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 . + */ + +package org.alfresco.util.test.testusers; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.security.authentication.AuthenticationUtil.RunAsWork; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Neil Mc Erlean + * @since 4.2 + */ +public class TestUserComponentImpl implements TestUserComponent +{ + private static final Log log = LogFactory.getLog(TestUserComponentImpl.class); + + protected MutableAuthenticationService authService; + protected PersonService personService; + + public void setAuthenticationService(MutableAuthenticationService service) { this.authService = service; } + public void setPersonService(PersonService service) { this.personService = service; } + + // Fixed defaults for the usual Alfresco cm:person metadata. + protected static final String PASSWORD = "PWD"; + protected static final String FIRST_NAME = "firstName"; + protected static final String LAST_NAME = "lastName"; + protected static final String EMAIL = "email@email.com"; + protected static final String JOB_TITLE = "jobTitle"; + + @Override public NodeRef createTestUser(final String userName) + { + // Pre-create a person, if not already created. + return AuthenticationUtil.runAs(new RunAsWork() + { + @Override public NodeRef doWork() throws Exception + { + if (! authService.authenticationExists(userName)) + { + log.debug("Creating authentication " + userName + "..."); + authService.createAuthentication(userName, PASSWORD.toCharArray()); + } + + NodeRef person; + + if (personService.personExists(userName)) + { + person = personService.getPerson(userName, false); + } + else + { + log.debug("Creating personNode " + userName + "..."); + + PropertyMap ppOne = new PropertyMap(); + ppOne.put(ContentModel.PROP_USERNAME, userName); + ppOne.put(ContentModel.PROP_FIRSTNAME, FIRST_NAME); + ppOne.put(ContentModel.PROP_LASTNAME, LAST_NAME); + ppOne.put(ContentModel.PROP_EMAIL, EMAIL); + ppOne.put(ContentModel.PROP_JOBTITLE, JOB_TITLE); + + person = personService.createPerson(ppOne); + } + + return person; + } + }, AuthenticationUtil.getAdminUserName()); + } + + @Override public void deleteTestUser(final String userName) + { + // And tear down afterwards. + AuthenticationUtil.runAs(new RunAsWork() + { + @Override public Void doWork() throws Exception + { + try + { + if (personService.personExists(userName)) + { + log.debug("Deleting person " + userName + "..."); + personService.deletePerson(userName); + } + } catch (InvalidNodeRefException ignoreIfThrown) + { + // It seems that in cloud code, asking if a person exists when the tenant they would be in also doesn't + // exist, can give this exception. + } + return null; + } + }, AuthenticationUtil.getAdminUserName()); + } +} diff --git a/source/test-resources/alfresco/test/global-integration-test-context.xml b/source/test-resources/alfresco/test/global-integration-test-context.xml new file mode 100644 index 0000000000..f2765c3c39 --- /dev/null +++ b/source/test-resources/alfresco/test/global-integration-test-context.xml @@ -0,0 +1,14 @@ + + + + + + + + diff --git a/source/test-resources/alfresco/test/integration/cloud/readme.txt b/source/test-resources/alfresco/test/integration/cloud/readme.txt new file mode 100644 index 0000000000..d12302e0fd --- /dev/null +++ b/source/test-resources/alfresco/test/integration/cloud/readme.txt @@ -0,0 +1,13 @@ +Please do not add any contents to this package. +----------------------------------------------- + + +The three packages alfresco/test/integration/{community,enterprise,cloud} all exist within the "Community" project despite our +only really needing the community package. The packages exist in order to let us define spring beans which should be +available for test code in the various versions of the product. + +Unfortunately, the current method of importing these spring context files +(see alfresco/test/integration/global-integration-test-context.xml) *requires* us to have the {enterprise,cloud} packages. + +Please do not add any content to this directory. Please consider putting your content in a folder (at the same path) in the +relevant project instead i.e. enterprise-repo or cloud. diff --git a/source/test-resources/alfresco/test/integration/community/community-integration-test-context.xml b/source/test-resources/alfresco/test/integration/community/community-integration-test-context.xml new file mode 100644 index 0000000000..3ae6b5b60c --- /dev/null +++ b/source/test-resources/alfresco/test/integration/community/community-integration-test-context.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/source/test-resources/alfresco/test/integration/enterprise/readme.txt b/source/test-resources/alfresco/test/integration/enterprise/readme.txt new file mode 100644 index 0000000000..d12302e0fd --- /dev/null +++ b/source/test-resources/alfresco/test/integration/enterprise/readme.txt @@ -0,0 +1,13 @@ +Please do not add any contents to this package. +----------------------------------------------- + + +The three packages alfresco/test/integration/{community,enterprise,cloud} all exist within the "Community" project despite our +only really needing the community package. The packages exist in order to let us define spring beans which should be +available for test code in the various versions of the product. + +Unfortunately, the current method of importing these spring context files +(see alfresco/test/integration/global-integration-test-context.xml) *requires* us to have the {enterprise,cloud} packages. + +Please do not add any content to this directory. Please consider putting your content in a folder (at the same path) in the +relevant project instead i.e. enterprise-repo or cloud. diff --git a/source/test-resources/cache-test/cache-test-context.xml b/source/test-resources/cache-test/cache-test-context.xml new file mode 100644 index 0000000000..cb6b137440 --- /dev/null +++ b/source/test-resources/cache-test/cache-test-context.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + transactionalCache + 200000 + + + + + \ No newline at end of file diff --git a/source/test-resources/quick/quickImg2.ppt b/source/test-resources/quick/quickImg2.ppt index ce68bcfd65..35db1e08ac 100644 Binary files a/source/test-resources/quick/quickImg2.ppt and b/source/test-resources/quick/quickImg2.ppt differ diff --git a/config/test/alfresco/TestImageFile.png b/source/test-resources/test/alfresco/TestImageFile.png similarity index 100% rename from config/test/alfresco/TestImageFile.png rename to source/test-resources/test/alfresco/TestImageFile.png diff --git a/config/test/alfresco/TestPresentation.pptx b/source/test-resources/test/alfresco/TestPresentation.pptx similarity index 100% rename from config/test/alfresco/TestPresentation.pptx rename to source/test-resources/test/alfresco/TestPresentation.pptx diff --git a/config/test/alfresco/TestPresentation2.ppt b/source/test-resources/test/alfresco/TestPresentation2.ppt similarity index 100% rename from config/test/alfresco/TestPresentation2.ppt rename to source/test-resources/test/alfresco/TestPresentation2.ppt diff --git a/config/test/alfresco/TestPresentation3.odp b/source/test-resources/test/alfresco/TestPresentation3.odp similarity index 100% rename from config/test/alfresco/TestPresentation3.odp rename to source/test-resources/test/alfresco/TestPresentation3.odp diff --git a/config/test/alfresco/TestPresentation4.pdf b/source/test-resources/test/alfresco/TestPresentation4.pdf similarity index 100% rename from config/test/alfresco/TestPresentation4.pdf rename to source/test-resources/test/alfresco/TestPresentation4.pdf diff --git a/config/test/alfresco/TestVideoFile.MP4 b/source/test-resources/test/alfresco/TestVideoFile.MP4 similarity index 100% rename from config/test/alfresco/TestVideoFile.MP4 rename to source/test-resources/test/alfresco/TestVideoFile.MP4 diff --git a/config/test/alfresco/fake-context/README.txt b/source/test-resources/test/alfresco/fake-context/README.txt similarity index 100% rename from config/test/alfresco/fake-context/README.txt rename to source/test-resources/test/alfresco/fake-context/README.txt diff --git a/config/test/alfresco/fake-context/application-context.xml b/source/test-resources/test/alfresco/fake-context/application-context.xml similarity index 100% rename from config/test/alfresco/fake-context/application-context.xml rename to source/test-resources/test/alfresco/fake-context/application-context.xml diff --git a/config/test/alfresco/fake-context/context-a.xml b/source/test-resources/test/alfresco/fake-context/context-a.xml similarity index 100% rename from config/test/alfresco/fake-context/context-a.xml rename to source/test-resources/test/alfresco/fake-context/context-a.xml diff --git a/config/test/alfresco/fake-context/context-b.xml b/source/test-resources/test/alfresco/fake-context/context-b.xml similarity index 100% rename from config/test/alfresco/fake-context/context-b.xml rename to source/test-resources/test/alfresco/fake-context/context-b.xml diff --git a/config/test/alfresco/fake-context/context-c.xml b/source/test-resources/test/alfresco/fake-context/context-c.xml similarity index 100% rename from config/test/alfresco/fake-context/context-c.xml rename to source/test-resources/test/alfresco/fake-context/context-c.xml diff --git a/config/test/alfresco/fake-context/nested.xml b/source/test-resources/test/alfresco/fake-context/nested.xml similarity index 100% rename from config/test/alfresco/fake-context/nested.xml rename to source/test-resources/test/alfresco/fake-context/nested.xml diff --git a/config/test/alfresco/fake-context/override-1.xml b/source/test-resources/test/alfresco/fake-context/override-1.xml similarity index 100% rename from config/test/alfresco/fake-context/override-1.xml rename to source/test-resources/test/alfresco/fake-context/override-1.xml diff --git a/config/test/alfresco/fake-context/override-2.xml b/source/test-resources/test/alfresco/fake-context/override-2.xml similarity index 100% rename from config/test/alfresco/fake-context/override-2.xml rename to source/test-resources/test/alfresco/fake-context/override-2.xml diff --git a/config/test/alfresco/jbpm.cfg.xml b/source/test-resources/test/alfresco/jbpm.cfg.xml similarity index 100% rename from config/test/alfresco/jbpm.cfg.xml rename to source/test-resources/test/alfresco/jbpm.cfg.xml diff --git a/config/test/alfresco/model/testSubscriptionModel.xml b/source/test-resources/test/alfresco/model/testSubscriptionModel.xml similarity index 100% rename from config/test/alfresco/model/testSubscriptionModel.xml rename to source/test-resources/test/alfresco/model/testSubscriptionModel.xml diff --git a/config/test/alfresco/model/testWcmModel.xml b/source/test-resources/test/alfresco/model/testWcmModel.xml similarity index 100% rename from config/test/alfresco/model/testWcmModel.xml rename to source/test-resources/test/alfresco/model/testWcmModel.xml diff --git a/config/test/alfresco/parallel_loop_review_processdefinition.xml b/source/test-resources/test/alfresco/parallel_loop_review_processdefinition.xml similarity index 100% rename from config/test/alfresco/parallel_loop_review_processdefinition.xml rename to source/test-resources/test/alfresco/parallel_loop_review_processdefinition.xml diff --git a/config/test/alfresco/test-context.xml b/source/test-resources/test/alfresco/test-context.xml similarity index 100% rename from config/test/alfresco/test-context.xml rename to source/test-resources/test/alfresco/test-context.xml diff --git a/config/test/alfresco/test-database-context.xml b/source/test-resources/test/alfresco/test-database-context.xml similarity index 100% rename from config/test/alfresco/test-database-context.xml rename to source/test-resources/test/alfresco/test-database-context.xml diff --git a/config/test/alfresco/test-hibernate-cfg.properties b/source/test-resources/test/alfresco/test-hibernate-cfg.properties similarity index 100% rename from config/test/alfresco/test-hibernate-cfg.properties rename to source/test-resources/test/alfresco/test-hibernate-cfg.properties diff --git a/config/test/alfresco/test-subscriptions-context.xml b/source/test-resources/test/alfresco/test-subscriptions-context.xml similarity index 100% rename from config/test/alfresco/test-subscriptions-context.xml rename to source/test-resources/test/alfresco/test-subscriptions-context.xml diff --git a/config/test/alfresco/test-web-publishing--workflow-context.xml b/source/test-resources/test/alfresco/test-web-publishing--workflow-context.xml similarity index 100% rename from config/test/alfresco/test-web-publishing--workflow-context.xml rename to source/test-resources/test/alfresco/test-web-publishing--workflow-context.xml diff --git a/config/test/alfresco/test-web-publishing-context.xml b/source/test-resources/test/alfresco/test-web-publishing-context.xml similarity index 100% rename from config/test/alfresco/test-web-publishing-context.xml rename to source/test-resources/test/alfresco/test-web-publishing-context.xml diff --git a/config/test/alfresco/test-workflow-context.xml b/source/test-resources/test/alfresco/test-workflow-context.xml similarity index 100% rename from config/test/alfresco/test-workflow-context.xml rename to source/test-resources/test/alfresco/test-workflow-context.xml diff --git a/config/test/alfresco/wcm-template-node-test-context.xml b/source/test-resources/test/alfresco/wcm-template-node-test-context.xml similarity index 100% rename from config/test/alfresco/wcm-template-node-test-context.xml rename to source/test-resources/test/alfresco/wcm-template-node-test-context.xml