From bce28a5599115de8b67d8d663c4fd9b6bd89506b Mon Sep 17 00:00:00 2001 From: Neil McErlean Date: Fri, 5 Mar 2010 20:02:52 +0000 Subject: [PATCH] Merging DEV_TEMPORARY to HEAD (RenditionService) git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@19103 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../application-context-highlevel.xml | 1 + .../bootstrap/systemRenditionDefinitions.acp | Bin 0 -> 4152 bytes config/alfresco/core-services-context.xml | 1 + config/alfresco/import-export-context.xml | 7 + .../messages/bootstrap-spaces.properties | 3 + .../messages/patch-service.properties | 7 + .../messages/rendition-config.properties | 4 + config/alfresco/model/contentModel.xml | 49 +- config/alfresco/model/renditionModel.xml | 111 ++ .../alfresco/patch/patch-services-context.xml | 65 +- .../alfresco/rendition-services-context.xml | 147 ++ config/alfresco/repository.properties | 3 +- config/alfresco/thumbnail-service-context.xml | 120 +- config/alfresco/version.properties | 2 +- .../java/org/alfresco/model/ContentModel.java | 2 +- .../org/alfresco/model/RenditionModel.java | 41 + .../org/alfresco/repo/action/ActionImpl.java | 488 ++--- .../alfresco/repo/action/ActionListImpl.java | 128 ++ .../repo/action/ActionServiceImpl.java | 1047 +++++----- .../repo/action/CompositeActionImpl.java | 177 +- .../repo/action/ParameterizedItemImpl.java | 1 + .../executer/ActionExecuterAbstractBase.java | 14 +- .../repo/admin/patch/impl/QNamePatch.java | 169 ++ .../metadata/xml/XmlMetadataExtracter.java | 83 +- .../transform/magick/ImageCropOptions.java | 175 ++ .../ImageMagickContentTransformerWorker.java | 58 + .../magick/ImageTransformationOptions.java | 19 + .../repo/rendition/AllRenditionTests.java | 45 + .../CompositeRenditionDefinitionImpl.java | 159 ++ .../rendition/MockedTestServiceRegistry.java | 485 +++++ .../PerformRenditionActionExecuter.java | 518 +++++ .../RenderingEngineDefinitionImpl.java | 40 + .../rendition/RenditionDefinitionImpl.java | 132 ++ .../RenditionDefinitionPersister.java | 83 + .../RenditionDefinitionPersisterImpl.java | 202 ++ .../repo/rendition/RenditionLocation.java | 30 + .../repo/rendition/RenditionLocationImpl.java | 50 + .../rendition/RenditionLocationResolver.java | 30 + .../repo/rendition/RenditionServiceImpl.java | 406 ++++ .../rendition/RenditionServiceImplTest.java | 120 ++ .../RenditionServiceIntegrationTest.java | 1753 +++++++++++++++++ .../RenditionServicePermissionsTest.java | 231 +++ .../repo/rendition/RenditionedAspect.java | 360 ++++ ...StandardRenditionLocationResolverImpl.java | 256 +++ ...StandardRenditionLocationResolverTest.java | 209 ++ .../executer/AbstractRenderingEngine.java | 553 ++++++ ...AbstractTransformationRenderingEngine.java | 88 + .../executer/CompositeRenderingEngine.java | 129 ++ .../executer/ImageRenderingEngine.java | 227 +++ .../executer/ReformatRenderingEngine.java | 98 + .../executer/TemplateModelHelper.java | 113 ++ .../executer/TemplatingRenderingEngine.java | 225 +++ .../repo/rendition/renditionTestTemplate.ftl | 59 + .../template/TemplateServiceImplTest.java | 2 +- .../repo/thumbnail/ThumbnailRegistry.java | 103 +- .../ThumbnailRenditionConvertor.java | 164 ++ .../repo/thumbnail/ThumbnailServiceImpl.java | 326 ++- .../ThumbnailServiceImplParameterTest.java | 177 ++ .../thumbnail/ThumbnailServiceImplTest.java | 517 +++-- .../repo/thumbnail/ThumbnailedAspect.java | 293 --- .../UpdateThumbnailActionExecuter.java | 30 +- .../thumbnail/script/ScriptThumbnail.java | 33 +- .../service/cmr/action/ActionList.java | 100 + .../service/cmr/action/CompositeAction.java | 70 +- .../CompositeRenditionDefinition.java | 37 + .../service/cmr/rendition/NodeLocator.java | 43 + .../service/cmr/rendition/RenderCallback.java | 47 + .../rendition/RenderingEngineDefinition.java | 33 + .../cmr/rendition/RenditionDefinition.java | 102 + .../cmr/rendition/RenditionService.java | 159 ++ .../rendition/RenditionServiceException.java | 52 + .../service/namespace/NamespaceService.java | 3 + .../org/alfresco/util/FreeMarkerUtil.java | 66 + source/java/org/alfresco/util/XMLUtil.java | 281 +++ .../util/json/AbstractJsonSerializerBean.java | 57 + .../util/json/AlfrescoJsonSerializer.java | 117 ++ .../util/json/DefaultJsonSerializer.java | 51 + .../alfresco/util/json/JsonSerializer.java | 40 + source/test-resources/images/gray21.512.png | Bin 0 -> 2904 bytes source/test-resources/images/gray21.512.tiff | Bin 0 -> 262278 bytes 80 files changed, 10611 insertions(+), 1815 deletions(-) create mode 100644 config/alfresco/bootstrap/systemRenditionDefinitions.acp create mode 100644 config/alfresco/messages/rendition-config.properties create mode 100644 config/alfresco/model/renditionModel.xml create mode 100644 config/alfresco/rendition-services-context.xml create mode 100644 source/java/org/alfresco/model/RenditionModel.java create mode 100644 source/java/org/alfresco/repo/action/ActionListImpl.java create mode 100644 source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java create mode 100644 source/java/org/alfresco/repo/content/transform/magick/ImageCropOptions.java create mode 100644 source/java/org/alfresco/repo/rendition/AllRenditionTests.java create mode 100644 source/java/org/alfresco/repo/rendition/CompositeRenditionDefinitionImpl.java create mode 100644 source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java create mode 100644 source/java/org/alfresco/repo/rendition/PerformRenditionActionExecuter.java create mode 100644 source/java/org/alfresco/repo/rendition/RenderingEngineDefinitionImpl.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionDefinitionImpl.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionLocation.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionLocationImpl.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionLocationResolver.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionServiceImplTest.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionServicePermissionsTest.java create mode 100644 source/java/org/alfresco/repo/rendition/RenditionedAspect.java create mode 100644 source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverImpl.java create mode 100644 source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverTest.java create mode 100644 source/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java create mode 100644 source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java create mode 100644 source/java/org/alfresco/repo/rendition/executer/CompositeRenderingEngine.java create mode 100644 source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java create mode 100644 source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java create mode 100644 source/java/org/alfresco/repo/rendition/executer/TemplateModelHelper.java create mode 100644 source/java/org/alfresco/repo/rendition/executer/TemplatingRenderingEngine.java create mode 100644 source/java/org/alfresco/repo/rendition/renditionTestTemplate.ftl create mode 100644 source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java create mode 100644 source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java delete mode 100644 source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java create mode 100644 source/java/org/alfresco/service/cmr/action/ActionList.java create mode 100644 source/java/org/alfresco/service/cmr/rendition/CompositeRenditionDefinition.java create mode 100644 source/java/org/alfresco/service/cmr/rendition/NodeLocator.java create mode 100644 source/java/org/alfresco/service/cmr/rendition/RenderCallback.java create mode 100644 source/java/org/alfresco/service/cmr/rendition/RenderingEngineDefinition.java create mode 100644 source/java/org/alfresco/service/cmr/rendition/RenditionDefinition.java create mode 100644 source/java/org/alfresco/service/cmr/rendition/RenditionService.java create mode 100644 source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java create mode 100644 source/java/org/alfresco/util/FreeMarkerUtil.java create mode 100644 source/java/org/alfresco/util/XMLUtil.java create mode 100644 source/java/org/alfresco/util/json/AbstractJsonSerializerBean.java create mode 100644 source/java/org/alfresco/util/json/AlfrescoJsonSerializer.java create mode 100644 source/java/org/alfresco/util/json/DefaultJsonSerializer.java create mode 100644 source/java/org/alfresco/util/json/JsonSerializer.java create mode 100644 source/test-resources/images/gray21.512.png create mode 100644 source/test-resources/images/gray21.512.tiff diff --git a/config/alfresco/application-context-highlevel.xml b/config/alfresco/application-context-highlevel.xml index f722763f49..87b719a815 100644 --- a/config/alfresco/application-context-highlevel.xml +++ b/config/alfresco/application-context-highlevel.xml @@ -9,6 +9,7 @@ + diff --git a/config/alfresco/bootstrap/systemRenditionDefinitions.acp b/config/alfresco/bootstrap/systemRenditionDefinitions.acp new file mode 100644 index 0000000000000000000000000000000000000000..6637b71576f8e45ddeda308fa86a7b6863236798 GIT binary patch literal 4152 zcmaJ^XHXN`)=lUjAkw8Hpp<|hTtT{sh@cW$Xd*p82ptlFR1rZ4MM@L|0i{SFU~Z&K zkt!uL=~X&}qV%3T?)QFt@BMju|2TVQ@0qh_&N^$&0vl4(Z~_=EvKH^84)~7{x~Tn8 z{sAtYCN5si?g8%JUb-$&cd!4e{N;i?;hWhHQx~e!JAR)_ajFsplA9Zx%{t2%C+AW% z25b+$zhx@T*cgGzTBR#0%!X>;O8i6z3U!h5ErD!^ZB>m91hs=r#_bey_9S)uzYpvS|rKtJQKc~!{nf^|K znxh5YmA1F{QI2wK#^LN6OZ$iN0;}U2Gfz4cHBjUo6~R+dgv`dR!u|o^QjWitZxl*MpJ5UoPY3>WneJb zdbs3`md*G;Au7H60N(krGfY-XaDQ^%z1s&>`4?w5jvdGCn*Gfh{v2sb*{A%L{xXxDOgZf(kTj64Ih`(1mvN zZHvWlSgL!-k5>Aq$u3Fm)u(y|#M`LeT0g!L7<~s>SFrKxXE#$2X3|q^hvtSTyYgwS z#&ds{o27MqaLw%F;~i8CyR3j1QmA&Djs;%4wZIpZ#gEU+=4U3>Q@x{pHj<(#X3d z?6Fu4!ifDQZ9EGbz0bSxcCNq@q4YE-L5tt@nYG?rRhRnfDz!7;(4u;y{p^FVO!xJY zZU`Nb|DEn3(3MF!UuS4(RshBak-f?(caV3DGfcDemhG9DP}$E-tBskHlO~U|JHdmW z(#uOclw^hTgiphEmV~wTlV)`lm)5!H>fVk`O~Samoy&I`L8PflA|Guc5&MntkbUY} z#IS4Y7XF*-UZUekIVrOol8}bj>edTa5lsnft{7;DacYw*La6d#9@iRoWfSUnvf7!7 zV=FqtYR#$T4&I>@Y#LtmZj$Cq%cbw#)LgdhQ$NHcHCTMYS7jYQC6>6mXG+4K-z3+o z85`3Z4E{M93H>JIagaJ)*tqpQzE~D}V;k~TvZg$qh!0Z2Y&XcQ2uXE$1Y4B0pC_DO z(fbxnwYlbpzGOqIsvWa@9I6K|vx@{6Y-KS3#{ks1t(GFPhfPjQ6{aI9*AKqnC%;RZ z$mb_EIh98?(uyX;?as~p8cYnz398t5SVHSC-RZhG#&37pR9sqvlG z5+sfBd^;I9L-J0RDG1VR6OUfriML`%d4!TC9nNTXH^DGB)uSVeUPhu7Wz_ zfZoS4$U(IV9-zV_5RM`b?tryxY|btkam zeU-(9@GZi@un*73QtQGF3UXiKzQBtbvWHs$dh+HSmI+(MNUQfWvYi!jof+hzkp6Gn zDygKw{1jFB@3g??!Lj1xKyCMzk)t4yrD6wz!Z%E|*=7s8!*Ac57j-3RVT_u(eGI4X zeomKIO*-Z%Rx_fqGRR!fqznqOBGem3qqzrO99Y zeUVTHgkjGKay<8>h2nX>yh|Z|5FF8}BBbsc-DdIF;(wCVNoP?H9U`=1Z z!}7#xZyuIb%V3d_-s0&zy-wM?vhx*xC;7#AiO!GcLD!Dp;G zem&kV!1i=tZ#53(jZOCvVwh{0YnA%H^J?9B#m3O9uPqg=&C#L##f-n;&9A3Lk%@MK z0r=6@ppD(V-P)?BOuh{B&aD;xKFC(7Ftylj!f}Rm3~s{YlBu-YhFKatlVn`>U6c5T z_Q+sTw7;B!x>}*-wBcTuw`e?`-i1=li<37fGMDK<6mseOLAdSnC7U-leD71FWVczV zAqldAHc0Zl2|RQr;Zu=0!3*{v`dH44fMG0Ufizivc6$4h$&Fh*SyUcWll-b0gS7$~ zQGrUJ;fL69M8eu8)+#VY^m!fZwoXpYi!carSyEns$(d7{9t9XA~` zg6Tx_SIIwngHd`PZNS8K*3(xEU$RZr9z^g?_n!`wW)kyN;ln9r(m`<iR=yejU>F2l^N^aXsa7MbKE^#Xupo}4PG0rB>ihZ< zwVAe3vR?tqp>F^OsK57;eS#Up9rm@rJ+_YA91)L&;`MV#u$7@LuoDTBi=oCpDz+`b zr2TCz^)a1pDw9`&Wo(b0oO0*t$R59q9>j9kG~=M|R)48sNE zris^QGcv`Zwy1U;(Qfoki@CR=ai-L)rtdwZ~@(cmCWrKV?|?UVGgG8CssmS6l#uKJ4Z6yk9X0 zL(HG}8z!^PqY~u7=%ez-Br-8l7k|jMLbKOzO~Wx8_{?rZSTA_{Xs5m=9vEMgWHxzR zsrKclm}TcsfXolXbd`3M8l-)2XQka2aC2}BXKw~PKndL1Pfes z3x9i#T=3i*2``v8Az*DB8Yk%_=OoACKP7hkss-SuUzXUL*Y#H%n_?NBFzUkR%P4y% z>i{|Oazt}Lb%j41SzXNkVp6Yb;OND=@|U2j3A+Z;jmud3lhYSt<)^pvc(p=|ygB3@ zd-PWU0X@JjG_{Ei4ceRzg|3El9B5B2Osu6-H=b89qWBZVEWA&ldA3fZ9cX0sjN9%M zA)?Fly~DEdr8_E*m|0ooBKgf2g&pH>RA#Y`($JJLLMD8PR@Od2=M{`Rp@gPc5h?eT zq25T*x`HI_CFM&QW*5v622IspKvzY??!+g9@8pWSjPP0hI^-*YsUDZDC!f4TPQ8i} zK26+1X4Xt;%lPNUE3d^fPg(=b z_fEQ5;721N{7EWljg2MYu_ZML5);ph27gqr#c9Z{o-#Nw~&p1)_@;P0|qk zaovksx4iM&FTvSpzYk=E$bWt}$j`LLAe^T*<5%yy?stZ1A(8@0&8#YROzj#=+6r#K z9kfk%ni*)@c6Gl|!)E}eQmtMbG8?EPyW_Srv}M1byq}&XfOaw1w64fcD{Ss?69puh z9pBfNi1ZbWF!8bHY8EG_>J_hdpU;XD#G?Fg7`uq%EgglI#MRczxs+Tu9{+g~_VB2HKiH0p*?8$+GXqkF+{5mwm zjUlU@yQuOk0YgkI=p8zrM8=PIIZ}=!0ubjF)I2=HOc|PnBfa=(_rTPWTe}E>#upI~ zt@|B)jhD7T+dn*5?#N@z)dEquT*!!~+={#E+DNqH^E1_YNs)Nh@Wc5F;w_F3g3Lde z{lal$-_J_@eNXVo`x0LC)L{iuN>q+F+QvFIWWn^F`BaQ`IFosSoMiyk(XC zuoVC}g+KUxavR~_XlO~a7mhIjig+{TK4K8bQ_{9B{C4T7Rn9@hV?<~MMJ>*CiGSur z<%P(4;rS=B#f%6pUV@ddQ)4PL^@_n<>Pyn_J*tj*%c-4PYs$Bsjig zkGqmI_d-Fz$DUhqow@(J;lmfrPYOMwzn3*?{+bpiz|O69(ysmuVckkp3BDKG?f-AJ zE`v4C@Hsow$Uti9(2GqXQ2K6CFytA(YFo$Xu-hQN&f3cYFrQsLyq- zY-n-cuRL$t(-%s2`Fw3^?y^TxNBAqsWw0SV!{*&~Qru+#V0;h&02>0SI066f>;JR& dkFWpl_}?xcY)DJ@&phxVA6=Xy7k2;v{{k5z91#Ej literal 0 HcmV?d00001 diff --git a/config/alfresco/core-services-context.xml b/config/alfresco/core-services-context.xml index 2646892966..55121b0fa1 100644 --- a/config/alfresco/core-services-context.xml +++ b/config/alfresco/core-services-context.xml @@ -1068,6 +1068,7 @@ alfresco/model/contentModel.xml + alfresco/model/renditionModel.xml alfresco/model/bpmModel.xml alfresco/model/wcmModel.xml alfresco/model/forumModel.xml diff --git a/config/alfresco/import-export-context.xml b/config/alfresco/import-export-context.xml index e2ed67e4b5..0ecf791056 100644 --- a/config/alfresco/import-export-context.xml +++ b/config/alfresco/import-export-context.xml @@ -356,6 +356,7 @@ ${spaces.imap_templates.childname} ${spaces.emailActions.childname} ${spaces.searchAction.childname} + ${spaces.rendition.rendering_actions.childname} ${spaces.wcm_deployed.childname} @@ -565,6 +566,12 @@ alfresco/bootstrap/transferSpaces.xml alfresco/messages/bootstrap-spaces + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/systemRenditionDefinitions.acp + alfresco/messages/bootstrap-spaces + diff --git a/config/alfresco/messages/bootstrap-spaces.properties b/config/alfresco/messages/bootstrap-spaces.properties index af8d4fb3e6..dc4d4601ab 100644 --- a/config/alfresco/messages/bootstrap-spaces.properties +++ b/config/alfresco/messages/bootstrap-spaces.properties @@ -30,6 +30,9 @@ spaces.templates.email.description=Email templates spaces.templates.rss.name=RSS Templates spaces.templates.rss.description=RSS templates +spaces.rendition.rendering_actions.name=Rendering Actions Space +spaces.rendition.rendering_actions.description=A space used by the system to persist rendering actions. + spaces.savedsearches.name=Saved Searches spaces.savedsearches.description=Saved Searches diff --git a/config/alfresco/messages/patch-service.properties b/config/alfresco/messages/patch-service.properties index 10c6ce6e73..3757e92f38 100644 --- a/config/alfresco/messages/patch-service.properties +++ b/config/alfresco/messages/patch-service.properties @@ -96,6 +96,10 @@ patch.rssTemplatesFolder.description=Ensures the existence of the 'RSS Templates patch.rssTemplatesFolder.result.exists=The RSS Templates folder already exists: {0}. Re-applying guest permissions. patch.rssTemplatesFolder.result.created=The RSS Templates folder was successfully created: {0} +patch.rendition.rendering_actions.exists=The Rendering Actions folder already exists: {0}. +patch.rendition.rendering_actions.created=The Rendering Actions folder was successfully created: {0} +patch.rendition.rendering_actions.description=Creates the Rendering Actions folder. + patch.uifacetsAspectRemovalPatch.description=Removes the incorrectly applied uifacets aspect from presentation template files. patch.uifacetsAspectRemovalPatch.updated=Successfully removed the uifacets aspect from {0} presentation template files. @@ -295,3 +299,6 @@ patch.transferDefinitions.result=Transfer definitions folder added to data dicti patch.redeployNominatedInvitationProcessWithPropsForShare.description=Redeploy nominated invitation workflow patch.redeployNominatedInvitationProcessWithPropsForShare.result=Nominated invitation workflow redeployed + +patch.thumbnailsAssocQName.description=Update the 'cm:thumbnails' association QName to 'rn:rendition'. +patch.QNamePatch.result=Successfully updated the ''{0}'' QName to ''{1}''. diff --git a/config/alfresco/messages/rendition-config.properties b/config/alfresco/messages/rendition-config.properties new file mode 100644 index 0000000000..963c416b67 --- /dev/null +++ b/config/alfresco/messages/rendition-config.properties @@ -0,0 +1,4 @@ +# Rendering Actions + +reformat.title=Reformat content +reformat.description=Renders a piece of content in another format (MIME type). diff --git a/config/alfresco/model/contentModel.xml b/config/alfresco/model/contentModel.xml index b023d279a4..5c95c5aaee 100644 --- a/config/alfresco/model/contentModel.xml +++ b/config/alfresco/model/contentModel.xml @@ -417,27 +417,7 @@ sys:localized - - - - Thumbnail - cm:content - true - false - - - Thumbnail Name - d:text - false - - - Thumbnailed Content Property Name - d:qname - true - - - - + @@ -1008,32 +988,6 @@ - - - Thumbnailed - - - Automatic Update - d:boolean - true - true - - - - - - false - true - - - cm:thumbnail - false - true - - - - - ContentStore Selector @@ -1093,7 +1047,6 @@ - diff --git a/config/alfresco/model/renditionModel.xml b/config/alfresco/model/renditionModel.xml new file mode 100644 index 0000000000..c84b1a0c07 --- /dev/null +++ b/config/alfresco/model/renditionModel.xml @@ -0,0 +1,111 @@ + + + + Alfresco Rendition Model + Alfresco + 2010-01-14 + 1.0 + + + + + + + + + + + + + + Thumbnail + cm:content + true + false + + + Thumbnail Name + d:text + false + + + Thumbnailed Content Property Name + d:qname + true + + + + + + + + + Rendition + + + + Hidden Rendition + rn:rendition + + + + Visible Rendition + rn:rendition + + + + + Renditioned + + + + + false + true + + + cm:content + false + true + + + + + + + + + + + Thumbnailed + rn:renditioned + + + Automatic Update + d:boolean + true + true + + + + + + + + + + diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 44cbd114cc..172849a217 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1371,7 +1371,7 @@ - + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.templates.email.childname}/${spaces.templates.email.invite.childname} @@ -2008,7 +2008,7 @@ - + patch.db-V3.3-Remove-VersionCount patch.schemaUpgradeScript.description @@ -2020,6 +2020,64 @@ + + patch.rendition.rendering_actions + patch.rendition.rendering_actions.description + 0 + 4003 + 4004 + + + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname}/${spaces.rendition.rendering_actions.childname} + + + + /${spaces.company_home.childname}/${spaces.dictionary.childname} + alfresco/bootstrap/systemRenditionDefinitions.acp + + + + + + + patch.thumbnailsAssocQName + patch.thumbnailsAssocQName.description + 0 + 4004 + 4005 + + + + + + + + + + + + {http://www.alfresco.org/model/content/1.0}thumbnails + + + {http://www.alfresco.org/model/rendition/1.0}rendition + + + + + + patch.transferServiceFolder patch.transferDefinitions.description @@ -2042,4 +2100,5 @@ - + + \ No newline at end of file diff --git a/config/alfresco/rendition-services-context.xml b/config/alfresco/rendition-services-context.xml new file mode 100644 index 0000000000..3f7849ad8a --- /dev/null +++ b/config/alfresco/rendition-services-context.xml @@ -0,0 +1,147 @@ + + + + + + + + + + org.alfresco.service.cmr.rendition.RenditionService + + + + + + + + + + + + + + + + + + + + + + ${server.transaction.mode.default} + + + + + + + + + + + + + + + + + + false + + + + + + + + + + + + + + + + + + + + + + + + alfresco.messages.rendition-config + + + + + + + + + + + + + + + + + + + + {http://www.alfresco.org/model/content/1.0}content + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/alfresco/repository.properties b/config/alfresco/repository.properties index 9fa1c90dfd..5aa3191dab 100644 --- a/config/alfresco/repository.properties +++ b/config/alfresco/repository.properties @@ -315,6 +315,7 @@ spaces.content_forms.childname=app:forms spaces.user_homes.childname=app:user_homes spaces.sites.childname=st:sites spaces.templates.email.invite.childname=cm:invite +spaces.rendition.rendering_actions.childname=app:rendering_actions spaces.wcm_deployed.childname=cm:wcm_deployed spaces.transfers.childname=app:transfers spaces.transfer_groups.childname=app:transfer_groups @@ -398,10 +399,8 @@ deployment.rmi.service.port=50507 mbean.server.locateExistingServerIfPossible=true # External executable locations -#ooo.exe=/Applications/OpenOffice.org.app/Contents/MacOS/soffice ooo.exe=soffice ooo.user=${dir.root}/oouser - img.root=./ImageMagick img.dyn=${img.root}/lib img.exe=${img.root}/bin/convert diff --git a/config/alfresco/thumbnail-service-context.xml b/config/alfresco/thumbnail-service-context.xml index 3db734f550..1ee9d7d99a 100644 --- a/config/alfresco/thumbnail-service-context.xml +++ b/config/alfresco/thumbnail-service-context.xml @@ -36,11 +36,12 @@ - + + @@ -48,96 +49,15 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + medium + doclib + webpreview + imgpreview + avatar + @@ -156,26 +76,20 @@ - - false - + + false + + + + - - - - - - - - - diff --git a/config/alfresco/version.properties b/config/alfresco/version.properties index b208615df6..b548957d3a 100644 --- a/config/alfresco/version.properties +++ b/config/alfresco/version.properties @@ -19,4 +19,4 @@ version.build=@build-number@ # Schema number -version.schema=4003 +version.schema=4005 diff --git a/source/java/org/alfresco/model/ContentModel.java b/source/java/org/alfresco/model/ContentModel.java index 3d386bc9d3..e9ae701754 100644 --- a/source/java/org/alfresco/model/ContentModel.java +++ b/source/java/org/alfresco/model/ContentModel.java @@ -264,7 +264,7 @@ public interface ContentModel static final QName ASSOC_MULTILINGUAL_CHILD = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "mlChild"); static final QName ASPECT_MULTILINGUAL_DOCUMENT = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "mlDocument"); static final QName ASPECT_MULTILINGUAL_EMPTY_TRANSLATION = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "mlEmptyTranslation"); - + // Thumbnail Type static final QName TYPE_THUMBNAIL = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "thumbnail"); static final QName PROP_THUMBNAIL_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "thumbnailName"); diff --git a/source/java/org/alfresco/model/RenditionModel.java b/source/java/org/alfresco/model/RenditionModel.java new file mode 100644 index 0000000000..02f18041ea --- /dev/null +++ b/source/java/org/alfresco/model/RenditionModel.java @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have received a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.model; + +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * Rendition Model Constants + */ +public interface RenditionModel +{ + static final QName ASPECT_RENDITION = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "rendition"); + static final QName ASPECT_HIDDEN_RENDITION = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "hiddenRendition"); + static final QName ASPECT_VISIBLE_RENDITION = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "visibleRendition"); + + static final QName ASPECT_RENDITIONED = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "renditioned"); + static final QName ASSOC_RENDITION = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "rendition"); +} diff --git a/source/java/org/alfresco/repo/action/ActionImpl.java b/source/java/org/alfresco/repo/action/ActionImpl.java index 1cdf34c1b7..da1b70c221 100644 --- a/source/java/org/alfresco/repo/action/ActionImpl.java +++ b/source/java/org/alfresco/repo/action/ActionImpl.java @@ -16,6 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ + package org.alfresco.repo.action; import java.io.Serializable; @@ -34,72 +35,71 @@ import org.alfresco.service.cmr.repository.NodeRef; * * @author Roy Wetherall */ -public class ActionImpl extends ParameterizedItemImpl - implements Serializable, Action +public class ActionImpl extends ParameterizedItemImpl implements Serializable, Action { /** * Serial version UID */ private static final long serialVersionUID = 3258135760426186548L; - + /** The node reference for the action */ private NodeRef nodeRef; - + /** - * The title + * The title */ private String title; - + /** - * The description + * The description */ private String description; /** * Inidcates whether the action should be executed asynchronously or not */ - private boolean executeAsynchronously = false; - - /** - * The compensating action - */ - private Action compensatingAction; - + private boolean executeAsynchronously = false; + /** - * The created date + * The compensating action + */ + private Action compensatingAction; + + /** + * The created date */ private Date createdDate; - + /** * The creator */ private String creator; - + /** * The modified date */ private Date modifiedDate; - + /** * The modifier */ private String modifier; - + /** * Rule action definition name */ private String actionDefinitionName; - + /** * The run as user name */ private String runAsUserName; - + /** * The chain of actions that have lead to this action */ private Set actionChain; - + /** * Action conditions */ @@ -108,42 +108,62 @@ public class ActionImpl extends ParameterizedItemImpl /** * Constructor * - * @param nodeRef the action node reference (null if not saved) - * @param id the action id - * @param actionDefinitionName the name of the action definition + * @param nodeRef the action node reference (null if not saved) + * @param id the action id + * @param actionDefinitionName the name of the action definition */ public ActionImpl(NodeRef nodeRef, String id, String actionDefinitionName) { - this(nodeRef, id, actionDefinitionName, null); + this(nodeRef, id, actionDefinitionName, null); } /** - * Constructor + * Constructor * - * @param nodeRef the action node reference (null if not saved) - * @param id the action id - * @param actionDefinitionName the action definition name - * @param parameterValues the parameter values + * @param nodeRef the action node reference (null if not saved) + * @param id the action id + * @param actionDefinitionName the action definition name + * @param parameterValues the parameter values */ - public ActionImpl( - NodeRef nodeRef, - String id, - String actionDefinitionName, - Map parameterValues) + public ActionImpl(NodeRef nodeRef, String id, String actionDefinitionName, Map parameterValues) { super(id, parameterValues); this.nodeRef = nodeRef; this.actionDefinitionName = actionDefinitionName; } - + + public ActionImpl(Action action, String actionDefinitionName) + { + super(action.getId(), action.getParameterValues()); + this.actionDefinitionName = actionDefinitionName; + this.actionConditions = action.getActionConditions(); + this.compensatingAction = action.getCompensatingAction(); + this.createdDate = action.getCreatedDate(); + this.creator = action.getCreator(); + this.description = action.getDescription(); + this.executeAsynchronously = action.getExecuteAsychronously(); + this.modifiedDate = action.getModifiedDate(); + this.modifier = action.getModifier(); + this.nodeRef = action.getNodeRef(); + this.title = action.getTitle(); + if (action instanceof ActionImpl) + { + ActionImpl actionImpl = (ActionImpl) action; + this.runAsUserName = actionImpl.getRunAsUser(); + this.actionChain = actionImpl.actionChain; + } + } + + public ActionImpl(Action action) + { + this(action, action.getActionDefinitionName()); + } + @Override public String toString() { StringBuilder sb = new StringBuilder(); - sb.append("Action") - .append("[ id=").append(getId()) - .append(", node=").append(nodeRef) - .append(" ]"); + sb.append("Action").append("[ id=").append(getId()).append(", node=").append(nodeRef).append(" ]"); return sb.toString(); } @@ -151,232 +171,234 @@ public class ActionImpl extends ParameterizedItemImpl * @see org.alfresco.service.cmr.action.Action#getTitle() */ public String getTitle() - { - return this.title; - } + { + return this.title; + } /** * @see org.alfresco.service.cmr.action.Action#setTitle(java.lang.String) */ - public void setTitle(String title) - { - this.title = title; - } + public void setTitle(String title) + { + this.title = title; + } - /** - * @see org.alfresco.service.cmr.action.Action#getDescription() - */ - public String getDescription() - { - return this.description; - } + /** + * @see org.alfresco.service.cmr.action.Action#getDescription() + */ + public String getDescription() + { + return this.description; + } - /** - * @see org.alfresco.service.cmr.action.Action#setDescription(java.lang.String) - */ - public void setDescription(String description) - { - this.description = description; - } - - /** - * @see org.alfresco.service.cmr.action.Action#getExecuteAsychronously() - */ - public boolean getExecuteAsychronously() - { - return this.executeAsynchronously ; - } + /** + * @see org.alfresco.service.cmr.action.Action#setDescription(java.lang.String) + */ + public void setDescription(String description) + { + this.description = description; + } - /** - * @see org.alfresco.service.cmr.action.Action#setExecuteAsynchronously(boolean) - */ - public void setExecuteAsynchronously(boolean executeAsynchronously) - { - this.executeAsynchronously = executeAsynchronously; - } - - /** - * @see org.alfresco.service.cmr.action.Action#getCompensatingAction() - */ - public Action getCompensatingAction() - { - return this.compensatingAction; - } - - /** - * @see org.alfresco.service.cmr.action.Action#setCompensatingAction(org.alfresco.service.cmr.action.Action) - */ - public void setCompensatingAction(Action action) - { - this.compensatingAction = action; - } + /** + * @see org.alfresco.service.cmr.action.Action#getExecuteAsychronously() + */ + public boolean getExecuteAsychronously() + { + return this.executeAsynchronously; + } - /** - * @see org.alfresco.service.cmr.action.Action#getCreatedDate() - */ - public Date getCreatedDate() - { - return this.createdDate; - } - - /** - * Set the created date - * - * @param createdDate the created date - */ - public void setCreatedDate(Date createdDate) - { - this.createdDate = createdDate; - } + /** + * @see org.alfresco.service.cmr.action.Action#setExecuteAsynchronously(boolean) + */ + public void setExecuteAsynchronously(boolean executeAsynchronously) + { + this.executeAsynchronously = executeAsynchronously; + } - /** - * @see org.alfresco.service.cmr.action.Action#getCreator() - */ - public String getCreator() - { - return this.creator; - } - - /** - * Set the creator - * - * @param creator the creator - */ - public void setCreator(String creator) - { - this.creator = creator; - } + /** + * @see org.alfresco.service.cmr.action.Action#getCompensatingAction() + */ + public Action getCompensatingAction() + { + return this.compensatingAction; + } - /** - * @see org.alfresco.service.cmr.action.Action#getModifiedDate() - */ - public Date getModifiedDate() - { - return this.modifiedDate; - } - - /** - * Set the modified date - * - * @param modifiedDate the modified date - */ - public void setModifiedDate(Date modifiedDate) - { - this.modifiedDate = modifiedDate; - } + /** + * @see org.alfresco.service.cmr.action.Action#setCompensatingAction(org.alfresco.service.cmr.action.Action) + */ + public void setCompensatingAction(Action action) + { + this.compensatingAction = action; + } - /** - * @see org.alfresco.service.cmr.action.Action#getModifier() - */ - public String getModifier() - { - return this.modifier; - } - - /** - * Set the modifier - * - * @param modifier the modifier - */ - public void setModifier(String modifier) - { - this.modifier = modifier; - } - - /** - * @see org.alfresco.service.cmr.action.Action#getActionDefinitionName() - */ + /** + * @see org.alfresco.service.cmr.action.Action#getCreatedDate() + */ + public Date getCreatedDate() + { + return this.createdDate; + } + + /** + * Set the created date + * + * @param createdDate the created date + */ + public void setCreatedDate(Date createdDate) + { + this.createdDate = createdDate; + } + + /** + * @see org.alfresco.service.cmr.action.Action#getCreator() + */ + public String getCreator() + { + return this.creator; + } + + /** + * Set the creator + * + * @param creator the creator + */ + public void setCreator(String creator) + { + this.creator = creator; + } + + /** + * @see org.alfresco.service.cmr.action.Action#getModifiedDate() + */ + public Date getModifiedDate() + { + return this.modifiedDate; + } + + /** + * Set the modified date + * + * @param modifiedDate the modified date + */ + public void setModifiedDate(Date modifiedDate) + { + this.modifiedDate = modifiedDate; + } + + /** + * @see org.alfresco.service.cmr.action.Action#getModifier() + */ + public String getModifier() + { + return this.modifier; + } + + /** + * Set the modifier + * + * @param modifier the modifier + */ + public void setModifier(String modifier) + { + this.modifier = modifier; + } + + /** + * @see org.alfresco.service.cmr.action.Action#getActionDefinitionName() + */ public String getActionDefinitionName() { - return this.actionDefinitionName; + return this.actionDefinitionName; } /** * @see org.alfresco.service.cmr.action.Action#hasActionConditions() */ - public boolean hasActionConditions() - { - return (this.actionConditions.isEmpty() == false); - } + public boolean hasActionConditions() + { + return (this.actionConditions.isEmpty() == false); + } - /** - * @see org.alfresco.service.cmr.action.Action#indexOfActionCondition(org.alfresco.service.cmr.action.ActionCondition) - */ - public int indexOfActionCondition(ActionCondition actionCondition) - { - return this.actionConditions.indexOf(actionCondition); - } + /** + * @see org.alfresco.service.cmr.action.Action#indexOfActionCondition(org.alfresco.service.cmr.action.ActionCondition) + */ + public int indexOfActionCondition(ActionCondition actionCondition) + { + return this.actionConditions.indexOf(actionCondition); + } - /** - * @see org.alfresco.service.cmr.action.Action#getActionConditions() - */ - public List getActionConditions() - { - return this.actionConditions; - } + /** + * @see org.alfresco.service.cmr.action.Action#getActionConditions() + */ + public List getActionConditions() + { + return this.actionConditions; + } - /** - * @see org.alfresco.service.cmr.action.Action#getActionCondition(int) - */ - public ActionCondition getActionCondition(int index) - { - return this.actionConditions.get(index); - } + /** + * @see org.alfresco.service.cmr.action.Action#getActionCondition(int) + */ + public ActionCondition getActionCondition(int index) + { + return this.actionConditions.get(index); + } - /** - * @see org.alfresco.service.cmr.action.Action#addActionCondition(org.alfresco.service.cmr.action.ActionCondition) - */ - public void addActionCondition(ActionCondition actionCondition) - { - this.actionConditions.add(actionCondition); - } + /** + * @see org.alfresco.service.cmr.action.Action#addActionCondition(org.alfresco.service.cmr.action.ActionCondition) + */ + public void addActionCondition(ActionCondition actionCondition) + { + this.actionConditions.add(actionCondition); + } - /** - * @see org.alfresco.service.cmr.action.Action#addActionCondition(int, org.alfresco.service.cmr.action.ActionCondition) - */ - public void addActionCondition(int index, ActionCondition actionCondition) - { - this.actionConditions.add(index, actionCondition); - } + /** + * @see org.alfresco.service.cmr.action.Action#addActionCondition(int, + * org.alfresco.service.cmr.action.ActionCondition) + */ + public void addActionCondition(int index, ActionCondition actionCondition) + { + this.actionConditions.add(index, actionCondition); + } - /** - * @see org.alfresco.service.cmr.action.Action#setActionCondition(int, org.alfresco.service.cmr.action.ActionCondition) - */ - public void setActionCondition(int index, ActionCondition actionCondition) - { - this.actionConditions.set(index, actionCondition); - } + /** + * @see org.alfresco.service.cmr.action.Action#setActionCondition(int, + * org.alfresco.service.cmr.action.ActionCondition) + */ + public void setActionCondition(int index, ActionCondition actionCondition) + { + this.actionConditions.set(index, actionCondition); + } - /** - * @see org.alfresco.service.cmr.action.Action#removeActionCondition(org.alfresco.service.cmr.action.ActionCondition) - */ - public void removeActionCondition(ActionCondition actionCondition) - { - this.actionConditions.remove(actionCondition); - } + /** + * @see org.alfresco.service.cmr.action.Action#removeActionCondition(org.alfresco.service.cmr.action.ActionCondition) + */ + public void removeActionCondition(ActionCondition actionCondition) + { + this.actionConditions.remove(actionCondition); + } + + /** + * @see org.alfresco.service.cmr.action.Action#removeAllActionConditions() + */ + public void removeAllActionConditions() + { + this.actionConditions.clear(); + } - /** - * @see org.alfresco.service.cmr.action.Action#removeAllActionConditions() - */ - public void removeAllActionConditions() - { - this.actionConditions.clear(); - } - /** * Set the action chain * - * @param actionChain the list of actions that lead to this action + * @param actionChain the list of actions that lead to this action */ public void setActionChain(Set actionChain) { this.actionChain = actionChain; } - + /** - * Get the action chain + * Get the action chain * - * @return the list of actions that lead to this action + * @return the list of actions that lead to this action */ public Set getActionChain() { @@ -400,11 +422,11 @@ public class ActionImpl extends ParameterizedItemImpl { return this.nodeRef; } - + /** * Set the node reference * - * @param nodeRef the node reference + * @param nodeRef the node reference */ public void setNodeRef(NodeRef nodeRef) { diff --git a/source/java/org/alfresco/repo/action/ActionListImpl.java b/source/java/org/alfresco/repo/action/ActionListImpl.java new file mode 100644 index 0000000000..2f2862ce0e --- /dev/null +++ b/source/java/org/alfresco/repo/action/ActionListImpl.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.action; + +import java.util.LinkedList; +import java.util.List; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionList; + +/** + * @author Nick Smith + */ +public class ActionListImpl implements ActionList +{ + /** + * Serial Version UID + */ + private static final long serialVersionUID = -1578631012627795870L; + + /** + * The action list + */ + private final List actions; + + public ActionListImpl() + { + this.actions = new LinkedList(); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#hasActions() + */ + public boolean hasActions() + { + return (this.actions.isEmpty() == false); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#addAction(org.alfresco.service.cmr.action.Action) + */ + public void addAction(A action) + { + this.actions.add(action); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#addAction(int, + * org.alfresco.service.cmr.action.Action) + */ + public void addAction(int index, A action) + { + this.actions.add(index, action); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#setAction(int, + * org.alfresco.service.cmr.action.Action) + */ + public void setAction(int index, A action) + { + this.actions.set(index, action); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#indexOfAction(org.alfresco.service.cmr.action.Action) + */ + public int indexOfAction(A action) + { + return this.actions.indexOf(action); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#getActions() + */ + public List getActions() + { + return this.actions; + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#getAction(int) + */ + public A getAction(int index) + { + return this.actions.get(index); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#removeAction(org.alfresco.service.cmr.action.Action) + */ + public void removeAction(A action) + { + this.actions.remove(action); + } + + /** + * @see org.alfresco.service.cmr.action.CompositeAction#removeAllActions() + */ + public void removeAllActions() + { + this.actions.clear(); + } + +} diff --git a/source/java/org/alfresco/repo/action/ActionServiceImpl.java b/source/java/org/alfresco/repo/action/ActionServiceImpl.java index dc5810876e..4c39ab60fc 100644 --- a/source/java/org/alfresco/repo/action/ActionServiceImpl.java +++ b/source/java/org/alfresco/repo/action/ActionServiceImpl.java @@ -16,6 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ + package org.alfresco.repo.action; import java.io.Serializable; @@ -42,6 +43,7 @@ import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ActionCondition; import org.alfresco.service.cmr.action.ActionConditionDefinition; import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionList; import org.alfresco.service.cmr.action.ActionService; import org.alfresco.service.cmr.action.ActionServiceException; import org.alfresco.service.cmr.action.CompositeAction; @@ -70,31 +72,29 @@ import org.springframework.extensions.surf.util.PropertyCheck; * * @author Roy Wetherall */ -public class ActionServiceImpl implements - ActionService, RuntimeActionService, ApplicationContextAware, - CopyServicePolicies.OnCopyNodePolicy, - CopyServicePolicies.OnCopyCompletePolicy -{ +public class ActionServiceImpl implements ActionService, RuntimeActionService, ApplicationContextAware, + CopyServicePolicies.OnCopyNodePolicy, CopyServicePolicies.OnCopyCompletePolicy +{ /** * Transaction resource name */ private static final String POST_TRANSACTION_PENDING_ACTIONS = "postTransactionPendingActions"; - + /** * Error message */ private static final String ERR_FAIL = "The action failed to execute due to an error."; - + /** * The logger */ - private static Log logger = LogFactory.getLog(ActionServiceImpl.class); - + private static Log logger = LogFactory.getLog(ActionServiceImpl.class); + /** * Thread local containing the current action chain */ ThreadLocal> currentActionChain = new ThreadLocal>(); - + /** * The application context used to retrieve defined actions */ @@ -104,37 +104,36 @@ public class ActionServiceImpl implements private DictionaryService dictionaryService; private AuthenticationContext authenticationContext; private PolicyComponent policyComponent; - + /** - * The asynchronous action execution queues - * map of name, queue + * The asynchronous action execution queues map of name, queue */ private Map asynchronousActionExecutionQueues; - + /** * Action transaction listener */ private ActionTransactionListener transactionListener = new ActionTransactionListener(this); - + /** * All the condition definitions currently registered */ private Map conditionDefinitions = new HashMap(); - + /** * All the action definitions currently registered */ - private Map actionDefinitions = new HashMap(); - + private Map actionDefinitions = new HashMap(); + /** * All the parameter constraints */ private Map parameterConstraints = new HashMap(); - + /** * Set the application context * - * @param applicationContext the application context + * @param applicationContext the application context */ public void setApplicationContext(ApplicationContext applicationContext) throws BeansException { @@ -144,37 +143,37 @@ public class ActionServiceImpl implements /** * Set the node service * - * @param nodeService the node service + * @param nodeService the node service */ public void setNodeService(NodeService nodeService) { this.nodeService = nodeService; } - + /** * Set the search service * - * @param searchService the search service + * @param searchService the search service */ public void setSearchService(SearchService searchService) { this.searchService = searchService; } - + /** * Set the authentication component * - * @param authenticationContext the authentication component + * @param authenticationContext the authentication component */ public void setAuthenticationContext(AuthenticationContext authenticationContext) { this.authenticationContext = authenticationContext; } - + /** * Set the dictionary service * - * @param dictionaryService the dictionary service + * @param dictionaryService the dictionary service */ public void setDictionaryService(DictionaryService dictionaryService) { @@ -182,8 +181,7 @@ public class ActionServiceImpl implements } /** - * - * @param policyComponent used to set up the action-based policy behaviour + * @param policyComponent used to set up the action-based policy behaviour */ public void setPolicyComponent(PolicyComponent policyComponent) { @@ -193,59 +191,55 @@ public class ActionServiceImpl implements /** * Set the asynchronous action execution queues * - * @param asynchronousActionExecutionQueue the asynchronous action execution queues + * @param asynchronousActionExecutionQueue the asynchronous action execution + * queues */ public void setAsynchronousActionExecutionQueues( - Map asynchronousActionExecutionQueues) + Map asynchronousActionExecutionQueues) { this.asynchronousActionExecutionQueues = asynchronousActionExecutionQueues; - } - + } + public void init() { PropertyCheck.mandatory(this, "policyComponent", policyComponent); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), - ActionModel.TYPE_ACTION_PARAMETER, - new JavaBehaviour(this, "getCopyCallback")); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), - ActionModel.TYPE_ACTION_PARAMETER, - new JavaBehaviour(this, "onCopyComplete")); + this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + ActionModel.TYPE_ACTION_PARAMETER, new JavaBehaviour(this, "getCopyCallback")); + this.policyComponent.bindClassBehaviour(QName.createQName(NamespaceService.ALFRESCO_URI, "onCopyComplete"), + ActionModel.TYPE_ACTION_PARAMETER, new JavaBehaviour(this, "onCopyComplete")); } - + /** * Gets the saved action folder reference * - * @param nodeRef the node reference - * @return the node reference + * @param nodeRef the node reference + * @return the node reference */ private NodeRef getSavedActionFolderRef(NodeRef nodeRef) { - List assocs = this.nodeService.getChildAssocs( - nodeRef, - RegexQNamePattern.MATCH_ALL, - ActionModel.ASSOC_ACTION_FOLDER); + List assocs = this.nodeService.getChildAssocs(nodeRef, RegexQNamePattern.MATCH_ALL, + ActionModel.ASSOC_ACTION_FOLDER); if (assocs.size() != 1) { throw new ActionServiceException("Unable to retrieve the saved action folder reference."); } - + return assocs.get(0).getChildRef(); } - + /** * @see org.alfresco.service.cmr.action.ActionService#getActionDefinition(java.lang.String) */ public ActionDefinition getActionDefinition(String name) { - // get direct access to action definition (i.e. ignoring public flag of executer) + // get direct access to action definition (i.e. ignoring public flag of + // executer) ActionDefinition definition = null; Object bean = this.applicationContext.getBean(name); if (bean != null && bean instanceof ActionExecuter) { - ActionExecuter executer = (ActionExecuter)bean; + ActionExecuter executer = (ActionExecuter) bean; definition = executer.getActionDefinition(); } return definition; @@ -257,8 +251,8 @@ public class ActionServiceImpl implements public List getActionDefinitions() { return new ArrayList(this.actionDefinitions.values()); - } - + } + /** * @see org.alfresco.service.cmr.action.ActionService#getActionDefinitions(org.alfresco.service.cmr.repository.NodeRef) */ @@ -270,7 +264,8 @@ public class ActionServiceImpl implements } else { - // TODO for now we will only filter by type, we will introduce filtering by aspect later + // TODO for now we will only filter by type, we will introduce + // filtering by aspect later QName nodeType = this.nodeService.getType(nodeRef); List result = new ArrayList(); for (ActionDefinition actionDefinition : getActionDefinitions()) @@ -292,9 +287,9 @@ public class ActionServiceImpl implements result.add(actionDefinition); } } - + return result; - } + } } /** @@ -312,7 +307,7 @@ public class ActionServiceImpl implements { return new ArrayList(this.conditionDefinitions.values()); } - + /** * @see org.alfresco.service.cmr.action.ActionService#getParameterConstraint(java.lang.String) */ @@ -320,7 +315,7 @@ public class ActionServiceImpl implements { return this.parameterConstraints.get(name); } - + /** * @see org.alfresco.service.cmr.action.ActionService#getParameterConstraints() */ @@ -328,7 +323,7 @@ public class ActionServiceImpl implements { return new ArrayList(this.parameterConstraints.values()); } - + /** * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String) */ @@ -337,16 +332,17 @@ public class ActionServiceImpl implements if (logger.isDebugEnabled()) logger.debug("Creating Action Condition - [" + name + "]"); - if (CompositeActionCondition.COMPOSITE_CONDITION.equals(name)) + if (CompositeActionCondition.COMPOSITE_CONDITION.equals(name)) { return new CompositeActionConditionImpl(GUID.generate()); } - + return new ActionConditionImpl(GUID.generate(), name); } /** - * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String, java.util.Map) + * @see org.alfresco.service.cmr.action.ActionService#createActionCondition(java.lang.String, + * java.util.Map) */ public ActionCondition createActionCondition(String name, Map params) { @@ -360,11 +356,12 @@ public class ActionServiceImpl implements */ public Action createAction(String name) { - return new ActionImpl(null, GUID.generate(),name, null); + return new ActionImpl(null, GUID.generate(), name, null); } - + /** - * @see org.alfresco.service.cmr.action.ActionService#createAction(java.lang.String, java.util.Map) + * @see org.alfresco.service.cmr.action.ActionService#createAction(java.lang.String, + * java.util.Map) */ public Action createAction(String name, Map params) { @@ -384,43 +381,48 @@ public class ActionServiceImpl implements /** * @see org.alfresco.service.cmr.action.ActionService#createCompositeActionCondition() */ - public CompositeActionCondition createCompositeActionCondition() + public CompositeActionCondition createCompositeActionCondition() { return new CompositeActionConditionImpl(GUID.generate()); } /** - * @see org.alfresco.service.cmr.action.ActionService#evaluateAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + * @see org.alfresco.service.cmr.action.ActionService#evaluateAction(org.alfresco.service.cmr.action.Action, + * org.alfresco.service.cmr.repository.NodeRef) */ public boolean evaluateAction(Action action, NodeRef actionedUponNodeRef) { boolean result = true; - + if (action.hasActionConditions() == true) { List actionConditions = action.getActionConditions(); for (ActionCondition condition : actionConditions) { boolean tempresult = evaluateActionCondition(condition, actionedUponNodeRef); - + if (logger.isDebugEnabled()) - logger.debug("\tCondition " + condition.getActionConditionDefinitionName() + " Result - " + tempresult); - + logger.debug("\tCondition " + condition.getActionConditionDefinitionName() + " Result - " + + tempresult); + result = result && tempresult; } } - + if (logger.isDebugEnabled()) logger.debug("\tAll Condition Evaluation Result - " + result); - + return result; } - + /** - * Evaluates the actions by finding corresponding actionEvaluators in applicationContext (registered through Spring). - * Composite conditions are evaluated here as well. It is also possible to have composite actions inside composite actions. + * Evaluates the actions by finding corresponding actionEvaluators in + * applicationContext (registered through Spring). Composite conditions are + * evaluated here as well. It is also possible to have composite actions + * inside composite actions. * - * @see org.alfresco.service.cmr.action.ActionService#evaluateActionCondition(org.alfresco.service.cmr.action.ActionCondition, org.alfresco.service.cmr.repository.NodeRef) + * @see org.alfresco.service.cmr.action.ActionService#evaluateActionCondition(org.alfresco.service.cmr.action.ActionCondition, + * org.alfresco.service.cmr.repository.NodeRef) */ public boolean evaluateActionCondition(ActionCondition condition, NodeRef actionedUponNodeRef) { @@ -429,15 +431,16 @@ public class ActionServiceImpl implements CompositeActionCondition compositeCondition = (CompositeActionCondition) condition; if (logger.isDebugEnabled()) { - logger.debug("Evaluating Composite Condition - BOOLEAN CONDITION IS " + (compositeCondition.isORCondition()?"OR":"AND")); + logger.debug("Evaluating Composite Condition - BOOLEAN CONDITION IS " + + (compositeCondition.isORCondition() ? "OR" : "AND")); } - - if (!compositeCondition.hasActionConditions()) + + if (!compositeCondition.hasActionConditions()) { - throw new IllegalStateException("CompositeActionCondition has no subconditions."); + throw new IllegalStateException("CompositeActionCondition has no subconditions."); } - - boolean result ; + + boolean result; if (compositeCondition.isORCondition()) { result = false; @@ -446,70 +449,78 @@ public class ActionServiceImpl implements { result = true; } - - for (ActionCondition simplecondition : compositeCondition.getActionConditions()) + + for (ActionCondition simplecondition : compositeCondition.getActionConditions()) { if (logger.isDebugEnabled()) { - logger.debug("Evaluating composite condition " + simplecondition.getActionConditionDefinitionName()); + logger + .debug("Evaluating composite condition " + + simplecondition.getActionConditionDefinitionName()); } - - if (compositeCondition.isORCondition()) + + if (compositeCondition.isORCondition()) { result = result || evaluateSimpleCondition(simplecondition, actionedUponNodeRef); - - //Short circuit for the OR condition - if (result) break; - } - else + + // Short circuit for the OR condition + if (result) + break; + } + else { result = result && evaluateSimpleCondition(simplecondition, actionedUponNodeRef); - //Short circuit for the AND condition - if (!result) break; + // Short circuit for the AND condition + if (!result) + break; } } - + if (compositeCondition.getInvertCondition()) { - return !result; + return !result; } else { return result; } - } + } else { return evaluateSimpleCondition(condition, actionedUponNodeRef); } } - private boolean evaluateSimpleCondition(ActionCondition condition, NodeRef actionedUponNodeRef) + private boolean evaluateSimpleCondition(ActionCondition condition, NodeRef actionedUponNodeRef) { if (logger.isDebugEnabled()) { - logger.debug("Evaluating simple condition " + condition.getActionConditionDefinitionName()); + logger.debug("Evaluating simple condition " + condition.getActionConditionDefinitionName()); } // Evaluate the condition - ActionConditionEvaluator evaluator = (ActionConditionEvaluator)this.applicationContext.getBean(condition.getActionConditionDefinitionName()); + ActionConditionEvaluator evaluator = (ActionConditionEvaluator) this.applicationContext.getBean(condition + .getActionConditionDefinitionName()); return evaluator.evaluate(condition, actionedUponNodeRef); } - + /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, + * org.alfresco.service.cmr.repository.NodeRef, boolean) */ public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions) { executeAction(action, actionedUponNodeRef, checkConditions, action.getExecuteAsychronously()); - } - + } + /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean) + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, + * org.alfresco.service.cmr.repository.NodeRef, boolean) */ - public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, boolean executeAsychronously) + public void executeAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, + boolean executeAsychronously) { Set actionChain = this.currentActionChain.get(); - + if (executeAsychronously == false) { executeActionImpl(action, actionedUponNodeRef, checkConditions, false, actionChain); @@ -520,7 +531,7 @@ public class ActionServiceImpl implements addPostTransactionPendingAction(action, actionedUponNodeRef, checkConditions, actionChain); } } - + /** * called by transaction service. */ @@ -528,29 +539,24 @@ public class ActionServiceImpl implements { for (PendingAction pendingAction : getPostTransactionPendingActions()) { - queueAction(pendingAction); + queueAction(pendingAction); } - } - + } + /** * */ - private void queueAction(PendingAction action) + private void queueAction(PendingAction action) { // Get the right queue AsynchronousActionExecutionQueue queue = getQueue(action.action); // Queue the action for execution - queue.executeAction( - this, - action.getAction(), - action.getActionedUponNodeRef(), - action.getCheckConditions(), - action.getActionChain()); + queue.executeAction(this, action.getAction(), action.getActionedUponNodeRef(), action.getCheckConditions(), + action.getActionChain()); } - + /** - * * @param compensatingAction * @param actionedUponNodeRef */ @@ -558,44 +564,42 @@ public class ActionServiceImpl implements { // Get the right queue AsynchronousActionExecutionQueue queue = getQueue(compensatingAction); - + // Queue the action for execution - queue.executeAction(this, compensatingAction, actionedUponNodeRef, false, null); + queue.executeAction(this, compensatingAction, actionedUponNodeRef, false, null); } - + private AsynchronousActionExecutionQueue getQueue(Action action) { - ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); - AsynchronousActionExecutionQueue queue = null; - + ActionExecuter executer = (ActionExecuter) this.applicationContext.getBean(action.getActionDefinitionName()); + AsynchronousActionExecutionQueue queue = null; + String queueName = executer.getQueueName(); - if(queueName == null) + if (queueName == null) { queue = asynchronousActionExecutionQueues.get(""); } - else + else { queue = asynchronousActionExecutionQueues.get(queueName); } - if(queue == null) + if (queue == null) { // can't get queue - throw new ActionServiceException("Unable to get AsynchronousActionExecutionQueue name: "+ queueName); + throw new ActionServiceException("Unable to get AsynchronousActionExecutionQueue name: " + queueName); } - + return queue; } /** - * @see org.alfresco.repo.action.RuntimeActionService#executeActionImpl(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef, boolean, org.alfresco.service.cmr.repository.NodeRef) + * @see org.alfresco.repo.action.RuntimeActionService#executeActionImpl(org.alfresco.service.cmr.action.Action, + * org.alfresco.service.cmr.repository.NodeRef, boolean, + * org.alfresco.service.cmr.repository.NodeRef) */ - public void executeActionImpl( - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - boolean executedAsynchronously, - Set actionChain) - { + public void executeActionImpl(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, + boolean executedAsynchronously, Set actionChain) + { if (logger.isDebugEnabled() == true) { StringBuilder builder = new StringBuilder("Execute action impl action chain = "); @@ -609,28 +613,29 @@ public class ActionServiceImpl implements { builder.append(value).append(" "); } - } + } logger.debug(builder.toString()); logger.debug("Current action = " + action.getId()); } - - // get the current user early in case the process fails and we are unable to do it later + + // get the current user early in case the process fails and we are + // unable to do it later String currentUserName = this.authenticationContext.getCurrentUserName(); - + if (actionChain == null || actionChain.contains(action.getId()) == false) { if (logger.isDebugEnabled() == true) { logger.debug("Doing executeActionImpl"); } - + try { Set origActionChain = null; - + if (actionChain == null) { - actionChain = new HashSet(); + actionChain = new HashSet(); } else { @@ -638,12 +643,12 @@ public class ActionServiceImpl implements } actionChain.add(action.getId()); this.currentActionChain.set(actionChain); - + if (logger.isDebugEnabled() == true) { logger.debug("Adding " + action.getId() + " to action chain."); } - + try { // Check and execute now @@ -663,7 +668,7 @@ public class ActionServiceImpl implements { this.currentActionChain.set(origActionChain); } - + if (logger.isDebugEnabled() == true) { logger.debug("Resetting the action chain."); @@ -672,52 +677,58 @@ public class ActionServiceImpl implements } catch (Throwable exception) { - // DH: No logging of the exception. Leave the logging decision to the client code, - // which can handle the rethrown exception. + // DH: No logging of the exception. Leave the logging decision + // to the client code, + // which can handle the rethrown exception. if (executedAsynchronously == true) - { - // If one is specified, queue the compensating action ready for execution + { + // If one is specified, queue the compensating action ready + // for execution Action compensatingAction = action.getCompensatingAction(); if (compensatingAction != null) - { + { // Set the current user - ((ActionImpl)compensatingAction).setRunAsUser(currentUserName); - queueAction(compensatingAction, actionedUponNodeRef); + ((ActionImpl) compensatingAction).setRunAsUser(currentUserName); + queueAction(compensatingAction, actionedUponNodeRef); } } - + // Rethrow the exception if (exception instanceof RuntimeException) { - throw (RuntimeException)exception; + throw (RuntimeException) exception; } else { throw new ActionServiceException(ERR_FAIL, exception); } - + } } } /** - * @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action, org.alfresco.service.cmr.repository.NodeRef) + * @see org.alfresco.repo.action.RuntimeActionService#directActionExecution(org.alfresco.service.cmr.action.Action, + * org.alfresco.service.cmr.repository.NodeRef) */ public void directActionExecution(Action action, NodeRef actionedUponNodeRef) { // Debug output if (logger.isDebugEnabled() == true) { - logger.debug("The action is being executed as the user: " + this.authenticationContext.getCurrentUserName()); + logger + .debug("The action is being executed as the user: " + + this.authenticationContext.getCurrentUserName()); } - + // Get the action executer and execute - ActionExecuter executer = (ActionExecuter)this.applicationContext.getBean(action.getActionDefinitionName()); + ActionExecuter executer = (ActionExecuter) this.applicationContext.getBean(action.getActionDefinitionName()); executer.execute(action, actionedUponNodeRef); } /** - * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, NodeRef) + * @see org.alfresco.service.cmr.action.ActionService#executeAction(org.alfresco.service.cmr.action.Action, + * NodeRef) */ public void executeAction(Action action, NodeRef actionedUponNodeRef) { @@ -727,7 +738,7 @@ public class ActionServiceImpl implements /** * @see org.alfresco.repo.action.RuntimeActionService#registerActionConditionEvaluator(org.alfresco.repo.action.evaluator.ActionConditionEvaluator) */ - public void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator) + public void registerActionConditionEvaluator(ActionConditionEvaluator actionConditionEvaluator) { ActionConditionDefinition cond = actionConditionEvaluator.getActionConditionDefintion(); this.conditionDefinitions.put(cond.getName(), cond); @@ -736,12 +747,12 @@ public class ActionServiceImpl implements /** * @see org.alfresco.repo.action.RuntimeActionService#registerActionExecuter(org.alfresco.repo.action.executer.ActionExecuter) */ - public void registerActionExecuter(ActionExecuter actionExecuter) + public void registerActionExecuter(ActionExecuter actionExecuter) { ActionDefinition action = actionExecuter.getActionDefinition(); this.actionDefinitions.put(action.getName(), action); } - + /** * @see org.alfresco.repo.action.RuntimeActionService#registerParameterConstraint(org.alfresco.service.cmr.action.ParameterConstraint) */ @@ -749,119 +760,117 @@ public class ActionServiceImpl implements { this.parameterConstraints.put(parameterConstraint.getName(), parameterConstraint); } - + /** * Gets the action node ref from the action id * - * @param nodeRef the node reference - * @param actionId the action id - * @return the action node reference + * @param nodeRef the node reference + * @param actionId the action id + * @return the action node reference */ private NodeRef getActionNodeRefFromId(NodeRef nodeRef, String actionId) { NodeRef result = null; - + if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) { DynamicNamespacePrefixResolver namespacePrefixResolver = new DynamicNamespacePrefixResolver(); - namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, NamespaceService.SYSTEM_MODEL_1_0_URI); - - List nodeRefs = searchService.selectNodes( - getSavedActionFolderRef(nodeRef), - "*[@sys:" + ContentModel.PROP_NODE_UUID.getLocalName() + "='" + actionId + "']", - null, - namespacePrefixResolver, - false); + namespacePrefixResolver.registerNamespace(NamespaceService.SYSTEM_MODEL_PREFIX, + NamespaceService.SYSTEM_MODEL_1_0_URI); + + List nodeRefs = searchService.selectNodes(getSavedActionFolderRef(nodeRef), "*[@sys:" + + ContentModel.PROP_NODE_UUID.getLocalName() + "='" + actionId + "']", null, + namespacePrefixResolver, false); if (nodeRefs.size() != 0) { result = nodeRefs.get(0); } } - + return result; } /** - * @see org.alfresco.service.cmr.action.ActionService#saveAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + * @see org.alfresco.service.cmr.action.ActionService#saveAction(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.cmr.action.Action) */ public void saveAction(NodeRef nodeRef, Action action) { NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); if (actionNodeRef == null) - { + { if (this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == false) { // Apply the actionable aspect this.nodeService.addAspect(nodeRef, ActionModel.ASPECT_ACTIONS, null); } - + // Create the action and reference - actionNodeRef = createActionNodeRef(action, - getSavedActionFolderRef(nodeRef), - ContentModel.ASSOC_CONTAINS, - ActionModel.ASSOC_NAME_ACTIONS); - } + actionNodeRef = createActionNodeRef(action, getSavedActionFolderRef(nodeRef), ContentModel.ASSOC_CONTAINS, + ActionModel.ASSOC_NAME_ACTIONS); + } saveActionImpl(actionNodeRef, action); } - + public NodeRef createActionNodeRef(Action action, NodeRef parentNodeRef, QName assocTypeName, QName assocName) { Map props = new HashMap(2); props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); props.put(ContentModel.PROP_NODE_UUID, action.getId()); - + QName actionType = ActionModel.TYPE_ACTION; - if(action instanceof CompositeAction) + if (action instanceof ActionList) { actionType = ActionModel.TYPE_COMPOSITE_ACTION; } - + // Create the action node - NodeRef actionNodeRef = this.nodeService.createNode( - parentNodeRef, - assocTypeName, - assocName, - actionType, - props).getChildRef(); - + NodeRef actionNodeRef = this.nodeService.createNode(parentNodeRef, assocTypeName, assocName, actionType, props) + .getChildRef(); + // Update the created details and the node reference - ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); - ((ActionImpl)action).setNodeRef(actionNodeRef); - - return actionNodeRef; + ((ActionImpl) action).setCreator((String) this.nodeService + .getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); + ((ActionImpl) action).setCreatedDate((Date) this.nodeService.getProperty(actionNodeRef, + ContentModel.PROP_CREATED)); + ((ActionImpl) action).setNodeRef(actionNodeRef); + + return actionNodeRef; } - + /** - * @see org.alfresco.repo.action.RuntimeActionService#saveActionImpl(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + * @see org.alfresco.repo.action.RuntimeActionService#saveActionImpl(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.cmr.action.Action) */ public void saveActionImpl(NodeRef actionNodeRef, Action action) { // Save action properties saveActionProperties(actionNodeRef, action); - + // Update the parameters of the action saveParameters(actionNodeRef, action); - + // Update the conditions of the action saveConditions(actionNodeRef, action); - - if (action instanceof CompositeAction) + + if (action instanceof ActionList) { // Update composite action - saveCompositeActions(actionNodeRef, (CompositeAction)action); + saveCompositeActions(actionNodeRef, (ActionList) action); } - + // Update the modified details - ((ActionImpl)action).setModifier((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIER)); - ((ActionImpl)action).setModifiedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_MODIFIED)); + ((ActionImpl) action).setModifier((String) this.nodeService.getProperty(actionNodeRef, + ContentModel.PROP_MODIFIER)); + ((ActionImpl) action).setModifiedDate((Date) this.nodeService.getProperty(actionNodeRef, + ContentModel.PROP_MODIFIED)); } /** * Save the action property values * - * @param actionNodeRef the action node reference - * @param action the action + * @param actionNodeRef the action node reference + * @param action the action */ private void saveActionProperties(NodeRef actionNodeRef, Action action) { @@ -871,27 +880,34 @@ public class ActionServiceImpl implements props.put(ActionModel.PROP_ACTION_DESCRIPTION, action.getDescription()); props.put(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY, action.getExecuteAsychronously()); this.nodeService.setProperties(actionNodeRef, props); - - // Update the compensating action (model should enforce the singularity of this association) + + // Update the compensating action (model should enforce the singularity + // of this association) Action compensatingAction = action.getCompensatingAction(); - List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); + List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, + ActionModel.ASSOC_COMPENSATING_ACTION); if (assocs.size() == 0) { if (compensatingAction != null) { - //Map props2 = new HashMap(2); - //props2.put(ActionModel.PROP_DEFINITION_NAME, compensatingAction.getActionDefinitionName()); - //props2.put(ContentModel.PROP_NODE_UUID, compensatingAction.getId()); - - //NodeRef compensatingActionNodeRef = this.nodeService.createNode( - /// actionNodeRef, - // ActionModel.ASSOC_COMPENSATING_ACTION, - // ActionModel.ASSOC_COMPENSATING_ACTION, - // ActionModel.TYPE_ACTION, - // props2).getChildRef(); - + // Map props2 = new HashMap(2); + // props2.put(ActionModel.PROP_DEFINITION_NAME, + // compensatingAction.getActionDefinitionName()); + // props2.put(ContentModel.PROP_NODE_UUID, + // compensatingAction.getId()); + + // NodeRef compensatingActionNodeRef = + // this.nodeService.createNode( + // / actionNodeRef, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.ASSOC_COMPENSATING_ACTION, + // ActionModel.TYPE_ACTION, + // props2).getChildRef(); + // Create the compensating node reference - NodeRef compensatingActionNodeRef = createActionNodeRef(compensatingAction, actionNodeRef, ActionModel.ASSOC_COMPENSATING_ACTION, ActionModel.ASSOC_COMPENSATING_ACTION); + NodeRef compensatingActionNodeRef = createActionNodeRef(compensatingAction, actionNodeRef, + ActionModel.ASSOC_COMPENSATING_ACTION, ActionModel.ASSOC_COMPENSATING_ACTION); saveActionImpl(compensatingActionNodeRef, compensatingAction); } } @@ -912,22 +928,23 @@ public class ActionServiceImpl implements /** * Save the actions of a composite action * - * @param compositeActionNodeRef the node reference of the composite action - * @param compositeAction the composite action + * @param compositeActionNodeRef the node reference of the composite action + * @param action2 the composite action */ - private void saveCompositeActions(NodeRef compositeActionNodeRef, CompositeAction compositeAction) + private void saveCompositeActions(NodeRef compositeActionNodeRef, ActionList action2) { // TODO Need a way of sorting the order of the actions Map idToAction = new HashMap(); List orderedIds = new ArrayList(); - for (Action action : compositeAction.getActions()) - { + for (Action action : action2.getActions()) + { idToAction.put(action.getId(), action); orderedIds.add(action.getId()); } - - List actionRefs = this.nodeService.getChildAssocs(compositeActionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); + + List actionRefs = this.nodeService.getChildAssocs(compositeActionNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); for (ChildAssociationRef actionRef : actionRefs) { NodeRef actionNodeRef = actionRef.getChildRef(); @@ -939,70 +956,68 @@ public class ActionServiceImpl implements else { // Update the action - Action action = idToAction.get(actionNodeRef.getId()); + Action action = idToAction.get(actionNodeRef.getId()); saveActionImpl(actionNodeRef, action); orderedIds.remove(actionNodeRef.getId()); } - + } - + // Create the actions remaining for (String actionId : orderedIds) { Action action = idToAction.get(actionId); - + Map props = new HashMap(2); props.put(ActionModel.PROP_DEFINITION_NAME, action.getActionDefinitionName()); props.put(ContentModel.PROP_NODE_UUID, action.getId()); - - NodeRef actionNodeRef = this.nodeService.createNode( - compositeActionNodeRef, - ActionModel.ASSOC_ACTIONS, - ActionModel.ASSOC_ACTIONS, - ActionModel.TYPE_ACTION, - props).getChildRef(); - + + NodeRef actionNodeRef = this.nodeService.createNode(compositeActionNodeRef, ActionModel.ASSOC_ACTIONS, + ActionModel.ASSOC_ACTIONS, ActionModel.TYPE_ACTION, props).getChildRef(); + // Update the created details and the node reference - ((ActionImpl)action).setCreator((String)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)this.nodeService.getProperty(actionNodeRef, ContentModel.PROP_CREATED)); - ((ActionImpl)action).setNodeRef(actionNodeRef); - + ((ActionImpl) action).setCreator((String) this.nodeService.getProperty(actionNodeRef, + ContentModel.PROP_CREATOR)); + ((ActionImpl) action).setCreatedDate((Date) this.nodeService.getProperty(actionNodeRef, + ContentModel.PROP_CREATED)); + ((ActionImpl) action).setNodeRef(actionNodeRef); + saveActionImpl(actionNodeRef, action); } } /** - * Saves the conditions associated with an action. + * Saves the conditions associated with an action. * - * @param actionNodeRef the action node reference - * @param action the action + * @param actionNodeRef the action node reference + * @param action the action */ private void saveConditions(NodeRef actionNodeRef, Action action) { // TODO Need a way of sorting out the order of the conditions - List actionConditionsList = action.getActionConditions(); + List actionConditionsList = action.getActionConditions(); saveActionConditionList(actionNodeRef, actionConditionsList, false); } - private void saveActionConditionList(NodeRef actionNodeRef, - List actionConditionsList, boolean isComposite) + private void saveActionConditionList(NodeRef actionNodeRef, List actionConditionsList, + boolean isComposite) { if (logger.isDebugEnabled()) - logger.debug("SaveActionCondition list, "+ actionConditionsList.size() + - (isComposite?" Composite":"") + " conditions to be saved"); - + logger.debug("SaveActionCondition list, " + actionConditionsList.size() + (isComposite ? " Composite" : "") + + " conditions to be saved"); + Map idToCondition = new HashMap(); List orderedIds = new ArrayList(); - + for (ActionCondition actionCondition : actionConditionsList) - { + { idToCondition.put(actionCondition.getId(), actionCondition); orderedIds.add(actionCondition.getId()); } - - List conditionRefs = this.nodeService.getChildAssocs( - actionNodeRef, RegexQNamePattern.MATCH_ALL, - !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); + + List conditionRefs = this.nodeService.getChildAssocs(actionNodeRef, + RegexQNamePattern.MATCH_ALL, !isComposite ? ActionModel.ASSOC_CONDITIONS + : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); for (ChildAssociationRef conditionRef : conditionRefs) { @@ -1013,67 +1028,6 @@ public class ActionServiceImpl implements this.nodeService.removeChild(actionNodeRef, conditionNodeRef); } else - { - saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - // Update the conditions parameters - saveParameters(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); - orderedIds.remove(conditionNodeRef.getId()); - } - } - - // Create the conditions remaining - for (String nextId : orderedIds) - { - ActionCondition actionCondition = idToCondition.get(nextId); - - if (!isComposite && actionCondition instanceof CompositeActionCondition) - { - if (logger.isDebugEnabled()) - logger.debug("Saving Composite Condition"); - - NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, - ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_COMPOSITE_ACTION_CONDITION); - saveActionConditionList(conditionNodeRef, ((CompositeActionCondition) actionCondition).getActionConditions(), true); - } - else - { - if (logger.isDebugEnabled()) - logger.debug("Saving Condition " + actionCondition.getActionConditionDefinitionName()); - - @SuppressWarnings("unused") - NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, - !isComposite? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION, - ActionModel.TYPE_ACTION_CONDITION); - } - } - } - - /* - private void saveCompositeActionConditionList(NodeRef compositeConditionRef, - List actionConditionsList) - { - if (logger.isDebugEnabled()) - logger.debug("SaveActionCondition list Composite, "+ actionConditionsList.size() + " conditions to be saved"); - - Map idToCondition = new HashMap(); - List orderedIds = new ArrayList(); - - for (ActionCondition actionCondition : actionConditionsList) - { - idToCondition.put(actionCondition.getId(), actionCondition); - orderedIds.add(actionCondition.getId()); - } - - List conditionRefs = this.nodeService.getChildAssocs(compositeConditionRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); - for (ChildAssociationRef conditionRef : conditionRefs) - { - NodeRef conditionNodeRef = conditionRef.getChildRef(); - if (idToCondition.containsKey(conditionNodeRef.getId()) == false) - { - // Delete the condition - this.nodeService.removeChild(compositeConditionRef, conditionNodeRef); - } - else { saveConditionProperties(conditionNodeRef, idToCondition.get(conditionNodeRef.getId())); // Update the conditions parameters @@ -1081,31 +1035,73 @@ public class ActionServiceImpl implements orderedIds.remove(conditionNodeRef.getId()); } } - + // Create the conditions remaining for (String nextId : orderedIds) { ActionCondition actionCondition = idToCondition.get(nextId); - NodeRef conditionNodeRef = saveActionCondition(compositeConditionRef, actionCondition, ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_ACTION_CONDITION); - + + if (!isComposite && actionCondition instanceof CompositeActionCondition) + { + if (logger.isDebugEnabled()) + logger.debug("Saving Composite Condition"); + + NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, + ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_COMPOSITE_ACTION_CONDITION); + saveActionConditionList(conditionNodeRef, ((CompositeActionCondition) actionCondition) + .getActionConditions(), true); + } + else + { + if (logger.isDebugEnabled()) + logger.debug("Saving Condition " + actionCondition.getActionConditionDefinitionName()); + + @SuppressWarnings("unused") + NodeRef conditionNodeRef = saveActionCondition(actionNodeRef, actionCondition, + !isComposite ? ActionModel.ASSOC_CONDITIONS : ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION, + ActionModel.TYPE_ACTION_CONDITION); + } } - }*/ - - - private NodeRef saveActionCondition(NodeRef actionNodeRef, - ActionCondition actionCondition, QName AssociationQName, QName typeName) + } + + /* + * private void saveCompositeActionConditionList(NodeRef + * compositeConditionRef, List actionConditionsList) { if + * (logger.isDebugEnabled()) + * logger.debug("SaveActionCondition list Composite, "+ + * actionConditionsList.size() + " conditions to be saved"); Map idToCondition = new HashMap(); + * List orderedIds = new ArrayList(); for (ActionCondition + * actionCondition : actionConditionsList) { + * idToCondition.put(actionCondition.getId(), actionCondition); + * orderedIds.add(actionCondition.getId()); } List + * conditionRefs = this.nodeService.getChildAssocs(compositeConditionRef, + * RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); for + * (ChildAssociationRef conditionRef : conditionRefs) { NodeRef + * conditionNodeRef = conditionRef.getChildRef(); if + * (idToCondition.containsKey(conditionNodeRef.getId()) == false) { // + * Delete the condition this.nodeService.removeChild(compositeConditionRef, + * conditionNodeRef); } else { saveConditionProperties(conditionNodeRef, + * idToCondition.get(conditionNodeRef.getId())); // Update the conditions + * parameters saveParameters(conditionNodeRef, + * idToCondition.get(conditionNodeRef.getId())); + * orderedIds.remove(conditionNodeRef.getId()); } } // Create the conditions + * remaining for (String nextId : orderedIds) { ActionCondition + * actionCondition = idToCondition.get(nextId); NodeRef conditionNodeRef = + * saveActionCondition(compositeConditionRef, actionCondition, + * ActionModel.ASSOC_CONDITIONS, ActionModel.TYPE_ACTION_CONDITION); } } + */ + + private NodeRef saveActionCondition(NodeRef actionNodeRef, ActionCondition actionCondition, QName AssociationQName, + QName typeName) { Map props = new HashMap(2); props.put(ActionModel.PROP_DEFINITION_NAME, actionCondition.getActionConditionDefinitionName()); props.put(ContentModel.PROP_NODE_UUID, actionCondition.getId()); - NodeRef conditionNodeRef = this.nodeService.createNode( - actionNodeRef, - AssociationQName, - AssociationQName, - typeName, - props).getChildRef(); - + NodeRef conditionNodeRef = this.nodeService.createNode(actionNodeRef, AssociationQName, AssociationQName, + typeName, props).getChildRef(); + saveConditionProperties(conditionNodeRef, actionCondition); saveParameters(conditionNodeRef, actionCondition); return conditionNodeRef; @@ -1119,40 +1115,42 @@ public class ActionServiceImpl implements */ private void saveConditionProperties(NodeRef conditionNodeRef, ActionCondition condition) { - this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT, condition.getInvertCondition()); - - if (condition instanceof CompositeActionCondition) + this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT, condition + .getInvertCondition()); + + if (condition instanceof CompositeActionCondition) { if (logger.isDebugEnabled()) { - logger.debug("SAVING OR = " + ((CompositeActionCondition)condition).isORCondition()); + logger.debug("SAVING OR = " + ((CompositeActionCondition) condition).isORCondition()); } - this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_ANDOR, new Boolean(((CompositeActionCondition)condition).isORCondition())); + this.nodeService.setProperty(conditionNodeRef, ActionModel.PROP_CONDITION_ANDOR, new Boolean( + ((CompositeActionCondition) condition).isORCondition())); } } /** * Saves the parameters associated with an action or condition * - * @param parameterizedNodeRef the parameterized item node reference - * @param item the parameterized item + * @param parameterizedNodeRef the parameterized item node reference + * @param item the parameterized item */ private void saveParameters(NodeRef parameterizedNodeRef, ParameterizedItem item) { Map parameterMap = new HashMap(); parameterMap.putAll(item.getParameterValues()); - - List parameters = this.nodeService.getChildAssocs(parameterizedNodeRef, - ActionModel.ASSOC_PARAMETERS, ActionModel.ASSOC_PARAMETERS); + + List parameters = this.nodeService.getChildAssocs(parameterizedNodeRef, + ActionModel.ASSOC_PARAMETERS, ActionModel.ASSOC_PARAMETERS); for (ChildAssociationRef ref : parameters) { NodeRef paramNodeRef = ref.getChildRef(); Map nodeRefParameterMap = this.nodeService.getProperties(paramNodeRef); - String paramName = (String)nodeRefParameterMap.get(ActionModel.PROP_PARAMETER_NAME); + String paramName = (String) nodeRefParameterMap.get(ActionModel.PROP_PARAMETER_NAME); if (parameterMap.containsKey(paramName) == false) { // Delete parameter from node ref - this.nodeService.removeChild(parameterizedNodeRef, paramNodeRef); + this.nodeService.removeChild(parameterizedNodeRef, paramNodeRef); } else { @@ -1162,23 +1160,19 @@ public class ActionServiceImpl implements parameterMap.remove(paramName); } } - + // Add any remaining parameters for (Map.Entry entry : parameterMap.entrySet()) { Map nodeRefProperties = new HashMap(2); nodeRefProperties.put(ActionModel.PROP_PARAMETER_NAME, entry.getKey()); nodeRefProperties.put(ActionModel.PROP_PARAMETER_VALUE, entry.getValue()); - - this.nodeService.createNode( - parameterizedNodeRef, - ActionModel.ASSOC_PARAMETERS, - ActionModel.ASSOC_PARAMETERS, - ActionModel.TYPE_ACTION_PARAMETER, - nodeRefProperties); + + this.nodeService.createNode(parameterizedNodeRef, ActionModel.ASSOC_PARAMETERS, + ActionModel.ASSOC_PARAMETERS, ActionModel.TYPE_ACTION_PARAMETER, nodeRefProperties); } } - + // TODO: Add copy behaviour /** @@ -1187,108 +1181,112 @@ public class ActionServiceImpl implements public List getActions(NodeRef nodeRef) { List result = new ArrayList(); - - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + + if (this.nodeService.exists(nodeRef) == true + && this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) { - List actions = this.nodeService.getChildAssocs( - getSavedActionFolderRef(nodeRef), - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS); + List actions = this.nodeService.getChildAssocs(getSavedActionFolderRef(nodeRef), + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS); for (ChildAssociationRef action : actions) { NodeRef actionNodeRef = action.getChildRef(); result.add(createAction(actionNodeRef)); } } - + return result; } /** * Create an action from the action node reference * - * @param actionNodeRef the action node reference - * @return the action + * @param actionNodeRef the action node reference + * @return the action */ public Action createAction(NodeRef actionNodeRef) { Action result = null; - + Map properties = this.nodeService.getProperties(actionNodeRef); - + QName actionType = this.nodeService.getType(actionNodeRef); if (ActionModel.TYPE_COMPOSITE_ACTION.equals(actionType) == true) { // Create a composite action result = new CompositeActionImpl(actionNodeRef, actionNodeRef.getId()); - populateCompositeAction(actionNodeRef, (CompositeAction)result); + populateCompositeAction(actionNodeRef, (CompositeAction) result); } else { // Create an action - result = new ActionImpl(actionNodeRef, actionNodeRef.getId(), (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); + result = new ActionImpl(actionNodeRef, actionNodeRef.getId(), (String) properties + .get(ActionModel.PROP_DEFINITION_NAME)); populateAction(actionNodeRef, result); } - + return result; } /** * Populate the details of the action from the node reference * - * @param actionNodeRef the action node reference - * @param action the action + * @param actionNodeRef the action node reference + * @param action the action */ private void populateAction(NodeRef actionNodeRef, Action action) { // Populate the action properties populateActionProperties(actionNodeRef, action); - + // Set the parameters populateParameters(actionNodeRef, action); - - // Set the conditions - List conditions = this.nodeService.getChildAssocs(actionNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); - - if (logger.isDebugEnabled()) - logger.debug("Retrieving " + (conditions==null ? " null" : conditions.size()) + " conditions"); - for (ChildAssociationRef condition : conditions) + // Set the conditions + List conditions = this.nodeService.getChildAssocs(actionNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_CONDITIONS); + + if (logger.isDebugEnabled()) + logger.debug("Retrieving " + (conditions == null ? " null" : conditions.size()) + " conditions"); + + if (conditions != null) { - NodeRef conditionNodeRef = condition.getChildRef(); - action.addActionCondition(createActionCondition(conditionNodeRef)); + for (ChildAssociationRef condition : conditions) + { + NodeRef conditionNodeRef = condition.getChildRef(); + action.addActionCondition(createActionCondition(conditionNodeRef)); + } } } /** * Populates the action properties from the node reference - * - * @param actionNodeRef the action node reference - * @param action the action + * + * @param actionNodeRef the action node reference + * @param action the action */ private void populateActionProperties(NodeRef actionNodeRef, Action action) { Map props = this.nodeService.getProperties(actionNodeRef); - - action.setTitle((String)props.get(ActionModel.PROP_ACTION_TITLE)); - action.setDescription((String)props.get(ActionModel.PROP_ACTION_DESCRIPTION)); - + + action.setTitle((String) props.get(ActionModel.PROP_ACTION_TITLE)); + action.setDescription((String) props.get(ActionModel.PROP_ACTION_DESCRIPTION)); + boolean value = false; - Boolean executeAsynchronously = (Boolean)props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); - if (executeAsynchronously != null) + Boolean executeAsynchronously = (Boolean) props.get(ActionModel.PROP_EXECUTE_ASYNCHRONOUSLY); + if (executeAsynchronously != null) { value = executeAsynchronously.booleanValue(); } - action.setExecuteAsynchronously(value); - - ((ActionImpl)action).setCreator((String)props.get(ContentModel.PROP_CREATOR)); - ((ActionImpl)action).setCreatedDate((Date)props.get(ContentModel.PROP_CREATED)); - ((ActionImpl)action).setModifier((String)props.get(ContentModel.PROP_MODIFIER)); - ((ActionImpl)action).setModifiedDate((Date)props.get(ContentModel.PROP_MODIFIED)); - + action.setExecuteAsynchronously(value); + + ((ActionImpl) action).setCreator((String) props.get(ContentModel.PROP_CREATOR)); + ((ActionImpl) action).setCreatedDate((Date) props.get(ContentModel.PROP_CREATED)); + ((ActionImpl) action).setModifier((String) props.get(ContentModel.PROP_MODIFIER)); + ((ActionImpl) action).setModifiedDate((Date) props.get(ContentModel.PROP_MODIFIED)); + // Get the compensating action - List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPENSATING_ACTION); + List assocs = this.nodeService.getChildAssocs(actionNodeRef, RegexQNamePattern.MATCH_ALL, + ActionModel.ASSOC_COMPENSATING_ACTION); if (assocs.size() != 0) { Action compensatingAction = createAction(assocs.get(0).getChildRef()); @@ -1297,29 +1295,30 @@ public class ActionServiceImpl implements } /** - * Populate the parameters of a parameterized item from the parameterized item node reference + * Populate the parameters of a parameterized item from the parameterized + * item node reference * - * @param parameterizedItemNodeRef the parameterized item node reference - * @param parameterizedItem the parameterized item + * @param parameterizedItemNodeRef the parameterized item node reference + * @param parameterizedItem the parameterized item */ private void populateParameters(NodeRef parameterizedItemNodeRef, ParameterizedItem parameterizedItem) { - List parameters = this.nodeService.getChildAssocs(parameterizedItemNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); + List parameters = this.nodeService.getChildAssocs(parameterizedItemNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_PARAMETERS); for (ChildAssociationRef parameter : parameters) { NodeRef parameterNodeRef = parameter.getChildRef(); Map properties = this.nodeService.getProperties(parameterNodeRef); - parameterizedItem.setParameterValue( - (String)properties.get(ActionModel.PROP_PARAMETER_NAME), - properties.get(ActionModel.PROP_PARAMETER_VALUE)); + parameterizedItem.setParameterValue((String) properties.get(ActionModel.PROP_PARAMETER_NAME), properties + .get(ActionModel.PROP_PARAMETER_VALUE)); } } - + /** * Creates an action condition from an action condition node reference * - * @param conditionNodeRef the condition node reference - * @return the action condition + * @param conditionNodeRef the condition node reference + * @return the action condition */ private ActionCondition createActionCondition(NodeRef conditionNodeRef) { @@ -1328,12 +1327,12 @@ public class ActionServiceImpl implements Map properties = this.nodeService.getProperties(conditionNodeRef); QName conditionType = this.nodeService.getType(conditionNodeRef); - + ActionCondition condition = null; - if (ActionModel.TYPE_COMPOSITE_ACTION_CONDITION.equals(conditionType) == false) + if (ActionModel.TYPE_COMPOSITE_ACTION_CONDITION.equals(conditionType) == false) { - condition = new ActionConditionImpl(conditionNodeRef.getId(), - (String)properties.get(ActionModel.PROP_DEFINITION_NAME)); + condition = new ActionConditionImpl(conditionNodeRef.getId(), (String) properties + .get(ActionModel.PROP_DEFINITION_NAME)); } else { @@ -1341,26 +1340,27 @@ public class ActionServiceImpl implements { logger.debug("\tRetrieving Composite Condition from repository"); } - + // Create a composite condition CompositeActionCondition compositeCondition = new CompositeActionConditionImpl(GUID.generate()); populateCompositeActionCondition(conditionNodeRef, compositeCondition); - + condition = compositeCondition; } - Boolean invert = (Boolean)this.nodeService.getProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT); - condition.setInvertCondition(invert == null? false: invert.booleanValue()); + Boolean invert = (Boolean) this.nodeService.getProperty(conditionNodeRef, ActionModel.PROP_CONDITION_INVERT); + condition.setInvertCondition(invert == null ? false : invert.booleanValue()); populateParameters(conditionNodeRef, condition); return condition; } - - private void populateCompositeActionCondition(NodeRef compositeNodeRef, CompositeActionCondition compositeActionCondition) + + private void populateCompositeActionCondition(NodeRef compositeNodeRef, + CompositeActionCondition compositeActionCondition) { - List conditions = this.nodeService.getChildAssocs(compositeNodeRef, - RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); - + List conditions = this.nodeService.getChildAssocs(compositeNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_COMPOSITE_ACTION_CONDITION); + Boolean OR = (Boolean) this.nodeService.getProperty(compositeNodeRef, ActionModel.PROP_CONDITION_ANDOR); if (logger.isDebugEnabled()) @@ -1368,48 +1368,49 @@ public class ActionServiceImpl implements logger.debug("\tPopulating Composite Condition with subconditions, Condition OR = " + OR); } - compositeActionCondition.setORCondition(OR == null? false: OR.booleanValue()); + compositeActionCondition.setORCondition(OR == null ? false : OR.booleanValue()); for (ChildAssociationRef conditionNodeRef : conditions) { NodeRef actionNodeRef = conditionNodeRef.getChildRef(); ActionCondition currentCondition = createActionCondition(actionNodeRef); - + if (logger.isDebugEnabled()) logger.debug("\t\tAdding subcondition " + currentCondition.getActionConditionDefinitionName()); - + compositeActionCondition.addActionCondition(currentCondition); - } + } } - /** * Populates a composite action from a composite action node reference * - * @param compositeNodeRef the composite action node reference - * @param compositeAction the composite action + * @param compositeNodeRef the composite action node reference + * @param compositeAction the composite action */ public void populateCompositeAction(NodeRef compositeNodeRef, CompositeAction compositeAction) { populateAction(compositeNodeRef, compositeAction); - - List actions = this.nodeService.getChildAssocs(compositeNodeRef, RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); + + List actions = this.nodeService.getChildAssocs(compositeNodeRef, + RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_ACTIONS); for (ChildAssociationRef action : actions) { NodeRef actionNodeRef = action.getChildRef(); compositeAction.addAction(createAction(actionNodeRef)); - } + } } /** - * @see org.alfresco.service.cmr.action.ActionService#getAction(org.alfresco.service.cmr.repository.NodeRef, java.lang.String) + * @see org.alfresco.service.cmr.action.ActionService#getAction(org.alfresco.service.cmr.repository.NodeRef, + * java.lang.String) */ public Action getAction(NodeRef nodeRef, String actionId) { Action result = null; - - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + + if (this.nodeService.exists(nodeRef) == true + && this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) { NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, actionId); if (actionNodeRef != null) @@ -1417,24 +1418,25 @@ public class ActionServiceImpl implements result = createAction(actionNodeRef); } } - + return result; } /** - * @see org.alfresco.service.cmr.action.ActionService#removeAction(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.action.Action) + * @see org.alfresco.service.cmr.action.ActionService#removeAction(org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.cmr.action.Action) */ public void removeAction(NodeRef nodeRef, Action action) { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + if (this.nodeService.exists(nodeRef) == true + && this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) { NodeRef actionNodeRef = getActionNodeRefFromId(nodeRef, action.getId()); if (actionNodeRef != null) { this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), actionNodeRef); } - } + } } /** @@ -1442,30 +1444,30 @@ public class ActionServiceImpl implements */ public void removeAllActions(NodeRef nodeRef) { - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) + if (this.nodeService.exists(nodeRef) == true + && this.nodeService.hasAspect(nodeRef, ActionModel.ASPECT_ACTIONS) == true) { - List actions = new ArrayList(this.nodeService.getChildAssocs(getSavedActionFolderRef(nodeRef), RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS)); + List actions = new ArrayList(this.nodeService.getChildAssocs( + getSavedActionFolderRef(nodeRef), RegexQNamePattern.MATCH_ALL, ActionModel.ASSOC_NAME_ACTIONS)); for (ChildAssociationRef action : actions) { this.nodeService.removeChild(getSavedActionFolderRef(nodeRef), action.getChildRef()); } - } + } } - + /** - * Add a pending action to the list to be queued for execution once the transaction is completed. + * Add a pending action to the list to be queued for execution once the + * transaction is completed. * - * @param action the action - * @param actionedUponNodeRef the actioned upon node reference - * @param checkConditions indicates whether to check the conditions before execution + * @param action the action + * @param actionedUponNodeRef the actioned upon node reference + * @param checkConditions indicates whether to check the conditions before + * execution */ @SuppressWarnings("unchecked") - private void addPostTransactionPendingAction( - Action action, - NodeRef actionedUponNodeRef, - boolean checkConditions, - Set actionChain) + private void addPostTransactionPendingAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, + Set actionChain) { if (logger.isDebugEnabled() == true) { @@ -1480,55 +1482,56 @@ public class ActionServiceImpl implements { builder.append(value).append(" "); } - } + } logger.debug(builder.toString()); logger.debug("Current action = " + action.getId()); } - + // Don't continue if the action is already in the action chain if (actionChain == null || actionChain.contains(action.getId()) == false) - { + { if (logger.isDebugEnabled() == true) { logger.debug("Doing addPostTransactionPendingAction"); } - + // Set the run as user to the current user if (logger.isDebugEnabled() == true) { logger.debug("The current user is: " + this.authenticationContext.getCurrentUserName()); } - ((ActionImpl)action).setRunAsUser(this.authenticationContext.getCurrentUserName()); - + ((ActionImpl) action).setRunAsUser(this.authenticationContext.getCurrentUserName()); + // Ensure that the transaction listener is bound to the transaction AlfrescoTransactionSupport.bindListener(this.transactionListener); - + // Add the pending action to the transaction resource - List pendingActions = (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); + List pendingActions = (List) AlfrescoTransactionSupport + .getResource(POST_TRANSACTION_PENDING_ACTIONS); if (pendingActions == null) { pendingActions = new ArrayList(); AlfrescoTransactionSupport.bindResource(POST_TRANSACTION_PENDING_ACTIONS, pendingActions); } - + // Check that action has only been added to the list once PendingAction pendingAction = new PendingAction(action, actionedUponNodeRef, checkConditions, actionChain); if (pendingActions.contains(pendingAction) == false) { pendingActions.add(pendingAction); - } + } } } - + /** * @see org.alfresco.repo.action.RuntimeActionService#getPostTransactionPendingActions() */ @SuppressWarnings("unchecked") private List getPostTransactionPendingActions() { - return (List)AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); + return (List) AlfrescoTransactionSupport.getResource(POST_TRANSACTION_PENDING_ACTIONS); } - + /** * Pending action details class */ @@ -1538,80 +1541,83 @@ public class ActionServiceImpl implements * The action */ private Action action; - + /** * The actioned upon node reference */ private NodeRef actionedUponNodeRef; - + /** - * Indicates whether the conditions should be checked before the action is executed + * Indicates whether the conditions should be checked before the action + * is executed */ private boolean checkConditions; - + private Set actionChain; - + /** - * Constructor + * Constructor * - * @param action the action - * @param actionedUponNodeRef the actioned upon node reference - * @param checkConditions indicated whether the conditions need to be checked + * @param action the action + * @param actionedUponNodeRef the actioned upon node reference + * @param checkConditions indicated whether the conditions need to be + * checked */ - public PendingAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, Set actionChain) + public PendingAction(Action action, NodeRef actionedUponNodeRef, boolean checkConditions, + Set actionChain) { this.action = action; this.actionedUponNodeRef = actionedUponNodeRef; this.checkConditions = checkConditions; this.actionChain = actionChain; } - + /** * Get the action * - * @return the action + * @return the action */ public Action getAction() { return action; } - + /** * Get the actioned upon node reference * - * @return the actioned upon node reference + * @return the actioned upon node reference */ public NodeRef getActionedUponNodeRef() { return actionedUponNodeRef; } - + /** * Get the check conditions value * - * @return indicates whether the condition should be checked + * @return indicates whether the condition should be checked */ public boolean getCheckConditions() { return this.checkConditions; } - + public Set getActionChain() { return this.actionChain; } - + /** * @see java.lang.Object#hashCode() */ @Override public int hashCode() { - int hashCode = 37 * this.actionedUponNodeRef.hashCode(); + int hashCode = 37 * this.actionedUponNodeRef.hashCode(); hashCode += 37 * this.action.hashCode(); return hashCode; } - + /** * @see java.lang.Object#equals(java.lang.Object) */ @@ -1633,28 +1639,29 @@ public class ActionServiceImpl implements } } } + /** - * @return Returns {@link AdctionParameterTypeCopyBehaviourCallback} + * @return Returns {@link AdctionParameterTypeCopyBehaviourCallback} */ public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) { return AdctionParameterTypeCopyBehaviourCallback.INSTANCE; } - + /** - * Records any potential d:noderef properties for once the copy is done. + * Records any potential d:noderef properties for once the copy is + * done. * * @author Derek Hulley * @since 3.2 */ private static class AdctionParameterTypeCopyBehaviourCallback extends DefaultCopyBehaviourCallback { - private static final AdctionParameterTypeCopyBehaviourCallback INSTANCE = - new AdctionParameterTypeCopyBehaviourCallback(); + private static final AdctionParameterTypeCopyBehaviourCallback INSTANCE = new AdctionParameterTypeCopyBehaviourCallback(); @Override - public Map getCopyProperties( - QName classQName, CopyDetails copyDetails, Map properties) + public Map getCopyProperties(QName classQName, CopyDetails copyDetails, + Map properties) { NodeRef sourceNodeRef = copyDetails.getSourceNodeRef(); recordNodeRefsForRepointing(sourceNodeRef, properties, ActionModel.PROP_PARAMETER_VALUE); @@ -1662,23 +1669,15 @@ public class ActionServiceImpl implements return properties; } } - + /** - * Ensures that d:noderef properties are repointed if the target was also copied as part of the - * hierarchy. + * Ensures that d:noderef properties are repointed if the target was + * also copied as part of the hierarchy. */ - public void onCopyComplete( - QName classRef, - NodeRef sourceNodeRef, - NodeRef targetNodeRef, - boolean copyToNewNode, - Map copyMap) + public void onCopyComplete(QName classRef, NodeRef sourceNodeRef, NodeRef targetNodeRef, boolean copyToNewNode, + Map copyMap) { - AdctionParameterTypeCopyBehaviourCallback.INSTANCE.repointNodeRefs( - sourceNodeRef, - targetNodeRef, - ActionModel.PROP_PARAMETER_VALUE, - copyMap, - nodeService); + AdctionParameterTypeCopyBehaviourCallback.INSTANCE.repointNodeRefs(sourceNodeRef, targetNodeRef, + ActionModel.PROP_PARAMETER_VALUE, copyMap, nodeService); } } diff --git a/source/java/org/alfresco/repo/action/CompositeActionImpl.java b/source/java/org/alfresco/repo/action/CompositeActionImpl.java index ee08cc735d..7ce561b807 100644 --- a/source/java/org/alfresco/repo/action/CompositeActionImpl.java +++ b/source/java/org/alfresco/repo/action/CompositeActionImpl.java @@ -16,13 +16,14 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ + package org.alfresco.repo.action; -import java.util.ArrayList; import java.util.List; import org.alfresco.repo.action.executer.CompositeActionExecuter; import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionList; import org.alfresco.service.cmr.action.CompositeAction; import org.alfresco.service.cmr.repository.NodeRef; @@ -33,96 +34,106 @@ import org.alfresco.service.cmr.repository.NodeRef; */ public class CompositeActionImpl extends ActionImpl implements CompositeAction { - /** - * Serial version UID - */ - private static final long serialVersionUID = -5348203599304776812L; - - /** - * The action list - */ - private List actions = new ArrayList(); - - /** - * Constructor - * - * @param id the action id - */ - public CompositeActionImpl(NodeRef nodeRef, String id) - { - super(nodeRef, id, CompositeActionExecuter.NAME); - } + /** + * Serial version UID + */ + private static final long serialVersionUID = -5348203599304776812L; - /** - * @see org.alfresco.service.cmr.action.CompositeAction#hasActions() - */ - public boolean hasActions() - { - return (this.actions.isEmpty() == false); - } + private final ActionList actions = new ActionListImpl(); - /** - * @see org.alfresco.service.cmr.action.CompositeAction#addAction(org.alfresco.service.cmr.action.Action) - */ - public void addAction(Action action) - { - this.actions.add(action); - } + /** + * Constructor + * + * @param id the action id + */ + public CompositeActionImpl(NodeRef nodeRef, String id) + { + super(nodeRef, id, CompositeActionExecuter.NAME); + } - /** - * @see org.alfresco.service.cmr.action.CompositeAction#addAction(int, org.alfresco.service.cmr.action.Action) - */ - public void addAction(int index, Action action) - { - this.actions.add(index, action); - } + /** + * @param action + * @see org.alfresco.service.cmr.action.ActionList#addAction(org.alfresco.service.cmr.action.Action) + */ + public void addAction(Action action) + { + this.actions.addAction(action); + } - /** - * @see org.alfresco.service.cmr.action.CompositeAction#setAction(int, org.alfresco.service.cmr.action.Action) - */ - public void setAction(int index, Action action) - { - this.actions.set(index, action); - } + /** + * @param index + * @param action + * @see org.alfresco.service.cmr.action.ActionList#addAction(int, + * org.alfresco.service.cmr.action.Action) + */ + public void addAction(int index, Action action) + { + this.actions.addAction(index, action); + } - /** - * @see org.alfresco.service.cmr.action.CompositeAction#indexOfAction(org.alfresco.service.cmr.action.Action) - */ - public int indexOfAction(Action action) - { - return this.actions.indexOf(action); - } + /** + * @param index + * @return + * @see org.alfresco.service.cmr.action.ActionList#getAction(int) + */ + public Action getAction(int index) + { + return this.actions.getAction(index); + } - /** - * @see org.alfresco.service.cmr.action.CompositeAction#getActions() - */ - public List getActions() - { - return this.actions; - } + /** + * @return + * @see org.alfresco.service.cmr.action.ActionList#getActions() + */ + public List getActions() + { + return this.actions.getActions(); + } - /** - * @see org.alfresco.service.cmr.action.CompositeAction#getAction(int) - */ - public Action getAction(int index) - { - return this.actions.get(index); - } + /** + * @return + * @see org.alfresco.service.cmr.action.ActionList#hasActions() + */ + public boolean hasActions() + { + return this.actions.hasActions(); + } - /** - * @see org.alfresco.service.cmr.action.CompositeAction#removeAction(org.alfresco.service.cmr.action.Action) - */ - public void removeAction(Action action) - { - this.actions.remove(action); - } + /** + * @param action + * @return + * @see org.alfresco.service.cmr.action.ActionList#indexOfAction(org.alfresco.service.cmr.action.Action) + */ + public int indexOfAction(Action action) + { + return this.actions.indexOfAction(action); + } - /** - * @see org.alfresco.service.cmr.action.CompositeAction#removeAllActions() - */ - public void removeAllActions() - { - this.actions.clear(); - } + /** + * @param action + * @see org.alfresco.service.cmr.action.ActionList#removeAction(org.alfresco.service.cmr.action.Action) + */ + public void removeAction(Action action) + { + this.actions.removeAction(action); + } + /** + * @see org.alfresco.service.cmr.action.ActionList#removeAllActions() + */ + public void removeAllActions() + { + this.actions.removeAllActions(); + } + + /** + * @param index + * @param action + * @see org.alfresco.service.cmr.action.ActionList#setAction(int, + * org.alfresco.service.cmr.action.Action) + */ + public void setAction(int index, Action action) + { + this.actions.setAction(index, action); + } } diff --git a/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java b/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java index a1cf45c930..fc278940d8 100644 --- a/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java +++ b/source/java/org/alfresco/repo/action/ParameterizedItemImpl.java @@ -29,6 +29,7 @@ import org.alfresco.service.cmr.action.ParameterizedItem; * * @author Roy Wetherall */ +@SuppressWarnings("serial") public abstract class ParameterizedItemImpl implements ParameterizedItem, Serializable { /** diff --git a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java index 5fbff070f3..cb322faea7 100644 --- a/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java +++ b/source/java/org/alfresco/repo/action/executer/ActionExecuterAbstractBase.java @@ -98,7 +98,7 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra { if (this.actionDefinition == null) { - this.actionDefinition = new ActionDefinitionImpl(this.name); + this.actionDefinition = createActionDefinition(this.name); ((ActionDefinitionImpl)this.actionDefinition).setTitleKey(getTitleKey()); ((ActionDefinitionImpl)this.actionDefinition).setDescriptionKey(getDescriptionKey()); ((ActionDefinitionImpl)this.actionDefinition).setAdhocPropertiesAllowed(getAdhocPropertiesAllowed()); @@ -109,6 +109,18 @@ public abstract class ActionExecuterAbstractBase extends ParameterizedItemAbstra return this.actionDefinition; } + /** + * This method returns an instance of an ActionDefinition implementation class. By default + * this will be an {@link ActionDefinitionImpl}, but this could be overridden. + * + * @param name + * @return + */ + protected ActionDefinition createActionDefinition(String name) + { + return new ActionDefinitionImpl(name); + } + /** * @see org.alfresco.repo.action.executer.ActionExecuter#execute(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.repository.NodeRef) */ diff --git a/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java new file mode 100644 index 0000000000..2013090650 --- /dev/null +++ b/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java @@ -0,0 +1,169 @@ +/* + * 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.admin.patch.impl; + +import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.domain.qname.QNameDAO; +import org.alfresco.repo.importer.ImporterBootstrap; +import org.alfresco.repo.search.Indexer; +import org.alfresco.repo.search.IndexerAndSearcher; +import org.alfresco.repo.search.impl.lucene.LuceneQueryParser; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.QName; +import org.springframework.extensions.surf.util.I18NUtil; + +/** + * A patch to update the value of a QName. + * This patch will only succeed if the target QName has not been used i.e. if there is no content + * that actually references the QName. + *

+ * A property 'reindexClass' can be optionally injected. If it is not injected then the QName is + * updated and no reindexing is requested by this patch. + * If it is set to either 'TYPE' or 'ASPECT' (as appropriate) then that String will be used to + * locate out-of-date references to the old QName and have them reindexed in a targetted way. + *

+ * Please refer to the implementation in this class for the details of how this is achieved. + * + * @author Neil McErlean + */ +public class QNamePatch extends AbstractPatch +{ + private static final String MSG_SUCCESS = "patch.QNamePatch.result"; + + /* Injected properties */ + private String qnameStringBefore; + private String qnameStringAfter; + private String reindexClass; + + /* Injected services */ + private ImporterBootstrap importerBootstrap; + private IndexerAndSearcher indexerAndSearcher; + private QNameDAO qnameDAO; + + /** + * Sets the importerBootstrap. + * @param importerBootstrap. + */ + public void setImporterBootstrap(ImporterBootstrap importerBootstrap) + { + this.importerBootstrap = importerBootstrap; + } + + /** + * Sets the IndexerAndSearcher. + * @param indexerAndSearcher + */ + public void setIndexerAndSearcher(IndexerAndSearcher indexerAndSearcher) + { + this.indexerAndSearcher = indexerAndSearcher; + } + + /** + * Sets the QNameDAO. + * @param qnameDAO + */ + public void setQnameDAO(QNameDAO qnameDAO) + { + this.qnameDAO = qnameDAO; + } + + /** + * Sets the QName to be patched. + * @param qnameStringBefore the long-form QName to be patched from. {namespaceURI}localName + */ + public void setQnameBefore(String qnameStringBefore) + { + this.qnameStringBefore = qnameStringBefore; + } + + /** + * Sets the new QName value to be used. + * @param qnameStringAfter the long-form QName to be patched to. {namespaceURI}localName + */ + public void setQnameAfter(String qnameStringAfter) + { + this.qnameStringAfter = qnameStringAfter; + } + + /** + * Sets a value for the class to reindex. This will be used in the Lucene query below and + * should be either "TYPE" or "ASPECT" or not set if reindexing is not required. + * @param reindexClass "TYPE" or "ASPECT" or not set. + */ + public void setReindexClass(String reindexClass) + { + this.reindexClass = reindexClass; + } + + @Override + protected String applyInternal() throws Exception + { + // We don't need to catch the potential InvalidQNameException here as it will be caught + // in AbstractPatch and correctly handled there + QName qnameBefore = QName.createQName(this.qnameStringBefore); + QName qnameAfter = QName.createQName(this.qnameStringAfter); + + if (qnameDAO.getQName(qnameBefore) != null) + { + qnameDAO.updateQName(qnameBefore, qnameAfter); + } + + // Optionally perform a focussed reindexing of the removed QName. + if ("TYPE".equals(reindexClass) || + "ASPECT".equals(reindexClass)) + { + reindex(reindexClass + ":" + LuceneQueryParser.escape(qnameStringBefore), importerBootstrap.getStoreRef()); + } + + return I18NUtil.getMessage(MSG_SUCCESS, qnameBefore, qnameAfter); + } + + private int reindex(String query, StoreRef store) + { + SearchParameters sp = new SearchParameters(); + sp.setLanguage(SearchService.LANGUAGE_LUCENE); + sp.setQuery(query); + sp.addStore(store); + Indexer indexer = indexerAndSearcher.getIndexer(store); + ResultSet rs = null; + int count = 0; + try + { + rs = searchService.query(sp); + count = rs.length(); + for (ResultSetRow row : rs) + { + indexer.updateNode(row.getNodeRef()); + } + } + finally + { + if (rs != null) + { + rs.close(); + } + } + return count; + } +} diff --git a/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java b/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java index 281f72fab7..8bf5ba48cd 100644 --- a/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java +++ b/source/java/org/alfresco/repo/content/metadata/xml/XmlMetadataExtracter.java @@ -16,6 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ + package org.alfresco.repo.content.metadata.xml; import java.io.Serializable; @@ -26,37 +27,38 @@ import java.util.List; import java.util.Map; import java.util.Set; -import org.alfresco.repo.content.selector.ContentWorkerSelector; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.metadata.AbstractMappingMetadataExtracter; import org.alfresco.repo.content.metadata.MetadataExtracter; +import org.alfresco.repo.content.selector.ContentWorkerSelector; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.namespace.QName; -import org.springframework.extensions.surf.util.PropertyCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.PropertyCheck; /** * A metadata extractor that selects an appropiate workder for the extraction. *

- * The {@linkplain #setSelectors(List) selectors} are used to find an extracter most - * appropriate of a given XML document. The chosen extracter is then asked to extract - * the values, passing through the {@linkplain MetadataExtracter.OverwritePolicy overwrite policy} - * as {@linkplain #setOverwritePolicy(String)} on this instance. The overwrite policy of the - * embedded extracters is not relevant unless they are used separately in another context. + * The {@linkplain #setSelectors(List) selectors} are used to find an extracter + * most appropriate of a given XML document. The chosen extracter is then asked + * to extract the values, passing through the + * {@linkplain MetadataExtracter.OverwritePolicy overwrite policy} as + * {@linkplain #setOverwritePolicy(String)} on this instance. The overwrite + * policy of the embedded extracters is not relevant unless they are used + * separately in another context. * * @see ContentWorkerSelector * @see MetadataExtracter - * * @since 2.1 * @author Derek Hulley */ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter { - public static String[] SUPPORTED_MIMETYPES = new String[] {MimetypeMap.MIMETYPE_XML}; - + public static String[] SUPPORTED_MIMETYPES = new String[] { MimetypeMap.MIMETYPE_XML }; + private static Log logger = LogFactory.getLog(XPathMetadataExtracter.class); - + private List> selectors; /** @@ -68,11 +70,11 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter } /** - * Sets the list of metadata selectors to use to find the extracter to use, given - * some content. The evaluations are done in the order that they occur in the - * list. + * Sets the list of metadata selectors to use to find the extracter to use, + * given some content. The evaluations are done in the order that they occur + * in the list. * - * @param selectors A list of selectors + * @param selectors A list of selectors */ public void setSelectors(List> selectors) { @@ -88,9 +90,10 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter } /** - * It is not possible to have any default mappings, but something has to be returned. + * It is not possible to have any default mappings, but something has to be + * returned. * - * @return Returns an empty map + * @return Returns an empty map */ @Override protected Map> getDefaultMapping() @@ -102,25 +105,22 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter * Selects and extracter to perform the work and redirects to it. */ @Override - public Map extract( - ContentReader reader, - OverwritePolicy overwritePolicy, - Map destination, - Map> mapping) + public Map extract(ContentReader reader, OverwritePolicy overwritePolicy, + Map destination, Map> mapping) { // Check the content length if (reader.getSize() == 0) { - // There is no content. We don't spoof any properties so there can be nothing extracted. + // There is no content. We don't spoof any properties so there can + // be nothing extracted. if (logger.isDebugEnabled()) { - logger.debug("\n" + - "XML document has zero length, so bypassing extraction: \n" + - " Document: " + reader); + logger.debug("\n" + "XML document has zero length, so bypassing extraction: \n" + " Document: " + + reader); } return destination; } - + MetadataExtracter extracter = null; // Select a worker for (ContentWorkerSelector selector : selectors) @@ -138,9 +138,8 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter { if (reader.isChannelOpen()) { - logger.error("Content reader not closed by MetadataExtractor selector: \n" + - " reader: " + reader + "\n" + - " selector: " + selector); + logger.error("Content reader not closed by MetadataExtractor selector: \n" + " reader: " + + reader + "\n" + " selector: " + selector); } } // Just take the first successful one @@ -148,10 +147,8 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter { if (logger.isDebugEnabled()) { - logger.debug("\n" + - "Found metadata extracter to process XML document: \n" + - " Selector: " + selector + "\n" + - " Document: " + reader); + logger.debug("\n" + "Found metadata extracter to process XML document: \n" + " Selector: " + + selector + "\n" + " Document: " + reader); } break; } @@ -162,9 +159,7 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter { if (logger.isDebugEnabled()) { - logger.debug("\n" + - "No working metadata extractor could be found: \n" + - " Document: " + reader); + logger.debug("\n" + "No working metadata extractor could be found: \n" + " Document: " + reader); } // There will be no properties extracted modifiedProperties = destination; @@ -180,26 +175,22 @@ public class XmlMetadataExtracter extends AbstractMappingMetadataExtracter { if (reader.isChannelOpen()) { - logger.error("Content reader not closed by MetadataExtractor: \n" + - " Reader: " + reader + "\n" + - " extracter: " + extracter); + logger.error("Content reader not closed by MetadataExtractor: \n" + " Reader: " + reader + "\n" + + " extracter: " + extracter); } } } // Done if (logger.isDebugEnabled()) { - logger.debug("\n" + - "XML metadata extractor redirected: \n" + - " Reader: " + reader + "\n" + - " Extracter: " + extracter + "\n" + - " Metadata: " + modifiedProperties); + logger.debug("\n" + "XML metadata extractor redirected: \n" + " Reader: " + reader + "\n" + + " Extracter: " + extracter + "\n" + " Metadata: " + modifiedProperties); } return modifiedProperties; } /** - * This is not required as the + * This is not required as the */ protected Map extractRaw(ContentReader reader) throws Throwable { diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageCropOptions.java b/source/java/org/alfresco/repo/content/transform/magick/ImageCropOptions.java new file mode 100644 index 0000000000..4570a967b0 --- /dev/null +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageCropOptions.java @@ -0,0 +1,175 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.content.transform.magick; + +/** + * DTO used to store options for ImageMagick cropping. + * + * @author Nick Smith + */ +public class ImageCropOptions +{ + private int height = -1; + private int width = -1; + private int xOffset = 0; + private int yOffset = 0; + private boolean isPercentageCrop = false; + private String gravity = null; + + /** + * Gets the height of the cropped image. By default this is in pixels but if + * isPercentageCrop is set to true then it changes to + * percentage. + * + * @return the height + */ + public int getHeight() + { + return this.height; + } + + /** + * Sets the height of the cropped image. By default this is in pixels but if + * isPercentageCrop is set to true then it changes to + * percentage. + * + * @param height the height to set + */ + public void setHeight(int height) + { + this.height = height; + } + + /** + * Sets the width of the cropped image. By default this is in pixels but if + * isPercentageCrop is set to true then it changes to + * percentage. + * + * @return the width + */ + public int getWidth() + { + return this.width; + } + + /** + * Sets the width of the cropped image. By default this is in pixels but if + * isPercentageCrop is set to true then it changes to + * percentage. + * + * @param width the width to set + */ + public void setWidth(int width) + { + this.width = width; + } + + /** + * Gets the horizontal offset. By default this starts fromt he top-left + * corner of the image and moves right, however the gravity + * property can change this. + * + * @return the xOffset + */ + public int getXOffset() + { + return this.xOffset; + } + + /** + * Sets the horizontal offset. By default this starts fromt he top-left + * corner of the image and moves right, however the gravity + * property can change this. + * + * @param xOffset the xOffset to set + */ + public void setXOffset(int xOffset) + { + this.xOffset = xOffset; + } + + /** + * Gets the vertical offset. By default this starts fromt he top-left corner + * of the image and moves down, however the gravity property + * can change this. + * + * @return the yOffset + */ + public int getYOffset() + { + return this.yOffset; + } + + /** + * Sets the vertical offset. By default this starts fromt he top-left corner + * of the image and moves down, however the gravity property + * can change this. + * + * @param yOffset the yOffset to set + */ + public void setYOffset(int yOffset) + { + this.yOffset = yOffset; + } + + /** + * @return the isPercentageCrop + */ + public boolean isPercentageCrop() + { + return this.isPercentageCrop; + } + + /** + * @param isPercentageCrop the isPercentageCrop to set + */ + public void setPercentageCrop(boolean isPercentageCrop) + { + this.isPercentageCrop = isPercentageCrop; + } + + /** + * Sets the 'gravity' which determines how the offset is applied. It affects + * both the origin of offset and the direction(s). + * + * @param gravity the gravity to set + */ + public void setGravity(String gravity) + { + this.gravity = gravity; + } + + /** + * Gets the 'gravity' which determines how the offset is applied. It affects + * both the origin of offset and the direction(s). + * + * @return the gravity + */ + public String getGravity() + { + return this.gravity; + } +} 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 6a736e4725..73e6c3a7e7 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageMagickContentTransformerWorker.java @@ -149,8 +149,13 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont if (options instanceof ImageTransformationOptions) { ImageTransformationOptions imageOptions = (ImageTransformationOptions)options; + ImageCropOptions cropOptions = imageOptions.getCropOptions(); ImageResizeOptions resizeOptions = imageOptions.getResizeOptions(); String commandOptions = imageOptions.getCommandOptions(); + if (cropOptions != null) + { + commandOptions = commandOptions + " " + getImageCropCommandOptions(cropOptions); + } if (resizeOptions != null) { commandOptions = commandOptions + " " + getImageResizeCommandOptions(resizeOptions); @@ -173,6 +178,59 @@ public class ImageMagickContentTransformerWorker extends AbstractImageMagickCont } } + /** + * Gets the imagemagick command string for the image crop options provided + * + * @param imageResizeOptions image resize options + * @return String the imagemagick command options + */ + private String getImageCropCommandOptions(ImageCropOptions cropOptions) + { + StringBuilder builder = new StringBuilder(32); + String gravity = cropOptions.getGravity(); + if(gravity!=null) + { + builder.append("-gravity "); + builder.append(gravity); + builder.append(" "); + } + builder.append("-crop "); + int width = cropOptions.getWidth(); + if (width > -1) + { + builder.append(width); + } + + int height = cropOptions.getHeight(); + if (height > -1) + { + builder.append("x"); + builder.append(height); + } + + if (cropOptions.isPercentageCrop()) + { + builder.append("%"); + } + appendOffset(builder, cropOptions.getXOffset()); + appendOffset(builder, cropOptions.getYOffset()); + builder.append(" +repage"); + return builder.toString(); + } + + /** + * @param builder + * @param xOffset + */ + private void appendOffset(StringBuilder builder, int xOffset) + { + if(xOffset>=0) + { + builder.append("+"); + } + builder.append(xOffset); + } + /** * Gets the imagemagick command string for the image resize options provided * diff --git a/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java b/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java index 6c727fcbcb..85d7d6d65c 100644 --- a/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java +++ b/source/java/org/alfresco/repo/content/transform/magick/ImageTransformationOptions.java @@ -33,6 +33,9 @@ public class ImageTransformationOptions extends TransformationOptions /** Image resize options */ private ImageResizeOptions resizeOptions; + /** Image crop options */ + private ImageCropOptions cropOptions; + /** * Set the command string options * @@ -84,4 +87,20 @@ public class ImageTransformationOptions extends TransformationOptions return msg.toString(); } + + /** + * @param cropOptions the cropOptions to set + */ + public void setCropOptions(ImageCropOptions cropOptions) + { + this.cropOptions = cropOptions; + } + + /** + * @return the cropOptions + */ + public ImageCropOptions getCropOptions() + { + return this.cropOptions; + } } diff --git a/source/java/org/alfresco/repo/rendition/AllRenditionTests.java b/source/java/org/alfresco/repo/rendition/AllRenditionTests.java new file mode 100644 index 0000000000..fd6d779f9e --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/AllRenditionTests.java @@ -0,0 +1,45 @@ +/* + * 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.rendition; + +import org.alfresco.repo.thumbnail.ThumbnailServiceImplParameterTest; +import org.alfresco.repo.thumbnail.ThumbnailServiceImplTest; +import org.junit.runner.RunWith; +import org.junit.runners.Suite; + +/** + * This class is a holder for the various test classes associated with the Rendition Service. + * It is not (at the time of writing) intended to be incorporated into the automatic build + * which will find the various test classes and run them individually. + * + * @author Neil McErlean + */ +@RunWith(Suite.class) +@Suite.SuiteClasses({ + RenditionServiceImplTest.class, + ThumbnailServiceImplParameterTest.class, + ThumbnailServiceImplTest.class, + StandardRenditionLocationResolverTest.class, + RenditionServiceIntegrationTest.class, + RenditionServicePermissionsTest.class +}) +public class AllRenditionTests +{ + // Intentionally empty +} diff --git a/source/java/org/alfresco/repo/rendition/CompositeRenditionDefinitionImpl.java b/source/java/org/alfresco/repo/rendition/CompositeRenditionDefinitionImpl.java new file mode 100644 index 0000000000..bf1e7290f5 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/CompositeRenditionDefinitionImpl.java @@ -0,0 +1,159 @@ +/* + * 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.rendition; + +import java.util.List; + +import org.alfresco.repo.action.ActionListImpl; +import org.alfresco.repo.rendition.executer.CompositeRenderingEngine; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionList; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.namespace.QName; + +/** + * @author Nick Smith + */ +public class CompositeRenditionDefinitionImpl extends RenditionDefinitionImpl implements CompositeRenditionDefinition +{ + /** + * Serial Version UID + */ + private static final long serialVersionUID = -770880495976834168L; + + private final ActionList actions = new ActionListImpl(); + + /** + * @param nodeRef + * @param id + */ + public CompositeRenditionDefinitionImpl(String id, QName renditionName) + { + super(id, renditionName, CompositeRenderingEngine.NAME); + } + + public CompositeRenditionDefinitionImpl(CompositeAction compositeAction) + { + super(compositeAction, CompositeRenderingEngine.NAME); + for (Action action : compositeAction.getActions()) + { + RenditionDefinition subDefinition; + if (action instanceof CompositeAction) + { + CompositeAction compAction = (CompositeAction) action; + subDefinition = new CompositeRenditionDefinitionImpl(compAction); + } + else + { + subDefinition = new RenditionDefinitionImpl(action); + } + addAction(subDefinition); + } + } + + /** + * @param index + * @param action + * @see org.alfresco.service.cmr.action.ActionList#addAction(int, + * org.alfresco.service.cmr.action.Action) + */ + public void addAction(int index, RenditionDefinition action) + { + this.actions.addAction(index, action); + } + + /** + * @param action + * @see org.alfresco.service.cmr.action.ActionList#addAction(org.alfresco.service.cmr.action.Action) + */ + public void addAction(RenditionDefinition action) + { + this.actions.addAction(action); + } + + /** + * @param index + * @return + * @see org.alfresco.service.cmr.action.ActionList#getAction(int) + */ + public RenditionDefinition getAction(int index) + { + return this.actions.getAction(index); + } + + /** + * @return + * @see org.alfresco.service.cmr.action.ActionList#getActions() + */ + public List getActions() + { + return this.actions.getActions(); + } + + /** + * @return + * @see org.alfresco.service.cmr.action.ActionList#hasActions() + */ + public boolean hasActions() + { + return this.actions.hasActions(); + } + + /** + * @param action + * @return + * @see org.alfresco.service.cmr.action.ActionList#indexOfAction(org.alfresco.service.cmr.action.Action) + */ + public int indexOfAction(RenditionDefinition action) + { + return this.actions.indexOfAction(action); + } + + /** + * @param action + * @see org.alfresco.service.cmr.action.ActionList#removeAction(org.alfresco.service.cmr.action.Action) + */ + public void removeAction(RenditionDefinition action) + { + this.actions.removeAction(action); + } + + /** + * @see org.alfresco.service.cmr.action.ActionList#removeAllActions() + */ + public void removeAllActions() + { + this.actions.removeAllActions(); + } + + /** + * @param index + * @param action + * @see org.alfresco.service.cmr.action.ActionList#setAction(int, + * org.alfresco.service.cmr.action.Action) + */ + public void setAction(int index, RenditionDefinition action) + { + this.actions.setAction(index, action); + } + +} diff --git a/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java new file mode 100644 index 0000000000..e891e42803 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/MockedTestServiceRegistry.java @@ -0,0 +1,485 @@ +/* + * 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.rendition; + +import static org.mockito.Mockito.mock; + +import java.util.Collection; + +import org.alfresco.cmis.CMISDictionaryService; +import org.alfresco.cmis.CMISQueryService; +import org.alfresco.cmis.CMISServices; +import org.alfresco.mbeans.VirtServerRegistry; +import org.alfresco.repo.forms.FormService; +import org.alfresco.repo.imap.ImapService; +import org.alfresco.repo.lock.JobLockService; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.attributes.AttributeService; +import org.alfresco.service.cmr.audit.AuditService; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.avm.deploy.DeploymentService; +import org.alfresco.service.cmr.avm.locking.AVMLockingService; +import org.alfresco.service.cmr.avmsync.AVMSyncService; +import org.alfresco.service.cmr.coci.CheckOutCheckInService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.invitation.InvitationService; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.ml.ContentFilterLanguagesService; +import org.alfresco.service.cmr.ml.EditionService; +import org.alfresco.service.cmr.ml.MultilingualContentService; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.CopyService; +import org.alfresco.service.cmr.repository.CrossRepositoryCopyService; +import org.alfresco.service.cmr.repository.MimetypeService; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.ScriptService; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.cmr.rule.RuleService; +import org.alfresco.service.cmr.search.CategoryService; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.cmr.security.AuthorityService; +import org.alfresco.service.cmr.security.MutableAuthenticationService; +import org.alfresco.service.cmr.security.OwnableService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PersonService; +import org.alfresco.service.cmr.security.PublicServiceAccessService; +import org.alfresco.service.cmr.site.SiteService; +import org.alfresco.service.cmr.tagging.TaggingService; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.cmr.version.VersionService; +import org.alfresco.service.cmr.view.ExporterService; +import org.alfresco.service.cmr.view.ImporterService; +import org.alfresco.service.cmr.workflow.WorkflowService; +import org.alfresco.service.descriptor.DescriptorService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.transaction.TransactionService; +import org.alfresco.wcm.asset.AssetService; +import org.alfresco.wcm.preview.PreviewURIService; +import org.alfresco.wcm.sandbox.SandboxService; +import org.alfresco.wcm.webproject.WebProjectService; + +public class MockedTestServiceRegistry implements ServiceRegistry +{ + private final ActionService actionService = mock(ActionService.class); + private final ContentService contentService = mock(ContentService.class); + private final NodeService nodeService = mock(NodeService.class); + private final TemplateService templateService = mock(TemplateService.class); + private final PersonService personService = mock(PersonService.class); + private final MutableAuthenticationService authenticationService = mock(MutableAuthenticationService.class); + private final NamespaceService namespaceService = mock(NamespaceService.class); + + public boolean isServiceProvided(QName service) + { + // TODO Auto-generated method stub + return false; + } + + + public WorkflowService getWorkflowService() + { + // TODO Auto-generated method stub + return null; + } + + + public WebProjectService getWebProjectService() + { + // TODO Auto-generated method stub + return null; + } + + + public VirtServerRegistry getVirtServerRegistry() + { + // TODO Auto-generated method stub + return null; + } + + + public VersionService getVersionService() + { + // TODO Auto-generated method stub + return null; + } + + + public TransactionService getTransactionService() + { + // TODO Auto-generated method stub + return null; + } + + + public ThumbnailService getThumbnailService() + { + // TODO Auto-generated method stub + return null; + } + + + public TemplateService getTemplateService() + { + return this.templateService; + } + + + public TaggingService getTaggingService() + { + // TODO Auto-generated method stub + return null; + } + + + public SiteService getSiteService() + { + // TODO Auto-generated method stub + return null; + } + + + public Collection getServices() + { + // TODO Auto-generated method stub + return null; + } + + + public Object getService(QName service) + { + // TODO Auto-generated method stub + return null; + } + + + public SearchService getSearchService() + { + // TODO Auto-generated method stub + return null; + } + + + public ScriptService getScriptService() + { + // TODO Auto-generated method stub + return null; + } + + + public SandboxService getSandboxService() + { + // TODO Auto-generated method stub + return null; + } + + + public RuleService getRuleService() + { + // TODO Auto-generated method stub + return null; + } + + + public RetryingTransactionHelper getRetryingTransactionHelper() + { + // TODO Auto-generated method stub + return null; + } + + + public PublicServiceAccessService getPublicServiceAccessService() + { + // TODO Auto-generated method stub + return null; + } + + + public PreviewURIService getPreviewURIService() + { + // TODO Auto-generated method stub + return null; + } + + + public PersonService getPersonService() + { + return personService; + } + + + public PermissionService getPermissionService() + { + // TODO Auto-generated method stub + return null; + } + + + public OwnableService getOwnableService() + { + // TODO Auto-generated method stub + return null; + } + + + public NodeService getNodeService() + { + return nodeService; + } + + + public NamespaceService getNamespaceService() + { + // TODO Auto-generated method stub + return namespaceService; + } + + + public MultilingualContentService getMultilingualContentService() + { + // TODO Auto-generated method stub + return null; + } + + + public MimetypeService getMimetypeService() + { + // TODO Auto-generated method stub + return null; + } + + + public LockService getLockService() + { + // TODO Auto-generated method stub + return null; + } + + + public JobLockService getJobLockService() + { + // TODO Auto-generated method stub + return null; + } + + + public InvitationService getInvitationService() + { + // TODO Auto-generated method stub + return null; + } + + + public ImporterService getImporterService() + { + // TODO Auto-generated method stub + return null; + } + + + public ImapService getImapService() + { + // TODO Auto-generated method stub + return null; + } + + + public FormService getFormService() + { + // TODO Auto-generated method stub + return null; + } + + + public FileFolderService getFileFolderService() + { + // TODO Auto-generated method stub + return null; + } + + + public ExporterService getExporterService() + { + // TODO Auto-generated method stub + return null; + } + + + public EditionService getEditionService() + { + // TODO Auto-generated method stub + return null; + } + + + public DictionaryService getDictionaryService() + { + // TODO Auto-generated method stub + return null; + } + + + public DescriptorService getDescriptorService() + { + // TODO Auto-generated method stub + return null; + } + + + public DeploymentService getDeploymentService() + { + // TODO Auto-generated method stub + return null; + } + + + public CrossRepositoryCopyService getCrossRepositoryCopyService() + { + // TODO Auto-generated method stub + return null; + } + + + public CopyService getCopyService() + { + // TODO Auto-generated method stub + return null; + } + + + public ContentService getContentService() + { + // TODO Auto-generated method stub + return contentService; + } + + + public ContentFilterLanguagesService getContentFilterLanguagesService() + { + // TODO Auto-generated method stub + return null; + } + + + public CheckOutCheckInService getCheckOutCheckInService() + { + // TODO Auto-generated method stub + return null; + } + + + public CategoryService getCategoryService() + { + // TODO Auto-generated method stub + return null; + } + + + public CMISServices getCMISService() + { + // TODO Auto-generated method stub + return null; + } + + + public CMISQueryService getCMISQueryService() + { + // TODO Auto-generated method stub + return null; + } + + + public CMISDictionaryService getCMISDictionaryService() + { + // TODO Auto-generated method stub + return null; + } + + + public AuthorityService getAuthorityService() + { + // TODO Auto-generated method stub + return null; + } + + + public MutableAuthenticationService getAuthenticationService() + { + // TODO Auto-generated method stub + return authenticationService; + } + + + public AuditService getAuditService() + { + // TODO Auto-generated method stub + return null; + } + + + public AttributeService getAttributeService() + { + // TODO Auto-generated method stub + return null; + } + + + public AssetService getAssetService() + { + // TODO Auto-generated method stub + return null; + } + + + public ActionService getActionService() + { + // TODO Auto-generated method stub + return actionService; + } + + + public AVMSyncService getAVMSyncService() + { + // TODO Auto-generated method stub + return null; + } + + + public AVMService getAVMService() + { + // TODO Auto-generated method stub + return null; + } + + + public AVMLockingService getAVMLockingService() + { + // TODO Auto-generated method stub + return null; + } + + + public AVMService getAVMLockingAwareService() + { + // TODO Auto-generated method stub + return null; + } +} diff --git a/source/java/org/alfresco/repo/rendition/PerformRenditionActionExecuter.java b/source/java/org/alfresco/repo/rendition/PerformRenditionActionExecuter.java new file mode 100644 index 0000000000..94bc09818f --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/PerformRenditionActionExecuter.java @@ -0,0 +1,518 @@ +/* + * 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.rendition; + +import static org.alfresco.model.ContentModel.PROP_NODE_DBID; +import static org.alfresco.model.ContentModel.PROP_NODE_REF; +import static org.alfresco.model.ContentModel.PROP_NODE_UUID; +import static org.alfresco.model.ContentModel.PROP_STORE_IDENTIFIER; +import static org.alfresco.model.ContentModel.PROP_STORE_NAME; +import static org.alfresco.model.ContentModel.PROP_STORE_PROTOCOL; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuter; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +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.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.rendition.NodeLocator; +import org.alfresco.service.cmr.rendition.RenderCallback; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.ParameterCheck; + +/* + * This class is the action executer for the perform-rendition action. All renditions are + * executed by this class as wrapping them all in a containing action facilitates + * asynchronous renditions. + *

+ * Some of the logic is executed directly by this class (handling of rendition-related + * aspects and associations) and the rest is executed by subordinate actions called + * from within this action (the actual rendering code). These subordinate actions are + * renditionDefinitions. + * + * @author Neil McErlean + * @since 3.3 + */ +public class PerformRenditionActionExecuter extends ActionExecuterAbstractBase +{ + private static final Log log = LogFactory.getLog(PerformRenditionActionExecuter.class); + + /** Action name and parameters */ + public static final String NAME = "perform-rendition"; + public static final String PARAM_RENDITION_DEFINITION = "renditionDefinition"; + + private static final String DEFAULT_RUN_AS_NAME = AuthenticationUtil.getSystemUserName(); + + private static final List unchangedProperties = Arrays.asList(PROP_NODE_DBID, PROP_NODE_REF, PROP_NODE_UUID, + PROP_STORE_IDENTIFIER, PROP_STORE_NAME, PROP_STORE_PROTOCOL); + /** + * Default {@link NodeLocator} simply returns the source node. + */ + private final static NodeLocator defaultNodeLocator = new NodeLocator() + { + public NodeRef getNode(NodeRef sourceNode, Map params) + { + return sourceNode; + } + }; + + /* + * Injected beans + */ + private RenditionLocationResolver renditionLocationResolver; + private ActionService actionService; + private NodeService nodeService; + private RenditionService renditionService; + + private final NodeLocator temporaryParentNodeLocator; + private final QName temporaryRenditionLinkType; + + public PerformRenditionActionExecuter(NodeLocator temporaryParentNodeLocator, QName temporaryRenditionLinkType) + { + this.temporaryParentNodeLocator = temporaryParentNodeLocator != null ? temporaryParentNodeLocator + : defaultNodeLocator; + this.temporaryRenditionLinkType = temporaryRenditionLinkType != null ? temporaryRenditionLinkType + : RenditionModel.ASSOC_RENDITION; + } + + public PerformRenditionActionExecuter() + { + this(null, null); + } + + /** + * Injects the actionService bean. + * + * @param actionService + * the actionService. + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Injects the nodeService bean. + * + * @param nodeService + * the nodeService. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Injects the renditionService bean. + * + * @param renditionService + */ + public void setRenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + + public void setRenditionLocationResolver(RenditionLocationResolver renditionLocationResolver) + { + this.renditionLocationResolver = renditionLocationResolver; + } + + @Override + protected void executeImpl(final Action containingAction, final NodeRef actionedUponNodeRef) + { + final RenditionDefinition renditionDefinition = getRenditionDefinition(containingAction); + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Rendering node ").append(actionedUponNodeRef).append(" with rendition definition ").append( + renditionDefinition.getRenditionName()); + log.debug(msg.toString()); + } + + Serializable runAsParam = renditionDefinition.getParameterValue(AbstractRenderingEngine.PARAM_RUN_AS); + String runAsName = runAsParam == null ? DEFAULT_RUN_AS_NAME : (String) runAsParam; + + // Renditions should all be created by system by default. + // When renditions are created by a user and are to be created under a + // node + // other than the source node, it is possible that the user will not + // have + // permissions to create content under that node. + // For that reason, we execute all perform-rendition actions as system + // by default. + AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + { + public Void doWork() throws Exception + { + ChildAssociationRef result = null; + try + { + setTemporaryRenditionProperties(actionedUponNodeRef, renditionDefinition); + + // Adds the 'Renditioned' aspect to the source node if it + // doesn't exist. + if (!nodeService.hasAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED)) + { + nodeService.addAspect(actionedUponNodeRef, RenditionModel.ASPECT_RENDITIONED, null); + } + ChildAssociationRef tempRendAssoc = executeRendition(actionedUponNodeRef, renditionDefinition); + result = createOrUpdateRendition(actionedUponNodeRef, tempRendAssoc, renditionDefinition); + containingAction.setParameterValue(PARAM_RESULT, result); + } catch (Throwable t) + { + notifyCallbackOfException(renditionDefinition, t); + throwWrappedException(t); + } + if (result != null) + { + notifyCallbackOfResult(renditionDefinition, result); + } + return null; + } + }, runAsName); + } + + private RenditionDefinition getRenditionDefinition(final Action containingAction) + { + Serializable rendDefObj = containingAction.getParameterValue(PARAM_RENDITION_DEFINITION); + ParameterCheck.mandatory(PARAM_RENDITION_DEFINITION, rendDefObj); + return (RenditionDefinition) rendDefObj; + } + + // Rendition has failed. If there is a callback, it needs to be notified + private void notifyCallbackOfException(RenditionDefinition renditionDefinition, Throwable t) + { + if (renditionDefinition != null) + { + RenderCallback callback = renditionDefinition.getCallback(); + if (callback != null) + { + callback.handleFailedRendition(t); + } + } + } + + // and rethrow Exception + private void throwWrappedException(Throwable t) + { + if (t instanceof AlfrescoRuntimeException) + { + throw (AlfrescoRuntimeException) t; + } else + { + throw new RenditionServiceException(t.getMessage(), t); + } + } + + private void notifyCallbackOfResult(RenditionDefinition renditionDefinition, ChildAssociationRef result) + { + // Rendition was successful. Notify the callback object. + if (renditionDefinition != null) + { + RenderCallback callback = renditionDefinition.getCallback(); + if (callback != null) + { + callback.handleSuccessfulRendition(result); + } + } + } + + /** + * This method sets the renditionParent and rendition assocType. + * + * @param sourceNode + * @param definition + */ + private void setTemporaryRenditionProperties(NodeRef sourceNode, RenditionDefinition definition) + { + // Set the parent and assoc type for the temporary rendition to be + // created. + NodeRef parent = temporaryParentNodeLocator.getNode(sourceNode, definition.getParameterValues()); + definition.setRenditionParent(parent); + definition.setRenditionAssociationType(temporaryRenditionLinkType); + } + + /** + * @param sourceNode + * @param definition + * @return + */ + private ChildAssociationRef executeRendition(NodeRef sourceNode, RenditionDefinition definition) + { + actionService.executeAction(definition, sourceNode); + // Extract the result from the action + Serializable serializableResult = definition.getParameterValue(ActionExecuter.PARAM_RESULT); + return (ChildAssociationRef) serializableResult; + } + + private ChildAssociationRef createOrUpdateRendition(NodeRef sourceNode, ChildAssociationRef tempRendition, + RenditionDefinition renditionDefinition) + { + NodeRef tempRenditionNode = tempRendition.getChildRef(); + RenditionLocation location = getDestinationParentAssoc(sourceNode, renditionDefinition, tempRenditionNode); + QName renditionQName = renditionDefinition.getRenditionName(); + if (log.isDebugEnabled()) + { + final String lineBreak = System.getProperty("line.separator", "\n"); + StringBuilder msg = new StringBuilder(); + msg.append("Creating/updating rendition based on:").append(lineBreak).append(" sourceNode: ").append( + sourceNode).append(lineBreak).append(" tempRendition: ").append(tempRendition).append( + lineBreak).append(" parentNode: ").append(location.getParentRef()).append(lineBreak).append( + " childName: ").append(location.getChildName()).append(lineBreak).append( + " renditionDefinition.name: ").append(renditionQName); + log.debug(msg.toString()); + } + ChildAssociationRef primaryAssoc = findOrCreatePrimaryRenditionAssociation(sourceNode, renditionDefinition, + location); + + // Copy relevant properties from the temporary node to the new rendition + // node. + NodeRef renditionNode = primaryAssoc.getChildRef(); + transferNodeProperties(tempRenditionNode, renditionNode); + + // Set the name property on the rendition if it has not already been + // set. + String renditionName = getRenditionName(tempRenditionNode, location, renditionDefinition); + nodeService.setProperty(renditionNode, ContentModel.PROP_NAME, renditionName); + + // Delete the temporary rendition. + nodeService.removeChildAssociation(tempRendition); + + // Handle the rendition aspects + manageRenditionAspects(sourceNode, primaryAssoc); + ChildAssociationRef renditionAssoc = renditionService.getRenditionByName(sourceNode, renditionQName); + if (renditionAssoc == null) + { + String msg = "A rendition of type: " + renditionQName + " should have been created for source node: " + + sourceNode; + throw new RenditionServiceException(msg); + } + return renditionAssoc; + } + + private void manageRenditionAspects(NodeRef sourceNode, ChildAssociationRef renditionParentAssoc) + { + NodeRef renditionNode = renditionParentAssoc.getChildRef(); + NodeRef primaryParent = renditionParentAssoc.getParentRef(); + + // If the rendition is located directly underneath its own source node + if (primaryParent.equals(sourceNode)) + { + // It should be a 'hidden' rendition. + nodeService.addAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION, null); + nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION); + // We remove the other aspect to cover the potential case where a + // rendition + // has been updated in a different location. + } else + { + // Renditions stored underneath any node other than their source are + // 'visible'. + nodeService.addAspect(renditionNode, RenditionModel.ASPECT_VISIBLE_RENDITION, null); + nodeService.removeAspect(renditionNode, RenditionModel.ASPECT_HIDDEN_RENDITION); + } + } + + private String getRenditionName(NodeRef tempRenditionNode, RenditionLocation location, + RenditionDefinition renditionDefinition) + { + // If a location name is set then use it. + String locName = location.getChildName(); + if (locName != null && locName.length() > 0) + { + return locName; + } + // Else if the temporary rendition specifies a name property use that. + Serializable tempName = nodeService.getProperty(tempRenditionNode, ContentModel.PROP_NAME); + if (tempName != null) + { + return (String) tempName; + } + // Otherwise use the rendition definition local name. + return renditionDefinition.getRenditionName().getLocalName(); + } + + private ChildAssociationRef findOrCreatePrimaryRenditionAssociation(NodeRef sourceNode, + RenditionDefinition renditionDefinition, RenditionLocation location) + { + // Get old Rendition if exists. + QName renditionName = renditionDefinition.getRenditionName(); + ChildAssociationRef oldRenditionAssoc = renditionService.getRenditionByName(sourceNode, renditionName); + // If no rendition already exists create anew rendition node and + // association. + if (oldRenditionAssoc == null) + { + return getSpecifiedRenditionOrCreateNewRendition(sourceNode, location, renditionName); + } + // If a rendition exists and is already in the correct location then + // return that renditions primary parent association + NodeRef oldRendition = oldRenditionAssoc.getChildRef(); + if (renditionLocationMatches(oldRendition, location)) + { + return nodeService.getPrimaryParent(oldRendition); + } + // If the old rendition is in the wrong location and the 'orphan + // existing rendition' param is set to true or the RenditionLocation + // specifies a destination NodeRef then ldelete the old + // rendition association and create a new rendition node. + if (orphanExistingRendition(renditionDefinition, location)) + { + orphanRendition(oldRenditionAssoc); + return getSpecifiedRenditionOrCreateNewRendition(sourceNode, location, renditionName); + } + // If the old rendition is in the wrong place and the 'orphan existing + // rendition' param is not set to true then move the existing rendition + // to the correct location. + return moveRendition(oldRendition, location, renditionName); + } + + private ChildAssociationRef moveRendition(NodeRef renditionNode, RenditionLocation location, QName associationName) + { + ChildAssociationRef assoc = nodeService.moveNode(renditionNode, location.getParentRef(), + ContentModel.ASSOC_CONTAINS, associationName); + return assoc; + } + + private void orphanRendition(ChildAssociationRef oldRenditionAssoc) + { + NodeRef oldRendition = oldRenditionAssoc.getChildRef(); + nodeService.removeAspect(oldRendition, RenditionModel.ASPECT_HIDDEN_RENDITION); + nodeService.removeAspect(oldRendition, RenditionModel.ASPECT_VISIBLE_RENDITION); + nodeService.removeChildAssociation(oldRenditionAssoc); + } + + private boolean orphanExistingRendition(RenditionDefinition renditionDefinition, RenditionLocation location) + { + if (location.getChildRef() != null) + return true; + else + return AbstractRenderingEngine.getParamWithDefault(RenditionService.PARAM_ORPHAN_EXISTING_RENDITION, + Boolean.FALSE, renditionDefinition); + } + + private boolean renditionLocationMatches(NodeRef oldRendition, RenditionLocation location) + { + NodeRef destination = location.getChildRef(); + if (destination != null) + { + return destination.equals(oldRendition); + } + ChildAssociationRef oldParentAssoc = nodeService.getPrimaryParent(oldRendition); + NodeRef oldParent = oldParentAssoc.getParentRef(); + if (oldParent.equals(location.getParentRef())) + { + String childName = location.getChildName(); + if (childName == null) + return true; + else + { + Serializable oldName = nodeService.getProperty(oldRendition, ContentModel.PROP_NAME); + return childName.equals(oldName); + } + } + return false; + } + + private ChildAssociationRef getSpecifiedRenditionOrCreateNewRendition(NodeRef sourceNode, + RenditionLocation location, QName renditionName) + { + NodeRef destination = location.getChildRef(); + if (destination != null) + return nodeService.getPrimaryParent(destination); + else + return createNewRendition(sourceNode, location, renditionName); + } + + private ChildAssociationRef createNewRendition(NodeRef sourceNode, RenditionLocation location, QName renditionName) + { + NodeRef parentRef = location.getParentRef(); + boolean parentIsSource = parentRef.equals(sourceNode); + QName renditionType = RenditionModel.ASSOC_RENDITION; + QName assocTypeQName = parentIsSource ? renditionType : ContentModel.ASSOC_CONTAINS; + QName nodeTypeQName = ContentModel.TYPE_CONTENT; + ChildAssociationRef primaryAssoc = nodeService.createNode(parentRef, assocTypeQName, renditionName, + nodeTypeQName); + + // If the new rendition is not directly under the source node then add + // the rendition association. + if (parentIsSource == false) + { + NodeRef rendition = primaryAssoc.getChildRef(); + nodeService.addChild(sourceNode, rendition, renditionType, renditionName); + } + return primaryAssoc; + } + + /** + * @param sourceNode + * @param targetNode + */ + private void transferNodeProperties(NodeRef sourceNode, NodeRef targetNode) + { + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Transferring some properties from ").append(sourceNode).append(" to ").append(targetNode); + log.debug(msg.toString()); + } + + Map newProps = nodeService.getProperties(sourceNode); + for (QName propKey : unchangedProperties) + { + newProps.remove(propKey); + } + nodeService.setProperties(targetNode, newProps); + } + + private RenditionLocation getDestinationParentAssoc(NodeRef sourceNode, RenditionDefinition definition, + NodeRef tempRendition) + { + return renditionLocationResolver.getRenditionLocation(sourceNode, definition, tempRendition); + } + + @Override + protected void addParameterDefinitions(List paramList) + { + paramList.add(new ParameterDefinitionImpl(PARAM_RENDITION_DEFINITION, DataTypeDefinition.ANY, true, + getParamDisplayLabel(PARAM_RENDITION_DEFINITION))); + + paramList.add(new ParameterDefinitionImpl(PARAM_RESULT, DataTypeDefinition.CHILD_ASSOC_REF, false, + getParamDisplayLabel(PARAM_RESULT))); + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenderingEngineDefinitionImpl.java b/source/java/org/alfresco/repo/rendition/RenderingEngineDefinitionImpl.java new file mode 100644 index 0000000000..fb544a7a80 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenderingEngineDefinitionImpl.java @@ -0,0 +1,40 @@ +/* + * 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.rendition; + +import org.alfresco.repo.action.ActionDefinitionImpl; +import org.alfresco.service.cmr.rendition.RenderingEngineDefinition; + +/** + * @author Nick Smith + * @since 3.3 + */ +public class RenderingEngineDefinitionImpl extends ActionDefinitionImpl implements RenderingEngineDefinition +{ + /** + * Serial version UID. + */ + private static final long serialVersionUID = 1L; + + public RenderingEngineDefinitionImpl(String name) + { + super(name); + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionImpl.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionImpl.java new file mode 100644 index 0000000000..c48fbccf6c --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionImpl.java @@ -0,0 +1,132 @@ +/* + * 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.rendition; + +import org.alfresco.repo.action.ActionImpl; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.rendition.RenderCallback; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * @author Nick Smith + * @author Neil McErlean + * @since 3.3 + */ +public class RenditionDefinitionImpl extends ActionImpl implements RenditionDefinition +{ + + /** + * Serial version UID + */ + private static final long serialVersionUID = 4336392868488634875L; + protected static final String RENDITION_DEFINITION_NAME = "renderingActionName"; + + public NodeRef renditionParent; + public QName renditionAssociationType; + private RenderCallback renderCallback; + + /** + * @param id the action id + * @param renditionName a unique name for the rendering action. + * @param renderingEngineName the name of the rendering action definition + */ + public RenditionDefinitionImpl(String id, QName renditionName, String renderingEngineName) + { + super(null, id, renderingEngineName); + setParameterValue(RENDITION_DEFINITION_NAME, renditionName); + } + + public RenditionDefinitionImpl(Action action) + { + super(action); + } + + public RenditionDefinitionImpl(Action action, String renderingEngineName) + { + super(action, renderingEngineName); + } + + /* + * @see + * org.alfresco.service.cmr.rendition.RenditionDefinition#getRenditionName() + */ + public QName getRenditionName() + { + return (QName) getParameterValue(RENDITION_DEFINITION_NAME); + } + + /* + * @see + * org.alfresco.service.cmr.rendition.RenditionDefinition#getRenditionParent + * () + */ + public NodeRef getRenditionParent() + { + return this.renditionParent; + } + + /* + * @see + * org.alfresco.service.cmr.rendition.RenditionDefinition#setRenditionParent + * (org.alfresco.service.cmr.repository.NodeRef) + */ + public void setRenditionParent(NodeRef renditionParent) + { + this.renditionParent = renditionParent; + } + + /* + * @seeorg.alfresco.service.cmr.rendition.RenditionDefinition# + * getRenditionAssociationType() + */ + public QName getRenditionAssociationType() + { + return this.renditionAssociationType; + } + + /* + * @seeorg.alfresco.service.cmr.rendition.RenditionDefinition# + * setRenditionAssociationType(org.alfresco.service.namespace.QName) + */ + public void setRenditionAssociationType(QName renditionAssociationType) + { + this.renditionAssociationType = renditionAssociationType; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionDefinition#setCallback(org.alfresco.service.cmr.rendition.RenderCallback) + */ + public void setCallback(RenderCallback callback) + { + this.renderCallback = callback; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionDefinition#setCallback(org.alfresco.service.cmr.rendition.RenderCallback) + */ + public RenderCallback getCallback() + { + return this.renderCallback; + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java new file mode 100644 index 0000000000..206910c568 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersister.java @@ -0,0 +1,83 @@ +/* + * 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.rendition; + +import java.util.List; + +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.namespace.QName; + +/** + * This class provides the implementation of RenditionDefinition persistence. + * + * @author Nick Smith + * @author Neil McErlean + * @since 3.3 + */ +public interface RenditionDefinitionPersister +{ + /** + * This method serializes the {@link RenditionDefinition} and stores it in + * the repository. {@link RenditionDefinition}s saved in this way may be + * retrieved using the load() method. + * + * @param renditionDefinition The {@link RenditionDefinition} to be + * persisted. + */ + void saveRenditionDefinition(RenditionDefinition renditionDefinition); + + /** + * This method retrieves a {@link RenditionDefinition} that has been stored + * in the repository using the save() method. If no + * {@link RenditionDefinition} exists in the repository with the specified + * rendition name then this method returns null. + * + * @param renditionName The unique identifier used to specify the + * {@link RenditionDefinition} to retrieve. + * @return The specified {@link RenditionDefinition} or null. + */ + RenditionDefinition loadRenditionDefinition(QName renditionName); + + /** + * This method retrieves the {@link RenditionDefinition}s that have been + * stored in the repository using the save() method. + *

+ * If there are no such {@link RenditionDefinition}s, an empty list is + * returned. + * + * @return The {@link RenditionDefinition}s. + */ + List loadRenditionDefinitions(); + + /** + * This method retrieves the stored {@link RenditionDefinition}s that have + * been registered for the specified rendering engine name. + *

+ * If there are no such rendering {@link RenditionDefinition}s, an empty + * list is returned. + * + * @param renderingEngineName the name of a rendering engine. This is + * usually the spring bean name. + * @return The {@link RenditionDefinition}s. + * @throws NullPointerException if the renderingEngineName is null. + * @see #saveRenditionDefinition(RenditionDefinition) + */ + List loadRenditionDefinitions(String renderingEngineName); +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java new file mode 100644 index 0000000000..f2c5f43087 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionDefinitionPersisterImpl.java @@ -0,0 +1,202 @@ +/* + * 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.rendition; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionModel; +import org.alfresco.repo.action.RuntimeActionService; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.CompositeAction; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.namespace.QName; + +/** + * This class provides the implementation of RenditionDefinition persistence. + * + * @author Nick Smith + * @author Neil McErlean + * @since 3.3 + */ +public class RenditionDefinitionPersisterImpl implements RenditionDefinitionPersister +{ + /** Reference to the rendering action space node */ + private static final StoreRef SPACES_STORE = new StoreRef(StoreRef.PROTOCOL_WORKSPACE, "SpacesStore"); + protected static final NodeRef RENDERING_ACTION_ROOT_NODE_REF = new NodeRef(SPACES_STORE, "rendering_actions_space"); + + /* Injected services */ + private NodeService nodeService; + private RuntimeActionService runtimeActionService; + + /** + * Injects the NodeService bean. + * + * @param nodeService the NodeService. + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Injects the RuntimeActionService bean. + * + * @param runtimeActionService the RuntimeActionService. + */ + public void setRuntimeActionService(RuntimeActionService runtimeActionService) + { + this.runtimeActionService = runtimeActionService; + } + + public List loadRenditionDefinitions() + { + checkRenderingActionRootNodeExists(); + + // Note that in the call to getChildAssocs below, only the specified + // types are included. + // Subtypes of the type action:action will not be returned. + Set actionTypes = new HashSet(); + actionTypes.add(ActionModel.TYPE_ACTION); + + List childAssocs = nodeService.getChildAssocs(RENDERING_ACTION_ROOT_NODE_REF, actionTypes); + + List renderingActions = new ArrayList(childAssocs.size()); + for (ChildAssociationRef actionAssoc : childAssocs) + { + Action nextAction = runtimeActionService.createAction(actionAssoc.getChildRef()); + renderingActions.add(new RenditionDefinitionImpl(nextAction)); + } + + return renderingActions; + } + + public List loadRenditionDefinitions(String renditionEngineName) + { + if (renditionEngineName == null) + { + throw new NullPointerException("Unexpected null renditionEngineName"); + } + + List allRenditionDefinitions = this.loadRenditionDefinitions(); + + List filteredRenditionDefinitions = new ArrayList(); + for (RenditionDefinition renderAction : allRenditionDefinitions) + { + if (renditionEngineName.equals(renderAction.getActionDefinitionName())) + { + filteredRenditionDefinitions.add(renderAction); + } + } + + return filteredRenditionDefinitions; + } + + + public RenditionDefinition loadRenditionDefinition(QName renderingActionName) + { + NodeRef actionNode = findActionNode(renderingActionName); + if (actionNode != null) + { + Action action = runtimeActionService.createAction(actionNode); + if (action instanceof CompositeAction) + { + CompositeAction compAction = (CompositeAction) action; + return new CompositeRenditionDefinitionImpl(compAction); + } + else + { + return new RenditionDefinitionImpl(action); + } + } + else + return null; + } + + public void saveRenditionDefinition(RenditionDefinition renderingAction) + { + NodeRef actionNodeRef = findOrCreateActionNode(renderingAction); + + // TODO Serialize using JSON content instead. + // The current serialization mechanism creates a complex content model + // structure which is verbose and a JSON-based approach using a simplified + // content model perhaps could offer performance improvements. + runtimeActionService.saveActionImpl(actionNodeRef, renderingAction); + } + + private NodeRef findActionNode(QName renderingActionName) + { + checkRenderingActionRootNodeExists(); + List childAssocs = nodeService.getChildAssocs(// + RENDERING_ACTION_ROOT_NODE_REF,// + ContentModel.ASSOC_CONTAINS,// + renderingActionName); + if (childAssocs.isEmpty()) + { + return null; + } + else + { + if (childAssocs.size() > 1) + {// + throw new RenditionServiceException("Multiple rendering actions with the name: " + renderingActionName + + " exist!"); + } + return childAssocs.get(0).getChildRef(); + } + } + + private NodeRef findOrCreateActionNode(RenditionDefinition renderingAction) + { + QName actionName = renderingAction.getRenditionName(); + NodeRef actionNode = findActionNode(actionName); + if (actionNode == null) + { + actionNode = runtimeActionService.createActionNodeRef(// + renderingAction,// + RENDERING_ACTION_ROOT_NODE_REF,// + ContentModel.ASSOC_CONTAINS,// + actionName); + } + return actionNode; + } + + /** + * This method checks whether the folder containing Rendering Action nodes + * exists. + * + * @throws RenditionServiceException if the folder node does not exist. + */ + private void checkRenderingActionRootNodeExists() + { + if (nodeService.exists(RENDERING_ACTION_ROOT_NODE_REF) == false) + { + throw new RenditionServiceException("Unable to find rendering action root node."); + } + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionLocation.java b/source/java/org/alfresco/repo/rendition/RenditionLocation.java new file mode 100644 index 0000000000..4cd3c1ed76 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionLocation.java @@ -0,0 +1,30 @@ +/* + * 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.rendition; + +import org.alfresco.service.cmr.repository.NodeRef; + +public interface RenditionLocation +{ + NodeRef getParentRef(); + + NodeRef getChildRef(); + + String getChildName(); +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionLocationImpl.java b/source/java/org/alfresco/repo/rendition/RenditionLocationImpl.java new file mode 100644 index 0000000000..71e81b048c --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionLocationImpl.java @@ -0,0 +1,50 @@ +/* + * 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.rendition; + +import org.alfresco.service.cmr.repository.NodeRef; + +public class RenditionLocationImpl implements RenditionLocation +{ + private final NodeRef parentRef; + private final NodeRef childRef; + private final String childName; + + public RenditionLocationImpl(NodeRef parentRef, NodeRef childRef, String childName) + { + this.parentRef = parentRef; + this.childRef = childRef; + this.childName = childName; + } + + public String getChildName() + { + return childName; + } + + public NodeRef getParentRef() + { + return parentRef; + } + + public NodeRef getChildRef() + { + return childRef; + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionLocationResolver.java b/source/java/org/alfresco/repo/rendition/RenditionLocationResolver.java new file mode 100644 index 0000000000..71dc3e6d79 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionLocationResolver.java @@ -0,0 +1,30 @@ +/* + * 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.rendition; + +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.repository.NodeRef; + +public interface RenditionLocationResolver +{ + + RenditionLocation getRenditionLocation(NodeRef sourceNode, RenditionDefinition definition, NodeRef tempRenditionLocation); + +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java new file mode 100644 index 0000000000..59b4ff8f55 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceImpl.java @@ -0,0 +1,406 @@ +/* + * 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.rendition; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition; +import org.alfresco.service.cmr.rendition.RenderCallback; +import org.alfresco.service.cmr.rendition.RenderingEngineDefinition; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/* + * @author Nick Smith + * @author Neil McErlean + * @since 3.3 + */ +public class RenditionServiceImpl implements RenditionService, RenditionDefinitionPersister +{ + private static final Log log = LogFactory.getLog(RenditionServiceImpl.class); + + private ActionService actionService; + private ContentService contentService; + private DictionaryService dictionaryService; + private NodeService nodeService; + + private RenditionDefinitionPersisterImpl renditionDefinitionPersister; + + /** + * Injects the RenditionDefinitionPersister bean. + * @param renditionDefinitionPersister + */ + public void setRenditionDefinitionPersister(RenditionDefinitionPersisterImpl renditionDefinitionPersister) + { + this.renditionDefinitionPersister = renditionDefinitionPersister; + } + + /** + * Injects the ServiceRegistry bean. + * @param serviceRegistry + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.contentService = serviceRegistry.getContentService(); + this.nodeService = serviceRegistry.getNodeService(); + } + + /** + * Injects the ActionService bean. + * @param actionService + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } + + /** + * Injects the DictionaryService bean. + * @param dictionaryService + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#getRenderingEngineDefinition(java.lang.String) + */ + public RenderingEngineDefinition getRenderingEngineDefinition(String name) + { + ActionDefinition actionDefinition = actionService.getActionDefinition(name); + if (actionDefinition instanceof RenderingEngineDefinition) + { + return (RenderingEngineDefinition) actionDefinition; + } + else + return null; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#getRenderingEngineDefinitions() + */ + public List getRenderingEngineDefinitions() + { + List results = new ArrayList(); + List actionDefs = actionService.getActionDefinitions(); + for (ActionDefinition actionDef : actionDefs) + { + if (actionDef instanceof RenderingEngineDefinition) + { + RenderingEngineDefinition renderingDef = (RenderingEngineDefinition) actionDef; + results.add(renderingDef); + } + } + return results; + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.service.cmr.rendition.RenditionService#createRenditionDefinition + * (org.alfresco.service.namespace.QName, java.lang.String) + */ + public RenditionDefinition createRenditionDefinition(QName renderingActionName, String actionDefinitionName) + { + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Creating rendition definition ") + .append(renderingActionName) + .append(" ") + .append(actionDefinitionName); + log.debug(msg.toString()); + } + return new RenditionDefinitionImpl(GUID.generate(), renderingActionName, actionDefinitionName); + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#createCompositeRenditionDefinition(org.alfresco.service.namespace.QName) + */ + public CompositeRenditionDefinition createCompositeRenditionDefinition(QName renditionName) + { + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Creating composite rendition definition ") + .append(renditionName); + log.debug(msg.toString()); + } + return new CompositeRenditionDefinitionImpl(GUID.generate(), renditionName); + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.cmr.rendition.RenditionDefinition) + */ + public ChildAssociationRef render(NodeRef sourceNode, RenditionDefinition definition) + { + ChildAssociationRef result = createAndExecuteRenditionAction(sourceNode, definition, false); + + return result; + } + + public void render(NodeRef sourceNode, RenditionDefinition definition, + RenderCallback callback) + { + // The asynchronous render can't return a ChildAssociationRef as it is created + // asynchronously after this method returns. + definition.setCallback(callback); + + createAndExecuteRenditionAction(sourceNode, definition, true); + + return; + } + + private ChildAssociationRef createAndExecuteRenditionAction(NodeRef sourceNode, + RenditionDefinition definition, boolean asynchronous) + { + if (log.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + if (asynchronous) + { + msg.append("Asynchronously"); + } + else + { + msg.append("Synchronously"); + } + msg.append(" rendering node ").append(sourceNode) + .append(" with ").append(definition.getRenditionName()); + log.debug(msg.toString()); + } + Action performRenditionAction = actionService.createAction(PerformRenditionActionExecuter.NAME); + performRenditionAction.setParameterValue(PerformRenditionActionExecuter.PARAM_RENDITION_DEFINITION, definition); + + final boolean checkConditions = false; + actionService.executeAction(performRenditionAction, sourceNode, checkConditions, asynchronous); + + ChildAssociationRef result = (ChildAssociationRef)performRenditionAction.getParameterValue(PerformRenditionActionExecuter.PARAM_RESULT); + return result; + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.service.cmr.rendition.RenditionService#saveRenditionDefinition + * (org.alfresco.service.cmr.rendition.RenditionDefinition) + */ + public void saveRenditionDefinition(RenditionDefinition renderingAction) + { + this.renditionDefinitionPersister.saveRenditionDefinition(renderingAction); + } + + /* + * @see + * org.alfresco.service.cmr.rendition.RenditionService#loadRenderingAction + * (org.alfresco.service.namespace.QName) + */ + public RenditionDefinition loadRenditionDefinition(QName renderingActionName) + { + return this.renditionDefinitionPersister.loadRenditionDefinition(renderingActionName); + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#loadRenditionDefinitions() + */ + public List loadRenditionDefinitions() + { + return this.renditionDefinitionPersister.loadRenditionDefinitions(); + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.service.cmr.rendition.RenditionService#loadRenderingActions + * (java.lang.String) + */ + public List loadRenditionDefinitions(String renditionEngineName) + { + return this.loadRenditionDefinitions(renditionEngineName); + } + + + /* + * (non-Javadoc) + * @see + * org.alfresco.service.cmr.rendition.RenditionService#getRenditions(org + * .alfresco.service.cmr.repository.NodeRef) + */ + public List getRenditions(NodeRef node) + { + List result = new ArrayList(); + + // Check that the node has the renditioned aspect applied + if (nodeService.hasAspect(node, RenditionModel.ASPECT_RENDITIONED) == true) + { + // Get all the renditions that match the given rendition name + result = nodeService.getChildAssocs(node, RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL); + } + return result; + } + + /* + * (non-Javadoc) + * @see + * org.alfresco.service.cmr.rendition.RenditionService#getRenditions(org + * .alfresco.service.cmr.repository.NodeRef, java.lang.String) + */ + public List getRenditions(NodeRef node, String mimeTypePrefix) + { + List allRenditions = this.getRenditions(node); + List filteredResults = new ArrayList(); + + for (ChildAssociationRef chAssRef : allRenditions) + { + NodeRef renditionNode = chAssRef.getChildRef(); + + QName contentProperty = ContentModel.PROP_CONTENT; + Serializable contentPropertyName = nodeService.getProperty(renditionNode, + ContentModel.PROP_CONTENT_PROPERTY_NAME); + if (contentPropertyName != null) + { + contentProperty = (QName) contentPropertyName; + } + + ContentReader reader = contentService.getReader(renditionNode, contentProperty); + if (reader != null && reader.exists()) + { + String readerMimeType = reader.getMimetype(); + if (readerMimeType.startsWith(mimeTypePrefix)) + { + filteredResults.add(chAssRef); + } + + } + } + return filteredResults; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#getRenditionByName(org.alfresco.service.cmr.repository.NodeRef, org.alfresco.service.namespace.QName) + */ + public ChildAssociationRef getRenditionByName(NodeRef node, QName renditionName) + { + List renditions = new ArrayList(); + + // Check that the node has the renditioned aspect applied + if (nodeService.hasAspect(node, RenditionModel.ASPECT_RENDITIONED) == true) + { + // Get all the renditions that match the given rendition name - + // there should only be 1 (or 0) + renditions = this.nodeService.getChildAssocs(node, RenditionModel.ASSOC_RENDITION, renditionName); + } + if (renditions.isEmpty()) + { + return null; + } + else + { + return renditions.get(0); + } + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#isRendition(org.alfresco.service.cmr.repository.NodeRef) + */ + public boolean isRendition(NodeRef node) + { + final QName aspectToCheckFor = RenditionModel.ASPECT_RENDITION; + + Set existingAspects = nodeService.getAspects(node); + for (QName nextAspect : existingAspects) + { + if (nextAspect.equals(aspectToCheckFor) || dictionaryService.isSubClass(nextAspect, aspectToCheckFor)) + { + return true; + } + } + return false; + } + + /* + * (non-Javadoc) + * @see org.alfresco.service.cmr.rendition.RenditionService#getSourceNode(org.alfresco.service.cmr.repository.NodeRef) + */ + public ChildAssociationRef getSourceNode(NodeRef renditionNode) + { + // In normal circumstances only a node which is itself a rendition can have + // a source node - as linked by the rn:rendition association. + // + // However there are some circumstances where a node which is not + // technically a rendition can still have a source. One such example is the case + // of thumbnail nodes created in a pre-3.3 Alfresco which have not been patched + // to have the correct rendition aspect applied. + // This will also occur *during* execution of the webscript patch and so the + // decision was made not to throw an exception or log a warning if such a + // situation is encountered. + + // A rendition node should have 1 and only 1 source node. + List parents = nodeService.getParentAssocs(renditionNode, + RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL); + if (parents.size() > 1) + { + StringBuilder msg = new StringBuilder(); + msg.append("NodeRef ") + .append(renditionNode) + .append(" unexpectedly has ") + .append(parents.size()) + .append(" rendition parents."); + if (log.isWarnEnabled()) + { + log.warn(msg.toString()); + } + throw new RenditionServiceException(msg.toString()); + } + else + { + return parents.isEmpty() ? null : parents.get(0); + } + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceImplTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceImplTest.java new file mode 100644 index 0000000000..6327395e9c --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceImplTest.java @@ -0,0 +1,120 @@ +/* + * 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.rendition; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.util.LinkedList; +import java.util.List; + +import junit.framework.TestCase; + +import org.alfresco.repo.action.ActionDefinitionImpl; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.rendition.RenderingEngineDefinition; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * @author Nick Smith + */ +public class RenditionServiceImplTest extends TestCase +{ + private final static String ENGINE_NAME = "Engine Name"; + + private ServiceRegistry serviceRegistry = new MockedTestServiceRegistry(); + private ActionService actionService = mock(ActionService.class); + + private final RenditionDefinitionPersisterImpl renditionDefinitionPersister = mock(RenditionDefinitionPersisterImpl.class); + private RenditionServiceImpl renditionService; + + private final QName ACTION_NAME = QName.createQName(NamespaceService.ALFRESCO_URI, "testName"); + + @Override + protected void setUp() throws Exception + { + renditionService = new RenditionServiceImpl(); + renditionService.setServiceRegistry(serviceRegistry); + renditionService.setActionService(actionService); + renditionService.setRenditionDefinitionPersister(renditionDefinitionPersister); + } + + public void testGetRenderingEngineDefinition() throws Exception + { + // Check returns null when unknown name specified. + assertNull(renditionService.getRenderingEngineDefinition("")); + + // Check returns null if action service returns an ActionDefinition + // which does not implement RenderingActionDefinition. + ActionDefinition actionDefinition = new ActionDefinitionImpl(ENGINE_NAME); + when(actionService.getActionDefinition(ENGINE_NAME)).thenReturn(actionDefinition); + assertNull(renditionService.getRenderingEngineDefinition(ENGINE_NAME)); + + // Check returns the definition if the action service returns an + // ActionDefinition + // which does implement RenderingActionDefinition. + ActionDefinition renderingDefinition = new RenderingEngineDefinitionImpl(ENGINE_NAME); + when(actionService.getActionDefinition(ENGINE_NAME)).thenReturn(renderingDefinition); + assertSame(renderingDefinition, renditionService.getRenderingEngineDefinition(ENGINE_NAME)); + } + + public void testGetRenderingEngineDefinitions() throws Exception + { + LinkedList actionDefs = new LinkedList(); + when(actionService.getActionDefinitions()).thenReturn(actionDefs); + + // Check case where no action definitions returned. + List engineDefs = renditionService.getRenderingEngineDefinitions(); + assertTrue("The list of rendering action definitions should be empty!", engineDefs.isEmpty()); + + // Check that when the action service returns a rendering engine + // definition then the rendering service includes this in the list of + // returned values. + ActionDefinition renderingDefinition = new RenderingEngineDefinitionImpl(ENGINE_NAME); + actionDefs.add(renderingDefinition); + engineDefs = renditionService.getRenderingEngineDefinitions(); + assertEquals(1, engineDefs.size()); + assertSame(renderingDefinition, engineDefs.get(0)); + + // Check that when the action service returns a non-rendering action + // definition then the rendering service does not include it. + ActionDefinition actionDefinition = new ActionDefinitionImpl(ENGINE_NAME); + actionDefs.add(actionDefinition); + engineDefs = renditionService.getRenderingEngineDefinitions(); + assertEquals(1, engineDefs.size()); + assertSame(renderingDefinition, engineDefs.get(0)); + } + + public void testCreateRenditionDefinition() throws Exception + { + RenditionDefinition renderingAction = renditionService.createRenditionDefinition(ACTION_NAME, ENGINE_NAME); + assertNotNull(renderingAction); + assertEquals(ENGINE_NAME, renderingAction.getActionDefinitionName()); + assertEquals(ACTION_NAME, renderingAction.getRenditionName()); + String id = renderingAction.getId(); + assertNotNull(id); + assertTrue(id.length() > 0); + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java new file mode 100644 index 0000000000..3aece0840a --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionServiceIntegrationTest.java @@ -0,0 +1,1753 @@ +/* + * 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.rendition; + +import java.awt.image.BufferedImage; +import java.io.File; +import java.io.Serializable; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; + +import javax.imageio.ImageIO; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.repo.action.executer.ExporterActionExecuter; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.AbstractContentTransformerTest; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.repo.rendition.executer.ImageRenderingEngine; +import org.alfresco.repo.rendition.executer.ReformatRenderingEngine; +import org.alfresco.repo.rendition.executer.TemplatingRenderingEngine; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition; +import org.alfresco.service.cmr.rendition.RenderCallback; +import org.alfresco.service.cmr.rendition.RenderingEngineDefinition; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.springframework.extensions.surf.util.Pair; + +/** + * @author Neil McErlean + * @author Nick Smith + * @since 3.3 + */ +@SuppressWarnings("deprecation") +public class RenditionServiceIntegrationTest extends BaseAlfrescoSpringTest +{ + private final static QName REFORMAT_RENDER_DEFN_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, + ReformatRenderingEngine.NAME + System.currentTimeMillis()); + private final static QName RESCALE_RENDER_DEFN_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, + ImageRenderingEngine.NAME + System.currentTimeMillis()); + private final static String QUICK_CONTENT = "The quick brown fox jumps over the lazy dog"; + private final static String FM_TEMPLATE = "/org/alfresco/repo/rendition/renditionTestTemplate.ftl"; + + private NodeRef nodeWithDocContent; + private NodeRef nodeWithImageContent; + private NodeRef nodeWithFreeMarkerContent; + private NodeRef testTargetFolder; + + private NodeRef renditionNode = null; + + private RenditionService renditionService; + private Repository repositoryHelper; + private RetryingTransactionHelper transactionHelper; + private NamespaceService namespaceService; + + @Override + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + this.renditionService = (RenditionService) this.applicationContext.getBean("renditionService"); + this.repositoryHelper = (Repository) this.applicationContext.getBean("repositoryHelper"); + this.namespaceService= (NamespaceService) this.applicationContext.getBean("namespaceService"); + this.transactionHelper = (RetryingTransactionHelper) this.applicationContext + .getBean("retryingTransactionHelper"); + + // Set the current security context as admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + NodeRef companyHome = this.repositoryHelper.getCompanyHome(); + + // Create the test folder used for these tests + this.testTargetFolder = createNode(companyHome, "testFolder", ContentModel.TYPE_FOLDER); + + // Create the node used as a content supplier for tests + this.nodeWithDocContent = createContentNode(companyHome, "testDocContent"); + + // Put some known PDF content in it. + File pdfQuickFile = AbstractContentTransformerTest.loadQuickTestFile("pdf"); + assertNotNull("Failed to load required test file.", pdfQuickFile); + + nodeService.setProperty(nodeWithDocContent, ContentModel.PROP_CONTENT, new ContentData(null, + MimetypeMap.MIMETYPE_PDF, 0L, null)); + ContentWriter writer = contentService.getWriter(nodeWithDocContent, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_PDF); + writer.setEncoding("UTF-8"); + writer.putContent(pdfQuickFile); + + // Put the titled aspect on it - used for testing rendition updates + Map titledProps = new HashMap(); + titledProps.put(ContentModel.PROP_TITLE, "Original test title"); + titledProps.put(ContentModel.PROP_DESCRIPTION, "Dummy description"); + nodeService.addAspect(nodeWithDocContent, ContentModel.ASPECT_TITLED, titledProps); + + + // Create a test image + this.nodeWithImageContent = createContentNode(companyHome, "testImageNode"); + // Stream some well-known image content into the node. + URL url = RenditionServiceIntegrationTest.class.getClassLoader().getResource("images/gray21.512.png"); + assertNotNull("url of test image was null", url); + File imageFile = new File(url.getFile()); + assertTrue(imageFile.exists()); + + nodeService.setProperty(nodeWithImageContent, ContentModel.PROP_CONTENT, new ContentData(null, + MimetypeMap.MIMETYPE_IMAGE_PNG, 0L, null)); + writer = contentService.getWriter(nodeWithImageContent, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_IMAGE_PNG); + writer.setEncoding("UTF-8"); + writer.putContent(imageFile); + + // Create a test template node. + this.nodeWithFreeMarkerContent = createFreeMarkerNode(companyHome); + } + + private NodeRef createContentNode(NodeRef companyHome, String name) + { + return createNode(companyHome, name, ContentModel.TYPE_CONTENT); + } + + private NodeRef createNode(NodeRef companyHome, String name, QName type) + { + Map props = new HashMap(); + String fullName = name + System.currentTimeMillis(); + props.put(ContentModel.PROP_NAME, fullName); + QName docContentQName = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, fullName); + NodeRef node = nodeService.createNode(companyHome, + ContentModel.ASSOC_CONTAINS, + docContentQName, + type, + props) + .getChildRef(); + return node; + } + + private NodeRef createFreeMarkerNode(NodeRef companyHome) + { + NodeRef fmNode = createContentNode(companyHome, "testFreeMarkerNode"); + nodeService.setProperty(fmNode, ContentModel.PROP_CONTENT, new ContentData(null, + MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, null)); + + URL url = getClass().getResource(FM_TEMPLATE); + assertNotNull("The url is null", url); + File templateFile = new File(url.getFile()); + assertTrue("The template file does not exist", templateFile.exists()); + + ContentWriter fmWriter = contentService.getWriter(fmNode, ContentModel.PROP_CONTENT, true); + fmWriter.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + fmWriter.setEncoding("UTF-8"); + fmWriter.putContent(templateFile); + return fmNode; + } + + @Override + protected void onTearDownInTransaction() throws Exception + { + nodeService.deleteNode(nodeWithImageContent); + nodeService.deleteNode(nodeWithDocContent); + nodeService.deleteNode(nodeWithFreeMarkerContent); + nodeService.deleteNode(testTargetFolder); + } + + public void testRenderFreeMarkerTemplate() throws Exception + { + this.setComplete(); + this.endTransaction(); + final QName renditionName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, + TemplatingRenderingEngine.NAME); + + this.renditionNode = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // create test model + RenditionDefinition definition = renditionService.createRenditionDefinition(renditionName, + TemplatingRenderingEngine.NAME); + definition.setParameterValue(TemplatingRenderingEngine.PARAM_TEMPLATE_NODE, + nodeWithFreeMarkerContent); + ChildAssociationRef renditionAssoc = renditionService + .render(nodeWithDocContent, definition); + assertNotNull("The rendition association was null", renditionAssoc); + return renditionAssoc.getChildRef(); + } + }); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + String output = readTextContent(renditionNode); + assertNotNull("The rendition content was null.", output); + // check the output contains root node Id as expected. + assertTrue(output.contains(nodeWithDocContent.getId())); + return null; + } + }); + } + + public void testRenderFreemarkerTemplatePath() throws Exception + { + //TODO displayName paths. + this.setComplete(); + this.endTransaction(); + final QName renditionName1 = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, + TemplatingRenderingEngine.NAME + "_UpdateOnAnyPropChange"); + final QName renditionName2 = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, + TemplatingRenderingEngine.NAME + "_UpdateOnContentPropChange"); + + final Pair renditions = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback>() + { + public Pair execute() throws Throwable + { + ChildAssociationRef parentAssoc = nodeService.getPrimaryParent(nodeWithFreeMarkerContent); + QName assocName = parentAssoc.getQName(); + String templatePath="/app:company_home/"+assocName.toPrefixString(namespaceService); + + // create test model 1 - rendition to update on any property change + RenditionDefinition definition1 = renditionService.createRenditionDefinition(renditionName1, + TemplatingRenderingEngine.NAME); + definition1.setParameterValue(TemplatingRenderingEngine.PARAM_TEMPLATE_PATH, + templatePath); + definition1.setParameterValue(AbstractRenderingEngine.PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE, Boolean.TRUE); + + // create test model 2 - rendition to update on content property change + RenditionDefinition definition2 = renditionService.createRenditionDefinition(renditionName2, + TemplatingRenderingEngine.NAME); + definition2.setParameterValue(TemplatingRenderingEngine.PARAM_TEMPLATE_PATH, + templatePath); + definition2.setParameterValue(AbstractRenderingEngine.PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE, Boolean.FALSE); + + // We need to save these renditions in order to have them eligible + // for automatic update. + if (null == renditionService.loadRenditionDefinition(renditionName1)) + { + renditionService.saveRenditionDefinition(definition1); + } + if (null == renditionService.loadRenditionDefinition(renditionName2)) + { + renditionService.saveRenditionDefinition(definition2); + } + + ChildAssociationRef renditionAssoc1 = renditionService + .render(nodeWithDocContent, definition1); + assertNotNull("The rendition association was null", renditionAssoc1); + + ChildAssociationRef renditionAssoc2 = renditionService + .render(nodeWithDocContent, definition2); + assertNotNull("The rendition association was null", renditionAssoc2); + + Pair result = new Pair(renditionAssoc1.getChildRef(), renditionAssoc2.getChildRef()); + return result; + } + }); + + final String titleInitialValue = "Original test title"; + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + assertEquals("The source node should have title " + titleInitialValue, titleInitialValue, + nodeService.getProperty(nodeWithDocContent, ContentModel.PROP_TITLE)); + + String output1 = readTextContent(renditions.getFirst()); + assertNotNull("The rendition content was null.", output1); + + assertRenditionContainsTitle(titleInitialValue, + output1); + + // check the output contains root node Id as expected. + assertTrue(output1.contains(nodeWithDocContent.getId())); + + String output2 = readTextContent(renditions.getSecond()); + assertNotNull("The rendition content was null.", output2); + + assertRenditionContainsTitle(titleInitialValue, + output2); + + return null; + } + }); + + // Now change some properties on the source node and ensure that the renditions + // are updated appropriately + final String updatedTitle = "updatedTitle"; + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + nodeService.setProperty(nodeWithDocContent, ContentModel.PROP_TITLE, updatedTitle); + return null; + } + }); + // Sleep to let the asynchronous action queue perform the updates to the renditions. + // TODO Is there a better way? + Thread.sleep(20000); + + // Get the renditions and check their content for the new title + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + String output1 = readTextContent(renditions.getFirst()); + assertNotNull("The rendition content was null.", output1); + + assertRenditionContainsTitle(updatedTitle, output1); + + String output2 = readTextContent(renditions.getSecond()); + assertNotNull("The rendition content was null.", output2); + + assertRenditionContainsTitle(titleInitialValue, output2); + + return null; + } + }); + } + + private void assertRenditionContainsTitle(final String titleValue, String output) + { + final String titleMarker = "TestTitle="; + final String beforeAfterMarker = "xxx"; + int indexOfTitleMarker = output.indexOf(titleMarker); + int indexOfStartMarker = output.indexOf(beforeAfterMarker, indexOfTitleMarker); + int indexOfEndMarker = output.indexOf(beforeAfterMarker, indexOfStartMarker + beforeAfterMarker.length()); + final String titleAsTakenFromRendition = output.substring(indexOfStartMarker + beforeAfterMarker.length(), indexOfEndMarker); + + assertEquals("The rendition should contain title " + titleValue, titleValue, titleAsTakenFromRendition); + } + + /** + * This test method uses the RenditionService to render a test document (of + * type PDF) into a different format (of type plain_text) and place the + * rendition under the source node. + */ + public void testRenderDocumentInAnotherFormatInSitu() throws Exception + { + this.setComplete(); + this.endTransaction(); + + this.renditionNode = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Initially the node that provides the content + // should not have the rn:renditioned aspect on it. + assertFalse("Source node has unexpected renditioned aspect.", nodeService.hasAspect( + nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED)); + // and no renditions + assertTrue("Renditions should have been empty", renditionService.getRenditions( + nodeWithDocContent).isEmpty()); + assertNull("Renditions should have been null", renditionService.getRenditionByName( + nodeWithDocContent, REFORMAT_RENDER_DEFN_NAME)); + + validateRenderingActionDefinition(ReformatRenderingEngine.NAME); + + RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN); + + // Render the content and put the result underneath + // the content node + ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent, action); + NodeRef rendition = renditionAssoc.getChildRef(); + assertEquals("The parent node was not correct", nodeWithDocContent, renditionAssoc + .getParentRef()); + validateRenditionAssociation(renditionAssoc, REFORMAT_RENDER_DEFN_NAME); + + // The rendition node should have no other + // parent-associations - in this case + assertEquals("Wrong value for rendition node parent count.", + 1, nodeService.getParentAssocs(rendition).size()); + + // Now the source content node should have the + // renditioned aspect + assertTrue("Source node is missing renditioned aspect.", nodeService.hasAspect( + nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED)); + // and one rendition + + assertEquals("Renditions size wrong", 1, renditionService.getRenditions(nodeWithDocContent) + .size()); + assertNotNull("Renditions should not have been null", renditionService.getRenditionByName( + nodeWithDocContent, REFORMAT_RENDER_DEFN_NAME)); + + return rendition; + } + }); + + // Now in a separate transaction, we'll check that the reformatted + // content is actually there. + assertNotNull("The rendition node was null.", renditionNode); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + String contentAsString = readTextContent(renditionNode); + assertTrue("Wrong content in rendition", contentAsString.contains(QUICK_CONTENT)); + return null; + } + }); + } + + /** + * This test method uses the RenditionService to render a test document (of + * type PDF) into a different format (of type + * application/x-shockwave-flash). + */ + public void testRenderPdfDocumentToFlash() throws Exception + { + this.setComplete(); + this.endTransaction(); + + this.renditionNode = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Initially the node that provides the content + // should not have the rn:renditioned aspect on it. + assertFalse("Source node has unexpected renditioned aspect.", nodeService.hasAspect( + nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED)); + + validateRenderingActionDefinition(ReformatRenderingEngine.NAME); + + RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_FLASH); + action.setParameterValue(ReformatRenderingEngine.PARAM_FLASH_VERSION, "9"); + + // Render the content and put the result underneath + // the content node + ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent, action); + + assertEquals("The parent node was not correct", nodeWithDocContent, renditionAssoc + .getParentRef()); + validateRenditionAssociation(renditionAssoc, REFORMAT_RENDER_DEFN_NAME); + + // The rendition node should have no other + // parent-associations - in this case + assertEquals("Wrong value for rendition node parent count.", 1, nodeService + .getParentAssocs(renditionNode).size()); + + // Now the source content node should have the + // renditioned aspect + assertTrue("Source node is missing renditioned aspect.", nodeService.hasAspect( + nodeWithDocContent, RenditionModel.ASPECT_RENDITIONED)); + return renditionNode; + } + }); + + // Now in a separate transaction, we'll check that the reformatted + // content is actually there. + assertNotNull("The rendition node was null.", renditionNode); + } + + public void testCompositeReformatAndResizeRendition() throws Exception + { + this.setComplete(); + this.endTransaction(); + + final QName renditionName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "composite"); + final int newX = 20; + final int newY = 30; + + renditionNode = transactionHelper.doInTransaction(new RetryingTransactionHelper.// + RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + CompositeRenditionDefinition compositeDefinition = makeCompositeReformatAndResizeDefinition( + renditionName, newX, newY); + ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent, + compositeDefinition); + validateRenditionAssociation(renditionAssoc, renditionName); + return renditionAssoc.getChildRef(); + } + + }); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.// + RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + List renditions = renditionService.getRenditions(nodeWithDocContent); + assertEquals("There should only be one rendition", 1, renditions.size()); + + ChildAssociationRef renditionAssoc = renditions.get(0); + assertEquals("The association name should match the composite rendition name", + renditionName, renditionAssoc.getQName()); + + NodeRef rendition = renditionAssoc.getChildRef(); + ContentReader reader = contentService.getReader(rendition, ContentModel.PROP_CONTENT); + assertEquals("The mimetype is wrong", MimetypeMap.MIMETYPE_IMAGE_JPEG, reader.getMimetype()); + + assertNotNull("Reader to rendered image was null", reader); + BufferedImage img = ImageIO.read(reader.getContentInputStream()); + + assertEquals("Rendered image had wrong height", newY, img.getHeight()); + assertEquals("Rendered image had wrong width", newX, img.getWidth()); + return null; + } + }); + + } + + /** + * This test method used the RenditionService to render a test document (of + * type PDF) into a different format (of type plain_text) and place the + * rendition under the specified folder. + */ + public void testRenderDocumentInAnotherFormatUnderSpecifiedFolder() throws Exception + { + this.setComplete(); + this.endTransaction(); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Initially the node that provides the content should not have + // the rn:renditioned aspect on it. + assertFalse("Source node has unexpected renditioned aspect.", nodeService.hasAspect(nodeWithDocContent, + RenditionModel.ASPECT_RENDITIONED)); + + validateRenderingActionDefinition(ReformatRenderingEngine.NAME); + + // Create the rendering action. + RenditionDefinition definition = makeReformatAction(ContentModel.TYPE_CONTENT, + MimetypeMap.MIMETYPE_TEXT_PLAIN); + Serializable targetFolderName = nodeService.getProperty(testTargetFolder, ContentModel.PROP_NAME); + String path = "${companyHome}/" + targetFolderName +"/test.txt"; + definition.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, path); + + // Perform the action with an explicit destination folder + ChildAssociationRef renditionAssoc = renditionService.render(nodeWithDocContent, definition); + NodeRef rendition = renditionAssoc.getChildRef(); + // A secondary parent + assertEquals("The parent node was not correct", nodeWithDocContent, renditionAssoc.getParentRef()); + + validateRenditionAssociation(renditionAssoc, REFORMAT_RENDER_DEFN_NAME); + + // The rendition node should have 2 parents: the containing + // folder and the source node + List parentAssocs = nodeService.getParentAssocs(rendition); + assertEquals("Wrong value for rendition node parent count.", 2, parentAssocs.size()); + + // Check the parent nodeRefs are correct + List parents = new ArrayList(2); + parents.add(parentAssocs.get(0).getParentRef()); + parents.add(parentAssocs.get(1).getParentRef()); + assertTrue("Missing containing folder as parent", parents.contains(testTargetFolder)); + assertTrue("Missing source node as parent", parents.contains(nodeWithDocContent)); + + // Now the source content node should have the rn:renditioned + // aspect + assertTrue("Source node is missing renditioned aspect.", nodeService.hasAspect(nodeWithDocContent, + RenditionModel.ASPECT_RENDITIONED)); + + return null; + } + }); + } + + /** + * This test method used the RenditionService to render a test image (of + * type PNG) as a cropped image of the same type. + */ + @SuppressWarnings("unused") + public void testRenderCropImage() throws Exception + { + this.setComplete(); + this.endTransaction(); + + final int originalImageWidth = 512; + final int originalImageHeight = 512; + + // Create a rendition of an existing image with specified absolute x and + // y scale. + final int imageNewXSize = 36; + final int imageNewYSize = 47; + final Map parameterValues = new HashMap(); + parameterValues.put(ImageRenderingEngine.PARAM_CROP_WIDTH, imageNewXSize); + parameterValues.put(ImageRenderingEngine.PARAM_CROP_HEIGHT, imageNewYSize); + + ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); + final NodeRef newRenditionNode = performImageRendition(parameterValues); + + // Assert that the rendition is of the correct size and has reasonable + // content. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + + // The rescaled image rendition is a child of the original test + // node. + List children = nodeService.getChildAssocs(nodeWithImageContent, + new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)), + new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME))); + + // There should only be one child of the image node: the + // rendition we've just created. + assertEquals("Unexpected number of children", 1, children.size()); + + NodeRef newImageRendition = children.get(0).getChildRef(); + assertEquals(newRenditionNode, newImageRendition); + + ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT); + assertNotNull("Reader to rendered image was null", reader); + BufferedImage img = ImageIO.read(reader.getContentInputStream()); + + assertEquals("Rendered image had wrong height", imageNewYSize, img.getHeight()); + assertEquals("Rendered image had wrong width", imageNewXSize, img.getWidth()); + + + ContentReader srcReader = contentService.getReader(nodeWithImageContent, ContentModel.PROP_CONTENT); + BufferedImage srcImg = ImageIO.read(srcReader.getContentInputStream()); + + // The upper left pixel of the image should be pure black. + int rgbAtTopLeft = img.getRGB(1, 1); + int expRgbAtTopLeft = img.getRGB(1, 1); + assertEquals("Incorrect image content.", expRgbAtTopLeft, rgbAtTopLeft); + + // The lower right pixel of the image should be pure white + int rightIndex = img.getWidth() - 1; + int bottomIndex = img.getHeight() - 1; + int rgbAtBottomRight = img.getRGB(rightIndex, bottomIndex); + int expRgbAtBottomRight = srcImg.getRGB(rightIndex, bottomIndex); + assertEquals("Incorrect image content.", expRgbAtBottomRight, rgbAtBottomRight); + + return null; + } + }); + + // Create a rendition of the same image, this time cropping by 50/25% + parameterValues.clear(); + parameterValues.put(ImageRenderingEngine.PARAM_CROP_WIDTH, 50); // 256 picels + parameterValues.put(ImageRenderingEngine.PARAM_CROP_HEIGHT, 25); // 128 pixels + parameterValues.put(ImageRenderingEngine.PARAM_IS_PERCENT_CROP, true); + + final NodeRef secondRenditionNode = performImageRendition(parameterValues); + + // Assert that the rendition is of the correct size and has reasonable + // content. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // The rescaled image rendition is a child of the original test + // node. + List children = nodeService.getChildAssocs(nodeWithImageContent, + new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)), + new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME))); + + // There should only be one child of the image node: the + // rendition we've just created. + assertEquals("Unexpected number of children", 1, children.size()); + + NodeRef newImageRendition = children.get(0).getChildRef(); + assertEquals(secondRenditionNode, newImageRendition); + + ContentReader srcReader = contentService.getReader(nodeWithImageContent, ContentModel.PROP_CONTENT); + BufferedImage srcImg = ImageIO.read(srcReader.getContentInputStream()); + + ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT); + assertNotNull("Reader to rendered image was null", reader); + BufferedImage img = ImageIO.read(reader.getContentInputStream()); + + assertEquals("Rendered image had wrong height", 128, img.getHeight()); + assertEquals("Rendered image had wrong width", 256, img.getWidth()); + + // The upper left pixel of the image should be pure black. + int rgbAtTopLeft = img.getRGB(1, 1); + int expRgbAtTopLeft = srcImg.getRGB(1, 1); + assertEquals("Incorrect image content.", expRgbAtTopLeft, rgbAtTopLeft); + + // The lower right pixel of the image should be pure white + int widthIndex = img.getWidth() - 1; + int heightIndex = img.getHeight() - 1; + int rgbAtBottomRight = img.getRGB(widthIndex, heightIndex); + int expRgbAtBottomRight = srcImg.getRGB(widthIndex, heightIndex); + assertEquals("Incorrect image content.", expRgbAtBottomRight, rgbAtBottomRight); + + return null; + } + }); + } + + /** + * This test method used the RenditionService to render a test image (of + * type PNG) as a rescaled image of the same type. + */ + public void testRenderRescaledImage() throws Exception + { + this.setComplete(); + this.endTransaction(); + + final int originalImageWidth = 512; + final int originalImageHeight = 512; + + // Create a rendition of an existing image with specified absolute x and + // y scale. + final Integer imageNewXSize = new Integer(36); + final Integer imageNewYSize = new Integer(48); + final Map parameterValues = new HashMap(); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, imageNewXSize); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, imageNewYSize); + + final NodeRef newRenditionNode = performImageRendition(parameterValues); + + // Assert that the rendition is of the correct size and has reasonable + // content. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // The rescaled image rendition is a child of the original test + // node. + List children = nodeService.getChildAssocs(nodeWithImageContent, + new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)), + new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME))); + + // There should only be one child of the image node: the + // rendition we've just created. + assertEquals("Unexpected number of children", 1, children.size()); + + NodeRef newImageRendition = children.get(0).getChildRef(); + assertEquals(newRenditionNode, newImageRendition); + + ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT); + assertNotNull("Reader to rendered image was null", reader); + BufferedImage img = ImageIO.read(reader.getContentInputStream()); + + assertEquals("Rendered image had wrong height", imageNewYSize, new Integer(img.getHeight())); + assertEquals("Rendered image had wrong width", imageNewXSize, new Integer(img.getWidth())); + + // The upper left pixel of the image should be pure black. + int rgbAtTopLeft = img.getRGB(1, 1); + assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000")); + + // The lower right pixel of the image should be pure white + int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1); + assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff")); + + return null; + } + }); + + // Create a rendition of the same image, this time rescaling by 200% + parameterValues.clear(); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 200); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 200); + parameterValues.put(ImageRenderingEngine.PARAM_IS_PERCENT_RESIZE, true); + + final NodeRef secondRenditionNode = performImageRendition(parameterValues); + + // Assert that the rendition is of the correct size and has reasonable + // content. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // The rescaled image rendition is a child of the original test + // node. + List children = nodeService.getChildAssocs(nodeWithImageContent, + new RegexQNamePattern(getLongNameWithEscapedBraces(RenditionModel.ASSOC_RENDITION)), + new RegexQNamePattern(getLongNameWithEscapedBraces(RESCALE_RENDER_DEFN_NAME))); + + // There should only be one child of the image node: the + // rendition we've just created. + assertEquals("Unexpected number of children", 1, children.size()); + + NodeRef newImageRendition = children.get(0).getChildRef(); + assertEquals(secondRenditionNode, newImageRendition); + + ContentReader reader = contentService.getReader(newImageRendition, ContentModel.PROP_CONTENT); + assertNotNull("Reader to rendered image was null", reader); + BufferedImage img = ImageIO.read(reader.getContentInputStream()); + + assertEquals("Rendered image had wrong height", originalImageWidth * 2, img.getHeight()); + assertEquals("Rendered image had wrong width", originalImageHeight * 2, img.getWidth()); + + // The upper left pixel of the image should be pure black. + int rgbAtTopLeft = img.getRGB(1, 1); + assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000")); + + // The lower right pixel of the image should be pure white + int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1); + assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff")); + + return null; + } + }); + } + + /** + * Tests that the ReformatActionExecutor can be used to render images into + * different formats. + * + * @throws Exception + */ + public void testReformatImage() throws Exception + { + setComplete(); + endTransaction(); + + this.renditionNode = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Initially the node that provides the content + // should not have the rn:renditioned aspect on it. + assertFalse("Source node has unexpected renditioned aspect.", nodeService.hasAspect( + nodeWithImageContent, RenditionModel.ASPECT_RENDITIONED)); + + RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN); + + // Set output Mimetype to JPEG. + action.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, + MimetypeMap.MIMETYPE_IMAGE_JPEG); + + ChildAssociationRef renditionAssoc = renditionService.render(nodeWithImageContent, action); + return renditionAssoc.getChildRef(); + } + }); + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + ContentReader reader = contentService.getReader(renditionNode, ContentModel.PROP_CONTENT); + assertNotNull("Reader to rendered image was null", reader); + assertEquals(MimetypeMap.MIMETYPE_IMAGE_JPEG, reader.getMimetype()); + + BufferedImage img = ImageIO.read(reader.getContentInputStream()); + + assertEquals("Rendered image had wrong height", 512, img.getHeight()); + assertEquals("Rendered image had wrong width", 512, img.getWidth()); + + // The upper left pixel of the image should be pure black. + int rgbAtTopLeft = img.getRGB(1, 1); + assertTrue("Incorrect image content.", Integer.toHexString(rgbAtTopLeft).endsWith("000000")); + + // The lower right pixel of the image should be pure white + int rgbAtBottomRight = img.getRGB(img.getWidth() - 1, img.getHeight() - 1); + assertTrue("Incorrect image content.", Integer.toHexString(rgbAtBottomRight).endsWith("ffffff")); + + return null; + } + }); + } + + public void testSuccessfulAsynchronousRendition() throws Exception + { + // There are two relevant threads here: the JUnit test thread and the background + // asynchronousActionExecution thread. It is this second thread that will do + // the rendering work and I want to make sure that any failures on that thread + // are returned to the JUnit thread in order to fail the test. + // I also need to ensure that the asynchronous rendering is complete before + // asserting anything about the results. + // The countdown latch below is the mechanism by which the JUnit test code + // waits for the completion of the other thread. + final CountDownLatch latch = new CountDownLatch(1); + final AsyncResultsHolder results = new AsyncResultsHolder(); + final RenderCallback callback = new RenderCallback() + { + public void handleFailedRendition(Throwable t) + { + results.setMessage("Rendition failed unexpectedly."); + latch.countDown(); + } + + public void handleSuccessfulRendition( + ChildAssociationRef primaryParentOfNewRendition) + { + results.setAssoc(primaryParentOfNewRendition); + latch.countDown(); + } + }; + + // We're performing this on a valid piece of content. + // We expect this to succeed. + performAsyncRendition(nodeWithImageContent, callback, latch, results); + + assertNotNull("ChildAssociationRef was null.", results.getAssoc()); + // We'll simply assert that the association has the correct parent. + assertEquals(nodeWithImageContent, results.getAssoc().getParentRef()); + assertNull(results.getThrowable()); + } + + + /** + * + * @throws Exception + * @see {@link #testSuccessfulAsynchronousRendition()} + */ + public void testFailedAsynchronousRendition() throws Exception + { + // see comment in method above for explanation of the countdown latch. + final CountDownLatch latch = new CountDownLatch(1); + final AsyncResultsHolder results = new AsyncResultsHolder(); + final RenderCallback callback = new RenderCallback() + { + public void handleFailedRendition(Throwable t) + { + results.setThrowable(t); + latch.countDown(); + } + + public void handleSuccessfulRendition( + ChildAssociationRef primaryParentOfNewRendition) + { + results.setMessage("Rendition succeeded unexpectedly."); + latch.countDown(); + } + }; + + // We're performing the render on an invalid node. We expect this to fail. + performAsyncRendition(testTargetFolder, callback, latch, results); + + assertNull(results.getAssoc()); + assertEquals("Expected a RenditionServiceException", RenditionServiceException.class, results.getThrowable().getClass()); + } + + /** + * This method performs an asynchronous rendition and calls back the result to the + * provided callback object. It uses the provided latch to coordinate the JUnit and + * the asynchronous action thread and uses the provided MutableString to send back + * any error messages for JUnit reporting. + *

+ * This method blocks until the action service thread either completes its work + * or times out. + * + * @param callback + * @param latch + * @param failureMessage + * @param nodeToRender + * @throws InterruptedException + */ + private void performAsyncRendition(final NodeRef nodeToRender, final RenderCallback callback, CountDownLatch latch, + final AsyncResultsHolder failureMessage) throws InterruptedException + { + setComplete(); + endTransaction(); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN); + + action.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG); + + renditionService.render(nodeToRender, action, callback); + return null; + } + }); + + // Now wait for the actionService thread to complete the rendering. + // We'll arbitrarily timeout after 30 seconds, which should be plenty for the + // action to execute. + boolean endedNormally = latch.await(30, TimeUnit.SECONDS); + + String failedString = failureMessage.getValue(); + if (failedString != null) + { + fail(failedString); + } + if (endedNormally == false) + { + fail("ActionService thread took too long to perform rendering."); + } + } + + /** + * A simple struct class to allow the return of failure messages, exception objects + * and ChildAssociationRef results from asynchronous calls. + */ + private static class AsyncResultsHolder + { + private String message; + private ChildAssociationRef assoc; + private Throwable throwable; + + public synchronized String getValue() + { + return message; + } + public synchronized void setMessage(String message) + { + this.message = message; + } + public synchronized ChildAssociationRef getAssoc() + { + return assoc; + } + public synchronized void setAssoc(ChildAssociationRef assoc) + { + this.assoc = assoc; + } + public synchronized Throwable getThrowable() + { + return throwable; + } + public synchronized void setThrowable(Throwable throwable) + { + this.throwable = throwable; + } + } + + public void testGetRenditionsForNode() throws Exception + { + setComplete(); + endTransaction(); + + this.renditionNode = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Initially the node that provides the content + // should have no renditions + assertTrue("Test node should have no renditions initially", renditionService.getRenditions( + nodeWithImageContent).isEmpty()); + + // Create 4 arbitrary rendition definitions + QName rendName1 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "definition1" + + System.currentTimeMillis()); + RenditionDefinition action1 = renditionService.createRenditionDefinition(rendName1, + ReformatRenderingEngine.NAME); + action1.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, + MimetypeMap.MIMETYPE_IMAGE_GIF); + + QName rendName2 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "definition2" + + System.currentTimeMillis()); + RenditionDefinition action2 = renditionService.createRenditionDefinition(rendName2, + ReformatRenderingEngine.NAME); + action2.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, + MimetypeMap.MIMETYPE_IMAGE_JPEG); + + QName rendName3 = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "definition3" + + System.currentTimeMillis()); + RenditionDefinition action3 = renditionService.createRenditionDefinition(rendName3, + ImageRenderingEngine.NAME); + action3.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 64); + action3.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 64); + action3.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, + MimetypeMap.MIMETYPE_IMAGE_PNG); + + // The 4th intentionally reuses the rendition name + // from the third + QName rendName4 = rendName3; + RenditionDefinition action4 = renditionService.createRenditionDefinition(rendName4, + ImageRenderingEngine.NAME); + action4.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 128); + action4.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 128); + action4.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, + MimetypeMap.MIMETYPE_IMAGE_PNG); + + // Execute the 4 renditions. + ChildAssociationRef createdRendition1 = renditionService.render(nodeWithImageContent, + action1); + ChildAssociationRef createdRendition2 = renditionService.render(nodeWithImageContent, + action2); + ChildAssociationRef createdRendition3 = renditionService.render(nodeWithImageContent, + action3); + ChildAssociationRef createdRendition4 = renditionService.render(nodeWithImageContent, + action4); + + // Now validate the getRenditions methods + List allRenditions = renditionService + .getRenditions(nodeWithImageContent); + ChildAssociationRef retrievedRendition1 = renditionService.getRenditionByName( + nodeWithImageContent, rendName1); + ChildAssociationRef retrievedRendition2 = renditionService.getRenditionByName( + nodeWithImageContent, rendName2); + ChildAssociationRef retrievedRendition3 = renditionService.getRenditionByName( + nodeWithImageContent, rendName3); + ChildAssociationRef retrievedRendition4 = renditionService.getRenditionByName( + nodeWithImageContent, rendName4); + + // allRenditions should contain only 3 renditions. + // The 4th should have replaced the 3rd. + assertEquals(3, allRenditions.size()); + assertTrue(allRenditions.contains(createdRendition1)); + assertTrue(allRenditions.contains(createdRendition2)); + assertTrue(allRenditions.contains(createdRendition4)); + for (ChildAssociationRef rendition : allRenditions) + { + assertNotSame(createdRendition3, rendition); + } + + assertEquals(createdRendition1, retrievedRendition1); + assertEquals(createdRendition2, retrievedRendition2); + assertEquals(createdRendition4, retrievedRendition3); + assertEquals(createdRendition4, retrievedRendition4); + + // CMIS-style filters. image/* + List imageRenditions = renditionService.getRenditions( + nodeWithImageContent, "image"); + assertEquals(3, imageRenditions.size()); + + List imageSlashJRenditions = renditionService.getRenditions( + nodeWithImageContent, "image/j"); + assertEquals(1, imageSlashJRenditions.size()); + + return null; + } + }); + } + + private NodeRef performImageRendition(final Map parameterValues) + { + final NodeRef newRenditionNode = transactionHelper + .doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + validateRenderingActionDefinition(ImageRenderingEngine.NAME); + + // Create the rendering action. + RenditionDefinition action = renditionService.createRenditionDefinition( + RESCALE_RENDER_DEFN_NAME, ImageRenderingEngine.NAME); + + // Set the parameters. Can't call + // action.setParameterValues as we don't want + // to obliterate existing parameters such as the + // name + for (String s : parameterValues.keySet()) + { + action.setParameterValue(s, parameterValues.get(s)); + } + + ChildAssociationRef renditionAssoc = renditionService.render(nodeWithImageContent, action); + + validateRenditionAssociation(renditionAssoc, RESCALE_RENDER_DEFN_NAME); + + return renditionAssoc.getChildRef(); + } + }); + return newRenditionNode; + } + + /** + * Checks that the saveRenderingAction Method creates the proper node in the + * repository. + */ + public void testSaveRenderingAction() throws Exception + { + this.setComplete(); + this.endTransaction(); + + final List savedRenditionsToDelete = new ArrayList(); + try + { + // Check that if no node exists already then a new node is created. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + assertTrue("The rendering action space has not been created in the repository!",// + nodeService.exists(RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF)); + List childAssocs = nodeService.getChildAssocs(// + RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,// + ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME); + assertTrue("There should be no persisted rendering actions of name: " + REFORMAT_RENDER_DEFN_NAME + + " at the start of this test!",// + childAssocs.isEmpty()); + RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN); + savedRenditionsToDelete.add(action); + + renditionService.saveRenditionDefinition(action); + List results = nodeService.getChildAssocs(// + RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,// + ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME); + assertEquals( + "There should be one persisted rendering action of name: " + REFORMAT_RENDER_DEFN_NAME,// + 1, results.size()); + return null; + } + }); + + // Check that if a node already exists then that node is updated. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + List childAssocs = nodeService.getChildAssocs(// + RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,// + ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME); + assertEquals( + "There should be one persisted rendering action of name: " + REFORMAT_RENDER_DEFN_NAME,// + 1, childAssocs.size()); + NodeRef actionNode = childAssocs.get(0).getChildRef(); + + RenditionDefinition action = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN); + savedRenditionsToDelete.add(action); + + renditionService.saveRenditionDefinition(action); + List results = nodeService.getChildAssocs(// + RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF,// + ContentModel.ASSOC_CONTAINS, REFORMAT_RENDER_DEFN_NAME); + assertEquals( + "There should be one persisted rendering action of name: " + REFORMAT_RENDER_DEFN_NAME,// + 1, results.size()); + assertEquals("The node in which the action is stored should be the same.",// + actionNode, results.get(0).getChildRef()); + return null; + } + }); + } + finally + { + cleanUpPersistedActions(savedRenditionsToDelete); + } + } + + /** + * This is not a real test method. It is used to create the ACP file that we have + * added to the RenditionService for importing at startup. + * This method should remain here as it will be needed if we need to change the + * contents of the ACP. + * + * @throws Exception + * @deprecated + */ + public void off_test_CleanPersistedRenditionsAndCreateExportedACP() throws Exception + { + this.setComplete(); + this.endTransaction(); + + // Check that if no node exists already then a new node is created. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Delete all renditionDefinitions and the folder that holds + // them. + // We can't delete and recreate the + // RENDERING_ACTION_ROOT_NODE_REF and recreate it + // here because we need to keep its well-known nodeRef id. + + assertTrue("The rendering action space has not been created in the repository!",// + nodeService.exists(RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF)); + + // Clean up all existing saved actions here so as to ensure a + // clean export. + // Also delete any old acp file + for (ChildAssociationRef chRef : nodeService + .getChildAssocs(RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF)) + { + System.out.println("Deleting rendition Definition " + + nodeService.getProperty(chRef.getChildRef(), ContentModel.PROP_NAME)); + nodeService.deleteNode(chRef.getChildRef()); + } + + // Create the rendition definitions. + + // 1. "medium" + Map parameterValues = new HashMap(); + parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_JPEG); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 100); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 100); + parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true); + parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH, + "alfresco/thumbnail/thumbnail_placeholder_medium.jpg"); + parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName()); + + RenditionDefinition action = createAction(ImageRenderingEngine.NAME, "medium", parameterValues); + renditionService.saveRenditionDefinition(action); + + // 2. "doclib" + parameterValues.clear(); + parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_PNG); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 100); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 100); + parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true); + parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH, + "alfresco/thumbnail/thumbnail_placeholder_doclib.png"); + parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName()); + + action = createAction(ImageRenderingEngine.NAME, "doclib", parameterValues); + renditionService.saveRenditionDefinition(action); + + // 3. "webpreview" + parameterValues.clear(); + parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_FLASH); + parameterValues.put(ReformatRenderingEngine.PARAM_FLASH_VERSION, "9"); + parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName()); + // no placeholder + + action = createAction("reformat", "webpreview", parameterValues); + renditionService.saveRenditionDefinition(action); + + // 4. "imgpreview" + parameterValues.clear(); + parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_PNG); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 480); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 480); + parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true); + parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH, + "alfresco/thumbnail/thumbnail_placeholder_imgpreview.png"); + parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName()); + + action = createAction(ImageRenderingEngine.NAME, "imgpreview", parameterValues); + renditionService.saveRenditionDefinition(action); + + // 5. "avatar" + parameterValues.clear(); + parameterValues.put(AbstractRenderingEngine.PARAM_MIME_TYPE, MimetypeMap.MIMETYPE_IMAGE_PNG); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 64); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, 64); + parameterValues.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, true); + parameterValues.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, true); + parameterValues.put(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH, + "alfresco/thumbnail/thumbnail_placeholder_avatar.png"); + parameterValues.put(AbstractRenderingEngine.PARAM_RUN_AS, AuthenticationUtil.getSystemUserName()); + + action = createAction(ImageRenderingEngine.NAME, "avatar", parameterValues); + renditionService.saveRenditionDefinition(action); + return null; + } + + private RenditionDefinition createAction(String renderingEngineName, String renditionLocalName, + Map parameterValues) + { + QName renditionName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, renditionLocalName); + RenditionDefinition action = renditionService.createRenditionDefinition(renditionName, + renderingEngineName); + for (String paramKey : parameterValues.keySet()) + { + action.setParameterValue(paramKey, parameterValues.get(paramKey)); + } + return action; + } + }); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + Action exportAction = actionService.createAction("export"); + exportAction.setParameterValue(ExporterActionExecuter.PARAM_STORE, + StoreRef.STORE_REF_WORKSPACE_SPACESSTORE.toString()); + exportAction.setParameterValue(ExporterActionExecuter.PARAM_PACKAGE_NAME, "systemRenditionDefinitions"); + exportAction.setParameterValue(ExporterActionExecuter.PARAM_ENCODING, "UTF-8"); + exportAction.setParameterValue(ExporterActionExecuter.PARAM_DESTINATION_FOLDER, + RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF); + exportAction.setParameterValue(ExporterActionExecuter.PARAM_INCLUDE_SELF, true); + exportAction.setParameterValue(ExporterActionExecuter.PARAM_INCLUDE_CHILDREN, true); + + actionService.executeAction(exportAction, RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF); + + return null; + } + }); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + List children = nodeService + .getChildAssocs(RenditionDefinitionPersisterImpl.RENDERING_ACTION_ROOT_NODE_REF); + for (ChildAssociationRef car : children) + { + System.err.println(car.getChildRef() + " " + + nodeService.getProperty(car.getChildRef(), ContentModel.PROP_NAME)); + } + + return null; + } + }); + } + + /** + * This test method saves one RenderingAction to the repository and loads it + * back up asserting that it is equivalent to the original. It then saves + * some further RenderingActions, loads them back with a RenderingEngine + * filter and asserts that the results are correct. + * + * @throws Exception + */ + public void testLoadRenderingAction() throws Exception + { + this.setComplete(); + this.endTransaction(); + + final RenditionDefinition reformatAction = makeReformatAction(null, MimetypeMap.MIMETYPE_TEXT_PLAIN); + final RenditionDefinition rescaleAction = makeRescaleImageAction(); + + final List savedRenditionDefinitions = new ArrayList(); + try + { + // First save one action. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + savedRenditionDefinitions.add(reformatAction); + renditionService.saveRenditionDefinition(reformatAction); + return null; + } + }); + + // Then load the action back up and check it matches the original. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + QName renditionName = reformatAction.getRenditionName(); + RenditionDefinition result = renditionService.loadRenditionDefinition(renditionName); + assertEquals(renditionName, result.getRenditionName()); + assertEquals(reformatAction.getActionDefinitionName(), result.getActionDefinitionName()); + assertEquals(reformatAction.getCompensatingAction(), result.getCompensatingAction()); + assertEquals(reformatAction.getDescription(), result.getDescription()); + assertEquals(reformatAction.getExecuteAsychronously(), result.getExecuteAsychronously()); + assertEquals(reformatAction.getModifiedDate(), result.getModifiedDate()); + assertEquals(reformatAction.getModifier(), result.getModifier()); + assertEquals(reformatAction.getTitle(), result.getTitle()); + + return null; + } + }); + + // Now save the second action. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + savedRenditionDefinitions.add(rescaleAction); + renditionService.saveRenditionDefinition(rescaleAction); + return null; + } + }); + + // Then load the actions back up and check they exist and are + // filtered correctly. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Retrieve persisted Rendering Definitions by name. + RenditionDefinition firstLoadedAction = renditionService.loadRenditionDefinition(reformatAction + .getRenditionName()); + RenditionDefinition secondLoadedAction = renditionService.loadRenditionDefinition(rescaleAction + .getRenditionName()); + assertNotNull("The reformat action was null.", firstLoadedAction); + assertNotNull("The rescale action was null.", secondLoadedAction); + + /* + * We'll not retest the action metadata here as that is + * tested in this method above. Just the names to ensure + * they are distinct. + */ + assertEquals(reformatAction.getRenditionName(), firstLoadedAction.getRenditionName()); + assertEquals(rescaleAction.getRenditionName(), secondLoadedAction.getRenditionName()); + + // Retrieve all rendering definitions + // List renderingDefinitions = + // renditionService.loadRenditionDefinitions(); + // assertEquals("Wrong number of 'all actions'.", 2, + // renderingDefinitions.size()); + + // Retrieve all rendering definitions for a given Rendering + // Engine. + // List renderDefinitionsForReformat = + // renditionService + // .loadRenditionDefinitions("reformat"); + // assertEquals("Wrong number of actions for engine name.", + // 1, renderDefinitionsForReformat.size()); + + return null; + } + }); + } + finally + { + cleanUpPersistedActions(savedRenditionDefinitions); + } + } + + public void testSaveAndLoadCompositeRenditionDefinition() throws Exception + { + this.setComplete(); + this.endTransaction(); + + final QName renditionName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "composite"); + final CompositeRenditionDefinition compositeDefinition = makeCompositeReformatAndResizeDefinition( + renditionName, 20, 30); + final List savedRenditionDefinitions = new ArrayList(); + + try + { + // First save composite rendition definition. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + renditionService.saveRenditionDefinition(compositeDefinition); + return null; + } + }); + + // Then load the definition back up and check it matches the + // original. + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + RenditionDefinition result = renditionService.loadRenditionDefinition(renditionName); + + // Check basic rendition definition properties. + assertEquals(renditionName, result.getRenditionName()); + assertEquals(compositeDefinition.getActionDefinitionName(), result.getActionDefinitionName()); + assertEquals(compositeDefinition.getCompensatingAction(), result.getCompensatingAction()); + assertEquals(compositeDefinition.getDescription(), result.getDescription()); + assertEquals(compositeDefinition.getExecuteAsychronously(), result.getExecuteAsychronously()); + assertEquals(compositeDefinition.getModifiedDate(), result.getModifiedDate()); + assertEquals(compositeDefinition.getModifier(), result.getModifier()); + assertEquals(compositeDefinition.getTitle(), result.getTitle()); + + // Check sub-actions. + if (result instanceof CompositeRenditionDefinition) + { + CompositeRenditionDefinition compositeResult = (CompositeRenditionDefinition) result; + savedRenditionDefinitions.add(compositeResult); + + List subDefinitions = compositeResult.getActions(); + assertEquals(2, subDefinitions.size()); + + // Check the first sub-definition is correct. + RenditionDefinition firstDef = subDefinitions.get(0); + assertEquals(ReformatRenderingEngine.NAME, firstDef.getActionDefinitionName()); + + // Check the second sub-definition is correct. + RenditionDefinition secondDef = subDefinitions.get(1); + assertEquals(ImageRenderingEngine.NAME, secondDef.getActionDefinitionName()); + } + else + fail("The retrieved rendition should be a CompositeRenditionDefinition."); + return null; + } + }); + } + finally + { + cleanUpPersistedActions(savedRenditionDefinitions); + } + + } + + /** + * This method deletes the specified saved action nodes. + */ + private void cleanUpPersistedActions(final List savedRenditionDefinitions) + { + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + for (RenditionDefinition savedRenditionDefinition : savedRenditionDefinitions) + { + NodeRef actionNodeRef = savedRenditionDefinition.getNodeRef(); + if (actionNodeRef != null) + { + nodeService.deleteNode(actionNodeRef); + } + } + return null; + } + }); + } + + /** + * This test method ensures that all the 'built-in' renditionDefinitions are + * available after startup. + * + * @throws Exception + */ + public void testEnsureBuiltinRenditionDefinitionsAvailable() throws Exception + { + final List renditionLocalNames = Arrays.asList(new String[] { "medium", "doclib", "imgpreview", + "webpreview", "avatar" }); + for (String renditionLocalName : renditionLocalNames) + { + validateRenditionDefinition(renditionLocalName); + } + } + + private void validateRenditionDefinition(String renditionLocalName) + { + System.out.println("Validating rendition definition: " + renditionLocalName); + + QName renditionQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, renditionLocalName); + RenditionDefinition renditionDefinition = renditionService.loadRenditionDefinition(renditionQName); + assertNotNull("'" + renditionLocalName + "' rendition definition was missing.", renditionDefinition); + assertEquals("'" + renditionLocalName + "' renditionDefinition had wrong renditionName", renditionQName, + renditionDefinition.getRenditionName()); + + assertNotNull("'" + renditionLocalName + "' renditionDefinition had null renderingActionName parameter", + renditionDefinition.getParameterValue("renderingActionName")); + + assertEquals("RunAs param was wrong for " + renditionLocalName, AuthenticationUtil.getSystemUserName(), + renditionDefinition.getParameterValue(AbstractRenderingEngine.PARAM_RUN_AS)); + } + + /** + * Creates a RenderingAction (RenditionDefinition) for the + * ReformatActionExecutor, setting the mimetype parameter value to plain + * text. + * + * @param renditionObjectType requested node type of the rendition object + * @param targetMimetype + * @return A new RenderingAction. + */ + private RenditionDefinition makeReformatAction(QName renditionObjectType, String targetMimetype) + { + // Create the rendering action. + RenditionDefinition action = renditionService.createRenditionDefinition(REFORMAT_RENDER_DEFN_NAME, + ReformatRenderingEngine.NAME); + action.setParameterValue(AbstractRenderingEngine.PARAM_MIME_TYPE, targetMimetype); + if (renditionObjectType != null) + { + action.setParameterValue(RenditionService.PARAM_RENDITION_NODETYPE, renditionObjectType); + } + return action; + } + + /** + * Creates a RenderingAction (RenditionDefinition) for the + * RescaleImageActionExecutor. + * + * @return A new RenderingAction. + */ + private RenditionDefinition makeRescaleImageAction() + { + RenditionDefinition action = renditionService.createRenditionDefinition(RESCALE_RENDER_DEFN_NAME, + ImageRenderingEngine.NAME); + action.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 42); + return action; + } + + private void validateRenderingActionDefinition(final String renderingEngineName) + { + // Make sure we can get the action definition. + RenderingEngineDefinition renderingActionDefn = renditionService + .getRenderingEngineDefinition(renderingEngineName); + assertNotNull("renderingActionDefn was null", renderingActionDefn); + assertEquals("Incorrect renderingActionDefn name", renderingEngineName, renderingActionDefn.getName()); + } + + private void validateRenditionAssociation(ChildAssociationRef chAssRef, QName renderingActionQName) + { + assertEquals("The assoc type name was wrong", RenditionModel.ASSOC_RENDITION, chAssRef.getTypeQName()); + assertEquals("The assoc name was wrong", renderingActionQName, chAssRef.getQName()); + + assertTrue("The source node should have the rn:renditioned aspect applied", nodeService.hasAspect(chAssRef + .getParentRef(), RenditionModel.ASPECT_RENDITIONED)); + + final NodeRef newRenditionNodeRef = chAssRef.getChildRef(); + assertTrue("The new rendition node was not a rendition.", renditionService.isRendition(newRenditionNodeRef)); + + // If the source node for the rendition equals the primary parent + NodeRef renditionSource = renditionService.getSourceNode(newRenditionNodeRef).getParentRef(); + NodeRef renditionPrimaryParent = nodeService.getPrimaryParent(newRenditionNodeRef).getParentRef(); + if (renditionSource.equals(renditionPrimaryParent)) + { + assertTrue("Rendition node was missing the hiddenRendition aspect", nodeService.hasAspect( + newRenditionNodeRef, RenditionModel.ASPECT_HIDDEN_RENDITION)); + } + else + { + assertTrue("Rendition node was missing the visibleRendition aspect", nodeService.hasAspect( + newRenditionNodeRef, RenditionModel.ASPECT_VISIBLE_RENDITION)); + } + + assertEquals(ContentModel.PROP_CONTENT, nodeService.getProperty(newRenditionNodeRef, + ContentModel.PROP_CONTENT_PROPERTY_NAME)); + } + + /** + * Given a QName this method returns the long-form String with the braces + * escaped. + */ + private String getLongNameWithEscapedBraces(QName qn) + { + String longName = qn.toString(); + String escapedBraces = longName.replace("{", "\\{").replace("}", "\\}"); + return escapedBraces; + } + + private String readTextContent(NodeRef nodeRef) + { + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + assertNotNull("reader was null", reader); + reader.setMimetype(MimetypeMap.MIMETYPE_TEXT_PLAIN); + return reader.getContentString(); + } + + private CompositeRenditionDefinition makeCompositeReformatAndResizeDefinition(final QName renditionName, + final int newX, final int newY) + { + CompositeRenditionDefinition compositeDefinition = renditionService + .createCompositeRenditionDefinition(renditionName); + RenditionDefinition reformatDefinition = makeReformatAction(null, MimetypeMap.MIMETYPE_IMAGE_JPEG); + RenditionDefinition rescaleImageDefinition = makeRescaleImageAction(); + rescaleImageDefinition.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH, newX); + rescaleImageDefinition.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, newY); + + compositeDefinition.addAction(reformatDefinition); + compositeDefinition.addAction(rescaleImageDefinition); + return compositeDefinition; + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionServicePermissionsTest.java b/source/java/org/alfresco/repo/rendition/RenditionServicePermissionsTest.java new file mode 100644 index 0000000000..8e0a9506b5 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionServicePermissionsTest.java @@ -0,0 +1,231 @@ +/* + * 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.rendition; + +import java.io.File; +import java.io.Serializable; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.rendition.executer.ImageRenderingEngine; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.transaction.RetryingTransactionHelper; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentData; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +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; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.PropertyMap; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Neil McErlean + * @since 3.3 + */ +public class RenditionServicePermissionsTest extends BaseAlfrescoSpringTest +{ + /** Logger */ + private static Log logger = LogFactory.getLog(RenditionServicePermissionsTest.class); + + private final static QName RESCALE_RENDER_DEFN_NAME = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, + ImageRenderingEngine.NAME + System.currentTimeMillis()); + + private NodeRef nodeWithImageContent; + private NodeRef testFolder; + + private PermissionService permissionService; + private PersonService personService; + private RenditionService renditionService; + private Repository repositoryHelper; + private RetryingTransactionHelper transactionHelper; + private String testFolderName; + + @SuppressWarnings("deprecation") + @Override + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + this.permissionService = (PermissionService)this.applicationContext.getBean("PermissionService"); + this.personService = (PersonService)this.applicationContext.getBean("PersonService"); + this.renditionService = (RenditionService) this.applicationContext.getBean("renditionService"); + this.repositoryHelper = (Repository) this.applicationContext.getBean("repositoryHelper"); + this.transactionHelper = (RetryingTransactionHelper) this.applicationContext + .getBean("retryingTransactionHelper"); + + NodeRef companyHome = this.repositoryHelper.getCompanyHome(); + + // Create the test folder used for these tests + this.testFolderName= "Test-folder-"+ System.currentTimeMillis(); + this.testFolder = nodeService.createNode(companyHome, + ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.APP_MODEL_1_0_URI,testFolderName), + ContentModel.TYPE_FOLDER).getChildRef(); + nodeService.setProperty(testFolder, ContentModel.PROP_NAME, testFolderName); + // Create the node used as a content supplier for tests + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, "Test-image-node-" + System.currentTimeMillis()); + + String testImageNodeName = "testImageNode" + System.currentTimeMillis(); + this.nodeWithImageContent = nodeService.createNode(companyHome, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.APP_MODEL_1_0_URI, testImageNodeName), + ContentModel.TYPE_CONTENT, props).getChildRef(); + // Stream some well-known image content into the node. + URL url = RenditionServicePermissionsTest.class.getClassLoader().getResource("images/gray21.512.png"); + assertNotNull("url of test image was null", url); + File imageFile = new File(url.getFile()); + assertTrue(imageFile.exists()); + + nodeService.setProperty(nodeWithImageContent, ContentModel.PROP_CONTENT, new ContentData(null, + MimetypeMap.MIMETYPE_IMAGE_PNG, 0L, null)); + ContentWriter writer = contentService.getWriter(nodeWithImageContent, ContentModel.PROP_CONTENT, true); + writer.setMimetype(MimetypeMap.MIMETYPE_IMAGE_PNG); + writer.setEncoding("UTF-8"); + writer.putContent(imageFile); + } + + + + @Override + protected void onTearDownInTransaction() throws Exception + { + nodeService.deleteNode(nodeWithImageContent); + nodeService.deleteNode(testFolder); + } + + /** + * This test method uses the RenditionService to render a test document and place the + * rendition under a folder for which the user does not have write permissions. + * This should be allowed as all renditions are performed as system. + */ + @SuppressWarnings("deprecation") + public void testRenditionAccessPermissions() throws Exception + { + this.setComplete(); + this.endTransaction(); + + final String normalUser = "renditionUser"; + + // As admin, create a user who has read-only access to the testFolder + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Set the current security context as admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + assertEquals("admin", authenticationService.getCurrentUserName()); + + if (authenticationService.authenticationExists(normalUser) == false) + { + authenticationService.createAuthentication(normalUser, "PWD".toCharArray()); + + PropertyMap personProperties = new PropertyMap(); + personProperties.put(ContentModel.PROP_USERNAME, normalUser); + personProperties.put(ContentModel.PROP_AUTHORITY_DISPLAY_NAME, "title" + normalUser); + personProperties.put(ContentModel.PROP_FIRSTNAME, "firstName"); + personProperties.put(ContentModel.PROP_LASTNAME, "lastName"); + personProperties.put(ContentModel.PROP_EMAIL, "email@email.com"); + personProperties.put(ContentModel.PROP_JOBTITLE, "jobTitle"); + + personService.createPerson(personProperties); + } + + // Restrict write access to the test folder + permissionService.setPermission(testFolder, normalUser, PermissionService.CONSUMER, true); + + return null; + } + }); + + // As the user, render a piece of content with the rendition going to testFolder + final NodeRef rendition = transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public NodeRef execute() throws Throwable + { + // Set the current security context as a rendition user + AuthenticationUtil.setFullyAuthenticatedUser(normalUser); + assertEquals(normalUser, authenticationService.getCurrentUserName()); + + assertFalse("Source node has unexpected renditioned aspect.", nodeService.hasAspect(nodeWithImageContent, + RenditionModel.ASPECT_RENDITIONED)); + + String path = testFolderName+"/testRendition.png"; + // Create the rendering action. + RenditionDefinition action = makeRescaleImageAction(); + action.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, path); + + // Perform the action with an explicit destination folder + logger.debug("Creating rendition of: " + nodeWithImageContent); + ChildAssociationRef renditionAssoc = renditionService.render(nodeWithImageContent, action); + logger.debug("Created rendition: " + renditionAssoc.getChildRef()); + + NodeRef renditionNode = renditionAssoc.getChildRef(); + + assertEquals("The parent node was not correct", nodeWithImageContent, renditionAssoc.getParentRef()); + logger.debug("rendition's primary parent: " + nodeService.getPrimaryParent(renditionNode)); + assertEquals("The parent node was not correct", testFolder, nodeService.getPrimaryParent(renditionNode).getParentRef()); + + // Now the source content node should have the rn:renditioned aspect + assertTrue("Source node is missing renditioned aspect.", nodeService.hasAspect(nodeWithImageContent, + RenditionModel.ASPECT_RENDITIONED)); + + return renditionNode; + } + }); + + transactionHelper.doInTransaction(new RetryingTransactionHelper.RetryingTransactionCallback() + { + public Void execute() throws Throwable + { + // Set the current security context as admin + AuthenticationUtil.setFullyAuthenticatedUser(AuthenticationUtil.getAdminUserName()); + + final Serializable renditionCreator = nodeService.getProperty(rendition, ContentModel.PROP_CREATOR); + assertEquals("Incorrect creator", normalUser, renditionCreator); + + return null; + } + }); + } + + /** + * Creates a RenditionDefinition for the RescaleImageActionExecutor. + * + * @return A new RenderingAction. + */ + private RenditionDefinition makeRescaleImageAction() + { + RenditionDefinition result = renditionService.createRenditionDefinition(RESCALE_RENDER_DEFN_NAME, + ImageRenderingEngine.NAME); + result.setParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH, 42); + return result; + } +} diff --git a/source/java/org/alfresco/repo/rendition/RenditionedAspect.java b/source/java/org/alfresco/repo/rendition/RenditionedAspect.java new file mode 100644 index 0000000000..70fc4d2d48 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/RenditionedAspect.java @@ -0,0 +1,360 @@ +/* + * 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.rendition; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; +import org.alfresco.repo.copy.CopyBehaviourCallback; +import org.alfresco.repo.copy.CopyDetails; +import org.alfresco.repo.copy.CopyServicePolicies; +import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; +import org.alfresco.repo.node.NodeServicePolicies; +import org.alfresco.repo.policy.Behaviour; +import org.alfresco.repo.policy.JavaBehaviour; +import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.dictionary.DictionaryService; +import org.alfresco.service.cmr.dictionary.PropertyDefinition; +import org.alfresco.service.cmr.rendition.RenderCallback; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.EqualsHelper; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Renditioned aspect behaviour bean. + * When any node with the renditioned aspect has a property updated, then all + * associated renditions are eligible for re-rendering. + * Each rendition (as identified by the name in its rn:rendition association) will + * be loaded and if the renditionDefinition exists, the rendition will be updated + * asynchronously, subject to the defined update policy. + * + * @author Neil McErlean + * @author Roy Wetherall + */ +public class RenditionedAspect implements NodeServicePolicies.OnUpdatePropertiesPolicy, + CopyServicePolicies.OnCopyNodePolicy +{ + /** logger */ + private static final Log logger = LogFactory.getLog(RenditionedAspect.class); + + /** Services */ + private PolicyComponent policyComponent; + private NodeService nodeService; + private DictionaryService dictionaryService; + private RenditionService renditionService; + + /** + * Set the policy component + * + * @param policyComponent policy component + */ + public void setPolicyComponent(PolicyComponent policyComponent) + { + this.policyComponent = policyComponent; + } + + /** + * Set the node service + * + * @param nodeService node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * Set the rendition service + * + * @param renditionService rendition service + */ + public void setRenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + + /** + * Set the dictionary service + * + * @param dictionaryService dictionary service + */ + public void setDictionaryService(DictionaryService dictionaryService) + { + this.dictionaryService = dictionaryService; + } + + /** + * Initialise method + */ + public void init() + { + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), + RenditionModel.ASPECT_RENDITIONED, + new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); + this.policyComponent.bindClassBehaviour( + QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), + RenditionModel.ASPECT_RENDITIONED, + new JavaBehaviour(this, "getCopyCallback")); + } + + /** + * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) + */ + public void onUpdateProperties( + NodeRef nodeRef, + Map before, + Map after) + { + // Ignore working copies + if (this.nodeService.exists(nodeRef) == true && + this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false) + { + + // Find the changed properties + List changedProperties = getChangedProperties(before, after); + + // There may be a different policy for different rendition kinds. + List renditions = this.renditionService.getRenditions(nodeRef); + for (ChildAssociationRef chAssRef : renditions) + { + QName renditionAssocName = chAssRef.getQName(); + RenditionDefinition rendDefn = this.renditionService.loadRenditionDefinition(renditionAssocName); + if (rendDefn == null) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Cannot update rendition ") + .append(renditionAssocName) + .append(" on node ").append(nodeRef) + .append(" as the renditionDefinition could not be loaded."); + logger.debug(msg.toString()); + } + continue; + } + Serializable updateRenditionsPolicy = rendDefn.getParameterValue(AbstractRenderingEngine.PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE); + boolean updateRenditionsAlways = updateRenditionsPolicy == null ? false : (Boolean)updateRenditionsPolicy; + + boolean renditionUpdateRequired = false; + + for (QName qname : changedProperties) + { + try + { + PropertyDefinition propertyDef = dictionaryService.getProperty(qname); + if (propertyDef == null) + { + // the property is not recognised + continue; + } + else if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) + { + // not a content type + if (updateRenditionsAlways) + { + renditionUpdateRequired = true; + } + continue; + } + else + { + // it is a content property. We always update renditions for changes to content. + renditionUpdateRequired = true; + } + } catch (ClassCastException ccx) + { + // the property does not confirm to the model + continue; + } + } + + if (renditionUpdateRequired) + { + this.queueUpdate(nodeRef, rendDefn, chAssRef); + } + } + } + } + + private List getChangedProperties(Map before, Map after) + { + List results = new ArrayList(); + for (QName propQName : before.keySet()) + { + if (after.keySet().contains(propQName) == false) + { + // property was deleted + results.add(propQName); + } + else + { + Serializable beforeValue = before.get(propQName); + Serializable afterValue = after.get(propQName); + if (EqualsHelper.nullSafeEquals(beforeValue, afterValue) == false) + { + // Property was changed + results.add(propQName); + } + } + } + for (QName propQName : after.keySet()) + { + if (before.containsKey(propQName) == false) + { + // property was added + results.add(propQName); + } + } + + return results; + } + + /** + * Queue the update to happen asynchronously + * + * @param sourceNodeRef node reference + */ + private void queueUpdate(final NodeRef sourceNodeRef, final RenditionDefinition rendDefn, + final ChildAssociationRef renditionAssoc) + { + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Queueing rendition update for node ").append(sourceNodeRef).append(": ").append(rendDefn.getRenditionName()); + logger.debug(msg.toString()); + } + + if (rendDefn != null) + { + renditionService.render(sourceNodeRef, rendDefn, new RenderCallback() + { + public void handleFailedRendition(Throwable t) + { + // In the event of a failed re-rendition, we will delete the rendition node + if (logger.isDebugEnabled()) + { + StringBuilder msg = new StringBuilder(); + msg.append("Re-rendering of node ") + .append(sourceNodeRef) + .append(" with renditionDefinition ") + .append(rendDefn.getRenditionName()) + .append(" failed. Deleting defunct rendition. ") + .append("The following exception is shown for informational purposes only ") + .append("and does not affect operation of the system."); + logger.debug(msg.toString(), t); + } + + if (nodeService.exists(renditionAssoc.getChildRef())) + { + nodeService.deleteNode(renditionAssoc.getChildRef()); + } + } + + public void handleSuccessfulRendition(ChildAssociationRef primaryParentOfNewRendition) + { + // Intentionally empty + } + }); + } + } + + /** + * @return Returns {@link RenditionedAspectCopyBehaviourCallback} + */ + public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) + { + return RenditionedAspectCopyBehaviourCallback.INSTANCE; + } + + /** + * Behaviour for the {@link RenditionModel#ASPECT_RENDITIONED rn:renditioned} aspect. + * + * @author Derek Hulley + * @author Neil McErlean + * @since 3.2 + */ + private static class RenditionedAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback + { + private static final CopyBehaviourCallback INSTANCE = new RenditionedAspectCopyBehaviourCallback(); + + /** + * @return Returns true always + */ + @Override + public boolean getMustCopy(QName classQName, CopyDetails copyDetails) + { + return true; + } + + /** + * Copy rendition-related associations, {@link RenditionModel#ASSOC_RENDITION} regardless of + * cascade options. + */ + @Override + public ChildAssocCopyAction getChildAssociationCopyAction( + QName classQName, + CopyDetails copyDetails, + CopyChildAssociationDetails childAssocCopyDetails) + { + ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); + if (childAssocRef.getTypeQName().equals(RenditionModel.ASSOC_RENDITION)) + { + return ChildAssocCopyAction.COPY_CHILD; + } + else + { + throw new IllegalStateException( + "Behaviour should have been invoked: \n" + + " Aspect: " + this.getClass().getName() + "\n" + + " " + childAssocCopyDetails + "\n" + + " " + copyDetails); + } + } + + /** + * Copy only the {@link ContentModel#PROP_AUTOMATIC_UPDATE} + */ + @Override + public Map getCopyProperties( + QName classQName, + CopyDetails copyDetails, + Map properties) + { + Map newProperties = new HashMap(5); +// Serializable value = properties.get(ContentModel.PROP_AUTOMATIC_UPDATE); +// newProperties.put(ContentModel.PROP_AUTOMATIC_UPDATE, value); + return newProperties; + } + } +} diff --git a/source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverImpl.java b/source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverImpl.java new file mode 100644 index 0000000000..10e6afe223 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverImpl.java @@ -0,0 +1,256 @@ +/* + * 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.rendition; + +import java.io.Serializable; +import java.util.Arrays; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.model.filefolder.FileFolderServiceImpl; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.template.TemplateNode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.repository.Path; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateException; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.util.FreeMarkerUtil; +import org.alfresco.util.XMLUtil; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; + +import freemarker.ext.dom.NodeModel; +import freemarker.template.SimpleDate; +import freemarker.template.SimpleHash; + +public class StandardRenditionLocationResolverImpl implements RenditionLocationResolver +{ + private final static Log log = LogFactory.getLog(StandardRenditionLocationResolverImpl.class); + + private ServiceRegistry serviceRegistry; + private String companyHomePath = "/app:company_home"; + + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + public void setCompanyHomePath(String companyHomePath) + { + this.companyHomePath = companyHomePath; + } + + /* + * (non-Javadoc) + * + * @seeorg.alfresco.repo.rendition.RenditionLocationResolver# + * resolveRenditionPrimaryParentAssoc + * (org.alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.cmr.rendition.RenditionDefinition, + * org.alfresco.service.cmr.repository.ChildAssociationRef) + */ + public RenditionLocation getRenditionLocation(NodeRef sourceNode, RenditionDefinition definition, NodeRef tempRenditionLocation) + { + // If a destination NodeRef is specified then don't botther to find the location as one has already been specified. + NodeRef destination = AbstractRenderingEngine.getCheckedParam(RenditionService.PARAM_DESTINATION_NODE, NodeRef.class, definition); + if(destination!=null) + { + RenditionLocationImpl location = createNodeLocation(destination); + return location; + } + // If the templated path has been specified and can be resolved then + // find or create the templated path location and return it. + String pathTemplate = (String) definition.getParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE); + if (pathTemplate != null) + { + + NodeRef companyHome = getCompanyHomeNode(sourceNode.getStoreRef()); + NodeService nodeService = serviceRegistry.getNodeService(); + Serializable companyHomeName = nodeService.getProperty(companyHome, ContentModel.PROP_NAME); + String path = renderPathTemplate(pathTemplate, sourceNode, tempRenditionLocation, companyHomeName); + if(path!=null) + { + return findOrCreateTemplatedPath(sourceNode, path, companyHome); + } + } + + // Otherwise the rendition will be created as a child of the source content node. + return new RenditionLocationImpl(sourceNode, null, null); + } + + private RenditionLocationImpl createNodeLocation(NodeRef destination) + { + NodeService nodeService = serviceRegistry.getNodeService(); + if(nodeService.exists(destination)==false) + throw new RenditionServiceException("The rendition destination node does not exist! NodeRef: "+destination); + NodeRef parentRef = nodeService.getPrimaryParent(destination).getParentRef(); + String childName = (String) nodeService.getProperty(destination, ContentModel.PROP_NAME); + RenditionLocationImpl location = new RenditionLocationImpl(parentRef, destination, childName); + return location; + } + + private RenditionLocationImpl findOrCreateTemplatedPath(NodeRef sourceNode, String path, NodeRef companyHome) + { + NodeService nodeService = serviceRegistry.getNodeService(); + + List pathElements = Arrays.asList(path.split("/")); + LinkedList folderElements = new LinkedList(pathElements); + + // Remove empty folder caused by path starting with / . + if(folderElements.getFirst().length() == 0) + { + folderElements.removeFirst(); + } + // Remove 'Company Home' if it is at the start of the path. + Serializable companyHomeName = nodeService.getProperty(companyHome, ContentModel.PROP_NAME); + if(folderElements.getFirst().equals(companyHomeName)) + { + folderElements.removeFirst(); + } + + String fileName = folderElements.removeLast(); + if (fileName == null || fileName.length() == 0) + { + throw new RenditionServiceException("The path must include a valid filename! Path: " + path); + } + FileFolderService fileFolderService = serviceRegistry.getFileFolderService(); + FileInfo parentInfo = FileFolderServiceImpl.makeFolders(fileFolderService, + companyHome, folderElements, + ContentModel.TYPE_FOLDER); + NodeRef parent = parentInfo.getNodeRef(); + NodeRef child = fileFolderService.searchSimple(parent, fileName); + return new RenditionLocationImpl(parent, child, fileName); + } + + private String renderPathTemplate(String pathTemplate, NodeRef sourceNode, NodeRef tempRenditionLocation, Serializable companyHomeName) + { + NodeService nodeService = serviceRegistry.getNodeService(); + NamespaceService namespaceService = serviceRegistry.getNamespaceService(); + + final Map root = new HashMap(); + ChildAssociationRef sourceAssoc = nodeService.getPrimaryParent(sourceNode); + String fullSourceName = sourceAssoc.getQName().getLocalName(); + String trimmedSourceName = fullSourceName; + String sourceExtension = ""; + int extensionIndex = fullSourceName.lastIndexOf('.'); + if (extensionIndex != -1) + { + trimmedSourceName = (extensionIndex == 0) ? "" : fullSourceName.substring(0, extensionIndex); + sourceExtension = (extensionIndex == fullSourceName.length() - 1) ? "" : fullSourceName + .substring(extensionIndex + 1); + } + + Path sourcePath = nodeService.getPath(sourceNode); + + StoreRef store = sourceNode.getStoreRef(); + getCompanyHomeNode( store); + + root.put("name", trimmedSourceName); + root.put("extension", sourceExtension); + root.put("date", new SimpleDate(new Date(), SimpleDate.DATETIME)); + root.put("cwd", sourcePath.toPrefixString(namespaceService)); + root.put("companyHome", companyHomeName); + root.put("sourceNode", new TemplateNode(sourceNode, serviceRegistry, null)); + root.put("sourceContentType", nodeService.getType(sourceNode).getLocalName()); + root.put("renditionContentType", nodeService.getType(tempRenditionLocation).getLocalName()); + NodeRef person = serviceRegistry.getPersonService().getPerson(AuthenticationUtil.getFullyAuthenticatedUser()); + root.put("person", new TemplateNode(person, serviceRegistry, null)); + + if (sourceNodeIsXml(sourceNode)) + { + try + { + Document xml = XMLUtil.parse(sourceNode, serviceRegistry.getContentService()); + pathTemplate = FreeMarkerUtil.buildNamespaceDeclaration(xml) + pathTemplate; + root.put("xml", NodeModel.wrap(xml)); + } catch (Exception ex) + { + log.warn("Failed to parse XML content into path template model: Node = " + sourceNode); + } + } + + if (log.isDebugEnabled()) + { + log.debug("Path template model: " + root); + } + + String result = null; + try + { + if (log.isDebugEnabled()) + { + log.debug("Processing " + pathTemplate + " using source node " + sourcePath); + } + result = serviceRegistry.getTemplateService().processTemplateString("freemarker", pathTemplate, + new SimpleHash(root)); + } catch (TemplateException te) + { + log.error("Error while trying to process rendition path template: " + pathTemplate); + log.error(te.getMessage(), te); + } + if (log.isDebugEnabled()) + { + log.debug("processed pattern " + pathTemplate + " as " + result); + } + return result; + } + + private NodeRef getCompanyHomeNode(StoreRef store) + { + SearchService searchService = serviceRegistry.getSearchService(); + ResultSet result = searchService.query(store, SearchService.LANGUAGE_XPATH, companyHomePath); + if(result.length()==0) + return null; + else + return result.getNodeRef(0); + } + + protected boolean sourceNodeIsXml(NodeRef sourceNode) + { + boolean result = false; + + // TODO: BJR 20100211: We can do better than this... + ContentReader reader = serviceRegistry.getContentService().getReader(sourceNode, ContentModel.PROP_CONTENT); + if ((reader != null) && reader.exists()) + { + result = (reader.getContentData().getMimetype().equals("text/xml")); + } + return result; + } + +} diff --git a/source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverTest.java b/source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverTest.java new file mode 100644 index 0000000000..296faed57b --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/StandardRenditionLocationResolverTest.java @@ -0,0 +1,209 @@ +/* + * 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.rendition; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +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.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.BaseAlfrescoSpringTest; +import org.alfresco.util.GUID; + +/** + * @author Brian Remmington + * @author Nick Smith + */ +public class StandardRenditionLocationResolverTest extends BaseAlfrescoSpringTest +{ + private ServiceRegistry serviceRegistry; + private NodeRef companyHome; + private StandardRenditionLocationResolverImpl locationResolver; + private RenditionService renditionService; + private SearchService searchService; + + /** + * Called during the transaction setup + */ + @Override + @SuppressWarnings("deprecation") + protected void onSetUpInTransaction() throws Exception + { + super.onSetUpInTransaction(); + + // Get the required services + this.serviceRegistry = (ServiceRegistry) this.getApplicationContext().getBean("ServiceRegistry"); + this.nodeService=serviceRegistry.getNodeService(); + this.searchService = serviceRegistry.getSearchService(); + this.renditionService = (RenditionService) this.getApplicationContext().getBean("RenditionService"); + ResultSet rs = searchService.query(StoreRef.STORE_REF_WORKSPACE_SPACESSTORE, SearchService.LANGUAGE_XPATH, + "/app:company_home"); + if (rs.length() != 1) + { + fail("Could not find company home"); + } + companyHome = rs.getNodeRef(0); + locationResolver = new StandardRenditionLocationResolverImpl(); + locationResolver.setServiceRegistry(serviceRegistry); + } + + public void testChildAssociationFinder() + { + QName renditionKind = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "test"); + NodeRef sourceNode = makeNode(companyHome, ContentModel.TYPE_CONTENT); + NodeRef tempRenditionNode = makeNode(sourceNode, ContentModel.TYPE_CONTENT); + + RenditionDefinition renditionDef = renditionService.createRenditionDefinition(renditionKind, + "brians_test_engine"); + + RenditionLocation location = + locationResolver.getRenditionLocation(sourceNode,renditionDef, tempRenditionNode); + + assertEquals(sourceNode, location.getParentRef()); + assertNull(location.getChildName()); + assertNull(location.getChildRef()); + + NodeRef targetFolder = makeNode(companyHome, ContentModel.TYPE_FOLDER); + String companyHomeName = (String) nodeService.getProperty(companyHome, ContentModel.PROP_NAME); + String targetFolderName = (String) nodeService.getProperty(targetFolder, ContentModel.PROP_NAME); + String template = "/"+companyHomeName+"/"+targetFolderName+"/brian.xml"; + renditionDef.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, template); + location = locationResolver.getRenditionLocation(sourceNode, renditionDef, tempRenditionNode); + + assertEquals(targetFolder, location.getParentRef()); + assertEquals("brian.xml", location.getChildName()); + assertNull(location.getChildRef()); + + template=targetFolderName+"/test-${sourceContentType}.xml"; + renditionDef.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, template); + location = locationResolver.getRenditionLocation(sourceNode, renditionDef, tempRenditionNode); + + assertEquals(targetFolder, location.getParentRef()); + assertEquals("test-"+ContentModel.PROP_CONTENT.getLocalName()+".xml", location.getChildName()); + assertNull(location.getChildRef()); + + // Test that when the template path specifies an existing node then that + // node is set as the 'childRef' property on the RenditionLocation. + NodeRef destinationNode = makeNode(targetFolder, ContentModel.TYPE_CONTENT); + String destinationName = (String) nodeService.getProperty(destinationNode, ContentModel.PROP_NAME); + template = targetFolderName + "/" + destinationName; + renditionDef.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, template); + location = locationResolver.getRenditionLocation(sourceNode, renditionDef, tempRenditionNode); + + assertEquals(targetFolder, location.getParentRef()); + assertEquals(destinationName, location.getChildName()); + assertEquals(destinationNode, location.getChildRef()); + + // Test that the 'destination node' param takes precedence over them 'template path' param. + template = "/" + targetFolderName + "/brian.xml"; + renditionDef.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, template); + renditionDef.setParameterValue(RenditionService.PARAM_DESTINATION_NODE, destinationNode); + location = locationResolver.getRenditionLocation(sourceNode, renditionDef, tempRenditionNode); + + assertEquals(targetFolder, location.getParentRef()); + assertEquals(destinationName, location.getChildName()); + assertEquals(destinationNode, location.getChildRef()); + } + + public void testCreatesFoldersForTemplatedLocation() throws Exception + { + QName fooName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFooFolder"); + QName barName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testBarFolder"); + String fooPath = "/testFooFolder"; + String barPath = fooPath + "/testBarFolder"; + + List childAssocs = nodeService.getChildAssocs(companyHome, ContentModel.ASSOC_CONTAINS, + fooName); + assertTrue("Folder " + fooPath + " should not exist!", childAssocs.isEmpty()); + + QName renditionKind = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "test"); + NodeRef sourceNode = makeNode(companyHome, ContentModel.TYPE_CONTENT); + NodeRef tempRenditionNode = makeNode(companyHome, ContentModel.TYPE_CONTENT); + + RenditionDefinition renditionDef = renditionService.createRenditionDefinition(renditionKind, + "nicks_test_engine"); + + String pathTemplate = barPath + "/nick.xml"; + renditionDef.setParameterValue(RenditionService.PARAM_DESTINATION_PATH_TEMPLATE, pathTemplate); + + RenditionLocation location = + locationResolver.getRenditionLocation(sourceNode,renditionDef, tempRenditionNode); + + NodeRef fooNode = checkFolder(fooName, companyHome, "Foo"); + NodeRef barNode = checkFolder(barName, fooNode, "Bar"); + + assertEquals("Bar is not the rendition parent!", barNode, location.getParentRef()); + assertEquals("nick.xml", location.getChildName()); + } + +// public void testOldRenditionIsOrphaned() throws Exception +// { +// QName renditionKind = QName.createQName(NamespaceService.APP_MODEL_1_0_URI, "test"); +// NodeRef sourceNode = makeNode(companyHome, ContentModel.TYPE_CONTENT); +// NodeRef tempRenditionNode = makeNode(sourceNode, ContentModel.TYPE_CONTENT); +// ChildAssociationRef tempRenditionLocation=nodeService.getPrimaryParent(tempRenditionNode); +// +// RenditionDefinition definition = renditionService.createRenditionDefinition(renditionKind, +// "nicks_test_engine"); +// ChildAssociationRef oldRendition = +// nodeService.createNode(sourceNode, RenditionModel.ASSOC_RENDITION, definition.getRenditionName(), ContentModel.TYPE_CONTENT); +// definition.setParameterValue(RenditionService.PARAM_ORPHAN_EXISTING_RENDITION, true); +// +// //Test deletes old rendition if it's under source Node and new rendition is not. +// locationResolver.getRenditionLocation(sourceNode, definition, tempRenditionLocation); +// +// List sourceChildren = nodeService.getChildAssocs(sourceNode); +// assertFalse("The old rendition association should ahve been removed!", sourceChildren.contains(oldRendition)); +// assertFalse("The old rendition should have been deleted!", nodeService.exists(oldRendition.getChildRef()) ); +// } + + private NodeRef checkFolder(QName folderName, NodeRef parentName, String folderMessageName) + { + List folderAssocs = nodeService.getChildAssocs(parentName, ContentModel.ASSOC_CONTAINS, folderName); + assertEquals("Folder " + folderMessageName + " should exist!", 1, folderAssocs.size()); + NodeRef folderNode = folderAssocs.get(0).getChildRef(); + assertEquals(folderMessageName+" node is wrong type!", ContentModel.TYPE_FOLDER, nodeService.getType(folderNode)); + assertEquals(folderMessageName+" node has wrong name!", folderName.getLocalName(), + nodeService.getProperty(folderNode, ContentModel.PROP_NAME)); + return folderNode; + } + + private NodeRef makeNode(NodeRef parent, QName nodeType) + { + String uuid = GUID.generate(); + Map props = new HashMap(); + props.put(ContentModel.PROP_NAME, uuid); + ChildAssociationRef assoc = nodeService.createNode(parent, ContentModel.ASSOC_CONTAINS, QName.createQName( + NamespaceService.APP_MODEL_1_0_URI, uuid), nodeType, props); + return assoc.getChildRef(); + } + +} diff --git a/source/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java new file mode 100644 index 0000000000..b1de310959 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/AbstractRenderingEngine.java @@ -0,0 +1,553 @@ +/* + * 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.rendition.executer; + +import static org.alfresco.service.cmr.rendition.RenditionService.*; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.rendition.RenderingEngineDefinitionImpl; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionDefinition; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.GUID; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This class adds some new behaviour to the standard ActionExecuterAbstractBase + * in order to support the RenditionService. + * + * @author Neil McErlean + * @author Nick Smith + * @since 3.3 + */ +public abstract class AbstractRenderingEngine extends ActionExecuterAbstractBase +{ + + /** Logger */ + private static Log logger = LogFactory.getLog(AbstractRenderingEngine.class); + + protected static final String CONTENT_READER_NOT_FOUND_MESSAGE = "Cannot find Content Reader for document. Operation can't be performed"; + + // A word on the default* fields below: + // + // RenditionExecuters can be executed with or without two optional + // parameters: "rendition node type" + // and a "rendition content property" parameter. + // These parameters can be specified on a per-action basis. + // If no value is specified, then the default is used. + // That default can be injected via Spring. + // If no default is injected via spring, then there is a "default default" + // for the two params + + /** + * This is the default default node type for renditions - used if no value + * is injected from spring. + */ + private static final QName DEFAULT_DEFAULT_RENDITION_NODE_TYPE = ContentModel.TYPE_CONTENT; + + /** + * This is the default default property used to specify where rendition + * content is stored - used if no value is injected from spring. + */ + private static final QName DEFAULT_DEFAULT_RENDITION_CONTENT_PROP = ContentModel.PROP_CONTENT; + + private static final String DEFAULT_MIMETYPE = MimetypeMap.MIMETYPE_TEXT_PLAIN; + private static final String DEFAULT_ENCODING = "UTF-8"; + + /** + * This is the default node type that is used when creating rendition + * objects. + */ + private QName defaultRenditionNodeType = DEFAULT_DEFAULT_RENDITION_NODE_TYPE; + + /** + * This is the default property that is used to store rendition objects' + * content. + */ + private QName defaultRenditionContentProp = DEFAULT_DEFAULT_RENDITION_CONTENT_PROP; + + /** + * This is the default content property. + */ + private static final QName DEFAULT_CONTENT_PROPERTY = ContentModel.TYPE_CONTENT; + + /* Injected Services */ + protected ContentService contentService; + protected MimetypeMap mimetypeMap; + protected NodeService nodeService; + + /* Parameter names common to all Rendering Actions */ + //TODO javadoc these + public static final String PARAM_PLACEHOLDER_RESOURCE_PATH = "placeHolderResourcePath"; + public static final String PARAM_SOURCE_CONTENT_PROPERTY = "sourceContentProperty"; + public static final String PARAM_TARGET_CONTENT_PROPERTY = "targetContentProperty"; + public static final String PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE = "update-renditions-on-any-property-change"; + public static final String PARAM_RUN_AS = "runAs"; + + /* + * mime-type is not a common parameter on all Rendering Actions, but it is + * common to many and is used in some common handling code in this class. + */ + public static final String PARAM_MIME_TYPE = "mime-type"; + public static final String PARAM_ENCODING = "encoding"; + + /** + * Sets the default rendition-node type. + * + * @param type + */ + public void setDefaultRenditionNodeType(String type) + { + QName qname; + try + { + qname = QName.createQName(type); + } + catch (NamespaceException nx) + { + if (logger.isErrorEnabled()) + { + logger.error("Error when setting default rendition node type: ", nx); + } + throw nx; + } + + if (logger.isInfoEnabled()) + { + logger.info("Using default rendition node type: " + qname); + } + this.defaultRenditionNodeType = qname; + } + + /** + * This method returns the type of the default rendition node type. + * + * @return the QName representing the type of the default rendition node + * type. + */ + protected QName getDefaultRenditionNodeType() + { + return defaultRenditionNodeType; + } + + protected String getTargetMimeType(RenderingContext context) + { + return context.getParamWithDefault(PARAM_MIME_TYPE, DEFAULT_MIMETYPE); + } + + protected String getTargetEncoding(RenderingContext context) + { + return context.getParamWithDefault(PARAM_ENCODING, DEFAULT_ENCODING); + } + + /** + * Sets the default rendition content property. + * + * @param prop + */ + public void setDefaultRenditionContentProp(String prop) + { + QName qname; + try + { + qname = QName.createQName(prop); + } + catch (NamespaceException nx) + { + if (logger.isErrorEnabled()) + { + logger.error("Error when setting default rendition content property: ", nx); + } + throw nx; + } + + if (logger.isInfoEnabled()) + { + logger.info("Using default rendition content property: " + qname); + } + this.defaultRenditionContentProp = qname; + } + + /** + * This method returns the QName of the property that defines the location + * of the rendition content. An example would be cm:content. + * + * @return the QName the property defining the location of the rendition + * content. + */ + protected QName getDefaultRenditionContentProp() + { + return defaultRenditionContentProp; + } + + /** + * Set the content service + * + * @param contentService the content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * This method sets the nodeService. + * + * @param nodeService the namespaceService + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setMimetypeMap(MimetypeMap mimetypeMap) + { + this.mimetypeMap = mimetypeMap; + } + + @Override + protected ActionDefinition createActionDefinition(String definitionName) + { + return new RenderingEngineDefinitionImpl(definitionName); + } + + @Override + protected void executeImpl(Action action, NodeRef sourceNode) + { + checkParameterValues(action); + checkActionIsRenditionDefinition(action); + checkSourceNodeExists(sourceNode); + RenditionDefinition renditionDefinition = (RenditionDefinition) action; + + ChildAssociationRef renditionAssoc = createRenditionNodeAssoc(sourceNode, renditionDefinition); + QName targetContentProp = getRenditionContentProperty(renditionDefinition); + NodeRef destinationNode = renditionAssoc.getChildRef(); + RenderingContext context = new RenderingContext(sourceNode,// + destinationNode,// + renditionDefinition,// + targetContentProp); + render(context); + // This is a workaround for the fact that actions don't have return + // values. + action.getParameterValues().put(PARAM_RESULT, renditionAssoc); + } + + /** + * This method can be overridden by subclasses to provide checking of parameter + * values. + * If a parameter value is illegal or inappropriate, an exception + * should be thrown. + */ + protected void checkParameterValues(Action action) + { + // Intentionally empty + } + + /** + * @param renditionDefinition + * @return + */ + protected QName getRenditionContentProperty(RenditionDefinition renditionDefinition) + { + return getParamWithDefault(PARAM_TARGET_CONTENT_PROPERTY, defaultRenditionContentProp, renditionDefinition); + } + + protected abstract void render(RenderingContext context); + + /** + * @param actionedUponNodeRef + */ + protected void checkSourceNodeExists(NodeRef actionedUponNodeRef) + { + if (nodeService.exists(actionedUponNodeRef) == false) + { + String msg = "Cannot execute action as node does not exist: " + actionedUponNodeRef; + logger.warn(msg); + throw new RenditionServiceException(msg); + } + } + + /** + * @param action + */ + protected void checkActionIsRenditionDefinition(Action action) + { + if (action instanceof RenditionDefinition == false) + { + String msg = "Cannot execute action as it is not a RenditionDefinition: " + action; + logger.warn(msg); + throw new RenditionServiceException(msg); + } + } + + /** + * If no rendition node type is specified, then the default is used + * + * @param renditionDefinition + * @return + */ + public QName getRenditionNodeType(RenditionDefinition renditionDefinition) + { + return getParamWithDefault(PARAM_RENDITION_NODETYPE, defaultRenditionNodeType, renditionDefinition); + } + + @Override + final protected void addParameterDefinitions(List paramList) + { + paramList.addAll(getParameterDefinitions()); + } + + /** + * Supplies the list of parameters required by this rendering engine. + * + * @return + */ + protected Collection getParameterDefinitions() + { + List paramList = new ArrayList(); + paramList.add(new ParameterDefinitionImpl(PARAM_RUN_AS, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_RUN_AS))); + + paramList.add(new ParameterDefinitionImpl(PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_UPDATE_RENDITIONS_ON_ANY_PROPERTY_CHANGE))); + + paramList.add(new ParameterDefinitionImpl(PARAM_RENDITION_NODETYPE, DataTypeDefinition.QNAME, false, + getParamDisplayLabel(PARAM_RENDITION_NODETYPE))); + + paramList.add(new ParameterDefinitionImpl(PARAM_PLACEHOLDER_RESOURCE_PATH, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_PLACEHOLDER_RESOURCE_PATH))); + + paramList.add(new ParameterDefinitionImpl(PARAM_SOURCE_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, + getParamDisplayLabel(PARAM_SOURCE_CONTENT_PROPERTY))); + + paramList.add(new ParameterDefinitionImpl(PARAM_TARGET_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, + getParamDisplayLabel(PARAM_TARGET_CONTENT_PROPERTY))); + + paramList.add(new ParameterDefinitionImpl(PARAM_DESTINATION_PATH_TEMPLATE, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_DESTINATION_PATH_TEMPLATE))); + + paramList.add(new ParameterDefinitionImpl(PARAM_ORPHAN_EXISTING_RENDITION, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_ORPHAN_EXISTING_RENDITION))); + return paramList; + } + + private ChildAssociationRef createRenditionNodeAssoc(NodeRef sourceNode, RenditionDefinition renditionDefinition) + { + QName renditionName = renditionDefinition.getRenditionName(); + + // The ThumbnailService puts a cm:name property on its thumbnail nodes. + Map nodeProps = new HashMap(); + nodeProps.put(ContentModel.PROP_NAME, renditionName.getLocalName()); + nodeProps.put(ContentModel.PROP_CONTENT_PROPERTY_NAME, getDefaultRenditionContentProp()); + QName assocName = QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, GUID.generate()); + NodeRef parentNode = renditionDefinition.getRenditionParent(); + QName assocType = renditionDefinition.getRenditionAssociationType(); + QName nodeType = getRenditionNodeType(renditionDefinition); + ChildAssociationRef childAssoc = nodeService.createNode(parentNode, assocType, assocName, nodeType, nodeProps); + return childAssoc; + } + + /** + * Gets the value for the named parameter. Checks the type of the parameter + * is correct and throws a {@link RenditionServiceException} if it isn't. + * Returns null if the parameter value is null + * + * @param paramName the name of the parameter being checked. + * @param clazz the expected {@link Class} of the parameter value. + * @param definition the {@link RenditionDefinition} containing the + * parameters. + * @return the parameter value or null. + */ + @SuppressWarnings("unchecked") + public static T getCheckedParam(String paramName, Class clazz, RenditionDefinition definition) + { + Serializable value = definition.getParameterValue(paramName); + if (value == null) + return null; + else + { + Class valueClass = value.getClass(); + if (!valueClass.isAssignableFrom(clazz)) + { + throw new RenditionServiceException("The parameter: " + paramName + " must be of type: " + + clazz.getName() + "but was of type: " + valueClass.getName()); + } + else + return (T) value; + } + } + + /** + * Gets the value for the named parameter. Checks the type of the parameter + * is the same as the type of defaultValue and throws a + * {@link RenditionServiceException} if it isn't. Returns + * defaultValue if the parameter value is null + * + * @param + * @param paramName + * @param defaultValue + * @param definition + * @return + */ + @SuppressWarnings("unchecked") + public static T getParamWithDefault(String paramName, T defaultValue, RenditionDefinition definition) + { + Class clazz = (Class) defaultValue.getClass(); + T result = getCheckedParam(paramName, clazz, definition); + if (result == null) + result = defaultValue; + return result; + } + + protected class RenderingContext + { + private final NodeRef sourceNode; + private final NodeRef destinationNode; + private final RenditionDefinition definition; + private final QName renditionContentProperty; + + /** + * @param sourceNode + * @param destinationNode + * @param definition + * @param renditionContentProperty + */ + public RenderingContext(NodeRef sourceNode,// + NodeRef destinationNode,// + RenditionDefinition definition,// + QName renditionContentProperty) + { + this.sourceNode = sourceNode; + this.destinationNode = destinationNode; + this.definition = definition; + this.renditionContentProperty = renditionContentProperty; + } + + /** + * @return the sourceNode + */ + public NodeRef getSourceNode() + { + return this.sourceNode; + } + + /** + * @return the destinationNode + */ + public NodeRef getDestinationNode() + { + return this.destinationNode; + } + + /** + * @return the definition + */ + public RenditionDefinition getDefinition() + { + return this.definition; + } + + /** + * Gets the value for the named parameter from the . Checks the type of + * the parameter is correct and throws and Exception if it isn't. + * Returns null if the parameter value is null + * + * @param paramName the name of the parameter being checked. + * @param clazz the expected {@link Class} of the parameter value. + * @return the parameter value or null. + */ + public T getCheckedParam(String paramName, Class clazz) + { + return AbstractRenderingEngine.getCheckedParam(paramName, clazz, definition); + } + + /** + * Gets the value for the named parameter. Checks the type of the + * parameter is the same as the type of defaultValue and + * throws a {@link RenditionServiceException} if it isn't. Returns + * defaultValue if the parameter value is null + * + * @param + * @param paramName + * @param defaultValue + * @return + */ + public T getParamWithDefault(String paramName, T defaultValue) + { + return AbstractRenderingEngine.getParamWithDefault(paramName, defaultValue, definition); + } + + public ContentReader makeContentReader() + { + QName srcContentProp = getParamWithDefault(PARAM_SOURCE_CONTENT_PROPERTY, DEFAULT_CONTENT_PROPERTY); + ContentReader contentReader = contentService.getReader(sourceNode, srcContentProp); + if (contentReader == null || !contentReader.exists()) + { + throw new RenditionServiceException(CONTENT_READER_NOT_FOUND_MESSAGE); + } + return contentReader; + } + + public ContentWriter makeContentWriter() + { + ContentWriter contentWriter = contentService.getWriter(destinationNode, renditionContentProperty, true); + String mimetype = getTargetMimeType(this); + contentWriter.setMimetype(mimetype); + String encoding = getTargetEncoding(this); + contentWriter.setEncoding(encoding); + return contentWriter; + } + + public int getIntegerParam(String key, int defaultValue) + { + Serializable serializable = definition.getParameterValue(key); + if (serializable == null) + return defaultValue; + else + { + Number number = (Number) serializable; + return number.intValue(); + } + } + } +} diff --git a/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java new file mode 100644 index 0000000000..043f543827 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/AbstractTransformationRenderingEngine.java @@ -0,0 +1,88 @@ +/* + * 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.rendition.executer; + +import org.alfresco.repo.content.transform.ContentTransformer; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NoTransformerException; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * @author Nick Smith + */ +public abstract class AbstractTransformationRenderingEngine extends AbstractRenderingEngine +{ + private static Log logger = LogFactory.getLog(AbstractTransformationRenderingEngine.class); + + /* 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"; + private static final String TRANSFORMING_ERROR_MESSAGE = "Some error occurred during document transforming. Error message: "; + + /* + * (non-Javadoc) + * @see org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org.alfresco.repo.rendition.executer.AbstractRenderingEngine.RenderingContext) + */ + @Override + protected void render(RenderingContext context) + { + ContentReader contentReader = context.makeContentReader(); + String sourceMimeType = contentReader.getMimetype(); + String targetMimeType = getTargetMimeType(context); + + TransformationOptions options = getTransformOptions(context); + + ContentTransformer transformer = this.contentService.getTransformer(sourceMimeType, targetMimeType, options); + + // Actually perform the rendition. + if (null == transformer) + { + throw new RenditionServiceException(String.format(TRANSFORMER_NOT_EXISTS_MESSAGE_PATTERN, sourceMimeType, + targetMimeType)); + } + + if (transformer.isTransformable(sourceMimeType, targetMimeType, options)) + { + ContentWriter contentWriter = context.makeContentWriter(); + try + { + contentService.transform(contentReader, contentWriter, options); + } + catch (NoTransformerException ntx) + { + { + logger.debug("No transformer found to execute rule: \n" + " reader: " + contentReader + "\n" + + " writer: " + contentWriter + "\n" + " action: " + this); + } + throw new RenditionServiceException(TRANSFORMING_ERROR_MESSAGE + ntx.getMessage(), ntx); + } + } + else + { + throw new RenditionServiceException(String.format(NOT_TRANSFORMABLE_MESSAGE_PATTERN, sourceMimeType, targetMimeType)); + } + } + + protected abstract TransformationOptions getTransformOptions(RenderingContext context); +} diff --git a/source/java/org/alfresco/repo/rendition/executer/CompositeRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/CompositeRenderingEngine.java new file mode 100644 index 0000000000..80f0f6b77e --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/CompositeRenderingEngine.java @@ -0,0 +1,129 @@ +/* + * 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.rendition.executer; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.rendition.CompositeRenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This RenderingEngine is used for rendering + * {@link CompositeRenditionDefinition}s, which specify a list of + * {@link RenditionDefinition}s. The {@link CompositeRenderingEngine} iterates + * over the {@link RenditionDefinition}s sequentially and feeds the output of + * one definition in as the input of the next definition. The output of the last + * definition executed is the output of this rendering engine. + * + * @author Nick Smith + */ +public class CompositeRenderingEngine extends AbstractRenderingEngine +{ + /** Logger */ + private static Log logger = LogFactory.getLog(CompositeRenderingEngine.class); + + public static final String NAME = "compositeRenderingEngine"; + + private ActionService actionService; + + /* + * @see + * org.alfresco.repo.rendition.executer.AbstractRenderingEngine#executeImpl + * (org.alfresco.service.cmr.action.Action, + * org.alfresco.service.cmr.repository.NodeRef) + */ + @Override + protected void executeImpl(Action action, NodeRef sourceNode) + { + checkSourceNodeExists(sourceNode); + if (action instanceof CompositeRenditionDefinition) + { + CompositeRenditionDefinition compositeDefinition = (CompositeRenditionDefinition) action; + ChildAssociationRef renditionAssoc = executeCompositeRendition(compositeDefinition, sourceNode); + + // Setting result. + compositeDefinition.setParameterValue(PARAM_RESULT, renditionAssoc); + } + else + { + String msg = "This method requires that the RenditionDefinition be of type CompositeRenditionDefinition"; + logger.warn(msg); + throw new RenditionServiceException(msg); + } + } + + private ChildAssociationRef executeCompositeRendition(CompositeRenditionDefinition definition, NodeRef sourceNode) + { + NodeRef source = sourceNode; + ChildAssociationRef result = null; + QName assocType = definition.getRenditionAssociationType(); + NodeRef parent = definition.getRenditionParent(); + for (RenditionDefinition subDefinition : definition.getActions()) + { + ChildAssociationRef newResult = executeSubDefinition(source, subDefinition, parent, assocType); + if (result != null) + { + // Clean up temporary renditions. + nodeService.removeChild(parent, result.getChildRef()); + } + result = newResult; + source = newResult.getChildRef(); + } + return result; + } + + private ChildAssociationRef executeSubDefinition(NodeRef source,// + RenditionDefinition subDefinition,// + NodeRef parent,// + QName assocType) + { + subDefinition.setRenditionParent(parent); + subDefinition.setRenditionAssociationType(assocType); + actionService.executeAction(subDefinition, source); + ChildAssociationRef newResult = (ChildAssociationRef) subDefinition.getParameterValue(PARAM_RESULT); + return newResult; + } + + /* + * @see + * org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org + * .alfresco + * .repo.rendition.executer.AbstractRenderingEngine.RenderingContext) + */ + @Override + protected void render(RenderingContext data) + { + throw new RenditionServiceException("This method should never be caleld!"); + } + + /** + * @param actionService the actionService to set + */ + public void setActionService(ActionService actionService) + { + this.actionService = actionService; + } +} diff --git a/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java new file mode 100644 index 0000000000..908333aab7 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/ImageRenderingEngine.java @@ -0,0 +1,227 @@ +/* + * 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.rendition.executer; + +import java.util.Collection; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.content.transform.magick.ImageCropOptions; +import org.alfresco.repo.content.transform.magick.ImageResizeOptions; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.TransformationOptions; + +/** + * This class is the implementation of the {@link RenditionService}'s + * "imageRenderingEngine" rendering engine. This action renders a piece of + * content in the same MIME type as its source node, having been rescaled as + * requested. + * + * @author Neil McErlean + * @since 3.3 + */ +public class ImageRenderingEngine extends AbstractTransformationRenderingEngine +{ + // TODO This rendering engine should only take input of MIME type image/* + // However, we'll defer the addition of an EngineInputFilter until after + // Sprint 3. + + public static final String NAME = "imageRenderingEngine"; + + // Resize params + public static final String PARAM_RESIZE_WIDTH = "xsize"; + public static final String PARAM_RESIZE_HEIGHT = "ysize"; + public static final String PARAM_IS_PERCENT_RESIZE = "isAbsolute"; + public static final String PARAM_MAINTAIN_ASPECT_RATIO = "maintainAspectRatio"; + public static final String PARAM_RESIZE_TO_THUMBNAIL = "resizeToThumbnail"; + + // Crop params + public static final String PARAM_CROP_WIDTH = "crop_width"; + public static final String PARAM_CROP_HEIGHT = "crop_height"; + public static final String PARAM_CROP_X_OFFSET = "crop_x"; + public static final String PARAM_CROP_Y_OFFSET = "crop_y"; + public static final String PARAM_CROP_GRAVITY = "crop_gravity"; + public static final String PARAM_IS_PERCENT_CROP = "percent_crop"; + + public static final String PARAM_COMMAND_OPTIONS = "commandOptions"; + + /* + * @seeorg.alfresco.repo.rendition.executer.ReformatRenderingEngine# + * getTransformOptions + * (org.alfresco.repo.rendition.executer.AbstractRenderingEngine + * .RenderingContext) + */ + @Override + protected TransformationOptions getTransformOptions(RenderingContext context) + { + String commandOptions = context.getCheckedParam(PARAM_COMMAND_OPTIONS, String.class); + ImageResizeOptions imageResizeOptions = getImageResizeOptions(context); + ImageCropOptions cropOptions = getImageCropOptions(context); + + ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); + imageTransformationOptions.setResizeOptions(imageResizeOptions); + imageTransformationOptions.setCropOptions(cropOptions); + if (commandOptions != null) + { + imageTransformationOptions.setCommandOptions(commandOptions); + } + return imageTransformationOptions; + } + + /* + * @seeorg.alfresco.repo.rendition.executer.ReformatRenderingEngine# + * getTargetMimeType + * (org.alfresco.repo.rendition.executer.AbstractRenderingEngine + * .RenderingContext) + */ + @Override + protected String getTargetMimeType(RenderingContext context) + { + String sourceMimeType = context.makeContentReader().getMimetype(); + return context.getParamWithDefault(PARAM_MIME_TYPE, sourceMimeType); + } + + private ImageResizeOptions getImageResizeOptions(RenderingContext context) + { + int newHeight = context.getIntegerParam(PARAM_RESIZE_WIDTH, -1); + int newWidth = context.getIntegerParam(PARAM_RESIZE_HEIGHT, -1); + if (newHeight == -1 && newWidth == -1) + { + return null; // Image is not being resized! + } + boolean isPercentResize = context.getParamWithDefault(PARAM_IS_PERCENT_RESIZE, false); + boolean maintainAspectRatio = context.getParamWithDefault(PARAM_MAINTAIN_ASPECT_RATIO, false); + + ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); + imageResizeOptions.setMaintainAspectRatio(maintainAspectRatio); + imageResizeOptions.setWidth(newHeight); + imageResizeOptions.setHeight(newWidth); + imageResizeOptions.setPercentResize(isPercentResize); + return imageResizeOptions; + } + + private ImageCropOptions getImageCropOptions(RenderingContext context) + { + int newWidth = context.getIntegerParam(PARAM_CROP_WIDTH, -1); + int newHeight = context.getIntegerParam(PARAM_CROP_HEIGHT, -1); + if (newHeight == -1 && newWidth == -1) + { + return null; + } + + int xOffset = context.getIntegerParam(PARAM_CROP_X_OFFSET, 0); + int yOffset = context.getIntegerParam(PARAM_CROP_Y_OFFSET, 0); + + boolean isPercentCrop = context.getParamWithDefault(PARAM_IS_PERCENT_CROP, false); + String gravity = context.getCheckedParam(PARAM_CROP_GRAVITY, String.class); + + ImageCropOptions cropOptions = new ImageCropOptions(); + cropOptions.setGravity(gravity); + cropOptions.setHeight(newHeight); + cropOptions.setPercentageCrop(isPercentCrop); + cropOptions.setWidth(newWidth); + cropOptions.setXOffset(xOffset); + cropOptions.setYOffset(yOffset); + return cropOptions; + } + + @Override + protected void checkParameterValues(Action action) + { + // Some numerical parameters should not be zero or negative. + checkNumericalParameterIsPositive(action, PARAM_RESIZE_WIDTH); + checkNumericalParameterIsPositive(action, PARAM_RESIZE_HEIGHT); + checkNumericalParameterIsPositive(action, PARAM_CROP_HEIGHT); + checkNumericalParameterIsPositive(action, PARAM_CROP_WIDTH); + + // Target mime type should only be an image MIME type + String mimeTypeParam = (String)action.getParameterValue(PARAM_MIME_TYPE); + if (mimeTypeParam != null && !mimeTypeParam.startsWith("image")) + { + StringBuilder msg = new StringBuilder(); + msg.append("Parameter ").append(PARAM_MIME_TYPE) + .append(" had illegal non-image MIME type: ").append(mimeTypeParam); + throw new IllegalArgumentException(msg.toString()); + } + } + + /** + * This method checks that if the specified parameter is non-null, that it has a + * positive numerical value. That is it is non-zero and positive. + * + * @param action + * @param numericalParamName must be an instance of java.lang.Number or null. + */ + private void checkNumericalParameterIsPositive(Action action, String numericalParamName) + { + Number param = (Number)action.getParameterValue(numericalParamName); + if (param != null && param.longValue() <= 0) + { + StringBuilder msg = new StringBuilder(); + msg.append("Parameter ").append(numericalParamName) + .append(" had illegal non-positive value: ").append(param.intValue()); + throw new IllegalArgumentException(msg.toString()); + } + } + + + /* + * @seeorg.alfresco.repo.rendition.executer.AbstractRenderingEngine# + * getParameterDefinitions() + */ + @Override + protected Collection getParameterDefinitions() + { + Collection paramList = super.getParameterDefinitions(); + + //Resize Params + paramList.add(new ParameterDefinitionImpl(PARAM_RESIZE_WIDTH, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_RESIZE_WIDTH))); + paramList.add(new ParameterDefinitionImpl(PARAM_RESIZE_HEIGHT, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_RESIZE_HEIGHT))); + paramList.add(new ParameterDefinitionImpl(PARAM_IS_PERCENT_RESIZE, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_IS_PERCENT_RESIZE))); + paramList.add(new ParameterDefinitionImpl(PARAM_MAINTAIN_ASPECT_RATIO, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_MAINTAIN_ASPECT_RATIO))); + paramList.add(new ParameterDefinitionImpl(PARAM_RESIZE_TO_THUMBNAIL, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_RESIZE_TO_THUMBNAIL))); + + //Crop Params + paramList.add(new ParameterDefinitionImpl(PARAM_CROP_GRAVITY, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_CROP_GRAVITY))); + paramList.add(new ParameterDefinitionImpl(PARAM_CROP_HEIGHT, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_CROP_HEIGHT))); + paramList.add(new ParameterDefinitionImpl(PARAM_CROP_WIDTH, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_CROP_WIDTH))); + paramList.add(new ParameterDefinitionImpl(PARAM_CROP_X_OFFSET, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_CROP_X_OFFSET))); + paramList.add(new ParameterDefinitionImpl(PARAM_CROP_Y_OFFSET, DataTypeDefinition.INT, false, + getParamDisplayLabel(PARAM_CROP_Y_OFFSET))); + paramList.add(new ParameterDefinitionImpl(PARAM_IS_PERCENT_CROP, DataTypeDefinition.BOOLEAN, false, + getParamDisplayLabel(PARAM_IS_PERCENT_CROP))); + + paramList.add(new ParameterDefinitionImpl(PARAM_COMMAND_OPTIONS, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_COMMAND_OPTIONS))); + return paramList; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java new file mode 100644 index 0000000000..8ba7cbf01a --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/ReformatRenderingEngine.java @@ -0,0 +1,98 @@ +/* + * 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.rendition.executer; + +import java.util.Collection; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * This class is the implementation of the {@link RenditionService}'s "reformat" + * action/rendering. This action renders a piece of content in the specified + * target MIME type. This is achieved using one of the standard transformers + * within the {@link ContentService}. + *

+ * Reformatting in this way is a simple conversion of one MIME type to another + * MIME type, without any other changes to the content. Therefore there is no + * support within this action for altering the content e.g. image + * cropping/resizing. + * + * @author Neil McErlean + * @since 3.3 + */ +public class ReformatRenderingEngine extends AbstractTransformationRenderingEngine +{ + private static Log logger = LogFactory.getLog(ReformatRenderingEngine.class); + + /** + * This parameter is only necessary when converting from pdf to flash. + */ + public static final String PARAM_FLASH_VERSION = "flashVersion"; + + /* + * Action constants + */ + public static final String NAME = "reformat"; + + @Override + protected String getTargetMimeType(RenderingContext context) + { + String targetMimeType = context.getCheckedParam(PARAM_MIME_TYPE, String.class); + if (targetMimeType == null) + { + String msg = "The parameter: " + PARAM_MIME_TYPE + " must be explicitly set for this rendering engine!"; + logger.warn(msg); + throw new RenditionServiceException(msg); + } + return targetMimeType; + } + + @Override + protected TransformationOptions getTransformOptions(RenderingContext context) + { + NodeRef sourceNode = context.getSourceNode(); + NodeRef destinationNode = context.getDestinationNode(); + return new TransformationOptions(sourceNode, null, destinationNode, null); + } + + /* + * @see org.alfresco.repo.rendition.executer.AbstractRenderingEngine# + * getParameterDefinitions() + */ + @Override + protected Collection getParameterDefinitions() + { + Collection paramList = super.getParameterDefinitions(); + paramList.add(new ParameterDefinitionImpl(PARAM_MIME_TYPE, DataTypeDefinition.TEXT, true, + getParamDisplayLabel(PARAM_MIME_TYPE))); + paramList.add(new ParameterDefinitionImpl(PARAM_FLASH_VERSION, DataTypeDefinition.TEXT, false, + getParamDisplayLabel(PARAM_FLASH_VERSION))); + return paramList; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/repo/rendition/executer/TemplateModelHelper.java b/source/java/org/alfresco/repo/rendition/executer/TemplateModelHelper.java new file mode 100644 index 0000000000..33e11bd193 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/TemplateModelHelper.java @@ -0,0 +1,113 @@ +/* + * 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.rendition.executer; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.repo.model.Repository; +import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.repo.template.TemplateNode; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateService; + +/** + * @author Nick Smith + */ +public class TemplateModelHelper +{ + public static final String KEY_NODE = "node"; + + private final Repository repository; + private final ServiceRegistry serviceRegistry; + private final TemplateService templateService; + + /** + * @param templateService + * @param repository + * @param serviceRegistry + */ + public TemplateModelHelper(TemplateService templateService, Repository repository, ServiceRegistry serviceRegistry) + { + super(); + this.templateService = templateService; + this.repository = repository; + this.serviceRegistry = serviceRegistry; + } + + /** + * Builds up the model map used by the {@link TemplateService} to process + * FreeMarker templates. + * + * @param sourceNode the node containing the content to be processed by the + * template. + * @param templateNode the node containing the template. Can be + * null. + * @param imgResolver the image resolver used to process images. Can be + * null. + * @param paramMap a map of parameters to add to the model. Can be + * null. + * @return the populated model {@link Map}. + */ + public Map buildModelMap(NodeRef sourceNode,// + NodeRef templateNode,// + TemplateImageResolver imgResolver,// + Map paramMap) + { + Map model = buildDefaultModel(templateNode, imgResolver); + TemplateNode sourceTemplateNode = new TemplateNode(sourceNode, serviceRegistry, null); + // TODO Add xml dom here. + // model.put("xml", NodeModel.wrap(null)); + model.put(KEY_NODE, sourceTemplateNode); + if (paramMap != null) + model.putAll(paramMap); + return model; + } + + /** + * Builds the default model populated with the current user, company home + * and user home. + * + * @param templateNode the node containing the template. Can be + * null. + * @param imgResolver the image resolver used to process images. Can be + * null. + * @return the default model {@link Map}. + */ + public Map buildDefaultModel(NodeRef templateNode, TemplateImageResolver imgResolver) + { + // The templateNode can be null. + NodeRef companyHome = repository.getCompanyHome(); + + // The fully authenticated user below is the username of the person who logged in and + // who requested the execution of the current rendition. This will not be the + // same person as the current user as renditions are executed by the system user. + String fullyAuthenticatedUser = AuthenticationUtil.getFullyAuthenticatedUser(); + NodeRef person = serviceRegistry.getPersonService().getPerson(fullyAuthenticatedUser); + + NodeRef userHome = repository.getUserHome(person); + Map model = templateService.buildDefaultModel(person, companyHome, userHome, templateNode, + imgResolver); + return model; + } + +} diff --git a/source/java/org/alfresco/repo/rendition/executer/TemplatingRenderingEngine.java b/source/java/org/alfresco/repo/rendition/executer/TemplatingRenderingEngine.java new file mode 100644 index 0000000000..a92f2bfd22 --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/executer/TemplatingRenderingEngine.java @@ -0,0 +1,225 @@ +/* + * 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.rendition.executer; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.Serializable; +import java.io.Writer; +import java.util.Collection; +import java.util.Map; + +import org.alfresco.repo.action.ParameterDefinitionImpl; +import org.alfresco.repo.model.Repository; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.action.ParameterDefinition; +import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.rendition.RenditionServiceException; +import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.StoreRef; +import org.alfresco.service.cmr.repository.TemplateImageResolver; +import org.alfresco.service.cmr.repository.TemplateService; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.SearchService; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; + +/** + * @author Nick Smith + */ +public class TemplatingRenderingEngine// + extends AbstractRenderingEngine// + implements InitializingBean +{ + private final static Log log = LogFactory.getLog(TemplatingRenderingEngine.class); + + public static final String NAME = "templatingRenderingEngine"; + public static final String PARAM_MODEL = "model"; + public static final String PARAM_TEMPLATE = "template_string"; + public static final String PARAM_TEMPLATE_NODE = "template_node"; + public static final String PARAM_TEMPLATE_PATH = "template_path"; + private static final String PARAM_IMAGE_RESOLVER = "image_resolver"; + + private TemplateService templateService; + private Repository repository; + private ServiceRegistry serviceRegistry; + private TemplateModelHelper modelHelper; + + /* + * @see + * org.alfresco.repo.rendition.executer.AbstractRenderingEngine#render(org + * .alfresco.service.cmr.repository.NodeRef, + * org.alfresco.service.cmr.rendition.RenditionDefinition, + * org.alfresco.service.cmr.repository.ContentReader, + * org.alfresco.service.cmr.repository.ChildAssociationRef) + */ + @SuppressWarnings("unchecked") + @Override + protected void render(RenderingContext context) + { + NodeRef sourceNode = context.getSourceNode(); + NodeRef templateNode = getTemplateNode(context); + Map paramMap = context.getCheckedParam(PARAM_MODEL, Map.class); + TemplateImageResolver imgResolver = context.getCheckedParam(PARAM_IMAGE_RESOLVER, TemplateImageResolver.class); + Map model = modelHelper.buildModelMap(sourceNode, templateNode, imgResolver, paramMap); + + processTemplate(context, templateNode, model); + } + + private void processTemplate(RenderingContext context, NodeRef templateNode, Map model) + { + String template = context.getCheckedParam(PARAM_TEMPLATE, String.class); + if ((template == null) && (templateNode == null)) + { + throwTemplateParamsNotFoundException(); + } + + ContentWriter contentWriter = context.makeContentWriter(); + Writer writer = new OutputStreamWriter(contentWriter.getContentOutputStream()); + try + { + if (template != null) + { + templateService.processTemplateString("freemarker", template, model, writer); + } + else if (templateNode != null) + { + templateService.processTemplate("freemarker", templateNode.toString(), model, writer); + } + } + finally + { + try + { + writer.close(); + } catch (IOException ex) + { + // Nothing that can be done. Log it and move on. + log.warn("Failed to close content writer: ", ex); + } + } + } + + private void throwTemplateParamsNotFoundException() + { + StringBuilder msg = new StringBuilder("This action requires that either the "); + msg.append(PARAM_TEMPLATE); + msg.append(" parameter or the "); + msg.append(PARAM_TEMPLATE_NODE); + msg.append(" parameter be specified. "); + throw new RenditionServiceException(msg.toString()); + } + + private NodeRef getTemplateNode(RenderingContext context) + { + NodeRef node = context.getCheckedParam(PARAM_TEMPLATE_NODE, NodeRef.class); + if (node == null) + { + String path = context.getCheckedParam(PARAM_TEMPLATE_PATH, String.class); + if (path != null && path.length() > 0) + { + SearchService searchService = serviceRegistry.getSearchService(); + StoreRef storeRef = context.getDestinationNode().getStoreRef(); + ResultSet result = searchService.query(storeRef, SearchService.LANGUAGE_XPATH, path); + if (result.length() != 1) + { + throw new RenditionServiceException("Could not find template node for path: " + path); + } + node = result.getNodeRef(0); + } + } + return node; + } + + /* + * @seeorg.alfresco.repo.rendition.executer.AbstractRenderingEngine# + * getParameterDefinitions() + */ + @Override + protected Collection getParameterDefinitions() + { + Collection paramList = super.getParameterDefinitions(); + ParameterDefinitionImpl modelParamDef = new ParameterDefinitionImpl(// + PARAM_MODEL,// + DataTypeDefinition.ANY,// + false,// + getParamDisplayLabel(PARAM_MODEL)); + ParameterDefinitionImpl templateParamDef = new ParameterDefinitionImpl(// + PARAM_TEMPLATE,// + DataTypeDefinition.TEXT,// + false,// + getParamDisplayLabel(PARAM_TEMPLATE)); + ParameterDefinitionImpl templateNodeParamDef = new ParameterDefinitionImpl(// + PARAM_TEMPLATE_NODE,// + DataTypeDefinition.NODE_REF,// + false,// + getParamDisplayLabel(PARAM_TEMPLATE_NODE)); + ParameterDefinitionImpl templatePathParamDef = new ParameterDefinitionImpl(// + PARAM_TEMPLATE_PATH,// + DataTypeDefinition.TEXT,// + false,// + getParamDisplayLabel(PARAM_TEMPLATE_PATH)); + ParameterDefinitionImpl imgResolverParamDef = new ParameterDefinitionImpl(// + PARAM_IMAGE_RESOLVER,// + DataTypeDefinition.ANY,// + false,// + getParamDisplayLabel(PARAM_IMAGE_RESOLVER)); + paramList.add(modelParamDef); + paramList.add(templateParamDef); + paramList.add(templateNodeParamDef); + paramList.add(templatePathParamDef); + paramList.add(imgResolverParamDef); + return paramList; + } + + /** + * @param templateService the templateService to set + */ + public void setTemplateService(TemplateService templateService) + { + this.templateService = templateService; + } + + /** + * @param repository the repository to set + */ + public void setRepositoryHelper(Repository repository) + { + this.repository = repository; + } + + /** + * @param serviceRegistry the serviceRegistry to set + */ + public void setServiceRegistry(ServiceRegistry serviceRegistry) + { + this.serviceRegistry = serviceRegistry; + } + + /** + * Sets up {@link TemplateModelHelper}. + */ + public void afterPropertiesSet() + { + this.modelHelper = new TemplateModelHelper(templateService, repository, serviceRegistry); + } +} diff --git a/source/java/org/alfresco/repo/rendition/renditionTestTemplate.ftl b/source/java/org/alfresco/repo/rendition/renditionTestTemplate.ftl new file mode 100644 index 0000000000..d78a58ab0f --- /dev/null +++ b/source/java/org/alfresco/repo/rendition/renditionTestTemplate.ftl @@ -0,0 +1,59 @@ +

Test Template 1
+ +<#-- Title is used in test code to ensure that updates to source node rerender properly --> +<#-- The xxx xxx demarcation here is used by the test code --> +TestTitle= xxx${node.properties["cm:title"]?string}xxx + +<#-- Test basic properties --> +${node.id}
+${node.name}
+${node.properties?size}
+${node.children?size}
+ +<#if node.assocs["cm:translations"]?exists> +node.assocs
+ +${node.aspects?size}
+<#if node.isContainer>node.isContainer
+<#if node.isDocument>node.isDocument
+<#--${node.content}
--> +${node.url}
+${node.displayPath}
+${node.icon16}
+${node.icon32}
+<#if node.mimetype?exists>node.mimetype
+<#if node.size?exists>node.size
+<#if node.isLocked>node.isLocked
+ +<#-- Test child walking and property resolving --> + +<#list node.children as child> + <#-- show properties of each child --> + <#assign props = child.properties?keys> + <#list props as t> + <#-- If the property exists --> + <#if child.properties[t]?exists> + <#-- If it is a date, format it accordingly--> + <#if child.properties[t]?is_date> + + + <#-- If it is a boolean, format it accordingly--> + <#elseif child.properties[t]?is_boolean> + + + <#-- Otherwise treat it as a string --> + <#else> + + + + + + +
${t} = ${child.properties[t]?date}
${t} = ${child.properties[t]?string("yes", "no")}
${t} = ${child.properties[t]}
+ +<#-- Test XPath --> +<#list node.childrenByXPath["//*[@sys:store-protocol='workspace']"] as child> + ${child.name} + + +
End Test Template 1
diff --git a/source/java/org/alfresco/repo/template/TemplateServiceImplTest.java b/source/java/org/alfresco/repo/template/TemplateServiceImplTest.java index f499c38228..376dfcaaf9 100644 --- a/source/java/org/alfresco/repo/template/TemplateServiceImplTest.java +++ b/source/java/org/alfresco/repo/template/TemplateServiceImplTest.java @@ -44,6 +44,7 @@ import org.springframework.context.ApplicationContext; */ public class TemplateServiceImplTest extends TestCase { + private static final String TEMPLATE_1 = "org/alfresco/repo/template/test_template1.ftl"; private static final ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); private TemplateService templateService; @@ -127,5 +128,4 @@ public class TemplateServiceImplTest extends TestCase }); } - private static final String TEMPLATE_1 = "org/alfresco/repo/template/test_template1.ftl"; } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index 7df1ab2569..1b90821b24 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -19,30 +19,43 @@ package org.alfresco.repo.thumbnail; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ContentService; import org.alfresco.service.cmr.thumbnail.ThumbnailException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; /** * Registry of all the thumbnail details available * * @author Roy Wetherall + * @author Neil McErlean */ public class ThumbnailRegistry { /** Content service */ private ContentService contentService; - /** Map of thumbnail defintion */ - private Map thumbnailDefinitions = new HashMap(7); + /** Rendition service */ + private RenditionService renditionService; + + private List thumbnails; + + /** This flag indicates whether the thumbnail definitions have been lazily loaded or not. */ + private boolean thumbnailDefinitionsInited = false; + + /** Map of thumbnail definition */ + private Map thumbnailDefinitions = new HashMap(); /** Cache to store mimetype to thumbnailDefinition mapping */ private Map> mimetypeMap = new HashMap>(17); - + /** * Content service * @@ -54,36 +67,63 @@ public class ThumbnailRegistry } /** - * Add a number of thumbnail defintions + * Rendition service * - * @param thumbnailDefinitions list of thumbnail details + * @param renditionService rendition service */ - public void setThumbnailDefinitions(List thumbnailDefinitions) + public void setRenditionService(RenditionService renditionService) { - for (ThumbnailDefinition value : thumbnailDefinitions) - { - addThumbnailDefinition(value); - } + this.renditionService = renditionService; + } + + public void setThumbnails(final List thumbnails) + { + this.thumbnails = thumbnails; + + // We'll not populate the data fields in the ThumbnailRegistry here, instead preferring + // to do it lazily later. } /** - * Get a list of all the thumbnail defintions + * Get a list of all the thumbnail definitions * - * @return Collection colleciton of thumbnail defintions + * @return Collection collection of thumbnail definitions */ public List getThumbnailDefinitions() { + if (thumbnailDefinitionsInited == false) + { + this.initThumbnailDefinitions(); + thumbnailDefinitionsInited = true; + } return new ArrayList(this.thumbnailDefinitions.values()); } - /** - * - * @param mimetype - * @return - */ - public List getThumnailDefintions(String mimetype) + private void initThumbnailDefinitions() { - List result = this.mimetypeMap.get(mimetype);; + ThumbnailRenditionConvertor thumbnailRenditionConvertor = new ThumbnailRenditionConvertor(); + + for (String thumbnailDefinitionName : this.thumbnails) + { + QName qName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbnailDefinitionName); + RenditionDefinition rAction = renditionService + .loadRenditionDefinition(qName); + + ThumbnailDefinition thDefn = thumbnailRenditionConvertor.convert(rAction); + + thumbnailDefinitions.put(thumbnailDefinitionName, thDefn); + } + } + + public List getThumbnailDefinitions(String mimetype) + { + if (thumbnailDefinitionsInited == false) + { + this.initThumbnailDefinitions(); + thumbnailDefinitionsInited = true; + } + + List result = this.mimetypeMap.get(mimetype); if (result == null) { @@ -107,12 +147,28 @@ public class ThumbnailRegistry } /** - * Add a thumnail details + * + * @param mimetype + * @return + * @deprecated Use {@link #getThumbnailDefinitions(String)} instead. + */ + public List getThumnailDefintions(String mimetype) + { + return this.getThumbnailDefinitions(mimetype); + } + + /** + * Add a thumbnail details * * @param thumbnailDetails thumbnail details */ public void addThumbnailDefinition(ThumbnailDefinition thumbnailDetails) { + if (thumbnailDefinitionsInited == false) + { + this.initThumbnailDefinitions(); + thumbnailDefinitionsInited = true; + } String thumbnailName = thumbnailDetails.getName(); if (thumbnailName == null) { @@ -130,6 +186,11 @@ public class ThumbnailRegistry */ public ThumbnailDefinition getThumbnailDefinition(String thumbnailName) { + if (thumbnailDefinitionsInited == false) + { + this.initThumbnailDefinitions(); + thumbnailDefinitionsInited = true; + } return this.thumbnailDefinitions.get(thumbnailName); } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java new file mode 100644 index 0000000000..a5685b055a --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRenditionConvertor.java @@ -0,0 +1,164 @@ +/* + * 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.thumbnail; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.repo.content.transform.magick.ImageResizeOptions; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.content.transform.swf.SWFTransformationOptions; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.repo.rendition.executer.ImageRenderingEngine; +import org.alfresco.repo.rendition.executer.ReformatRenderingEngine; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.TransformationOptions; +import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; + +/** + * A helper class to convert {@link TransformationOptions transformationOptions} (a thumbnail-specific + * class) to rendition-specific parameters and vice versa. + * + * @author Neil McErlean + */ +public class ThumbnailRenditionConvertor +{ + public Map convert(TransformationOptions transformationOptions, ThumbnailParentAssociationDetails assocDetails) + { + Map parameters = new HashMap(); + + // parameters common to all transformations + putParameterIfNotNull(AbstractRenderingEngine.PARAM_SOURCE_CONTENT_PROPERTY, transformationOptions.getSourceContentProperty(), parameters); + putParameterIfNotNull(AbstractRenderingEngine.PARAM_TARGET_CONTENT_PROPERTY, transformationOptions.getTargetContentProperty(), parameters); + putParameterIfNotNull(RenditionService.PARAM_DESTINATION_NODE, transformationOptions.getTargetNodeRef(), parameters); + +// putParameterIfNotNull(ImageRenderingEngine.PARAM_ASSOC_NAME, assocDetails.getAssociationName(), parameters); +// putParameterIfNotNull(ImageRenderingEngine.PARAM_ASSOC_TYPE, assocDetails.getAssociationType(), parameters); + + if (transformationOptions instanceof SWFTransformationOptions) + { + SWFTransformationOptions swfTransformationOptions = (SWFTransformationOptions)transformationOptions; + putParameterIfNotNull(ReformatRenderingEngine.PARAM_FLASH_VERSION, swfTransformationOptions.getFlashVersion(), parameters); + } + else if (transformationOptions instanceof ImageTransformationOptions) + { + ImageTransformationOptions imTransformationOptions = (ImageTransformationOptions)transformationOptions; + putParameterIfNotNull(ImageRenderingEngine.PARAM_COMMAND_OPTIONS, imTransformationOptions.getCommandOptions(), parameters); + + ImageResizeOptions imgResizeOptions = imTransformationOptions.getResizeOptions(); + if (imgResizeOptions != null) + { + int width = imgResizeOptions.getWidth(); + parameters.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, width); + + int height = imgResizeOptions.getHeight(); + parameters.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, height); + + boolean maintainAspectRatio = imgResizeOptions.isMaintainAspectRatio(); + parameters.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, maintainAspectRatio); + + boolean percentResize = imgResizeOptions.isPercentResize(); + parameters.put(ImageRenderingEngine.PARAM_IS_PERCENT_RESIZE, percentResize); + + boolean resizeToThumbnail = imgResizeOptions.isResizeToThumbnail(); + parameters.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, resizeToThumbnail); + } + } + + return parameters; + } + + private void putParameterIfNotNull(String paramName, Serializable paramValue, Map params) + { + if (paramValue != null) + { + params.put(paramName, paramValue); + } + } + + public ThumbnailDefinition convert(RenditionDefinition renditionDefinition) + { + ThumbnailDefinition thDefn = new ThumbnailDefinition(); + + Map params = renditionDefinition.getParameterValues(); + + //parameters common to all the built-in thumbnail definitions + Serializable mimeTypeParam = params.get(AbstractRenderingEngine.PARAM_MIME_TYPE); + thDefn.setMimetype((String) mimeTypeParam); + thDefn.setName(renditionDefinition.getRenditionName().getLocalName()); + + Serializable placeHolderResourcePathParam = params.get(AbstractRenderingEngine.PARAM_PLACEHOLDER_RESOURCE_PATH); + if (placeHolderResourcePathParam != null) + { + thDefn.setPlaceHolderResourcePath((String)placeHolderResourcePathParam); + } + + //TODO src/target contentProp & nodeRef + + TransformationOptions transformationOptions = null; + Serializable flashVersion = renditionDefinition.getParameterValue(ReformatRenderingEngine.PARAM_FLASH_VERSION); + if (flashVersion != null) + { + // Thumbnails based on SWFTransformationOptions + transformationOptions = new SWFTransformationOptions(); + SWFTransformationOptions swfTranOpts = (SWFTransformationOptions)transformationOptions; + swfTranOpts.setFlashVersion((String)flashVersion); + } + else + { + // Thumbnails based on ImageTransformationOptions + transformationOptions = new ImageTransformationOptions(); + ImageTransformationOptions imgTrOpts = (ImageTransformationOptions)transformationOptions; + + ImageResizeOptions resizeOptions = new ImageResizeOptions(); + Serializable xsize = renditionDefinition.getParameterValue(ImageRenderingEngine.PARAM_RESIZE_WIDTH); + if (xsize != null) + { + // Saved actions with int parameters seem to be coming back as Longs. TODO Investigate + resizeOptions.setWidth(((Long) xsize).intValue()); + } + + Serializable ysize = renditionDefinition.getParameterValue(ImageRenderingEngine.PARAM_RESIZE_HEIGHT); + if (ysize != null) + { + resizeOptions.setHeight(((Long) ysize).intValue()); + } + + Serializable maintainAspectRatio = renditionDefinition.getParameterValue(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO); + if (maintainAspectRatio != null) + { + resizeOptions.setMaintainAspectRatio((Boolean) maintainAspectRatio); + } + + Serializable resizeToThumbnail = renditionDefinition.getParameterValue(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL); + if (resizeToThumbnail != null) + { + resizeOptions.setResizeToThumbnail((Boolean) resizeToThumbnail); + } + + imgTrOpts.setResizeOptions(resizeOptions); + } + + thDefn.setTransformationOptions(transformationOptions); + + return thDefn; + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java index 37141d4e41..3a1dc95743 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImpl.java @@ -19,21 +19,24 @@ package org.alfresco.repo.thumbnail; import java.io.Serializable; -import java.text.MessageFormat; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.content.transform.swf.SWFTransformationOptions; import org.alfresco.repo.policy.BehaviourFilter; +import org.alfresco.repo.rendition.executer.ImageRenderingEngine; +import org.alfresco.repo.rendition.executer.ReformatRenderingEngine; import org.alfresco.repo.security.authentication.AuthenticationUtil; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.rendition.RenditionServiceException; import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.ContentData; -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.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.repository.TransformationOptions; @@ -42,14 +45,14 @@ import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; -import org.alfresco.service.namespace.RegexQNamePattern; import org.alfresco.util.GUID; -import org.springframework.extensions.surf.util.ParameterCheck; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.extensions.surf.util.ParameterCheck; /** * @author Roy Wetherall + * @author Neil McErlean */ public class ThumbnailServiceImpl implements ThumbnailService { @@ -79,6 +82,19 @@ public class ThumbnailServiceImpl implements ThumbnailService /** Thumbnail registry */ private ThumbnailRegistry thumbnailRegistry; + /** Rendition service */ + private RenditionService renditionService; + + /** + * Set the rendition service. + * + * @param renditionService + */ + public void setRenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + /** * Set the node service * @@ -168,118 +184,89 @@ public class ThumbnailServiceImpl implements ThumbnailService logger.debug("Creating thumbnail: There is already a thumbnail with the name '" + thumbnailName + "' (node=" + node.toString() + "; contentProperty=" + contentProperty.toString() + "; mimetype=" + mimetype); } - // We can't continue because there is already an thumbnail with the given name for that content property + // We can't continue because there is already a thumbnail with the given name for that content property throw new ThumbnailException(ERR_DUPLICATE_NAME); } - NodeRef thumbnail = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + ChildAssociationRef thumbnailRef = AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() { - public NodeRef doWork() throws Exception + public ChildAssociationRef doWork() throws Exception { - NodeRef thumbnail; - - // Apply the thumbnailed aspect to the node if it doesn't already have it - if (nodeService.hasAspect(node, ContentModel.ASPECT_THUMBNAILED) == false) - { - // Ensure we do not update the 'modifier' due to thumbnail addition - behaviourFilter.disableBehaviour(node, ContentModel.ASPECT_AUDITABLE); - try - { - nodeService.addAspect(node, ContentModel.ASPECT_THUMBNAILED, null); - } - finally - { - behaviourFilter.enableBehaviour(node, ContentModel.ASPECT_AUDITABLE); - } - } - + // Need a variable scoped to this inner class in order to assign a new value to it. + String localThumbnailName = thumbnailName; // Get the name of the thumbnail and add to properties map - String thumbName = thumbnailName; - Map properties = new HashMap(4); - if (thumbName == null || thumbName.length() == 0) + if (localThumbnailName == null || localThumbnailName.length() == 0) { - thumbName = GUID.generate(); + localThumbnailName = GUID.generate(); } - else - { - String thumbnailFileName = generateThumbnailFileName(thumbName, mimetype); - properties.put(ContentModel.PROP_NAME, thumbnailFileName); - } - properties.put(ContentModel.PROP_THUMBNAIL_NAME, thumbName); - // Add the name of the content property - properties.put(ContentModel.PROP_CONTENT_PROPERTY_NAME, contentProperty); - - // See if parent association details have been specified for the thumbnail - if (assocDetails == null) - { - // Create the thumbnail using the thumbnails child association - thumbnail = nodeService.createNode( - node, - ContentModel.ASSOC_THUMBNAILS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbName), - ContentModel.TYPE_THUMBNAIL, - properties).getChildRef(); - } - else - { - // Create the thumbnail using the specified parent assoc details - thumbnail = nodeService.createNode( - assocDetails.getParent(), - assocDetails.getAssociationType(), - assocDetails.getAssociationName(), - ContentModel.TYPE_THUMBNAIL, - properties).getChildRef(); + // We're prepending the cm namespace here. + QName thumbnailQName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, localThumbnailName); - // Associate the new thumbnail to the source - nodeService.addChild( - node, - thumbnail, - ContentModel.ASSOC_THUMBNAILS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbName)); - } - - // Get the content reader and writer for content nodes - ContentReader reader = contentService.getReader(node, contentProperty); - ContentWriter writer = contentService.getWriter(thumbnail, ContentModel.PROP_CONTENT, true); - writer.setMimetype(mimetype); - writer.setEncoding(reader.getEncoding()); - - // Catch the failure to create the thumbnail - if (contentService.isTransformable(reader, writer, transformationOptions) == false) + RenditionDefinition renderingAction = renditionService.loadRenditionDefinition(thumbnailQName); + if (renderingAction == null) { - if (logger.isDebugEnabled() == true) - { - logger.debug("Creating thumbnail: There is no transformer to generate the thumbnail required (node=" + node.toString() + "; contentProperty=" + contentProperty.toString() + "; mimetype=" + mimetype + ")"); - } + // If the provided thumbnailName does not map to a built-in rendition definition + // then we must create a dynamic rendition definition for this thumbnail. + // To do this we must have a renderingEngineName. + // + // The transformation will either be a imageRenderingEngine or a reformat (pdf2swf) + String renderingEngineName = getRenderingEngineNameFor(transformationOptions); - // Throw exception indicating that the thumbnail could not be created - throw new ThumbnailException(MessageFormat.format(ERR_NO_CREATE, reader.getMimetype(), writer.getMimetype())); + renderingAction = renditionService.createRenditionDefinition(thumbnailQName, renderingEngineName); } - else + Map params = new ThumbnailRenditionConvertor().convert(transformationOptions, assocDetails); + for (String key : params.keySet()) { - // Do the thumbnail transformation - contentService.transform(reader, writer, transformationOptions); + renderingAction.setParameterValue(key, params.get(key)); } + - return thumbnail; + ChildAssociationRef chAssRef = null; + try + { + chAssRef = renditionService.render(node, renderingAction); + } catch (RenditionServiceException rsx) + { + throw new ThumbnailException(rsx.getMessage(), rsx); + } + + return chAssRef; } }, AuthenticationUtil.getSystemUserName()); // Return the created thumbnail - return thumbnail; + return getThumbnailNode(thumbnailRef); + } + + private String getRenderingEngineNameFor(TransformationOptions options) + { + if (options instanceof ImageTransformationOptions) + { + return ImageRenderingEngine.NAME; + } + else if (options instanceof SWFTransformationOptions) + { + return ReformatRenderingEngine.NAME; + } + else + { + // TODO What can we do here? Can we treat this as an error? + // Isn't this a 'standard' TransformationOptions? + return ""; + } } /** - * Generates the thumbnail name from the name and destination mimertype + * This method returns the NodeRef for the thumbnail, using the ChildAssociationRef + * that links the sourceNode to its associated Thumbnail node. * - * @param thumbnailName the thumbnail name - * @param destinationMimetype the destination name - * @return String the thumbnail file name + * @param thumbnailRef the ChildAssociationRef containing the child NodeRef. + * @return the NodeRef of the thumbnail itself. */ - private String generateThumbnailFileName(String thumbnailName, String destinationMimetype) + public NodeRef getThumbnailNode(ChildAssociationRef thumbnailRef) { - return thumbnailName + "." + this.mimetypeMap.getExtension(destinationMimetype); + return thumbnailRef.getChildRef(); } /** @@ -287,75 +274,61 @@ public class ThumbnailServiceImpl implements ThumbnailService */ public void updateThumbnail(final NodeRef thumbnail, final TransformationOptions transformationOptions) { + // Seeing as how this always gives its own options, maybe this should be a delete and recreate? if (logger.isDebugEnabled() == true) { logger.debug("Updating thumbnail (thumbnail=" + thumbnail.toString() + ")"); } - AuthenticationUtil.runAs(new AuthenticationUtil.RunAsWork() + // First check that we are dealing with a rendition object + if (renditionService.isRendition(thumbnail)) { - public Object doWork() throws Exception + // Get the node that is the source of the thumbnail + ChildAssociationRef parentAssoc = renditionService.getSourceNode(thumbnail); + + if (parentAssoc == null) { - // First check that we are dealing with a thumbnail - if (ContentModel.TYPE_THUMBNAIL.equals(nodeService.getType(thumbnail)) == true) + if (logger.isDebugEnabled() == true) { - // Get the node that is the source of the thumbnail - NodeRef node = null; - List parents = nodeService.getParentAssocs(thumbnail, ContentModel.ASSOC_THUMBNAILS, RegexQNamePattern.MATCH_ALL); - if (parents.size() == 0) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Updating thumbnail: The thumbnails parent cannot be found (thumbnail=" + thumbnail.toString() + ")"); - } - - throw new ThumbnailException(ERR_NO_PARENT); - } - else - { - node = parents.get(0).getParentRef(); - } - - // Get the content property - QName contentProperty = (QName)nodeService.getProperty(thumbnail, ContentModel.PROP_CONTENT_PROPERTY_NAME); - - // Get the reader and writer - ContentReader reader = contentService.getReader(node, contentProperty); - ContentWriter writer = contentService.getWriter(thumbnail, ContentModel.PROP_CONTENT, true); - - // Set the basic detail of the transformation options - transformationOptions.setSourceNodeRef(node); - transformationOptions.setSourceContentProperty(contentProperty); - transformationOptions.setTargetNodeRef(thumbnail); - transformationOptions.setTargetContentProperty(ContentModel.PROP_CONTENT); - - // Catch the failure to create the thumbnail - if (contentService.isTransformable(reader, writer, transformationOptions) == false) - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Updating thumbnail: there is not transformer to update the thumbnail with (thumbnail=" + thumbnail.toString() + ")"); - } - - // Throw exception indicating that the thumbnail could not be created - throw new ThumbnailException(MessageFormat.format(ERR_NO_CREATE, reader.getMimetype(), writer.getMimetype())); - } - else - { - // Do the thumbnail transformation - contentService.transform(reader, writer, transformationOptions); - } + logger.debug("Updating thumbnail: The thumbnails parent cannot be found (thumbnail=" + thumbnail.toString() + ")"); } - else - { - if (logger.isDebugEnabled() == true) - { - logger.debug("Updating thumbnail: cannot update a thumbnail node that isn't the correct thumbnail type (thumbnail=" + thumbnail.toString() + ")"); - } - } - return null; + throw new ThumbnailException(ERR_NO_PARENT); } - }, AuthenticationUtil.getSystemUserName()); + + final QName renditionAssociationName = parentAssoc.getQName(); + NodeRef sourceNode = parentAssoc.getParentRef(); + + // Get the content property + QName contentProperty = (QName)nodeService.getProperty(thumbnail, ContentModel.PROP_CONTENT_PROPERTY_NAME); + + // Set the basic detail of the transformation options + transformationOptions.setSourceNodeRef(sourceNode); + transformationOptions.setSourceContentProperty(contentProperty); + transformationOptions.setTargetContentProperty(ContentModel.PROP_CONTENT); + + // Do the thumbnail transformation + RenditionDefinition rendDefn = renditionService.loadRenditionDefinition(renditionAssociationName); + if (rendDefn == null) + { + String renderingEngineName = getRenderingEngineNameFor(transformationOptions); + + rendDefn = renditionService.createRenditionDefinition(parentAssoc.getQName(), renderingEngineName); + } + Map params = new ThumbnailRenditionConvertor().convert(transformationOptions, null); + for (String key : params.keySet()) + { + rendDefn.setParameterValue(key, params.get(key)); + } + + renditionService.render(sourceNode, rendDefn); + } + else + { + if (logger.isDebugEnabled() == true) + { + logger.debug("Updating thumbnail: cannot update a thumbnail node that isn't the correct thumbnail type (thumbnail=" + thumbnail.toString() + ")"); + } + } } /** @@ -363,8 +336,6 @@ public class ThumbnailServiceImpl implements ThumbnailService */ public NodeRef getThumbnailByName(NodeRef node, QName contentProperty, String thumbnailName) { - NodeRef thumbnail = null; - // // NOTE: // @@ -377,21 +348,21 @@ public class ThumbnailServiceImpl implements ThumbnailService logger.debug("Getting thumbnail by name (nodeRef=" + node.toString() + "; contentProperty=" + contentProperty.toString() + "; thumbnailName=" + thumbnailName + ")"); } - // Check that the node has the thumbnailed aspect applied - if (nodeService.hasAspect(node, ContentModel.ASPECT_THUMBNAILED) == true) + // Thumbnails have a cm: prefix. + QName namespacedThumbnailName = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbnailName); + + ChildAssociationRef existingRendition = renditionService.getRenditionByName(node, namespacedThumbnailName); + NodeRef thumbnail = null; + + // Check the child to see if it matches the content property we are concerned about. + // We can assume there will only ever be one per content property since createThumbnail enforces this. + if (existingRendition != null) { - // Get all the thumbnails that match the thumbnail name - List assocs = this.nodeService.getChildAssocs(node, ContentModel.ASSOC_THUMBNAILS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, thumbnailName)); - for (ChildAssociationRef assoc : assocs) + NodeRef child = existingRendition.getChildRef(); + Serializable contentPropertyName = this.nodeService.getProperty(child, ContentModel.PROP_CONTENT_PROPERTY_NAME); + if (contentProperty.equals(contentPropertyName) == true) { - // Check the child to see if it matches the content property we are concerned about. - // We can assume there will only ever be one per content property since createThumbnail enforces this. - NodeRef child = assoc.getChildRef(); - if (contentProperty.equals(this.nodeService.getProperty(child, ContentModel.PROP_CONTENT_PROPERTY_NAME)) == true) - { - thumbnail = child; - break; - } + thumbnail = child; } } @@ -417,24 +388,21 @@ public class ThumbnailServiceImpl implements ThumbnailService logger.debug("Getting thumbnails (nodeRef=" + node.toString() + "; contentProperty=" + contentProperty.toString() + "; mimetype=" + mimetype + ")"); } - // Check that the node has the thumbnailed aspect applied - if (nodeService.hasAspect(node, ContentModel.ASPECT_THUMBNAILED) == true) + List renditions = this.renditionService.getRenditions(node); + + for (ChildAssociationRef assoc : renditions) { - // Get all the thumbnails that match the thumbnail name - List assocs = this.nodeService.getChildAssocs(node, ContentModel.ASSOC_THUMBNAILS, RegexQNamePattern.MATCH_ALL); - for (ChildAssociationRef assoc : assocs) + // Check the child to see if it matches the content property we are concerned about. + // We can assume there will only ever be one per content property since createThumbnail enforces this. + NodeRef child = assoc.getChildRef(); + if (contentProperty.equals(this.nodeService.getProperty(child, ContentModel.PROP_CONTENT_PROPERTY_NAME)) == true && + matchMimetypeOptions(child, mimetype, options) == true) { - // Check the child to see if it matches the content property we are concerned about. - // We can assume there will only ever be one per content property since createThumbnail enforces this. - NodeRef child = assoc.getChildRef(); - if (contentProperty.equals(this.nodeService.getProperty(child, ContentModel.PROP_CONTENT_PROPERTY_NAME)) == true && - matchMimetypeOptions(child, mimetype, options) == true) - { - thumbnails.add(child); - } + thumbnails.add(child); } } + //TODO Ensure this doesn't return non-thumbnail renditions. return thumbnails; } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java new file mode 100644 index 0000000000..e71c2cd7c9 --- /dev/null +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplParameterTest.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.repo.thumbnail; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.anyBoolean; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.repo.action.ActionImpl; +import org.alfresco.repo.content.MimetypeMap; +import org.alfresco.repo.content.transform.magick.ImageResizeOptions; +import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; +import org.alfresco.repo.rendition.PerformRenditionActionExecuter; +import org.alfresco.repo.rendition.RenditionServiceImpl; +import org.alfresco.repo.rendition.executer.AbstractRenderingEngine; +import org.alfresco.repo.rendition.executer.ImageRenderingEngine; +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.action.ActionService; +import org.alfresco.service.cmr.rendition.RenditionDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; +import org.alfresco.service.cmr.thumbnail.ThumbnailService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; + +/** + * Thumbnail service implementation unit test + * + * @author Neil McErlean + */ +public class ThumbnailServiceImplParameterTest +{ + // Mocked services. + private ActionService mockActionService = mock(ActionService.class); + + // Real services - backed by mocked services. + private RenditionServiceImpl renditionService; + private ThumbnailService thumbnailService; + + private final NodeRef dummyNodeRef1 = new NodeRef("workspace", "dummy", "dummyID_1"); + private final NodeRef dummyNodeRef2 = new NodeRef("workspace", "dummy", "dummyID_2"); + private final NodeRef dummyNodeRef3 = new NodeRef("workspace", "dummy", "dummyID_3"); + + @Before + public void initMockObjects() + { + when(mockActionService.createAction(PerformRenditionActionExecuter.NAME)) + .thenReturn(new ActionImpl(dummyNodeRef2, "id", PerformRenditionActionExecuter.NAME, new HashMap())); + renditionService = new RenditionServiceImpl() + { + @Override + public RenditionDefinition loadRenditionDefinition(QName renderingActionName) + { + // We're intentionally returning null for this test. + return null; + } + }; + + renditionService.setActionService(mockActionService); + + ThumbnailServiceImpl thumbs = new ThumbnailServiceImpl() + { + @Override + public NodeRef getThumbnailByName(NodeRef node, + QName contentProperty, String thumbnailName) + { + return null; + } + /** + * In this test the thumbnailRef will be null, so we need to ensure + * it is not dereferenced here. + */ + @Override + public NodeRef getThumbnailNode(ChildAssociationRef thumbnailRef) + { + return null; + } + }; + thumbs.setRenditionService(renditionService); + thumbnailService = thumbs; + } + + /** + * This test method ensures that the parameters on thumbnail-create are + * passed through the RenditionService to the ActionService + */ + @Test + public void createThumbnailPassesParametersToActionService() + { + // As most of the services are mocked out, the actual values used here + // don't matter. + final Map parametersUnderTest = new HashMap(); + parametersUnderTest.put(ImageRenderingEngine.PARAM_RESIZE_WIDTH, new Integer(42)); + parametersUnderTest.put(ImageRenderingEngine.PARAM_RESIZE_HEIGHT, new Integer(93)); + parametersUnderTest.put(ImageRenderingEngine.PARAM_COMMAND_OPTIONS, "foo"); + parametersUnderTest.put(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO, Boolean.TRUE); + parametersUnderTest.put(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL, Boolean.FALSE); + parametersUnderTest.put(AbstractRenderingEngine.PARAM_TARGET_CONTENT_PROPERTY, ContentModel.PROP_CONTENT); + parametersUnderTest.put(RenditionService.PARAM_DESTINATION_NODE, dummyNodeRef2); + + + ImageTransformationOptions imageTransOpts = new ImageTransformationOptions(); + imageTransOpts.setTargetNodeRef(dummyNodeRef2); + + imageTransOpts.setTargetContentProperty((QName) parametersUnderTest.get(ImageRenderingEngine.PARAM_TARGET_CONTENT_PROPERTY)); + imageTransOpts.setCommandOptions((String) parametersUnderTest.get(ImageRenderingEngine.PARAM_COMMAND_OPTIONS)); + + ImageResizeOptions resizeOptions = new ImageResizeOptions(); + resizeOptions.setHeight((Integer) parametersUnderTest.get(ImageRenderingEngine.PARAM_RESIZE_HEIGHT)); + resizeOptions.setWidth((Integer) parametersUnderTest.get(ImageRenderingEngine.PARAM_RESIZE_WIDTH)); + resizeOptions.setMaintainAspectRatio((Boolean) parametersUnderTest.get(ImageRenderingEngine.PARAM_MAINTAIN_ASPECT_RATIO)); + resizeOptions.setResizeToThumbnail((Boolean) parametersUnderTest.get(ImageRenderingEngine.PARAM_RESIZE_TO_THUMBNAIL)); + imageTransOpts.setResizeOptions(resizeOptions); + + ThumbnailParentAssociationDetails assocDetails = new ThumbnailParentAssociationDetails(dummyNodeRef3, + ContentModel.ASSOC_CONTAINS, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, + "homerSimpson")); + + // Now request the creation of the the thumbnail. + thumbnailService.createThumbnail(dummyNodeRef1, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG, + imageTransOpts, "bartSimpson", assocDetails); + + + ArgumentCaptor argument = ArgumentCaptor.forClass(Action.class); + verify(mockActionService).executeAction(argument.capture(), any(NodeRef.class), anyBoolean(), anyBoolean()); + final Action performRenditionAction = argument.getValue(); + final RenditionDefinition renditionDefn = (RenditionDefinition) performRenditionAction.getParameterValue(PerformRenditionActionExecuter.PARAM_RENDITION_DEFINITION); + Map parameters = renditionDefn.getParameterValues(); + + for (String s : parametersUnderTest.keySet()) + { + if (parameters.keySet().contains(s) == false || parameters.get(s) == null || parameters.get(s).toString().length() == 0) + { + fail("Missing parameter " + s); + } + assertEquals("Parameter " + s + " had wrong value.", + parametersUnderTest.get(s), parameters.get(s)); + } + } +} diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java index 8f98f4f561..1ff1a5bb87 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java @@ -16,6 +16,7 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ + package org.alfresco.repo.thumbnail; import java.io.File; @@ -26,6 +27,7 @@ import java.util.List; import java.util.Map; import org.alfresco.model.ContentModel; +import org.alfresco.model.RenditionModel; import org.alfresco.repo.content.MimetypeMap; import org.alfresco.repo.content.transform.AbstractContentTransformerTest; import org.alfresco.repo.content.transform.ContentTransformer; @@ -33,13 +35,16 @@ import org.alfresco.repo.content.transform.magick.ImageResizeOptions; import org.alfresco.repo.content.transform.magick.ImageTransformationOptions; import org.alfresco.repo.jscript.ClasspathScriptLocation; import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback; +import org.alfresco.service.cmr.rendition.RenditionService; import org.alfresco.service.cmr.repository.ChildAssociationRef; +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.NodeRef; import org.alfresco.service.cmr.repository.ScriptLocation; import org.alfresco.service.cmr.repository.ScriptService; import org.alfresco.service.cmr.thumbnail.ThumbnailException; +import org.alfresco.service.cmr.thumbnail.ThumbnailParentAssociationDetails; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.NamespaceService; import org.alfresco.service.namespace.QName; @@ -50,160 +55,189 @@ import org.alfresco.util.BaseAlfrescoSpringTest; * Thumbnail service implementation unit test * * @author Roy Wetherall + * @author Neil McErlean */ -public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest +public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest { - private ThumbnailService thumbnailService; + private RenditionService renditionService; + private ThumbnailService thumbnailService; private ScriptService scriptService; - private MimetypeMap mimetypeMap; + private MimetypeMap mimetypeMap; private NodeRef folder; - + /** * Called during the transaction setup */ + @SuppressWarnings("deprecation") + @Override protected void onSetUpInTransaction() throws Exception { super.onSetUpInTransaction(); - + // Get the required services - this.thumbnailService = (ThumbnailService)this.applicationContext.getBean("ThumbnailService"); - this.mimetypeMap = (MimetypeMap)this.applicationContext.getBean("mimetypeService"); - this.scriptService = (ScriptService)this.applicationContext.getBean("ScriptService"); - + this.renditionService = (RenditionService) this.applicationContext.getBean("RenditionService"); + this.thumbnailService = (ThumbnailService) this.applicationContext.getBean("ThumbnailService"); + this.mimetypeMap = (MimetypeMap) this.applicationContext.getBean("mimetypeService"); + this.scriptService = (ScriptService) this.applicationContext.getBean("ScriptService"); + // Create a folder and some content Map folderProps = new HashMap(1); folderProps.put(ContentModel.PROP_NAME, "testFolder"); - this.folder = this.nodeService.createNode( - this.rootNodeRef, - ContentModel.ASSOC_CHILDREN, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), - ContentModel.TYPE_FOLDER).getChildRef(); + this.folder = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "testFolder"), ContentModel.TYPE_FOLDER) + .getChildRef(); } private void checkTransformer() { ContentTransformer transformer = this.contentService.getImageTransformer(); - if (transformer == null) - { - fail("No transformer returned for 'getImageTransformer'"); - } + assertNotNull("No transformer returned for 'getImageTransformer'", transformer); + // Check that it is working ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); - if (!transformer.isTransformable( - MimetypeMap.MIMETYPE_IMAGE_JPEG, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions)) + if (!transformer.isTransformable(MimetypeMap.MIMETYPE_IMAGE_JPEG, MimetypeMap.MIMETYPE_IMAGE_JPEG, + imageTransformationOptions)) { fail("Image transformer is not working. Please check your image conversion command setup."); } } + + public void testCreateRenditionThumbnailFromImage() throws Exception + { + QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); + + ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition( + qname.getLocalName()); + assertEquals("doclib", details.getName()); + assertEquals("image/png", details.getMimetype()); + assertEquals("alfresco/thumbnail/thumbnail_placeholder_doclib.png", details.getPlaceHolderResourcePath()); + + checkTransformer(); + + NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + + NodeRef thumbnail0 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib"); + assertNotNull(thumbnail0); + checkRenditioned(jpgOrig, "doclib"); + checkRendition("doclib", thumbnail0); + outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test"); + } + public void testCreateRenditionThumbnailFromPdf() throws Exception + { + QName qname = QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "doclib"); + + ThumbnailDefinition details = thumbnailService.getThumbnailRegistry().getThumbnailDefinition( + qname.getLocalName()); + assertEquals("doclib", details.getName()); + assertEquals("image/png", details.getMimetype()); + assertEquals("alfresco/thumbnail/thumbnail_placeholder_doclib.png", details.getPlaceHolderResourcePath()); + + checkTransformer(); + + NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF); + + NodeRef thumbnail0 = this.thumbnailService.createThumbnail(pdfOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, details.getTransformationOptions(), "doclib"); + assertNotNull(thumbnail0); + checkRenditioned(pdfOrig, "doclib"); + checkRendition("doclib", thumbnail0); + outputThumbnailTempContentLocation(thumbnail0, "jpg", "doclib test"); + } + public void testCreateThumbnailFromImage() throws Exception { checkTransformer(); - - NodeRef jpgOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); - NodeRef gifOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_GIF); - + + NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + NodeRef gifOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_GIF); + // ===== small: 64x64, marked as thumbnail ==== - + ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); imageResizeOptions.setWidth(64); - imageResizeOptions.setHeight(64); + imageResizeOptions.setHeight(64); imageResizeOptions.setResizeToThumbnail(true); ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); - imageTransformationOptions.setResizeOptions(imageResizeOptions); - //ThumbnailDetails createOptions = new ThumbnailDetails(); - - NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions, - "small"); + imageTransformationOptions.setResizeOptions(imageResizeOptions); + // ThumbnailDetails createOptions = new ThumbnailDetails(); + + NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small"); assertNotNull(thumbnail1); - checkThumbnailed(jpgOrig, "small"); - checkThumbnail("small", thumbnail1); + checkRenditioned(jpgOrig, "small"); + checkRendition("small", thumbnail1); outputThumbnailTempContentLocation(thumbnail1, "jpg", "small - 64x64, marked as thumbnail"); - + // ===== small2: 64x64, aspect not maintained ==== - + ImageResizeOptions imageResizeOptions2 = new ImageResizeOptions(); imageResizeOptions2.setWidth(64); - imageResizeOptions2.setHeight(64); + imageResizeOptions2.setHeight(64); imageResizeOptions2.setMaintainAspectRatio(false); ImageTransformationOptions imageTransformationOptions2 = new ImageTransformationOptions(); - imageTransformationOptions2.setResizeOptions(imageResizeOptions2); - //ThumbnailDetails createOptions2 = new ThumbnailDetails(); - NodeRef thumbnail2 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions2, - "small2"); - checkThumbnailed(jpgOrig, "small2"); - checkThumbnail("small2", thumbnail2); + imageTransformationOptions2.setResizeOptions(imageResizeOptions2); + // ThumbnailDetails createOptions2 = new ThumbnailDetails(); + NodeRef thumbnail2 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions2, "small2"); + checkRenditioned(jpgOrig, "small2"); + checkRendition("small2", thumbnail2); outputThumbnailTempContentLocation(thumbnail2, "jpg", "small2 - 64x64, aspect not maintained"); - - // ===== half: 50%x50 ===== - + + // ===== half: 50%x50 ===== + ImageResizeOptions imageResizeOptions3 = new ImageResizeOptions(); imageResizeOptions3.setWidth(50); - imageResizeOptions3.setHeight(50); + imageResizeOptions3.setHeight(50); imageResizeOptions3.setPercentResize(true); ImageTransformationOptions imageTransformationOptions3 = new ImageTransformationOptions(); - imageTransformationOptions3.setResizeOptions(imageResizeOptions3); - // ThumbnailDetails createOptions3 = new ThumbnailDetails(); - NodeRef thumbnail3 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions3, - "half"); - checkThumbnailed(jpgOrig, "half"); - checkThumbnail("half", thumbnail3); + imageTransformationOptions3.setResizeOptions(imageResizeOptions3); + // ThumbnailDetails createOptions3 = new ThumbnailDetails(); + NodeRef thumbnail3 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions3, "half"); + checkRenditioned(jpgOrig, "half"); + checkRendition("half", thumbnail3); outputThumbnailTempContentLocation(thumbnail3, "jpg", "half - 50%x50%"); - - - // ===== half2: 50%x50 from gif ===== - + + // ===== half2: 50%x50 from gif ===== + ImageResizeOptions imageResizeOptions4 = new ImageResizeOptions(); imageResizeOptions4.setWidth(50); - imageResizeOptions4.setHeight(50); + imageResizeOptions4.setHeight(50); imageResizeOptions4.setPercentResize(true); ImageTransformationOptions imageTransformationOptions4 = new ImageTransformationOptions(); - imageTransformationOptions4.setResizeOptions(imageResizeOptions4); - // ThumbnailDetails createOptions4 = new ThumbnailDetails(); - NodeRef thumbnail4 = this.thumbnailService.createThumbnail(gifOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions4, - "half2"); - checkThumbnailed(gifOrig, "half2"); - checkThumbnail("half2", thumbnail4); + imageTransformationOptions4.setResizeOptions(imageResizeOptions4); + // ThumbnailDetails createOptions4 = new ThumbnailDetails(); + NodeRef thumbnail4 = this.thumbnailService.createThumbnail(gifOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions4, "half2"); + checkRenditioned(gifOrig, "half2"); + checkRendition("half2", thumbnail4); outputThumbnailTempContentLocation(thumbnail4, "jpg", "half2 - 50%x50%, from gif"); } - - public void testDuplicationNames() - throws Exception + + public void testDuplicationNames() throws Exception { checkTransformer(); - - NodeRef jpgOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + + NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); imageResizeOptions.setWidth(64); - imageResizeOptions.setHeight(64); + imageResizeOptions.setHeight(64); imageResizeOptions.setResizeToThumbnail(true); ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); - imageTransformationOptions.setResizeOptions(imageResizeOptions); - //ThumbnailDetails createOptions = new ThumbnailDetails(); - NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions, - "small"); + imageTransformationOptions.setResizeOptions(imageResizeOptions); + // ThumbnailDetails createOptions = new ThumbnailDetails(); + NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small"); assertNotNull(thumbnail1); - checkThumbnailed(jpgOrig, "small"); - checkThumbnail("small", thumbnail1); - + checkRenditioned(jpgOrig, "small"); + checkRendition("small", thumbnail1); + try { - this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions, - "small"); + this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG, + imageTransformationOptions, "small"); fail("A duplicate exception should have been raised"); } catch (ThumbnailException exception) @@ -211,180 +245,182 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest // OK since this should have been thrown } } - - public void testThumbnailUpdate() - throws Exception + + public void testThumbnailUpdate() throws Exception { checkTransformer(); - + // First create a thumbnail - NodeRef jpgOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); imageResizeOptions.setWidth(64); - imageResizeOptions.setHeight(64); + imageResizeOptions.setHeight(64); imageResizeOptions.setResizeToThumbnail(true); ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); - imageTransformationOptions.setResizeOptions(imageResizeOptions); - NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions, - "small"); - + imageTransformationOptions.setResizeOptions(imageResizeOptions); + NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "small"); + // Update the thumbnail this.thumbnailService.updateThumbnail(thumbnail1, imageTransformationOptions); } - - public void testGetThumbnailByName() - throws Exception + + public void testGetThumbnailByName() throws Exception { checkTransformer(); - - NodeRef jpgOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); - + + NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + // Check for missing thumbnail NodeRef result1 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "small"); assertNull("The thumbnail 'small' should have been missing", result1); - + // Create the thumbnail ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); imageResizeOptions.setWidth(64); - imageResizeOptions.setHeight(64); + imageResizeOptions.setHeight(64); imageResizeOptions.setResizeToThumbnail(true); ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); - imageTransformationOptions.setResizeOptions(imageResizeOptions); - this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, - MimetypeMap.MIMETYPE_IMAGE_JPEG, - imageTransformationOptions, - "small"); - + imageTransformationOptions.setResizeOptions(imageResizeOptions); + this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, MimetypeMap.MIMETYPE_IMAGE_JPEG, + imageTransformationOptions, "small"); + // Try and retrieve the thumbnail NodeRef result2 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "small"); assertNotNull(result2); - checkThumbnail("small", result2); - + checkRendition("small", result2); + // Check for an other thumbnail that doesn't exist NodeRef result3 = this.thumbnailService.getThumbnailByName(jpgOrig, ContentModel.PROP_CONTENT, "anotherone"); assertNull("The thumbnail 'anotherone' should have been missing", result3); } - - // TODO test getThumbnails - - private void checkThumbnailed(NodeRef thumbnailed, String assocName) + + private void checkRenditioned(NodeRef thumbnailed, String assocName) { - assertTrue("Thumbnailed aspect should have been applied", this.nodeService.hasAspect(thumbnailed, ContentModel.ASPECT_THUMBNAILED)); - List assocs = this.nodeService.getChildAssocs(thumbnailed, RegexQNamePattern.MATCH_ALL, QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, assocName)); - assertNotNull(assocs); - assertEquals(1, assocs.size()); + assertTrue("Renditioned aspect should have been applied", this.nodeService.hasAspect(thumbnailed, + RenditionModel.ASPECT_RENDITIONED)); + if (assocName != null) + { + List assocs = this.nodeService.getChildAssocs(thumbnailed, RegexQNamePattern.MATCH_ALL, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, assocName)); + assertNotNull(assocs); + assertEquals(1, assocs.size()); + } } - - private void checkThumbnail(String thumbnailName, NodeRef thumbnail) + + private void checkRendition(String thumbnailName, NodeRef thumbnail) { // Check the thumbnail is of the correct type - assertEquals(ContentModel.TYPE_THUMBNAIL, this.nodeService.getType(thumbnail)); - - // Check the name - assertEquals(thumbnailName, this.nodeService.getProperty(thumbnail, ContentModel.PROP_THUMBNAIL_NAME)); - - // Check the contet property value - assertEquals(ContentModel.PROP_CONTENT, this.nodeService.getProperty(thumbnail, ContentModel.PROP_CONTENT_PROPERTY_NAME)); - + assertTrue("Thumbnail should have been a rendition", + renditionService.isRendition(thumbnail)); + + // Check the name + if (thumbnailName != null) + { + assertEquals(thumbnailName, this.nodeService.getProperty(thumbnail, ContentModel.PROP_NAME)); + } + + // Check the content property value + assertEquals(ContentModel.PROP_CONTENT, this.nodeService.getProperty(thumbnail, + ContentModel.PROP_CONTENT_PROPERTY_NAME)); } - - private void outputThumbnailTempContentLocation(NodeRef thumbnail, String ext, String message) - throws IOException + + private void outputThumbnailTempContentLocation(NodeRef thumbnail, String ext, String message) throws IOException { File tempFile = File.createTempFile("thumbnailServiceImpTest", "." + ext); ContentReader reader = this.contentService.getReader(thumbnail, ContentModel.PROP_CONTENT); reader.getContent(tempFile); - System.out.println(message + ": " + tempFile.getPath()); + System.out.println(message + ": " + tempFile.getPath()); } - - private NodeRef createOrigionalContent(NodeRef folder, String mimetype) - throws IOException + + /** + * This method creates a node under the specified folder whose content is + * taken from the quick file corresponding to the specified MIME type. + * + * @param parentFolder + * @param mimetype + * @return + * @throws IOException + */ + private NodeRef createOriginalContent(NodeRef parentFolder, String mimetype) throws IOException { String ext = this.mimetypeMap.getExtension(mimetype); File origFile = AbstractContentTransformerTest.loadQuickTestFile(ext); - + Map props = new HashMap(1); - props.put(ContentModel.PROP_NAME, "origional." + ext); - NodeRef node = this.nodeService.createNode( - folder, - ContentModel.ASSOC_CONTAINS, - QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "origional." + ext), - ContentModel.TYPE_CONTENT, - props).getChildRef(); - + props.put(ContentModel.PROP_NAME, "origional." + ext); + NodeRef node = this.nodeService.createNode(parentFolder, ContentModel.ASSOC_CONTAINS, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "original." + ext), + ContentModel.TYPE_CONTENT, props).getChildRef(); + ContentWriter writer = this.contentService.getWriter(node, ContentModel.PROP_CONTENT, true); writer.setMimetype(mimetype); writer.setEncoding("UTF-8"); writer.putContent(origFile); - + return node; } - + + @SuppressWarnings("deprecation") public void testAutoUpdate() throws Exception { checkTransformer(); - - final NodeRef jpgOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); - + + final NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + ThumbnailDefinition details = this.thumbnailService.getThumbnailRegistry().getThumbnailDefinition("medium"); @SuppressWarnings("unused") - final NodeRef thumbnail = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, details.getMimetype(), details.getTransformationOptions(), details.getName()); - + final NodeRef thumbnail = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, details + .getMimetype(), details.getTransformationOptions(), details.getName()); + setComplete(); endTransaction(); - + transactionService.getRetryingTransactionHelper().doInTransaction(new RetryingTransactionCallback() { public Object execute() throws Exception - { + { String ext = ThumbnailServiceImplTest.this.mimetypeMap.getExtension(MimetypeMap.MIMETYPE_IMAGE_JPEG); File origFile = AbstractContentTransformerTest.loadQuickTestFile(ext); - - ContentWriter writer = ThumbnailServiceImplTest.this.contentService.getWriter(jpgOrig, ContentModel.PROP_CONTENT, true); + + ContentWriter writer = ThumbnailServiceImplTest.this.contentService.getWriter(jpgOrig, + ContentModel.PROP_CONTENT, true); writer.putContent(origFile); - + return null; } }); - - // TODO - // this test should wait for the async action to run .. will need to commit transaction for that thou! - - //Thread.sleep(1000); + + // TODO + // this test should wait for the async action to run .. will need to + // commit transaction for that thou! + + // Thread.sleep(1000); } - + public void testHTMLToImageAndSWF() throws Exception { - NodeRef nodeRef = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_HTML); + NodeRef nodeRef = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_HTML); ThumbnailDefinition def = this.thumbnailService.getThumbnailRegistry().getThumbnailDefinition("medium"); - - if (this.contentService.getTransformer(MimetypeMap.MIMETYPE_HTML, def.getMimetype(), def.getTransformationOptions()) != null) - { - NodeRef thumb = this.thumbnailService.createThumbnail( - nodeRef, - ContentModel.PROP_CONTENT, - def.getMimetype(), - def.getTransformationOptions(), - def.getName()); + + ContentTransformer transformer = this.contentService.getTransformer(MimetypeMap.MIMETYPE_HTML, def + .getMimetype(), def.getTransformationOptions()); + if (transformer != null) + { + NodeRef thumb = this.thumbnailService.createThumbnail(nodeRef, ContentModel.PROP_CONTENT, + def.getMimetype(), def.getTransformationOptions(), def.getName()); assertNotNull(thumb); ContentReader reader = this.contentService.getReader(thumb, ContentModel.PROP_CONTENT); assertNotNull(reader); assertEquals(def.getMimetype(), reader.getMimetype()); assertTrue(reader.getSize() != 0); } - def = this.thumbnailService.getThumbnailRegistry().getThumbnailDefinition("webpreview"); - if (this.contentService.getTransformer(MimetypeMap.MIMETYPE_HTML, def.getMimetype(), def.getTransformationOptions()) != null) - { - NodeRef thumb = this.thumbnailService.createThumbnail( - nodeRef, - ContentModel.PROP_CONTENT, - def.getMimetype(), - def.getTransformationOptions(), - def.getName()); + if (transformer != null) + { + NodeRef thumb = this.thumbnailService.createThumbnail(nodeRef, ContentModel.PROP_CONTENT, + def.getMimetype(), def.getTransformationOptions(), def.getName()); assertNotNull(thumb); ContentReader reader = this.contentService.getReader(thumb, ContentModel.PROP_CONTENT); assertNotNull(reader); @@ -393,34 +429,111 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest } } + public void testThumbnailServiceCreateApi() throws Exception + { + // Create a second folder + Map folderProps = new HashMap(); + folderProps.put(ContentModel.PROP_NAME, "otherTestFolder"); + NodeRef otherFolder = this.nodeService.createNode(this.rootNodeRef, ContentModel.ASSOC_CHILDREN, + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "otherTestFolder"), ContentModel.TYPE_FOLDER) + .getChildRef(); + + checkTransformer(); + NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + + ImageResizeOptions imageResizeOptions = new ImageResizeOptions(); + imageResizeOptions.setWidth(64); + imageResizeOptions.setHeight(64); + imageResizeOptions.setResizeToThumbnail(true); + ImageTransformationOptions imageTransformationOptions = new ImageTransformationOptions(); + imageTransformationOptions.setResizeOptions(imageResizeOptions); + + // Create thumbnail - same MIME type + NodeRef thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_JPEG, imageTransformationOptions, "smallJpeg"); + assertNotNull(thumbnail1); + checkRenditioned(jpgOrig, "smallJpeg"); + checkRendition("smallJpeg", thumbnail1); + outputThumbnailTempContentLocation(thumbnail1, "jpg", "smallJpeg - 64x64, marked as thumbnail"); + + // Create thumbnail - different MIME type + thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallPng"); + assertNotNull(thumbnail1); + checkRenditioned(jpgOrig, "smallPng"); + checkRendition("smallPng", thumbnail1); + outputThumbnailTempContentLocation(thumbnail1, "png", "smallPng - 64x64, marked as thumbnail"); + + // Create thumbnail - different content property + // TODO + + // Create thumbnail - different command options + // We'll pass illegal command options to ImageMagick in order to trigger an exception + Exception x = null; + try + { + imageTransformationOptions.setCommandOptions("-noSuchOption"); + thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "smallCO"); + } catch (ContentIOException ciox) + { + x = ciox; + ciox.printStackTrace(); + } + assertNotNull("Expected exception from ImageMagick due to invalid option", x); + // Reset the command options + imageTransformationOptions.setCommandOptions(""); + + + // Create thumbnail - different target assoc details + ThumbnailParentAssociationDetails tpad + = new ThumbnailParentAssociationDetails(otherFolder, + QName.createQName(NamespaceService.RENDITION_MODEL_1_0_URI, "foo"), + QName.createQName(NamespaceService.CONTENT_MODEL_1_0_URI, "bar")); + thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, "targetDetails", tpad); + assertNotNull(thumbnail1); + checkRenditioned(jpgOrig, "targetDetails"); + checkRendition("targetDetails", thumbnail1); + outputThumbnailTempContentLocation(thumbnail1, "png", "targetDetails - 64x64, marked as thumbnail"); + + + + // Create thumbnail - null thumbnail name + thumbnail1 = this.thumbnailService.createThumbnail(jpgOrig, ContentModel.PROP_CONTENT, + MimetypeMap.MIMETYPE_IMAGE_PNG, imageTransformationOptions, null); + assertNotNull(thumbnail1); + checkRenditioned(jpgOrig, null); + checkRendition(null, thumbnail1); + outputThumbnailTempContentLocation(thumbnail1, "png", "'null' - 64x64, marked as thumbnail"); + } + public void testRegistry() { ThumbnailRegistry thumbnailRegistry = this.thumbnailService.getThumbnailRegistry(); - List defs = thumbnailRegistry.getThumnailDefintions(MimetypeMap.MIMETYPE_HTML); + List defs = thumbnailRegistry.getThumbnailDefinitions(MimetypeMap.MIMETYPE_HTML); System.out.println("Definitions ..."); for (ThumbnailDefinition def : defs) { System.out.println("Thumbnail Available: " + def.getName()); } } - - - + // == Test the JavaScript API == - + public void testJSAPI() throws Exception { - NodeRef jpgOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); - NodeRef gifOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_GIF); - NodeRef pdfOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_PDF); - NodeRef docOrig = createOrigionalContent(this.folder, MimetypeMap.MIMETYPE_WORD); - + NodeRef jpgOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_JPEG); + NodeRef gifOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_IMAGE_GIF); + NodeRef pdfOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_PDF); + NodeRef docOrig = createOriginalContent(this.folder, MimetypeMap.MIMETYPE_WORD); + Map model = new HashMap(2); model.put("jpgOrig", jpgOrig); model.put("gifOrig", gifOrig); model.put("pdfOrig", pdfOrig); model.put("docOrig", docOrig); - + ScriptLocation location = new ClasspathScriptLocation("org/alfresco/repo/thumbnail/script/test_thumbnailAPI.js"); this.scriptService.executeScript(location, model); } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java deleted file mode 100644 index 53f5b7b70a..0000000000 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailedAspect.java +++ /dev/null @@ -1,293 +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.thumbnail; - -import java.io.Serializable; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.copy.CopyBehaviourCallback; -import org.alfresco.repo.copy.CopyDetails; -import org.alfresco.repo.copy.CopyServicePolicies; -import org.alfresco.repo.copy.DefaultCopyBehaviourCallback; -import org.alfresco.repo.node.NodeServicePolicies; -import org.alfresco.repo.policy.Behaviour; -import org.alfresco.repo.policy.JavaBehaviour; -import org.alfresco.repo.policy.PolicyComponent; -import org.alfresco.service.cmr.action.Action; -import org.alfresco.service.cmr.action.ActionService; -import org.alfresco.service.cmr.action.CompositeAction; -import org.alfresco.service.cmr.dictionary.DataTypeDefinition; -import org.alfresco.service.cmr.dictionary.DictionaryService; -import org.alfresco.service.cmr.dictionary.PropertyDefinition; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.ContentData; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.thumbnail.ThumbnailService; -import org.alfresco.service.namespace.NamespaceService; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.EqualsHelper; - -/** - * Thumbnailed aspect behaviour bean - * - * @author Roy Wetherall - */ -public class ThumbnailedAspect implements NodeServicePolicies.OnUpdatePropertiesPolicy, - CopyServicePolicies.OnCopyNodePolicy -{ - /** Services */ - private PolicyComponent policyComponent; - private ThumbnailService thumbnailService; - private ActionService actionService; - private NodeService nodeService; - private DictionaryService dictionaryService; - - /** - * Set the policy component - * - * @param policyComponent policy component - */ - public void setPolicyComponent(PolicyComponent policyComponent) - { - this.policyComponent = policyComponent; - } - - /** - * Set the action service - * - * @param actionService action service - */ - public void setActionService(ActionService actionService) - { - this.actionService = actionService; - } - - /** - * Set the node service - * - * @param nodeService node service - */ - public void setNodeService(NodeService nodeService) - { - this.nodeService = nodeService; - } - - /** - * Set the thumbnail service - * - * @param thumbnailService thumbnail service - */ - public void setThumbnailService(ThumbnailService thumbnailService) - { - this.thumbnailService = thumbnailService; - } - - /** - * Set the dictionary service - * - * @param dictionaryService dictionary service - */ - public void setDictionaryService(DictionaryService dictionaryService) - { - this.dictionaryService = dictionaryService; - } - - /** - * Initialise method - */ - public void init() - { - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "onUpdateProperties"), - ContentModel.ASPECT_THUMBNAILED, - new JavaBehaviour(this, "onUpdateProperties", Behaviour.NotificationFrequency.TRANSACTION_COMMIT)); - this.policyComponent.bindClassBehaviour( - QName.createQName(NamespaceService.ALFRESCO_URI, "getCopyCallback"), - ContentModel.ASPECT_THUMBNAILED, - new JavaBehaviour(this, "getCopyCallback")); - } - - /** - * @see org.alfresco.repo.node.NodeServicePolicies.OnUpdatePropertiesPolicy#onUpdateProperties(org.alfresco.service.cmr.repository.NodeRef, java.util.Map, java.util.Map) - */ - public void onUpdateProperties( - NodeRef nodeRef, - Map before, - Map after) - { - // Ignore working copies - if (this.nodeService.exists(nodeRef) == true && - this.nodeService.hasAspect(nodeRef, ContentModel.ASPECT_WORKING_COPY) == false) - { - // check if any of the content properties have changed - for (QName propertyQName : after.keySet()) - { - // is this a content property? - PropertyDefinition propertyDef = dictionaryService.getProperty(propertyQName); - if (propertyDef == null) - { - // the property is not recognised - continue; - } - if (!propertyDef.getDataType().getName().equals(DataTypeDefinition.CONTENT)) - { - // not a content type - continue; - } - - try - { - ContentData beforeValue = (ContentData) before.get(propertyQName); - ContentData afterValue = (ContentData) after.get(propertyQName); - - // Figure out if the content is new or not - boolean newContent = false; - String beforeContentUrl = null; - if (beforeValue != null) - { - beforeContentUrl = beforeValue.getContentUrl(); - } - String afterContentUrl = null; - if (afterValue != null) - { - afterContentUrl = afterValue.getContentUrl(); - } - if (beforeContentUrl == null && afterContentUrl != null) - { - newContent = true; - } - - if (afterValue != null && afterValue.getContentUrl() == null) - { - // no URL - ignore - } - else if (newContent == false && EqualsHelper.nullSafeEquals(beforeValue, afterValue) == false) - { - // Queue the update - queueUpdate(nodeRef, propertyQName); - } - } - catch (ClassCastException e) - { - // properties don't conform to model - continue; - } - } - } - } - - /** - * Queue the update to happen asynchronously - * - * @param nodeRef node reference - * @param contentProperty content property - */ - private void queueUpdate(NodeRef nodeRef, QName contentProperty) - { - Boolean automaticUpdate = (Boolean)this.nodeService.getProperty(nodeRef, ContentModel.PROP_AUTOMATIC_UPDATE); - if (automaticUpdate != null && automaticUpdate.booleanValue() == true) - { - CompositeAction compositeAction = actionService.createCompositeAction(); - List thumbnails = this.thumbnailService.getThumbnails(nodeRef, contentProperty, null, null); - - for (NodeRef thumbnail : thumbnails) - { - // Execute the update thumbnail action async for each thumbnail - Action action = actionService.createAction(UpdateThumbnailActionExecuter.NAME); - action.setParameterValue(UpdateThumbnailActionExecuter.PARAM_CONTENT_PROPERTY, contentProperty); - action.setParameterValue(UpdateThumbnailActionExecuter.PARAM_THUMBNAIL_NODE, thumbnail); - compositeAction.addAction(action); - } - - actionService.executeAction(compositeAction, nodeRef, false, true); - } - } - - /** - * @return Returns {@link ThumbnailedAspectCopyBehaviourCallback} - */ - public CopyBehaviourCallback getCopyCallback(QName classRef, CopyDetails copyDetails) - { - return ThumbnailedAspectCopyBehaviourCallback.INSTANCE; - } - - /** - * Behaviour for the {@link ContentModel#ASPECT_THUMBNAILED cm:thumbnailed} aspect. - * - * @author Derek Hulley - * @since 3.2 - */ - private static class ThumbnailedAspectCopyBehaviourCallback extends DefaultCopyBehaviourCallback - { - private static final CopyBehaviourCallback INSTANCE = new ThumbnailedAspectCopyBehaviourCallback(); - - /** - * @return Returns true always - */ - @Override - public boolean getMustCopy(QName classQName, CopyDetails copyDetails) - { - return true; - } - - /** - * Copy thumbnail-related associations, {@link ContentModel#ASSOC_THUMBNAILS} regardless of - * cascade options. - */ - @Override - public ChildAssocCopyAction getChildAssociationCopyAction( - QName classQName, - CopyDetails copyDetails, - CopyChildAssociationDetails childAssocCopyDetails) - { - ChildAssociationRef childAssocRef = childAssocCopyDetails.getChildAssocRef(); - if (childAssocRef.getTypeQName().equals(ContentModel.ASSOC_THUMBNAILS)) - { - return ChildAssocCopyAction.COPY_CHILD; - } - else - { - throw new IllegalStateException( - "Behaviour should have been invoked: \n" + - " Aspect: " + this.getClass().getName() + "\n" + - " " + childAssocCopyDetails + "\n" + - " " + copyDetails); - } - } - - /** - * Copy only the {@link ContentModel#PROP_AUTOMATIC_UPDATE} - */ - @Override - public Map getCopyProperties( - QName classQName, - CopyDetails copyDetails, - Map properties) - { - Map newProperties = new HashMap(5); - Serializable value = properties.get(ContentModel.PROP_AUTOMATIC_UPDATE); - newProperties.put(ContentModel.PROP_AUTOMATIC_UPDATE, value); - return newProperties; - } - } -} diff --git a/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java b/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java index 6078e6dbc8..3ae49eaeec 100644 --- a/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java +++ b/source/java/org/alfresco/repo/thumbnail/UpdateThumbnailActionExecuter.java @@ -27,20 +27,31 @@ import org.alfresco.repo.action.executer.ActionExecuterAbstractBase; import org.alfresco.service.cmr.action.Action; import org.alfresco.service.cmr.action.ParameterDefinition; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; +import org.alfresco.service.cmr.rendition.RenditionService; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.thumbnail.ThumbnailService; import org.alfresco.service.namespace.QName; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; /** * Update thumbnail action executer. * - * NOTE: This action is used to facilitate the async update of thumbnails. It is not intended for genereral usage. + * NOTE: This action is used to facilitate the async update of thumbnails. It is not intended for general usage. * * @author Roy Wetherall + * @author Neil McErlean */ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase { + /** Logger */ + private static Log logger = LogFactory.getLog(UpdateThumbnailActionExecuter.class); + + /** Rendition Service */ + private RenditionService renditionService; + /** Thumbnail Service */ private ThumbnailService thumbnailService; @@ -52,6 +63,16 @@ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase public static final String PARAM_CONTENT_PROPERTY = "content-property"; public static final String PARAM_THUMBNAIL_NODE = "thumbnail-node"; + /** + * Injects the rendition service. + * + * @param renditionService the rendition service. + */ + public void setRenditionService(RenditionService renditionService) + { + this.renditionService = renditionService; + } + /** * Set the thumbnail service * @@ -86,17 +107,17 @@ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase } if (this.nodeService.exists(thumbnailNodeRef) == true && - ContentModel.TYPE_THUMBNAIL.equals(this.nodeService.getType(thumbnailNodeRef)) == true) + renditionService.isRendition(thumbnailNodeRef)) { // Get the thumbnail Name - String thumbnailName = (String)this.nodeService.getProperty(thumbnailNodeRef, ContentModel.PROP_THUMBNAIL_NAME); + ChildAssociationRef parent = renditionService.getSourceNode(thumbnailNodeRef); + String thumbnailName = parent.getQName().getLocalName(); // Get the details of the thumbnail ThumbnailRegistry registry = this.thumbnailService.getThumbnailRegistry(); ThumbnailDefinition details = registry.getThumbnailDefinition(thumbnailName); if (details == null) { - // Throw exception throw new AlfrescoRuntimeException("The thumbnail name '" + thumbnailName + "' is not registered"); } @@ -121,5 +142,4 @@ public class UpdateThumbnailActionExecuter extends ActionExecuterAbstractBase paramList.add(new ParameterDefinitionImpl(PARAM_CONTENT_PROPERTY, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_CONTENT_PROPERTY))); paramList.add(new ParameterDefinitionImpl(PARAM_THUMBNAIL_NODE, DataTypeDefinition.QNAME, false, getParamDisplayLabel(PARAM_THUMBNAIL_NODE))); } - } diff --git a/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnail.java b/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnail.java index 3746ce74e5..6a1671f6bf 100644 --- a/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnail.java +++ b/source/java/org/alfresco/repo/thumbnail/script/ScriptThumbnail.java @@ -18,20 +18,31 @@ */ package org.alfresco.repo.thumbnail.script; -import org.alfresco.model.ContentModel; +import java.util.List; + +import org.alfresco.error.AlfrescoRuntimeException; +import org.alfresco.model.RenditionModel; import org.alfresco.repo.jscript.ScriptNode; import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.repository.ChildAssociationRef; import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.RegexQNamePattern; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.mozilla.javascript.Scriptable; /** * @author Roy Wetherall + * @author Neil McErlean */ public class ScriptThumbnail extends ScriptNode { private static final long serialVersionUID = 7854749986083635678L; + /** Logger */ + private static Log logger = LogFactory.getLog(ScriptThumbnail.class); + /** * Constructor * @@ -49,7 +60,25 @@ public class ScriptThumbnail extends ScriptNode */ public void update() { - String name = (String)services.getNodeService().getProperty(nodeRef, ContentModel.PROP_THUMBNAIL_NAME); + List parentRefs = services.getNodeService().getParentAssocs(nodeRef, RenditionModel.ASSOC_RENDITION, RegexQNamePattern.MATCH_ALL); + // There should in fact only ever be one parent association of type rendition on any rendition node. + if (parentRefs.size() != 1) + { + StringBuilder msg = new StringBuilder(); + msg.append("Node ") + .append(nodeRef) + .append(" has ") + .append(parentRefs.size()) + .append(" rendition parents. Unable to update."); + if (logger.isWarnEnabled()) + { + logger.warn(msg.toString()); + } + throw new AlfrescoRuntimeException(msg.toString()); + } + + String name = parentRefs.get(0).getQName().getLocalName(); + ThumbnailDefinition def = services.getThumbnailService().getThumbnailRegistry().getThumbnailDefinition(name); services.getThumbnailService().updateThumbnail(this.nodeRef, def.getTransformationOptions()); } diff --git a/source/java/org/alfresco/service/cmr/action/ActionList.java b/source/java/org/alfresco/service/cmr/action/ActionList.java new file mode 100644 index 0000000000..359ca83f9d --- /dev/null +++ b/source/java/org/alfresco/service/cmr/action/ActionList.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.service.cmr.action; + +import java.io.Serializable; +import java.util.List; + +/** + * @author Nick Smith + */ +public interface ActionList extends Serializable +{ + /** + * Indicates whether there are any actions + * + * @return true if there are actions, false otherwise + */ + boolean hasActions(); + + /** + * Add an action to the end of the list + * + * @param action the action + */ + void addAction(A action); + + /** + * Add an action to the list at the index specified + * + * @param index the index + * @param action the action + */ + void addAction(int index, A action); + + /** + * Replace the action at the specfied index with the passed action. + * + * @param index the index + * @param action the action + */ + void setAction(int index, A action); + + /** + * Gets the index of an action + * + * @param action the action + * @return the index + */ + int indexOfAction(A action); + + /** + * Get list containing the actions in their current order + * + * @return the list of actions + */ + List getActions(); + + /** + * Get an action at a given index + * + * @param index the index + * @return the action + */ + A getAction(int index); + + /** + * Remove an action from the list + * + * @param action the action + */ + void removeAction(A action); + + /** + * Remove all actions from the list + */ + void removeAllActions(); +} diff --git a/source/java/org/alfresco/service/cmr/action/CompositeAction.java b/source/java/org/alfresco/service/cmr/action/CompositeAction.java index 85c1931899..1f9a6eee8a 100644 --- a/source/java/org/alfresco/service/cmr/action/CompositeAction.java +++ b/source/java/org/alfresco/service/cmr/action/CompositeAction.java @@ -16,79 +16,15 @@ * You should have received a copy of the GNU Lesser General Public License * along with Alfresco. If not, see . */ -package org.alfresco.service.cmr.action; -import java.util.List; +package org.alfresco.service.cmr.action; /** * Composite action * * @author Roy Wetherall */ -public interface CompositeAction extends Action +public interface CompositeAction extends Action, ActionList { - /** - * Indicates whether there are any actions - * - * @return true if there are actions, false otherwise - */ - boolean hasActions(); - - /** - * Add an action to the end of the list - * - * @param action the action - */ - void addAction(Action action); - - /** - * Add an action to the list at the index specified - * - * @param index the index - * @param action the action - */ - void addAction(int index, Action action); - - /** - * Replace the action at the specfied index with the passed action. - * - * @param index the index - * @param action the action - */ - void setAction(int index, Action action); - - /** - * Gets the index of an action - * - * @param action the action - * @return the index - */ - int indexOfAction(Action action); - - /** - * Get list containing the actions in their current order - * - * @return the list of actions - */ - List getActions(); - - /** - * Get an action at a given index - * - * @param index the index - * @return the action - */ - Action getAction(int index); - - /** - * Remove an action from the list - * - * @param action the action - */ - void removeAction(Action action); - - /** - * Remove all actions from the list - */ - void removeAllActions(); + } diff --git a/source/java/org/alfresco/service/cmr/rendition/CompositeRenditionDefinition.java b/source/java/org/alfresco/service/cmr/rendition/CompositeRenditionDefinition.java new file mode 100644 index 0000000000..090af4fcbb --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/CompositeRenditionDefinition.java @@ -0,0 +1,37 @@ +/* + * 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.service.cmr.rendition; + +import org.alfresco.service.cmr.action.ActionList; + +/** + * This is a special {@link RenditionDefinition} which allows sequential + * execution of a list of other {@link RenditionDefinition}s. For example, it + * might be used to transform a PDF file to a JPEG imaged and then resize that + * image. This would be achieved by creating a + * {@link CompositeRenditionDefinition} that has two sub-definitions, one to + * reformat the PDF to a JPEG image and the second to resize the JPEG image. + * + * @author Nick Smith + */ +public interface CompositeRenditionDefinition extends RenditionDefinition, ActionList +{ + +} diff --git a/source/java/org/alfresco/service/cmr/rendition/NodeLocator.java b/source/java/org/alfresco/service/cmr/rendition/NodeLocator.java new file mode 100644 index 0000000000..90d59ab981 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/NodeLocator.java @@ -0,0 +1,43 @@ +/* + * 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.service.cmr.rendition; + +import java.io.Serializable; +import java.util.Map; + +import org.alfresco.service.cmr.repository.NodeRef; + +/** + * This interface defines a strategy object used for finding a {@link NodeRef}. + * + * @author Nick Smith + */ +public interface NodeLocator +{ + /** + * Finds a {@link NodeRef} given a starting {@link NodeRef} and a + * {@link Map} of parameters. + * + * @param sourceNode the starting point for locating a new node. + * @param params a {@link Map} of parameters. + * @return the {@link NodeRef}. + */ + NodeRef getNode(NodeRef sourceNode, Map params); +} diff --git a/source/java/org/alfresco/service/cmr/rendition/RenderCallback.java b/source/java/org/alfresco/service/cmr/rendition/RenderCallback.java new file mode 100644 index 0000000000..3c6e622f6e --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/RenderCallback.java @@ -0,0 +1,47 @@ +/* + * 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.service.cmr.rendition; + +import org.alfresco.service.cmr.repository.ChildAssociationRef; + +/** + * This interface defines a callback object which can be used to handle the ultimate + * result of asynchronous renditions. + * + * @author Neil McErlean + * @see RenditionService#render(org.alfresco.service.cmr.repository.NodeRef, RenditionDefinition, RenderCallback) + */ +public interface RenderCallback +{ + /** + * This callback method will be called upon successful completion of an asynchronous + * rendition. + * @param primaryParentOfNewRendition a ChildAssociationRef linking the new rendition + * object to its primary parent. + */ + void handleSuccessfulRendition(ChildAssociationRef primaryParentOfNewRendition); + + /** + * This callback method will be called upon unsuccessful completion of an + * asynchronous rendition. + * @param t the Throwable giving the cause of the rendition failure. + */ + void handleFailedRendition(Throwable t); +} diff --git a/source/java/org/alfresco/service/cmr/rendition/RenderingEngineDefinition.java b/source/java/org/alfresco/service/cmr/rendition/RenderingEngineDefinition.java new file mode 100644 index 0000000000..140499768f --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/RenderingEngineDefinition.java @@ -0,0 +1,33 @@ +/* + * 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.service.cmr.rendition; + +import org.alfresco.service.cmr.action.ActionDefinition; + +/** + * This class describes a rendering engine and defines what parameters can/must + * be specified for that rendering engine. + * + * @author Nick Smith + */ +public interface RenderingEngineDefinition extends ActionDefinition +{ + +} diff --git a/source/java/org/alfresco/service/cmr/rendition/RenditionDefinition.java b/source/java/org/alfresco/service/cmr/rendition/RenditionDefinition.java new file mode 100644 index 0000000000..a0575dc8f0 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/RenditionDefinition.java @@ -0,0 +1,102 @@ +/* + * 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.service.cmr.rendition; + +import java.io.Serializable; + +import org.alfresco.service.cmr.action.Action; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * This class is used to fully specify a type of rendition. It specifies which + * rendering engine will be used as well as the parameters that will be given to + * that engine. + *

+ * Every RenditionDefinition has a renditionName attribute which + * uniquely identifies it. + * + * @author Nick Smith + * @author Neil McErlean + */ +public interface RenditionDefinition extends Action, Serializable +{ + /** + * @return the name which uniquely identifies this rendering action. + */ + public QName getRenditionName(); + + /** + * Returns the node to which the rendition is linked when it is first + * created. Typically this location is only temporary temporary as the + * rendition will be moved to a different location by the + * {@link RenditionService} shortly after its creation. + * + * @return the renditionParent + */ + public NodeRef getRenditionParent(); + + /** + * Sets the node to which the rendition is linked when it is first created. + * Typically this location is only temporary temporary as the rendition will + * be moved to a different location by the {@link RenditionService} shortly + * after its creation. + * + * @param renditionParent the renditionParent to set + */ + public void setRenditionParent(NodeRef renditionParent); + + /** + * Returns the association type used to link the rendition to its parent + * node after it has been newly created. Typically this association is only + * temporary as the rendition will be moved to a different location by the + * {@link RenditionService} shortly after its creation. + * + * @return the renditionAssociationType + */ + public QName getRenditionAssociationType(); + + /** + * Sets the association type used to link the rendition to its parent node + * after it has been newly created. Typically this association is only + * temporary as the rendition will be moved to a different location by the + * {@link RenditionService} shortly after its creation. + * + * @param renditionAssociationType the renditionAssociationType to set + */ + public void setRenditionAssociationType(QName renditionAssociationType); + + /** + * This method sets a callback object for use in asynchronous renditions. It is + * this object that will be notified of the successful or unsuccessful completion + * of these renditions. + * + * @param callback a callback object, which may be null. + */ + public void setCallback(RenderCallback callback); + + /** + * This method gets the registered callback object for use with asynchronous + * renditions. + * + * @return the callback object + */ + public RenderCallback getCallback(); +} diff --git a/source/java/org/alfresco/service/cmr/rendition/RenditionService.java b/source/java/org/alfresco/service/cmr/rendition/RenditionService.java new file mode 100644 index 0000000000..e42707662e --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/RenditionService.java @@ -0,0 +1,159 @@ +/* + * 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.service.cmr.rendition; + +import java.util.List; + +import org.alfresco.repo.rendition.RenditionDefinitionPersister; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; + +/** + * @author Nick Smith + * @author Neil McErlean + */ +public interface RenditionService extends RenditionDefinitionPersister +{ + public static final String PARAM_DESTINATION_NODE = "rendition-destination-node"; + public static final String PARAM_DESTINATION_PATH_TEMPLATE = "destination-path-template"; + public static final String PARAM_RENDITION_NODETYPE = "rendition-nodetype"; + public static final String PARAM_ORPHAN_EXISTING_RENDITION = "orphan-existing-rendition"; + + /** + * Returns the {@link RenderingEngineDefinition} associated with the + * specified rendering engine name. + * + * @param name The rendering engine name. + * @return The {@link RenderingEngineDefinition} or null. + */ + RenderingEngineDefinition getRenderingEngineDefinition(String name); + + /** + * @return A {@link List} of all available {@link RenderingEngineDefinition} + * s. + */ + List getRenderingEngineDefinitions(); + + /** + * Creates a new {@link RenditionDefinition} and sets the rendition name and + * the rendering engine name to the specified values. + * + * @param renditionName A unique identifier used to specify the created + * {@link RenditionDefinition}. + * @param renderingEngineName The name of the rendering engine associated + * with this {@link RenditionDefinition}. + * @return the created {@link RenditionDefinition}. + */ + RenditionDefinition createRenditionDefinition(QName renditionName, String renderingEngineName); + + /** + * Creates a new {@link CompositeRenditionDefinition} and sets the rendition + * name and the rendering engine name to the specified values. + * + * @param renditionName A unique identifier used to specify the created + * {@link RenditionDefinition}. + * @return the created {@link CompositeRenditionDefinition}. + */ + CompositeRenditionDefinition createCompositeRenditionDefinition(QName renditionName); + + /** + * This method gets all the renditions of the specified node. + * + * @return a list of ChildAssociationRefs which link the source node to the + * renditions. + */ + List getRenditions(NodeRef node); + + /** + * This method gets all the renditions of the specified node filtered by + * MIME-type prefix. Renditions whose MIME-type string startsWith the prefix + * will be returned. + * + * @param node the source node for the renditions + * @param mimeTypePrefix a prefix to check against the rendition MIME-types. + * This must not be null and must not be an empty String + * @return a list of ChildAssociationRefs which link the source node to the + * filtered renditions. + */ + List getRenditions(NodeRef node, String mimeTypePrefix); + + /** + * This method gets the rendition of the specified node identified by + * the provided rendition name. + * + * @param node the source node for the renditions + * @param renditionName the renditionName used to identify a rendition. + * @return the ChildAssociationRef which links the source node to the + * rendition or null if there is no such rendition. + */ + ChildAssociationRef getRenditionByName(NodeRef node, QName renditionName); + + /** + * This method returns true if the specified NodeRef is a valid + * rendition node, else false. A nodeRef is a rendition node + * if it has the rn:rendition aspect (or sub-aspect) applied. + * + * @param node + * @return true if a rendition, else false + */ + boolean isRendition(NodeRef node); + + /** + * This method gets the source node for the specified rendition node. There + * should only be one source node for any given rendition node. + * + * @param renditionNode the nodeRef holding the rendition. + * @return the ChildAssociationRef whose parentNodeRef is the source node, or + * null if there is no source node. + * + * @see RenditionService#isRendition(NodeRef) + */ + ChildAssociationRef getSourceNode(NodeRef renditionNode); + + //TODO The result should be the link to the primary parent. + /** + * This method synchronously renders content as specified by the given + * {@link RenditionDefinition}. The content to be rendered is provided by + * the specified source node. + * + * @param sourceNode the node from which the content is retrieved. + * @param renditionDefinition this fully specifies the rendition which is to + * be performed. + * @return a child association reference which is the link from the source + * node to the newly rendered content. + * @throws RenditionServiceException if there is a problem in rendering the + * given sourceNode + */ + ChildAssociationRef render(NodeRef sourceNode, RenditionDefinition renditionDefinition); + + /** + * This method asynchronously renders content as specified by the given + * {@link RenditionDefinition}. The content to be rendered is provided by + * the specified source node. + * + * @param sourceNode the node from which the content is retrieved. + * @param renditionDefinition this fully specifies the rendition which is to + * be performed. + * @param callback a callback object to handle the ultimate result of the rendition. + */ + void render(NodeRef sourceNode, RenditionDefinition renditionDefinition, + RenderCallback callback); +} diff --git a/source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java b/source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java new file mode 100644 index 0000000000..a19b018478 --- /dev/null +++ b/source/java/org/alfresco/service/cmr/rendition/RenditionServiceException.java @@ -0,0 +1,52 @@ +/* + * 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.service.cmr.rendition; + +import org.alfresco.error.AlfrescoRuntimeException; + +/** + * Rendition Service Exception Class + * + * @author Neil McErlean + */ +public class RenditionServiceException extends AlfrescoRuntimeException +{ + private static final long serialVersionUID = -6947067735970465937L; + + /** + * Constructs a Rendition Service Exception with the specified message. + * + * @param message the message string + */ + public RenditionServiceException(String message) + { + super(message); + } + + /** + * Constructs a Rendition Service Exception with the specified message and source exception. + * + * @param message the message string + * @param source the source exception + */ + public RenditionServiceException(String message, Throwable source) + { + super(message, source); + } +} diff --git a/source/java/org/alfresco/service/namespace/NamespaceService.java b/source/java/org/alfresco/service/namespace/NamespaceService.java index b023309f9a..4330c2076f 100644 --- a/source/java/org/alfresco/service/namespace/NamespaceService.java +++ b/source/java/org/alfresco/service/namespace/NamespaceService.java @@ -88,6 +88,9 @@ public interface NamespaceService extends NamespacePrefixResolver /** Alfresco Forums Prefix */ static final String FORUMS_MODEL_PREFIX = "fm"; + /** Rendition Model URI */ + static final String RENDITION_MODEL_1_0_URI = "http://www.alfresco.org/model/rendition/1.0"; + /** Alfresco View Namespace URI */ static final String REPOSITORY_VIEW_1_0_URI = "http://www.alfresco.org/view/repository/1.0"; diff --git a/source/java/org/alfresco/util/FreeMarkerUtil.java b/source/java/org/alfresco/util/FreeMarkerUtil.java new file mode 100644 index 0000000000..80e062af4d --- /dev/null +++ b/source/java/org/alfresco/util/FreeMarkerUtil.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2005 Alfresco, Inc. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ +package org.alfresco.util; + +import java.io.*; +import java.util.LinkedList; +import javax.xml.XMLConstants; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.Node; +import org.w3c.dom.NamedNodeMap; +import org.xml.sax.SAXException; + +/** + * FreeMarker utility functions. + * + * @author Ariel Backenroth + */ +public class FreeMarkerUtil +{ + public static String buildNamespaceDeclaration(final Document xml) + { + final Element docEl = xml.getDocumentElement(); + final NamedNodeMap attributes = docEl.getAttributes(); + final StringBuilder result = new StringBuilder(); + for (int i = 0; i < attributes.getLength(); i++) + { + final Node a = attributes.item(i); + if (a.getNodeName().startsWith(XMLConstants.XMLNS_ATTRIBUTE)) + { + final String prefix = a.getNodeName().substring((XMLConstants.XMLNS_ATTRIBUTE + ":").length()); + final String uri = a.getNodeValue(); + if (result.length() != 0) + { + result.append(",\n"); + } + result.append("\"").append(prefix).append("\":\"").append(uri).append("\""); + } + } + return "<#ftl ns_prefixes={\n" + result.toString() + "}>\n"; + } +} \ No newline at end of file diff --git a/source/java/org/alfresco/util/XMLUtil.java b/source/java/org/alfresco/util/XMLUtil.java new file mode 100644 index 0000000000..d7a60826c8 --- /dev/null +++ b/source/java/org/alfresco/util/XMLUtil.java @@ -0,0 +1,281 @@ +/* + * Copyright (C) 2005-2008 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" */ +package org.alfresco.util; + +import java.io.*; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.avm.AVMService; +import org.alfresco.service.cmr.repository.ContentReader; +import org.alfresco.service.cmr.repository.ContentService; +import org.alfresco.service.cmr.repository.NodeRef; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.w3c.dom.*; +import org.xml.sax.SAXException; + +/** + * XML utility functions. + * + * @author Ariel Backenroth + */ +public class XMLUtil +{ + private static final Log LOGGER = LogFactory.getLog(XMLUtil.class); + + /** utility function for creating a document */ + public static Document newDocument() + { + return XMLUtil.getDocumentBuilder().newDocument(); + } + + /** utility function for serializing a node */ + public static void print(final Node n, final Writer output) + { + XMLUtil.print(n, output, true); + } + + /** utility function for serializing a node */ + public static void print(final Node n, final Writer output, final boolean indent) + { + try + { + final TransformerFactory tf = TransformerFactory.newInstance(); + final Transformer t = tf.newTransformer(); + t.setOutputProperty(OutputKeys.INDENT, indent ? "yes" : "no"); + t.setOutputProperty(OutputKeys.ENCODING, "UTF-8"); + t.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no"); + t.setOutputProperty(OutputKeys.METHOD, "xml"); + if (LOGGER.isDebugEnabled()) + { + LOGGER.debug("writing out a document for " + + (n instanceof Document + ? ((Document)n).getDocumentElement() + : n).getNodeName() + + " to " + (output instanceof StringWriter + ? "string" + : output)); + } + t.transform(new DOMSource(n), new StreamResult(output)); + } + catch (TransformerException te) + { + te.printStackTrace(); + assert false : te.getMessage(); + } + } + + /** utility function for serializing a node */ + public static void print(final Node n, final File output) + throws IOException + { + XMLUtil.print(n, new FileWriter(output)); + } + + /** utility function for serializing a node */ + public static String toString(final Node n) + { + return XMLUtil.toString(n, true); + } + + /** utility function for serializing a node */ + public static String toString(final Node n, final boolean indent) + { + final StringWriter result = new StringWriter(); + XMLUtil.print(n, result, indent); + return result.toString(); + } + + /** utility function for parsing xml */ + public static Document parse(final String source) + throws SAXException, + IOException + { + return XMLUtil.parse(new ByteArrayInputStream(source.getBytes("UTF-8"))); + } + + /** utility function for parsing xml */ + public static Document parse(final NodeRef nodeRef, + final ContentService contentService) + throws SAXException, + IOException + { + final ContentReader contentReader = + contentService.getReader(nodeRef, ContentModel.TYPE_CONTENT); + final InputStream in = contentReader.getContentInputStream(); + return XMLUtil.parse(in); + } + + /** utility function for parsing xml */ + public static Document parse(final int version, + final String path, + final AVMService avmService) + throws SAXException, + IOException + { + return XMLUtil.parse(avmService.getFileInputStream(version, path)); + } + + /** utility function for parsing xml */ + public static Document parse(final File source) + throws SAXException, + IOException + { + return XMLUtil.parse(new FileInputStream(source)); + } + + /** utility function for parsing xml */ + public static Document parse(final InputStream source) + throws SAXException, + IOException + { + try + { + final DocumentBuilder db = XMLUtil.getDocumentBuilder(); + return db.parse(source); + } + finally + { + source.close(); + } + } + + /** provides a document builder that is namespace aware but not validating by default */ + public static DocumentBuilder getDocumentBuilder() + { + return XMLUtil.getDocumentBuilder(true, false); + } + + /** + * FOR DIAGNOSTIC PURPOSES ONLY - incomplete
+ * Builds a path to the node relative to the to node provided. + * @param from the node from which to build the xpath + * @param to an ancestor of from which will be the root of the path + * @return an xpath to to rooted at from. + */ + public static String buildXPath(final Node from, final Element to) + { + String result = ""; + Node tmp = from; + do + { + if (tmp instanceof Attr) + { + assert result.length() == 0; + result = "@" + tmp.getNodeName(); + } + else if (tmp instanceof Element) + { + Node tmp2 = tmp; + int position = 1; + while (tmp2.getPreviousSibling() != null) + { + if (tmp2.getNodeName().equals(tmp.getNodeName())) + { + position++; + } + tmp2 = tmp2.getPreviousSibling(); + } + String part = tmp.getNodeName() + "[" + position + "]"; + result = "/" + part + result; + } + else if (tmp instanceof Text) + { + assert result.length() == 0; + result = "/text()"; + } + else + { + if (LOGGER.isDebugEnabled()) + { + throw new IllegalArgumentException("unsupported node type " + tmp); + } + } + tmp = tmp.getParentNode(); + } + while (tmp != to.getParentNode() && tmp != null); + return result; + } + + public static DocumentBuilder getDocumentBuilder(final boolean namespaceAware, + final boolean validating) + { + try + { + final DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setNamespaceAware(namespaceAware); + dbf.setValidating(validating); + return dbf.newDocumentBuilder(); + } + catch (ParserConfigurationException pce) + { + LOGGER.error(pce); + return null; + } + } + + /** + * Provides a NodeList of multiple nodelists + */ + public static NodeList combine(final NodeList... nls) + { + + return new NodeList() + { + public Node item(final int index) + { + int offset = 0; + for (int i = 0; i < nls.length; i++) + { + if (index - offset < nls[i].getLength()) + { + return nls[i].item(index - offset); + } + else + { + offset += nls[i].getLength(); + } + } + return null; + } + + public int getLength() + { + int result = 0; + for (int i = 0; i < nls.length; i++) + { + result += nls[i].getLength(); + } + return result; + } + }; + } +} diff --git a/source/java/org/alfresco/util/json/AbstractJsonSerializerBean.java b/source/java/org/alfresco/util/json/AbstractJsonSerializerBean.java new file mode 100644 index 0000000000..f0c51a8169 --- /dev/null +++ b/source/java/org/alfresco/util/json/AbstractJsonSerializerBean.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.util.json; + +/** + * @author Nick Smith + */ +public abstract class AbstractJsonSerializerBean implements JsonSerializer +{ + private Class classToSerialize; + private AlfrescoJsonSerializer jsonSerializer; + + /** + * @param classToSerialize the classToSerialize to set + */ + public void setClassToSerialize(Class classToSerialize) + { + this.classToSerialize = classToSerialize; + } + + /** + * @param jsonSerializer the jsonSerializer to set + */ + public void setJsonSerializer(AlfrescoJsonSerializer jsonSerializer) + { + this.jsonSerializer = jsonSerializer; + } + + public void init() + { + jsonSerializer.register(classToSerialize, this); + } + +} diff --git a/source/java/org/alfresco/util/json/AlfrescoJsonSerializer.java b/source/java/org/alfresco/util/json/AlfrescoJsonSerializer.java new file mode 100644 index 0000000000..98db813d3c --- /dev/null +++ b/source/java/org/alfresco/util/json/AlfrescoJsonSerializer.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2005-20010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.util.json; + +import java.util.Collection; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.apache.chemistry.tck.atompub.utils.ISO8601DateFormat; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * @author Nick Smith + */ +public class AlfrescoJsonSerializer +{ + private final NamespaceService namespaceService; + private final Map, JsonSerializer> serializers = new HashMap, JsonSerializer>(); + + public AlfrescoJsonSerializer(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void register(Class clazz, JsonSerializer serializer) + { + serializers.put(clazz, serializer); + } + + public Object getJsonValue(Object value) throws JSONException + { + if (value instanceof Date) { return getJsonDate((Date) value); } + if (value instanceof QName) { return getJsonQName((QName) value); } + if (value instanceof NodeRef) + { + return value.toString(); + } + else if (value instanceof Collection) + { + return getJsonArray((Collection) value); + } + else if (value instanceof Map) { return getJsonObject((Map) value); } + return value; + } + + private JSONObject getJsonObject(Map map) throws JSONException + { + JSONObject object = new JSONObject(); + for (Entry entry : map.entrySet()) + { + String key = getJsonKey(entry.getKey()); + Object value = getJsonValue(entry.getValue()); + object.put(key, value); + } + return object; + } + + private String getJsonKey(Object key) + { + if (key instanceof QName) { return getJsonQName((QName) key); } + return key.toString(); + } + + private String getJsonQName(QName name) + { + String nameString = name.toPrefixString(namespaceService); + return nameString.replaceFirst(":", "_"); + } + + private JSONObject getJsonDate(Date date) throws JSONException + { + JSONObject isoDate = new JSONObject(); + isoDate.put("iso8601", ISO8601DateFormat.format(date)); + return isoDate; + } + + private JSONArray getJsonArray(Collection values) throws JSONException + { + JSONArray array = new JSONArray(); + for (Object val : values) + { + array.put(getJsonValue(val)); + } + return array; + } + +} \ No newline at end of file diff --git a/source/java/org/alfresco/util/json/DefaultJsonSerializer.java b/source/java/org/alfresco/util/json/DefaultJsonSerializer.java new file mode 100644 index 0000000000..afe7f35f03 --- /dev/null +++ b/source/java/org/alfresco/util/json/DefaultJsonSerializer.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.util.json; + +/** + * + * @author Nick Smith + */ +public class DefaultJsonSerializer implements JsonSerializer +{ + + /* + * @see org.alfresco.util.json.JsonSerializer#deserialize(java.lang.Object) + */ + public Object deserialize(Object object) + { + return object; + } + + /* + * @see org.alfresco.util.json.JsonSerializer#serialize(java.lang.Object) + */ + public Object serialize(Object object) + { + return object; + } + +} diff --git a/source/java/org/alfresco/util/json/JsonSerializer.java b/source/java/org/alfresco/util/json/JsonSerializer.java new file mode 100644 index 0000000000..dd06499dd1 --- /dev/null +++ b/source/java/org/alfresco/util/json/JsonSerializer.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2005-2010 Alfresco Software Limited. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation; either version 2 + * of the License, or (at your option) any later version. + + * This program 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 General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + + * As a special exception to the terms and conditions of version 2.0 of + * the GPL, you may redistribute this Program in connection with Free/Libre + * and Open Source Software ("FLOSS") applications as described in Alfresco's + * FLOSS exception. You should have recieved a copy of the text describing + * the FLOSS exception, and it is also available here: + * http://www.alfresco.com/legal/licensing" + */ + +package org.alfresco.util.json; + +/** + * An interface for converting objects of a specified type (T) into a serialized + * Json format (of type S). + * + * @author Nick Smith + */ +public interface JsonSerializer +{ + + S serialize(T object); + + T deserialize(S object); +} diff --git a/source/test-resources/images/gray21.512.png b/source/test-resources/images/gray21.512.png new file mode 100644 index 0000000000000000000000000000000000000000..a0072e8f0b4bf2f34a89b93106d8c77817d88332 GIT binary patch literal 2904 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&w3=!$sk4H6X=V;1OBOz`!jG!i)^F=12e; zT%InDAr*{ouN=&Eb`&^#LFLBc?TuI7^Zn&65Pk395X_+Hapcdxl6QAItBeEx8+nRP zF)?8DSS$O2P37pC%(MPX~%0^tRlMLn%XmC_^x zs^VC@7w|6>dSSni>xErIhUt+x3=229T;WuR;x$xv$j}iiW$FlXKEo*> zLtr#(45C6AoHhCna%C;QTFbDpzQdByDYFVdQ&MBb@0Gf#KE&u=k literal 0 HcmV?d00001 diff --git a/source/test-resources/images/gray21.512.tiff b/source/test-resources/images/gray21.512.tiff new file mode 100644 index 0000000000000000000000000000000000000000..71a8c6c86440050ccace3c4e284dea9c220a46b6 GIT binary patch literal 262278 zcmeIx!HFGY6o%nDiAa!aT!;v+vv6wzHZTp?fDKrH1z11|NCP%t12$j-Hjo0;9289h z&*Fvehs<0q;hh7|InU3JZyujKUOhVRUFZFhc^&>ohyPuM|CjlTwVFTJD*=U?^PhiX z2Y{1GqFWW&hW$;cus~Npq2~PO-`D}*q>|`XMYdsoQz|Ub6;P--|M@p|063{6x>b>F z*x!^23v>k(YR-TDjU50^Dv54YWE=K3rNRPT0fn0LpMPTqfRjq1TNT-c{Y|N`KvzJa z=KSa1*a6_AlIT`Nwqbu$DlE_yP^dZo`8Rd|IH@GMRgrDj-;@dqbOjV@&VT-m9RN-$ ziEdS78}>J)!UA0Zg_`r9e`5!LlS-mn71@UUO{uUzS3sfW{O8};0pO&P=vGCxVSiI9 zEYKBDs5$@nH+BFxsU*5pk!{%DlnM)U1r%z|fBuaf08T22ZdGI(_BW-%0$l-xn)9E3 zV+VkfN}^j8*@pd1sjxs-K%wUR=ik@?;G~l1RzJV?T>*uf^PhiX2Y{1GqFWW&hW$;cus~Np zq2~PO-`D}*q>|`XMYdsoQz|Ub6;P--|M@p|063{6x>b>F*x!^23v>k(YR-TDjU50^ zDv54YWE=K3rNRPT0fn0LpMPTqfRjq1TNT-c{Y|N`KvzJa=KSa1*a6_AlIT`Nwqbu$ zDlE_yP^dZo`8Rd|IH@GMRgrDj-;@dqbOjV@&VT-m9eCY&Z#(bT%;Q05Np!0s+pxbW6&C0UDAb(){2My}oKzCss>n9%Z%Ty)x&jI{ z=Rg0(4ge>WM7Juk4f~r?VS%oILe2Tlzp(?rNhQ&(ifqIFrc_v zfv$i;&H2y2u>-(KCDE;lY{UMhR9K)Zpip!E^Ka|`a8gNht0LR5zbO?K=n5#*od5hA zI{=(i65XoEHtcUog$23-3N`0H|HcjgCzV9EDzXjxn^Iwcu7E<#`Om+x1Hefo(XEPX z!~UjJSfDGQP;>tCZ|neYQb}~HBHOUPDHRsz3MkZ^|NI*}0Gw13-Kxko>~Bhi1-b$X zHRnJ7#tr}{l|;8HvJLy2QelCvfI`jr&%dz)z)2<1t%_{J{-#t|pevwIbN=&h>;Q05 zNp!0s+pxbW6&C0UDAb(){2My}oKzCss>n9%Z%Ty)x&jI{=Rg0(4ge>WM7Juk4f~r? zVS%oILe2Tlzp(?rNhQ&(ifqIFrc_vfv$i;&5r+9o%eI+{g!zh z{;xXxKWF%Vo4;7A`GdU@P^dZo`8Rd|IH@GMRgrDj-;@dqbOjV@&VT-m9RN-$iEdS7 z8}>J)!UA0Zg_`r9e`5!LlS-mn71@UUO{uUzS3sfW{O8};0pO&P=vGCxVSiI9EYKBD zs5$@nH+BFxsU*5pk!{%DlnM)U1r%z|fBuaf08T22ZdGI(_BW-%0$l-xn)9E3V+Vkf zN}^j8*@pd1sjxs-K%wUR=ik@?;G~l1RzJV?T>*uf^PhiX2Y{1GqFWW&hW$;cus~Npq2~PO z-`D}*q>|`XMYdsoQz|Ub6;P--|M@p|063{6x>b>F*x!^23v>k(YR-TDjU50^Dv54Y zWE=K3rNRPT0fn0LpMPTqfRjq1TNT-c{Y|N`KvzJa=KSa1*a6_AlIT`Nwqbu$DlE_y zP^dZo`8Rd|IH@GMRgrDj-;@dqbOjV@&VT-m9RN-$iEdS78}>J)!UA0Zg_`r9e`5!L zlS-mn71@UUO{uUzS3sfW{O8};0pO&P=vGCxVSiI9EYKBDs5$@nH+BFxsU*5pk!{%D zlnM)U1r%z|fBuaf08T22ZdGI(_BW-%0$l-xn)9E3V+VkfN}^j8*@pd1sjxs-K%r*G z|4rxp)Oo*WUWflphySMx|L^k`Yc+qcR{{z(=Rg0(4ge>WM7Juk4f~r?VS%oILe2Tl zzp(?rNhQ&(ifqIFrc_vfv$i;&H2y2u>-(KCDE;lY{UMhR9K)Z zpip!E^Ka|`a8gNht0LR5zbO?K=n5#*od5hAI{=(i65XoEHtcUog$23-3N`0H|Hcjg zCzV9EDzXjxn^Iwcu7E<#`Om+x1Hefo(XEPX!~UjJSfDGQP;>tCZ|neYQb}~HBHOUP zDHRsz3MkZ^|NI*}0Gw13-Kxko>~Bhi1-b$XHRnJ7#tr}{l|;8HvJLy2QelCvfI`jr z&%dz)z)2<1t%_{J{-#t|pevwIbN=&h>;Q05Np!0s+pxbW6&C0UDAb(){2My}oKzCs zs>n9%Z%Ty)x&jI{=Rg0(4ge>WM7Juk4f~r?VS%oILe2Tlzp(?rNhQ&(ifqIFrc_v< zE1*zw{_}6_0B}-CbgLrUu)ir47U&8n)SUnP8#@4;R1)2)$TsY6N`(cw0tz+fKmW!K z04J41w<@v?`fv$i;&H2y2u>-(KCDE;lY{UMhR9K)Zpip!E^Ka|`a8gNht0LR5 zzbO?K=n5#*?D&7%c|UgEADP$T|F*;bV}}2a`HQuhKiDe)g_`r9e`5!LlS-mn71@UU zO{uUzS3sfW{O8};0pO&P=vGCxVSiI9EYKBDs5$@nH+BFxsU*5pk!{%DlnM)U1r%z| zfBuaf08T22ZdGI(_BW-%0$l-xn)9E3V+VkfN}^j8*@pd1sjxs-K%wUR=ik@?;G~l1 zRzJV? zT>*uf^PhiX2Y{1GqFWW&hW$;cus~Npq2~PO-`D}*q>|`XMYdsoQz|Ub6;P--|M@p| z063{6x>b>F*x!^23v>k(YR-TDjU50^Dv54YWE=K3rNRPT0fn0LpMPTqfRjq1TNT-c z{Y|N`KvzJa=KSa1*a6_AlIT`Nwqbu$DlE_yP^dZo`8Rd|IH@GMRgrDj-;@dqbOjV@ z&VT-m9RN-$iEdS78}>J)!UA0Zg_`r9e`5!LlS-mn71@UUO{uUzS3sfW{O8};0pO&P z=vGCxVSiI9EYKBDs5$@nH+BFxsU*5pk!{%DlnM)U1r%z|fBuaf08T22ZdGI(_BW-% z0$l-xn)9E3V+VkfN}^j8*@pd1sjxs-K%wUR=ik@?;G~l1Rzfv$i;&H2y2u>-(KCDE;lY{UMhR9K)Zpip!E^Ka|`a8gNht0LR5zbO?K=n5#* zod5hAI{=(i65XoEHtcUog$23-3N`0H|HcjgCzV9EDzXjxn^Iwcu7E<#`Om+x1Hefo z(XEPX!~UjJSfDGQP;>tCZ|neYQb}~HBHOUPDHRsz3MkZ^|NI*}0Gw13-Kxko>~Bhi z1-b$XHRnJ7#tr}{l|;8HvJLy2QelCvfI`jr&%dz)z)2<1t%_{J{-#t|pevwIbN=&h z>;Q05Np!0s+pxbW6&C0UDAb(){2My}oKzCss>n9%Z%Ty)x&jI{=Rg0(4ge>WM7Juk z4f~r?VS%oILe2Tlzp(?rNhQ&(ifqIFrc_vfv$i;&H2y2u>-(K zCDE;lY{UMhR9K)Zpip!E^Ka|`a8gNht0LR5zbO?K=n5#*od5hAI{=(i65XoEHtcUo zg$23-3N`0H|HcjgCzV9EDzXjxn^Iwcu7E<#j{kG#ecyS1WnPE>xx@c`hX1emi?y0R z*ed~rn)9E3V+VkfN}^j8*@pd1sjxs-K%wUR=ik@?;G~l1RzJV?T>*uf^PhiX2Y{1GqFWW& zhW$;cus~Npq2~PO-`D}*q>|`XMYdsoQz|Ub6;P--|M@p|063{6x>b>F*x!^23v>k( zYR-TDjU50^Dv54YWE=K3rNRPT0fn0LpMPTqfRjq1TNT-c{Y|N`KvzJa=KSa1*a6_A zlIT`Nwqbu$DlE_yP^dZo`8Rd|IH@GMRgrDj-;@dqbOjV@&VT-m9RN-$iEdS78}>J) z!UA0Zg_`r9e`5!LlS-mn71@UUO{uUzS3sfW{O8};0pO&P=vGCxVSiI9EYKBDs5$@n zH+BFxsU*5pk!{%DlnM)U1r%z|fBuaf08T22ZdGI(_BW-%0$l-xn)9E3V+VkfN}^j8 z*@pd1sjxs-K%wUR=ik@?;G~l1RzJV?T>*uf^PhiX2Y{1GqFWW&hW$;cus~Npq2~PO-`D}* zq>|`XMYdsoQz|Ub6;P--|M@p|063{6x>b>F*x!^23v>k(YIgh|pFRG3eDZiao<97$ zA209Mi~I5P(dQTce15$8@cqkwKY#eoPoIAD`Sbreub#g6==(4K{r