From c3a622e3c4ecd88797f070dd556d7d807da94cf5 Mon Sep 17 00:00:00 2001 From: Dave Ward Date: Mon, 13 Feb 2012 12:24:24 +0000 Subject: [PATCH] Merged V4.0-BUG-FIX to HEAD 33836: Fix for ALF-10651 Fix patches that trigger reindexing and ALF-10656 SOLR: Patches execute search during bootstrap causing deadlock 33842: Fixes ALF-12797: i18n strings in activiti-admin login-screen escaped properly 33844: Fix for ALF-10651 Fix patches that trigger reindexing and ALF-10656 SOLR: Patches execute search during bootstrap causing deadlock - batch touch to limit the in clause size generated 33845: Manually added extra core Share extensions needed for the V4.0 Records Management module from the development branch. - Refactored JSON property decorators for the Document Library data webscripts - Document List banners (e.g. working copy) moved into metadata template config - Ability to override default document/folder title within Document Library ( element in metadata template - unused in core code) - Additional extension point in surf-doclist to override remote data URL - Better handling for missing content property 33852: ALF-12725: Merged V3.4-BUG-FIX (3.4.9) to V4.0-BUG-FIX (4.0.1) 33849: Merged V3 (3.4.8) to V3.4-BUG-FIX (3.4.9) 33848: ALF-10976 (relates to ALF-10412) Fixed bug to do with preview being stuck as always being 'Content cannot be previewed. Do you wish to download?' or a 'blank preview after a transformer is not found' for all content with the same mimetype. Cache in ThumbnailRegistory.getThumbnailDefinitions() now understands that transformers may have an upper content size limit. The choice between the two options was based on the size of the first file previewed of each mimetype. Needed to add getMaxSourceSizeBytes() to support this (see below). - refactored (previous refactor was incomplete) ContentTransformer so that the two halfs of isTransformable is now split into sub methods isTransformableMimetypes and isTransformableSize. This is why there are so many files changed. - Moved getMaxSourceSizeBytes() from AbstractContentTransformerLimits to ContentTransformer as there were becomming too many places in the code that needed needed to check if the ContentTransformer was an instanceof AbstractContentTransformerLimits before calling this method. - TransformerDebug now uses KB MB GB values in output to make it simpler to read. - TransformerDebug now uses thousand separaters in millisecond values to make it simpler to read. - TransformerDebug now reports the 'parent' transformer name rather than the sub-transformer name when an unavailable transformer is found. Makes it simpler to tie up with the 'available transformer' list with the new pushIsTransformableSize() calls. - TransformerDebug now uses trace logging for calls from ThumbnailRegistory.isThumbnailDefinitionAvailable() as it is normally followed by a ContentService.transform() which is logged at debug level anyway. - TransformerDebug now turns logging level to trace if the file size is 0 bytes. Request from Jan. Not sure how one uploads such a file! - Modified ComplexContentTransformer.isTransformable() so that it checks the mimetypes before the sizes so that TransformerDebug does not report 'unavailable transformers' that don't support the mimetype conversion. - Modified ComplexContentTransformer.getLimits and ComplexContentTransformer.isPageLimitSupported() to include the limits from the first sub transformer. Was not an issue until ContentTransformer.getMaxSourceSizeBytes() was introduced. - Added logger to RhinoScriptProcessor to debug requests run javascript on the server. - Dropped the sourceUrl parameter from ThumbnailRegistry.getThumbnailDefinitions() which was introduced with limits as it is logicall not needed. 33853: DiskInterface.renameFile() can now throw PermissionDeniedException to return a different status to the client. Part of ALF-12717. 33856: Merged V3.4-BUG-FIX to V4.0-BUG-FIX 33835: ALF-12546: Remove references to retired RegPaths.exe from installed apply_amps.bat script 33843: Fix for ALF-12775 33855: Merged V3.4 to V3.4-BUG-FIX 33851: ALF-12588: Documents Intermittently Do Not Appear in Share - Fix by Alex Busel for regression I accidentally caused in 3.4.6 - Simple typo in mergeDeletions() caused path deletions to sometimes not get applied or get processed twice - Yikes! git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/HEAD/root@33857 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../patch-common-SqlMap.xml | 72 +++ .../alfresco/patch/patch-services-context.xml | 57 ++- .../public-services-security-context.xml | 1 + config/alfresco/script-services-context.xml | 103 +++-- .../patch/impl/CalendarModelUriPatch.java | 133 ++++-- .../impl/GenericMimetypeRenamePatch.java | 137 +++--- .../repo/admin/patch/impl/QNamePatch.java | 153 ++++--- .../repo/content/ContentServiceImpl.java | 43 +- .../transform/AbstractContentTransformer.java | 36 +- .../AbstractContentTransformerLimits.java | 65 ++- .../AbstractContentTransformerLimitsTest.java | 76 +++- .../BinaryPassThroughContentTransformer.java | 5 +- .../transform/ComplexContentTransformer.java | 139 ++++-- .../content/transform/ContentTransformer.java | 31 +- .../transform/ContentTransformerRegistry.java | 3 +- .../ContentTransformerRegistryTest.java | 4 +- .../transform/ContentTransformerWorker.java | 3 +- .../content/transform/EMLTransformer.java | 5 +- .../transform/FailoverContentTransformer.java | 86 ++-- .../FailoverContentTransformerTest.java | 5 +- .../HtmlParserContentTransformer.java | 5 +- .../MediaWikiContentTransformer.java | 6 +- .../PdfBoxPdfToImageContentTransformer.java | 5 +- .../PdfToImageContentTransformer.java | 5 +- .../transform/PoiHssfContentTransformer.java | 23 +- .../transform/ProxyContentTransformer.java | 5 +- .../StringExtractingContentTransformer.java | 5 +- .../TextToPdfContentTransformer.java | 18 +- .../TikaPoweredContentTransformer.java | 19 +- .../content/transform/TransformerDebug.java | 103 ++++- .../repo/domain/node/AbstractNodeDAOImpl.java | 26 ++ .../alfresco/repo/domain/node/NodeDAO.java | 8 + .../alfresco/repo/domain/patch/PatchDAO.java | 37 ++ .../domain/patch/ibatis/PatchDAOImpl.java | 50 +++ .../repo/jscript/ApplicationScriptUtils.java | 394 ++++------------- .../repo/jscript/RhinoScriptProcessor.java | 33 +- .../org/alfresco/repo/jscript/ScriptNode.java | 4 +- .../jscript/app/BasePropertyDecorator.java | 75 ++++ .../app/CategoryPropertyDecorator.java | 49 +-- .../jscript/app/IgnorePropertyDecorator.java | 42 ++ .../jscript/app/JSONConversionComponent.java | 413 ++++++++++++++++++ .../repo/jscript/app/PropertyDecorator.java | 10 +- .../jscript/app/TagPropertyDecorator.java | 44 +- .../app/UsernamePropertyDecorator.java | 33 +- .../search/impl/lucene/index/IndexInfo.java | 2 +- .../repo/thumbnail/SimpleThumbnailer.java | 2 +- .../repo/thumbnail/ThumbnailRegistry.java | 88 +++- .../thumbnail/ThumbnailServiceImplTest.java | 4 +- .../repo/version/ContentServiceImplTest.java | 39 +- .../cmr/repository/ContentService.java | 13 +- 50 files changed, 1916 insertions(+), 801 deletions(-) create mode 100644 source/java/org/alfresco/repo/jscript/app/BasePropertyDecorator.java create mode 100644 source/java/org/alfresco/repo/jscript/app/IgnorePropertyDecorator.java create mode 100644 source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java diff --git a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml index b7eff491cd..254c8761af 100644 --- a/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml +++ b/config/alfresco/ibatis/org.hibernate.dialect.Dialect/patch-common-SqlMap.xml @@ -110,6 +110,24 @@ <parameter property="namePattern" jdbcType="VARCHAR" javaType="java.lang.String"/> </parameterMap> + <parameterMap id="parameter_qnameIdAndMinMaxNodeId" type="map"> + <parameter property="qnameId" jdbcType="BIGINT" javaType="java.lang.Long"/> + <parameter property="minNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/> + <parameter property="maxNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/> + </parameterMap> + + <parameterMap id="parameter_nsIdAndMinMaxNodeId" type="map"> + <parameter property="nsId" jdbcType="BIGINT" javaType="java.lang.Long"/> + <parameter property="minNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/> + <parameter property="maxNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/> + </parameterMap> + + <parameterMap id="parameter_mimetypeIdAndMinMaxNodeId" type="map"> + <parameter property="mimetypeId" jdbcType="BIGINT" javaType="java.lang.Long"/> + <parameter property="minNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/> + <parameter property="maxNodeId" jdbcType="BIGINT" javaType="java.lang.Long"/> + </parameterMap> + <!-- --> <!-- Selects --> <!-- --> @@ -469,6 +487,60 @@ <foreach item="item" index="i" collection="ids" open="(" separator="," close=")">#{item}</foreach> </select> + <!-- Find nodes by type qname --> + + <select id="select_NodesByTypeQName" parameterMap="parameter_qnameIdAndMinMaxNodeId" resultType="java.lang.Long" > + select + n.id + from + alf_node n + where + n.type_qname_id = #{qnameId} + and n.id >= #{minNodeId} + and n.id < #{maxNodeId} + </select> + + <select id="select_NodesByTypeUriId" parameterMap="parameter_nsIdAndMinMaxNodeId" resultType="java.lang.Long" > + select + n.id + from + alf_node n + join alf_qname q on q.id = n.type_qname_id + where + q.ns_id = #{nsId} + and n.id >= #{minNodeId} + and n.id < #{maxNodeId} + </select> + + <!-- Find nodes by aspect qname --> + + <select id="select_NodesByAspectQName" parameterMap="parameter_qnameIdAndMinMaxNodeId" resultType="java.lang.Long" > + select + n.id + from + alf_node n + join alf_node_aspects a on n.id = a.node_id + where + a.qname_id = #{qnameId} + and n.id >= #{minNodeId} + and n.id < #{maxNodeId} + </select> + + <!-- Find nodes by content mimetype --> + + <select id="select_NodesByContentMimetype" parameterMap="parameter_mimetypeIdAndMinMaxNodeId" resultType="long"> + select + np.node_id + from + alf_node_properties np + join alf_content_data cd on np.long_value = cd.id + where + cd.content_mimetype_id = #{mimetypeId} + and (np.actual_type_n = 3 or np.actual_type_n = 21) + and np.node_id >= #{minNodeId} + and np.node_id < #{maxNodeId} + </select> + <!-- --> <!-- Updates --> <!-- --> diff --git a/config/alfresco/patch/patch-services-context.xml b/config/alfresco/patch/patch-services-context.xml index 42a9a181c3..0e402d63d3 100644 --- a/config/alfresco/patch/patch-services-context.xml +++ b/config/alfresco/patch/patch-services-context.xml @@ -1070,11 +1070,14 @@ <ref bean="patch.updateDmPermissions" /> </list> </property> - <property name="importerBootstrap"> - <ref bean="spacesBootstrap" /> + <property name="patchDAO"> + <ref bean="patchDAO" /> </property> - <property name="indexerAndSearcher"> - <ref bean="indexerAndSearcherFactory" /> + <property name="nodeDAO"> + <ref bean="nodeDAO" /> + </property> + <property name="retryingTransactionHelper"> + <ref bean="retryingTransactionHelper" /> </property> <property name="qnameDAO"> <ref bean="qnameDAO" /> @@ -1777,11 +1780,14 @@ <property name="fixesFromSchema"><value>0</value></property> <property name="fixesToSchema"><value>4004</value></property> <property name="targetSchema"><value>4005</value></property> - <property name="importerBootstrap"> - <ref bean="spacesBootstrap" /> + <property name="patchDAO"> + <ref bean="patchDAO" /> </property> - <property name="indexerAndSearcher"> - <ref bean="indexerAndSearcherFactory" /> + <property name="nodeDAO"> + <ref bean="nodeDAO" /> + </property> + <property name="retryingTransactionHelper"> + <ref bean="retryingTransactionHelper" /> </property> <property name="qnameDAO"> <ref bean="qnameDAO" /> @@ -1920,8 +1926,11 @@ <property name="nodeService"> <ref bean="nodeService" /> </property> - <property name="indexerAndSearcher"> - <ref bean="indexerAndSearcherFactory"/> + <property name="nodeDAO"> + <ref bean="nodeDAO"/> + </property> + <property name="retryingTransactionHelper"> + <ref bean="retryingTransactionHelper"/> </property> <property name="mimetypeMappings"> <map> @@ -2301,8 +2310,11 @@ <property name="nodeService"> <ref bean="nodeService" /> </property> - <property name="indexerAndSearcher"> - <ref bean="indexerAndSearcherFactory"/> + <property name="nodeDAO"> + <ref bean="nodeDAO"/> + </property> + <property name="retryingTransactionHelper"> + <ref bean="retryingTransactionHelper"/> </property> <property name="mimetypeMappings"> <map> @@ -2847,8 +2859,11 @@ <property name="nodeService"> <ref bean="nodeService" /> </property> - <property name="indexerAndSearcher"> - <ref bean="indexerAndSearcherFactory"/> + <property name="nodeDAO"> + <ref bean="nodeDAO"/> + </property> + <property name="retryingTransactionHelper"> + <ref bean="retryingTransactionHelper"/> </property> <property name="mimetypeMappings"> <map> @@ -2906,8 +2921,11 @@ <property name="nodeService"> <ref bean="nodeService" /> </property> - <property name="indexerAndSearcher"> - <ref bean="indexerAndSearcherFactory"/> + <property name="nodeDAO"> + <ref bean="nodeDAO"/> + </property> + <property name="retryingTransactionHelper"> + <ref bean="retryingTransactionHelper"/> </property> <property name="mimetypeMappings"> <map> @@ -2942,8 +2960,11 @@ <property name="nodeService"> <ref bean="nodeService" /> </property> - <property name="indexerAndSearcher"> - <ref bean="indexerAndSearcherFactory"/> + <property name="nodeDAO"> + <ref bean="nodeDAO"/> + </property> + <property name="retryingTransactionHelper"> + <ref bean="retryingTransactionHelper"/> </property> <property name="mimetypeMappings"> <map> diff --git a/config/alfresco/public-services-security-context.xml b/config/alfresco/public-services-security-context.xml index de227ea339..f8c3a9089a 100644 --- a/config/alfresco/public-services-security-context.xml +++ b/config/alfresco/public-services-security-context.xml @@ -464,6 +464,7 @@ org.alfresco.service.cmr.repository.ContentService.getWriter=ACL_NODE.0.sys:base.WriteContent org.alfresco.service.cmr.repository.ContentService.isTransformable=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getTransformer=ACL_ALLOW + org.alfresco.service.cmr.repository.ContentService.getMaxSourceSizeBytes=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getImageTransformer=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.transform=ACL_ALLOW org.alfresco.service.cmr.repository.ContentService.getTempWriter=ACL_ALLOW diff --git a/config/alfresco/script-services-context.xml b/config/alfresco/script-services-context.xml index 3b49a31d5f..058ef44570 100644 --- a/config/alfresco/script-services-context.xml +++ b/config/alfresco/script-services-context.xml @@ -223,54 +223,19 @@ </property> </bean> - <bean id="usernamePropertyDecorator" class="org.alfresco.repo.jscript.app.UsernamePropertyDecorator"> - <property name="serviceRegistry"> - <ref bean="ServiceRegistry"/> - </property> - </bean> - <bean id="tagPropertyDecorator" class="org.alfresco.repo.jscript.app.TagPropertyDecorator"> - <property name="serviceRegistry"> - <ref bean="ServiceRegistry"/> - </property> - </bean> - <bean id="categoryPropertyDecorator" class="org.alfresco.repo.jscript.app.CategoryPropertyDecorator"> - <property name="serviceRegistry"> - <ref bean="ServiceRegistry"/> - </property> + <bean id="applicationScriptUtils" parent="baseJavaScriptExtension" class="org.alfresco.repo.jscript.ApplicationScriptUtils"> + <property name="extensionName" value="appUtils" /> + <property name="jsonConversionComponent" ref="jsonConversionComponent" /> </bean> - <bean id="applicationScriptUtils" parent="baseJavaScriptExtension" class="org.alfresco.repo.jscript.ApplicationScriptUtils"> - <property name="extensionName"> - <value>appUtils</value> - </property> - <property name="serviceRegistry"> - <ref bean="ServiceRegistry"/> - </property> - <property name="decoratedProperties"> - <map> - <entry key="cm:creator"> - <ref bean="usernamePropertyDecorator"/> - </entry> - <entry key="cm:modifier"> - <ref bean="usernamePropertyDecorator"/> - </entry> - <entry key="cm:workingCopyOwner"> - <ref bean="usernamePropertyDecorator"/> - </entry> - <entry key="cm:lockOwner"> - <ref bean="usernamePropertyDecorator"/> - </entry> - <entry key="cm:owner"> - <ref bean="usernamePropertyDecorator"/> - </entry> - <entry key="cm:taggable"> - <ref bean="tagPropertyDecorator"/> - </entry> - <entry key="cm:categories"> - <ref bean="categoryPropertyDecorator"/> - </entry> - </map> - </property> + <bean id="baseJsonConversionComponent" abstract="true"> + <property name="nodeService" ref="NodeService"/> + <property name="publicServiceAccessService" ref="PublicServiceAccessService" /> + <property name="namespaceService" ref="NamespaceService" /> + <property name="fileFolderService" ref="FileFolderService" /> + <property name="lockService" ref="LockService" /> + <property name="permissionService" ref="PermissionService" /> + <property name="contentService" ref="ContentService" /> <property name="userPermissions"> <list> <value>CancelCheckOut</value> @@ -279,7 +244,51 @@ <value>Delete</value> <value>Write</value> </list> - </property> + </property> + </bean> + + <bean id="jsonConversionComponent" class="org.alfresco.repo.jscript.app.JSONConversionComponent" parent="baseJsonConversionComponent"> + </bean> + + <bean id="baseDecorator" abstract="true" init-method="init"> + <property name="nodeService" ref="NodeService"/> + <property name="namespaceService" ref="NamespaceService"/> + <property name="permissionService" ref="PermissionService"/> + <property name="jsonConversionComponent" ref="jsonConversionComponent"/> + </bean> + + <bean id="usernamePropertiesDecorator" parent="baseDecorator" class="org.alfresco.repo.jscript.app.UsernamePropertyDecorator"> + <property name="personService" ref="PersonService" /> + <property name="propertyNames"> + <set> + <value>cm:creator</value> + <value>cm:modifier</value> + <value>cm:workingCopyOwner</value> + <value>cm:lockOwner</value> + <value>cm:owner</value> + </set> + </property> + </bean> + + <bean id="ignorePropertiesDecorator" parent="baseDecorator" class="org.alfresco.repo.jscript.app.IgnorePropertyDecorator"> + <property name="propertyNames"> + <set> + <value>cm:content</value> + <value>sys:locale</value> + <value>sys:node-uuid</value> + <value>sys:store-protocol</value> + <value>sys:node-dbid</value> + <value>sys:store-identifier</value> + </set> + </property> + </bean> + + <bean id="tagPropertyDecorator" parent="baseDecorator" class="org.alfresco.repo.jscript.app.TagPropertyDecorator"> + <property name="propertyName" value="cm:taggable" /> + </bean> + + <bean id="categoryPropertyDecorator" parent="baseDecorator" class="org.alfresco.repo.jscript.app.CategoryPropertyDecorator"> + <property name="propertyName" value="cm:categories" /> </bean> </beans> diff --git a/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java b/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java index 7a81b69a41..69841e3ec9 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/CalendarModelUriPatch.java @@ -19,91 +19,134 @@ package org.alfresco.repo.admin.patch.impl; +import java.util.List; + import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.patch.PatchDAO; 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.AbstractLuceneQueryParser; +import org.alfresco.repo.transaction.RetryingTransactionHelper; 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.util.Pair; import org.springframework.extensions.surf.util.I18NUtil; /** - * Patch usr:user and cm:person objects so that the user name properties are in the - * index in untokenized form. If not authentication may fail in mixed language use. + * Patch usr:user and cm:person objects so that the user name properties are in the index in untokenized form. If not + * authentication may fail in mixed language use. * * @author andyh - * */ public class CalendarModelUriPatch extends AbstractPatch { private static final String MSG_SUCCESS = "patch.calendarModelNamespacePatch.result"; - + private static final String URI_BEFORE = "com.infoaxon.alfresco.calendar"; - private static final String URI_AFTER = "http://www.alfresco.org/model/calendar"; - - private ImporterBootstrap importerBootstrap; - private IndexerAndSearcher indexerAndSearcher; + + private static final String URI_AFTER = "http://www.alfresco.org/model/calendar"; + private QNameDAO qnameDAO; + + private PatchDAO patchDAO; + + private NodeDAO nodeDAO; + + private RetryingTransactionHelper retryingTransactionHelper; + + private static long BATCH_SIZE = 100000L; + + - - public void setImporterBootstrap(ImporterBootstrap importerBootstrap) - { - this.importerBootstrap = importerBootstrap; - } - - public void setIndexerAndSearcher(IndexerAndSearcher indexerAndSearcher) - { - this.indexerAndSearcher = indexerAndSearcher; - } - + /** + * @param qnameDAO the qnameDAO to set + */ public void setQnameDAO(QNameDAO qnameDAO) { this.qnameDAO = qnameDAO; } + /** + * @param patchDAO the patchDAO to set + */ + public void setPatchDAO(PatchDAO patchDAO) + { + this.patchDAO = patchDAO; + } + + /** + * @param nodeDAO the nodeDAO to set + */ + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + /** + * @param retryingTransactionHelper the retryingTransactionHelper to set + */ + public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) + { + this.retryingTransactionHelper = retryingTransactionHelper; + } + + protected void checkProperties() + { + super.checkProperties(); + checkPropertyNotNull(patchDAO, "patchDAO"); + checkPropertyNotNull(qnameDAO, "qnameDAO"); + checkPropertyNotNull(nodeDAO, "nodeDAO"); + checkPropertyNotNull(retryingTransactionHelper, "retryingTransactionHelper"); + } + @Override protected String applyInternal() throws Exception { + Long maxNodeId = patchDAO.getMaxAdmNodeID(); + long count = 0L; + // Make sure the old name spaces exists before we update it ... - qnameDAO.getOrCreateNamespace(URI_BEFORE); + Pair<Long, String> before = qnameDAO.getOrCreateNamespace(URI_BEFORE); + for (Long i = 0L; i < maxNodeId; i+=BATCH_SIZE) + { + Work work = new Work(before.getFirst(), i); + count += retryingTransactionHelper.doInTransaction(work, false, true); + } + // modify namespace for all calendar entries qnameDAO.updateNamespace(URI_BEFORE, URI_AFTER); - - // reindex the calendar entries - int count = reindex("TYPE:\\{" + AbstractLuceneQueryParser.escape(URI_BEFORE) + "\\}*", importerBootstrap.getStoreRef()); + return I18NUtil.getMessage(MSG_SUCCESS, count); } - - private int reindex(String query, StoreRef store) + + private class Work implements RetryingTransactionHelper.RetryingTransactionCallback<Integer> { - 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 + long nsId; + + long lower; + + Work(long nsId, long lower) { - rs = searchService.query(sp); - count = rs.length(); - for (ResultSetRow row : rs) - { - indexer.updateNode(row.getNodeRef()); - } + this.nsId = nsId; + this.lower = lower; } - finally + + /* + * (non-Javadoc) + * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute() + */ + @Override + public Integer execute() throws Throwable { - if (rs != null) - { - rs.close(); - } + List<Long> nodeIds = patchDAO.getNodesByTypeUriId(nsId, lower, lower + BATCH_SIZE); + nodeDAO.touchNodes(nodeDAO.getCurrentTransactionId(true), nodeIds); + return nodeIds.size(); } - return count; } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/GenericMimetypeRenamePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/GenericMimetypeRenamePatch.java index e71bc4188c..d6b50dc38a 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/GenericMimetypeRenamePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/GenericMimetypeRenamePatch.java @@ -18,23 +18,15 @@ */ package org.alfresco.repo.admin.patch.impl; -import java.util.HashSet; import java.util.List; import java.util.Map; -import java.util.Set; -import org.alfresco.model.ContentModel; import org.alfresco.repo.admin.patch.AbstractPatch; import org.alfresco.repo.domain.mimetype.MimetypeDAO; +import org.alfresco.repo.domain.node.NodeDAO; import org.alfresco.repo.domain.patch.PatchDAO; -import org.alfresco.repo.search.Indexer; -import org.alfresco.repo.search.IndexerAndSearcher; -import org.alfresco.repo.search.impl.lucene.AbstractLuceneQueryParser; -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.repo.domain.qname.QNameDAO; +import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.util.Pair; import org.springframework.extensions.surf.util.I18NUtil; @@ -57,18 +49,21 @@ public class GenericMimetypeRenamePatch extends AbstractPatch private static final String MSG_DONE_REINDEX = "patch.genericMimetypeUpdate.doneReindex"; /* Helper DAOs */ - private IndexerAndSearcher indexerAndSearcher; + private MimetypeDAO mimetypeDAO; + private PatchDAO patchDAO; - + + private NodeDAO nodeDAO; + + private RetryingTransactionHelper retryingTransactionHelper; + + private static long BATCH_SIZE = 100000L; + + /** Mimetype mappings */ private Map<String, String> mimetypeMappings; private boolean reindex; - - public void setIndexerAndSearcher(IndexerAndSearcher indexerAndSearcher) - { - this.indexerAndSearcher = indexerAndSearcher; - } public void setMimetypeDAO(MimetypeDAO mimetypeDAO) { @@ -90,36 +85,46 @@ public class GenericMimetypeRenamePatch extends AbstractPatch this.reindex = reindex; } + + + /** + * @param nodeDAO the nodeDAO to set + */ + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + /** + * @param retryingTransactionHelper the retryingTransactionHelper to set + */ + public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) + { + this.retryingTransactionHelper = retryingTransactionHelper; + } + protected void checkProperties() { super.checkProperties(); - checkPropertyNotNull(indexerAndSearcher, "indexerAndSearcher"); checkPropertyNotNull(mimetypeDAO, "mimetypeDAO"); checkPropertyNotNull(patchDAO, "patchDAO"); checkPropertyNotNull(mimetypeMappings, "mimetypeMappings"); + checkPropertyNotNull(nodeDAO, "nodeDAO"); + checkPropertyNotNull(retryingTransactionHelper, "retryingTransactionHelper"); } @Override protected String applyInternal() throws Exception { - // First get all the available stores that we might want to reindex - List<StoreRef> storeRefsList = nodeService.getStores(); - Set<StoreRef> storeRefs = new HashSet<StoreRef>(); - for (StoreRef storeRef : storeRefsList) - { - // We want workspace://SpacesStore or related MT stores - if (storeRef.getIdentifier().endsWith("SpacesStore")) - { - storeRefs.add(storeRef); - } - } - StringBuilder result = new StringBuilder(I18NUtil.getMessage(MSG_START)); + + Long maxNodeId = patchDAO.getMaxAdmNodeID(); + for (Map.Entry<String, String> element : mimetypeMappings.entrySet()) { String oldMimetype = element.getKey(); String newMimetype = element.getValue(); - + // First check if the mimetype is used at all Pair<Long, String> oldMimetypePair = mimetypeDAO.getMimetype(oldMimetype); if (oldMimetypePair == null) @@ -127,7 +132,20 @@ public class GenericMimetypeRenamePatch extends AbstractPatch // Not used continue; } - + + // pull all affectsed nodes into a new transaction id indexed + + if(reindex) + { + long count = 0L; + for (Long i = 0L; i < maxNodeId; i+=BATCH_SIZE) + { + Work work = new Work(oldMimetypePair.getFirst(), i); + count += retryingTransactionHelper.doInTransaction(work, false, true); + } + result.append(I18NUtil.getMessage(MSG_INDEXED, count, "(All stores)")); + } + // Check if the new mimetype exists Pair<Long, String> newMimetypePair = mimetypeDAO.getMimetype(newMimetype); int updateCount = 0; @@ -144,16 +162,6 @@ public class GenericMimetypeRenamePatch extends AbstractPatch updateCount = patchDAO.updateContentMimetypeIds(oldMimetypeId, newMimetypeId); } result.append(I18NUtil.getMessage(MSG_UPDATED, updateCount, oldMimetype, newMimetype)); - if (reindex) - { - // Update Lucene - int reindexCount = 0; - for (StoreRef storeRef : storeRefs) - { - reindexCount += reindex(oldMimetype, storeRef); - result.append(I18NUtil.getMessage(MSG_INDEXED, reindexCount, storeRef)); - } - } } // Done if (reindex) @@ -167,33 +175,30 @@ public class GenericMimetypeRenamePatch extends AbstractPatch return result.toString(); } - - private int reindex(String oldMimetype, StoreRef store) + + + private class Work implements RetryingTransactionHelper.RetryingTransactionCallback<Integer> { - SearchParameters sp = new SearchParameters(); - sp.setLanguage(SearchService.LANGUAGE_LUCENE); - sp.setQuery("@" + AbstractLuceneQueryParser.escape(ContentModel.PROP_CONTENT.toString()) + - ".mimetype:\"" + oldMimetype + "\""); - sp.addStore(store); - Indexer indexer = indexerAndSearcher.getIndexer(store); - ResultSet rs = null; - int count = 0; - try + long mimetypeId; + + long lower; + + Work(long mimetypeId, long lower) { - rs = searchService.query(sp); - count = rs.length(); - for (ResultSetRow row : rs) - { - indexer.updateNode(row.getNodeRef()); - } + this.mimetypeId = mimetypeId; + this.lower = lower; } - finally + + /* + * (non-Javadoc) + * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute() + */ + @Override + public Integer execute() throws Throwable { - if (rs != null) - { - rs.close(); - } + List<Long> nodeIds = patchDAO.getNodesByContentPropertyMimetypeId(mimetypeId, lower, lower + BATCH_SIZE); + nodeDAO.touchNodes(nodeDAO.getCurrentTransactionId(true), nodeIds); + return nodeIds.size(); } - return count; } } diff --git a/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java b/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java index 5b1cf84679..bbf8ef5a55 100644 --- a/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java +++ b/source/java/org/alfresco/repo/admin/patch/impl/QNamePatch.java @@ -19,18 +19,15 @@ package org.alfresco.repo.admin.patch.impl; +import java.util.List; + import org.alfresco.repo.admin.patch.AbstractPatch; +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.domain.patch.PatchDAO; 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.AbstractLuceneQueryParser; -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.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.namespace.QName; +import org.alfresco.util.Pair; import org.springframework.extensions.surf.util.I18NUtil; /** @@ -56,38 +53,50 @@ public class QNamePatch extends AbstractPatch 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; - } + private PatchDAO patchDAO; + + private NodeDAO nodeDAO; + + private RetryingTransactionHelper retryingTransactionHelper; + + private static long BATCH_SIZE = 100000L; + + /** - * Sets the IndexerAndSearcher. - * @param indexerAndSearcher - */ - public void setIndexerAndSearcher(IndexerAndSearcher indexerAndSearcher) - { - this.indexerAndSearcher = indexerAndSearcher; - } - - /** - * Sets the QNameDAO. - * @param qnameDAO + * @param qnameDAO the qnameDAO to set */ public void setQnameDAO(QNameDAO qnameDAO) { this.qnameDAO = qnameDAO; } + /** + * @param patchDAO the patchDAO to set + */ + public void setPatchDAO(PatchDAO patchDAO) + { + this.patchDAO = patchDAO; + } + + /** + * @param nodeDAO the nodeDAO to set + */ + public void setNodeDAO(NodeDAO nodeDAO) + { + this.nodeDAO = nodeDAO; + } + + /** + * @param retryingTransactionHelper the retryingTransactionHelper to set + */ + public void setRetryingTransactionHelper(RetryingTransactionHelper retryingTransactionHelper) + { + this.retryingTransactionHelper = retryingTransactionHelper; + } + /** * Sets the QName to be patched. * @param qnameStringBefore the long-form QName to be patched from. {namespaceURI}localName @@ -116,6 +125,17 @@ public class QNamePatch extends AbstractPatch this.reindexClass = reindexClass; } + protected void checkProperties() + { + super.checkProperties(); + checkPropertyNotNull(patchDAO, "patchDAO"); + checkPropertyNotNull(qnameDAO, "qnameDAO"); + checkPropertyNotNull(nodeDAO, "nodeDAO"); + checkPropertyNotNull(retryingTransactionHelper, "retryingTransactionHelper"); + checkPropertyNotNull(qnameStringAfter, "qnameStringAfter"); + checkPropertyNotNull(qnameStringBefore, "qnameStringBefore"); + } + @Override protected String applyInternal() throws Exception { @@ -124,46 +144,61 @@ public class QNamePatch extends AbstractPatch QName qnameBefore = QName.createQName(this.qnameStringBefore); QName qnameAfter = QName.createQName(this.qnameStringAfter); - if (qnameDAO.getQName(qnameBefore) != null) + Long maxNodeId = patchDAO.getMaxAdmNodeID(); + + Pair<Long, QName> before = qnameDAO.getQName(qnameBefore); + + for (Long i = 0L; i < maxNodeId; i+=BATCH_SIZE) + { + Work work = new Work(before.getFirst(), i); + retryingTransactionHelper.doInTransaction(work, false, true); + } + + if (before != null) { qnameDAO.updateQName(qnameBefore, qnameAfter); } - - // Optionally perform a focussed reindexing of the removed QName. - if ("TYPE".equals(reindexClass) || - "ASPECT".equals(reindexClass)) - { - reindex(reindexClass + ":" + AbstractLuceneQueryParser.escape(qnameStringBefore), importerBootstrap.getStoreRef()); - } - + return I18NUtil.getMessage(MSG_SUCCESS, qnameBefore, qnameAfter); } - private int reindex(String query, StoreRef store) + private class Work implements RetryingTransactionHelper.RetryingTransactionCallback<Integer> { - 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 + long qnameId; + + long lower; + + Work(long qnameId, long lower) { - rs = searchService.query(sp); - count = rs.length(); - for (ResultSetRow row : rs) - { - indexer.updateNode(row.getNodeRef()); - } + this.qnameId = qnameId; + this.lower = lower; } - finally + + /* + * (non-Javadoc) + * @see org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback#execute() + */ + @Override + public Integer execute() throws Throwable { - if (rs != null) + if ("TYPE".equals(reindexClass)) { - rs.close(); + List<Long> nodeIds = patchDAO.getNodesByTypeQNameId(qnameId, lower, lower + BATCH_SIZE); + nodeDAO.touchNodes(nodeDAO.getCurrentTransactionId(true), nodeIds); + return nodeIds.size(); } + else if ("ASPECT".equals(reindexClass)) + { + List<Long> nodeIds = patchDAO.getNodesByAspectQNameId(qnameId, lower, lower + BATCH_SIZE); + nodeDAO.touchNodes(nodeDAO.getCurrentTransactionId(true), nodeIds); + return nodeIds.size(); + } + else + { + // nothing to do + return 0; + } + } - return count; } } diff --git a/source/java/org/alfresco/repo/content/ContentServiceImpl.java b/source/java/org/alfresco/repo/content/ContentServiceImpl.java index 1114435fbf..29a1f1e5b4 100644 --- a/source/java/org/alfresco/repo/content/ContentServiceImpl.java +++ b/source/java/org/alfresco/repo/content/ContentServiceImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -33,6 +33,7 @@ import org.alfresco.repo.content.ContentServicePolicies.OnContentReadPolicy; import org.alfresco.repo.content.ContentServicePolicies.OnContentUpdatePolicy; import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner; import org.alfresco.repo.content.filestore.FileContentStore; +import org.alfresco.repo.content.transform.AbstractContentTransformerLimits; import org.alfresco.repo.content.transform.ContentTransformer; import org.alfresco.repo.content.transform.ContentTransformerRegistry; import org.alfresco.repo.content.transform.TransformerDebug; @@ -40,6 +41,7 @@ import org.alfresco.repo.node.NodeServicePolicies; import org.alfresco.repo.policy.ClassPolicyDelegate; import org.alfresco.repo.policy.JavaBehaviour; import org.alfresco.repo.policy.PolicyComponent; +import org.alfresco.repo.thumbnail.ThumbnailDefinition; import org.alfresco.repo.transaction.RetryingTransactionHelper; import org.alfresco.service.cmr.avm.AVMService; import org.alfresco.service.cmr.dictionary.DataTypeDefinition; @@ -628,6 +630,45 @@ public class ContentServiceImpl implements ContentService, ApplicationContextAwa } } + /** + * {@inheritDoc} + */ + public long getMaxSourceSizeBytes(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + try + { + long maxSourceSize = 0; + transformerDebug.pushAvailable(null, sourceMimetype, targetMimetype); + List<ContentTransformer> transformers = getActiveTransformers(sourceMimetype, 0, targetMimetype, options); + for (ContentTransformer transformer: transformers) + { + long maxSourceSizeKBytes = transformer.getMaxSourceSizeKBytes(sourceMimetype, targetMimetype, options); + if (maxSourceSize >= 0) + { + if (maxSourceSizeKBytes < 0) + { + maxSourceSize = -1; + } + else if (maxSourceSizeKBytes > 0 && maxSourceSize < maxSourceSizeKBytes) + { + maxSourceSize = maxSourceSizeKBytes; + } + } + // if maxSourceSizeKBytes == 0 this implies the transformation is disabled + } + if (transformerDebug.isEnabled()) + { + transformerDebug.availableTransformers(transformers, -1, + "ContentService.getMaxSourceSizeBytes() = "+transformerDebug.fileSize(maxSourceSize*1024)); + } + return (maxSourceSize > 0) ? maxSourceSize * 1024 : maxSourceSize; + } + finally + { + transformerDebug.popAvailable(); + } + } + public List<ContentTransformer> getActiveTransformers(String sourceMimetype, String targetMimetype, TransformationOptions options) { return getActiveTransformers(sourceMimetype, -1, targetMimetype, options); diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java index fbd6e4a0b8..96f65effae 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -208,7 +208,9 @@ public abstract class AbstractContentTransformer implements ContentTransformer @Override public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { - return isTransformable(sourceMimetype, targetMimetype, options); + return + isTransformableMimetype(sourceMimetype, targetMimetype, options) && + isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); } /** @@ -228,6 +230,36 @@ public abstract class AbstractContentTransformer implements ContentTransformer return result; } + /** + * Checks the supplied mimetypes are supported by calling the deprecated + * {@link #isTransformable(String, String, TransformationOptions)} method. + */ + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + return isTransformable(sourceMimetype, targetMimetype, options); + } + + /** + * Always returns {@code true} to indicate size is not an issue. + */ + @Override + public boolean isTransformableSize(String sourceMimetype, long sourceSize, + String targetMimetype, TransformationOptions options) + { + return true; + } + + /** + * Always returns {@code -1} to indicate an unlimited size. + */ + @Override + public long getMaxSourceSizeKBytes(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return -1; + } + /** * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) */ diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java index 78af71c77e..2334a6657e 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimits.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -84,6 +84,11 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme this.transformerDebug = transformerDebug; } + public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + throw new IllegalStateException("Method should no longer be called. Override isTransformableMimetype in subclass."); + } + /** * {@inheritDoc}<p> * @@ -91,14 +96,24 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme * and then {@link #isTransformableSize(String, long, String, TransformationOptions)}. */ @Override - @SuppressWarnings("deprecation") public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { return - isTransformable(sourceMimetype, targetMimetype, options) && + isTransformableMimetype(sourceMimetype, targetMimetype, options) && isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); } + /** + * Indicates if this transformer is able to transform the given source mimetype + * to the target mimetype. + */ + @Override + @SuppressWarnings("deprecation") + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return isTransformable(sourceMimetype, targetMimetype, options); + } + /** * Indicates if this transformer is able to transform the given {@code sourceSize}. * The {@code maxSourceSizeKBytes} property may indicate that only small source files @@ -106,31 +121,45 @@ public abstract class AbstractContentTransformerLimits extends ContentTransforme * @param sourceSize size in bytes of the source. If negative, the source size is unknown. * @return {@code true} if the source is transformable. */ - protected boolean isTransformableSize(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableSize(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { boolean sizeOkay = true; if (sourceSize >= 0) { - TransformationOptionLimits limits = getLimits(sourceMimetype, targetMimetype, options); - - // The maxSourceSizeKbytes value is ignored if this transformer is able to use - // page limits and the limits include a pageLimit. Normally used in the creation - // of icons. Note the readLimitKBytes value is not checked as the combined limits - // only have the max or limit kbytes value set (the smaller value is returned). - if (!isPageLimitSupported() || limits.getPageLimit() <= 0) + // if maxSourceSizeKBytes == 0 this implies the transformation is disabled + long maxSourceSizeKBytes = getMaxSourceSizeKBytes(sourceMimetype, targetMimetype, options); + sizeOkay = maxSourceSizeKBytes < 0 || (maxSourceSizeKBytes > 0 && sourceSize <= maxSourceSizeKBytes*1024); + if (!sizeOkay && transformerDebug.isEnabled()) { - // if maxSourceSizeKBytes == 0 this implies the transformation is disabled - long maxSourceSizeKBytes = limits.getMaxSourceSizeKBytes(); - sizeOkay = maxSourceSizeKBytes < 0 || (maxSourceSizeKBytes > 0 && sourceSize <= maxSourceSizeKBytes*1024); - if (!sizeOkay && transformerDebug.isEnabled()) - { - transformerDebug.unavailableTransformer(this, maxSourceSizeKBytes); - } + transformerDebug.unavailableTransformer(this, maxSourceSizeKBytes); } } return sizeOkay; } + /** + * Returns the maximum source size (in KBytes) allowed given the supplied values. + * @return 0 if the the transformation is disabled, -1 if there is no limit, otherwise the size in KBytes. + */ + @Override + public long getMaxSourceSizeKBytes(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + long maxSourceSizeKBytes = -1; + + // The maxSourceSizeKbytes value is ignored if this transformer is able to use + // page limits and the limits include a pageLimit. Normally used in the creation + // of icons. Note the readLimitKBytes value is not checked as the combined limits + // only have the max or limit KBytes value set (the smaller value is returned). + TransformationOptionLimits limits = getLimits(sourceMimetype, targetMimetype, options); + if (!isPageLimitSupported() || limits.getPageLimit() <= 0) + { + maxSourceSizeKBytes = limits.getMaxSourceSizeKBytes(); + } + + return maxSourceSizeKBytes; + } + /** * Gets the timeout (ms) on the InputStream after which an IOExecption is thrown * to terminate very slow transformations or a subprocess is terminated (killed). diff --git a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java index 0879af6dc0..1d5ce5d665 100644 --- a/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java +++ b/source/java/org/alfresco/repo/content/transform/AbstractContentTransformerLimitsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -63,7 +63,7 @@ public class AbstractContentTransformerLimitsTest transformer = new AbstractContentTransformer2() { @Override - public boolean isTransformable(String sourceMimetype, String targetMimetype, + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { return false; @@ -214,6 +214,70 @@ public class AbstractContentTransformerLimitsTest assertEquals("Getter did not return set value", value, actual); } + @Test + public void testGetMaxSourceSizeKBytes() throws Exception + { + long kValue = 12; + long byteValue = kValue*1024; + + // Not set mimetype limits yet + assertTrue("No limits so should have been ok", + transformer.isTransformableSize(A, byteValue+1, B, options)); + + // Set limit for A to B mimetypes and test + limits.setMaxSourceSizeKBytes(kValue); + addMimetypeLimits(A, B, limits); + transformer.setMimetypeLimits(mimetypeLimits); + + assertEquals("Expected to have set value returned", kValue, + transformer.getMaxSourceSizeKBytes(A, B, options)); + + // With a mimetype that does not have any specific limits + assertEquals("Expected to have -1 (unlimited) returned", -1, + transformer.getMaxSourceSizeKBytes(C, B, options)); + + + // Clear the mimetype limits and double check + limits.setMaxSourceSizeKBytes(-1); + + assertEquals("Expected to have -1 (unlimited) returned", -1, + transformer.getMaxSourceSizeKBytes(A, B, options)); + + // Check for combinations with transformer limits + + // a) Using just transformer limit to start with + transformer.setMaxSourceSizeKBytes(kValue); + assertEquals("Expected to have transformer set value returned", kValue, + transformer.getMaxSourceSizeKBytes(A, B, options)); + + // b) combination where transformer limit is used + transformer.setMaxSourceSizeKBytes(kValue); + limits.setMaxSourceSizeKBytes(kValue+1); + assertEquals("Expected to have transformer set value returned", kValue, + transformer.getMaxSourceSizeKBytes(A, B, options)); + + // c) combination where mimetype limit is used + transformer.setMaxSourceSizeKBytes(kValue+1); + limits.setMaxSourceSizeKBytes(kValue); + assertEquals("Expected to have transformer set value returned", kValue, + transformer.getMaxSourceSizeKBytes(A, B, options)); + + // Check no limit when page limit set on a transformer that does not support page limit + transformer.setMaxSourceSizeKBytes(kValue); + limits.setMaxSourceSizeKBytes(kValue+1); + limits.setPageLimit(1); + assertEquals("Expected to ignore the page limit as the transformer does not support it", kValue, + transformer.getMaxSourceSizeKBytes(A, B, options)); + + // Check no limit when page limit set on a transformer that does support page limit + transformer.setMaxSourceSizeKBytes(kValue); + limits.setMaxSourceSizeKBytes(kValue+1); + transformer.setPageLimitsSuported(true); + limits.setPageLimit(1); + assertEquals("Expected to have -1 (unlimited) returned when there are page limits", -1, + transformer.getMaxSourceSizeKBytes(A, B, options)); + } + @Test public void testIsTransformableSize() throws Exception { @@ -260,16 +324,16 @@ public class AbstractContentTransformerLimitsTest transformer.isTransformableSize(A, byteValue+1, B, options)); // b) combination where transformer limit is used - transformer.setMaxSourceSizeKBytes(kValue+1); - limits.setMaxSourceSizeKBytes(kValue); + transformer.setMaxSourceSizeKBytes(kValue); + limits.setMaxSourceSizeKBytes(kValue+1); assertTrue("Size is equal to limit so should have been ok", transformer.isTransformableSize(A, byteValue, B, options)); assertFalse("Size is greater than limit so should not have failed", transformer.isTransformableSize(A, byteValue+1, B, options)); // c) combination where mimetype limit is used - transformer.setMaxSourceSizeKBytes(kValue); - limits.setMaxSourceSizeKBytes(kValue+1); + transformer.setMaxSourceSizeKBytes(kValue+1); + limits.setMaxSourceSizeKBytes(kValue); assertTrue("Size is equal to limit so should have been ok", transformer.isTransformableSize(A, byteValue, B, options)); assertFalse("Size is greater than limit so should not have failed", diff --git a/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java b/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java index af5cae065c..31bfba5a93 100644 --- a/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/BinaryPassThroughContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -50,7 +50,8 @@ public class BinaryPassThroughContentTransformer extends AbstractContentTransfor } - public boolean isTransformable(String sourceMimetype, + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if (sourceMimetype.startsWith(StringExtractingContentTransformer.PREFIX_TEXT)) diff --git a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java index 7063739cd6..7b38d4af1e 100644 --- a/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ComplexContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -33,6 +33,7 @@ import org.alfresco.error.AlfrescoRuntimeException; import org.alfresco.repo.content.filestore.FileContentWriter; import org.alfresco.service.cmr.repository.ContentReader; import org.alfresco.service.cmr.repository.ContentWriter; +import org.alfresco.service.cmr.repository.TransformationOptionLimits; import org.alfresco.service.cmr.repository.TransformationOptions; import org.alfresco.service.cmr.repository.datatype.DefaultTypeConverter; import org.alfresco.util.TempFileProvider; @@ -126,15 +127,32 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple } /** - * Check we can transform all the way along the chain of mimetypes - * - * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) + * Overrides this method to avoid calling + * {@link #isTransformableMimetype(String, String, TransformationOptions)} + * twice on each transformer in the list, as + * {@link #isTransformableSize(String, long, String, TransformationOptions)} + * in this class must check the mimetype too. */ - public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, + TransformationOptions options) { - boolean result = true; - String currentSourceMimetype = sourceMimetype; + overrideTransformationOptions(options); + // To make TransformerDebug output clearer, check the mimetypes and then the sizes. + // If not done, 'unavailable' transformers due to size might be reported even + // though they cannot transform the source to the target mimetype. + + return + isTransformableMimetype(sourceMimetype, targetMimetype, options) && + isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); + } + + /** + * Sets any transformation option overrides it can. + */ + private void overrideTransformationOptions(TransformationOptions options) + { // Set any transformation options overrides if we can if(options != null && transformationOptionOverrides != null) { @@ -177,8 +195,28 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple } } } + } + + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return isTransformableMimetypeAndSize(sourceMimetype, -1, targetMimetype, options); + } + + @Override + public boolean isTransformableSize(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + return (sourceSize < 0) || + super.isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options) && + isTransformableMimetypeAndSize(sourceMimetype, sourceSize, targetMimetype, options); + } + + private boolean isTransformableMimetypeAndSize(String sourceMimetype, long sourceSize, + String targetMimetype, TransformationOptions options) + { + boolean result = true; + String currentSourceMimetype = sourceMimetype; - boolean first = true; Iterator<ContentTransformer> transformerIterator = transformers.iterator(); Iterator<String> intermediateMimetypeIterator = intermediateMimetypes.iterator(); while (transformerIterator.hasNext()) @@ -195,28 +233,78 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple // use an intermediate transformation mimetype currentTargetMimetype = intermediateMimetypeIterator.next(); } - - // check we can tranform the current stage (using -1 if not the first stage as we can't know the size) - long size = first ? sourceSize : -1; - if (transformer.isTransformable(currentSourceMimetype, size, currentTargetMimetype, options) == false) + + if (sourceSize < 0) { - result = false; - break; + // check we can transform the current stage's mimetypes + if (transformer.isTransformableMimetype(currentSourceMimetype, currentTargetMimetype, options) == false) + { + result = false; + break; + } + } + else + { + // check we can transform the current stage's sizes + try + { + transformerDebug.pushIsTransformableSize(this); + // (using -1 if not the first stage as we can't know the size) + if (transformer.isTransformableSize(currentSourceMimetype, sourceSize, currentTargetMimetype, options) == false) + { + result = false; + break; + } + + // As the size is unknown for the next stages stop. + // In future we might guess sizes such as excl to pdf + // is about 110% of the original size, in which case + // we would continue. + break; + // sourceSize += sourceSize * 10 / 100; + } + finally + { + transformerDebug.popIsTransformableSize(); + } } // move on currentSourceMimetype = currentTargetMimetype; - first = false; - } - - if (result && !isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options)) - { - result = false; } return result; } + /** + * Indicates if 'page' limits are supported by the first transformer in the chain. + * @return true if the first transformer supports them. + */ + protected boolean isPageLimitSupported() + { + ContentTransformer firstTransformer = transformers.iterator().next(); + return (firstTransformer instanceof AbstractContentTransformerLimits) + ? ((AbstractContentTransformerLimits)firstTransformer).isPageLimitSupported() + : false; + } + + /** + * Returns the limits from this transformer combined with those of the first transformer in the chain. + */ + protected TransformationOptionLimits getLimits(String sourceMimetype, String targetMimetype, + TransformationOptions options) + { + TransformationOptionLimits limits = super.getLimits(sourceMimetype, targetMimetype, options); + ContentTransformer firstTransformer = transformers.get(0); + if (firstTransformer instanceof AbstractContentTransformerLimits) + { + String firstTargetMimetype = intermediateMimetypes.get(0); + limits = limits.combine(((AbstractContentTransformerLimits) firstTransformer). + getLimits(sourceMimetype, firstTargetMimetype, options)); + } + return limits; + } + /** * @see org.alfresco.repo.content.transform.AbstractContentTransformer2#transformInternal(org.alfresco.service.cmr.repository.ContentReader, org.alfresco.service.cmr.repository.ContentWriter, org.alfresco.service.cmr.repository.TransformationOptions) */ @@ -263,15 +351,4 @@ public class ComplexContentTransformer extends AbstractContentTransformer2 imple { return Collections.unmodifiableList(intermediateMimetypes); } - - /** - * @deprecated This method should no longer be called as the overloaded method - * that calls it has the overridden. - */ - @Override - public boolean isTransformable(String sourceMimetype, String targetMimetype, - TransformationOptions options) - { - return false; - } } diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ContentTransformer.java index f644ba6aef..e683b88dbf 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -44,12 +44,39 @@ public interface ContentTransformer extends ContentWorker * * @param sourceMimetype the source mimetype * @param sourceSize the size (bytes) of the source. If negative it is unknown. - * @param targetMimetype the target mimetype + * @param targetMimetype the target mimetype * @param options the transformation options * @return boolean true if this content transformer can satify the mimetypes and options specified, false otherwise */ public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options); + /** + * Sub component of {@link #isTransformable(String, long, String, TransformationOptions) + * that checks just the mimetypes. + * @param sourceMimetype the source mimetype + * @param targetMimetype the target mimetype + * @param options the transformation options + * @return boolean true if this content transformer can satify the mimetypes, false otherwise + */ + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options); + + /** + * Sub component of {@link #isTransformable(String, long, String, TransformationOptions) + * that checks just the size limits. + * @param sourceMimetype the source mimetype + * @param sourceSize the size (bytes) of the source. If negative it is unknown. + * @param targetMimetype the target mimetype + * @param options the transformation options + * @return boolean true if this content transformer can satify the mimetypes, false otherwise + */ + public boolean isTransformableSize(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options); + + /** + * Returns the maximum source size (in KBytes) allowed given the supplied values. + * @return 0 if the the transformation is disabled, -1 if there is no limit, otherwise the size in KBytes. + */ + public long getMaxSourceSizeKBytes(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** * Indicates whether given the provided transformation parmaters this transformer can prvide an explict * transformation. diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java index c1206755db..b3b91eef97 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -111,6 +111,7 @@ public class ContentTransformerRegistry for (ContentTransformer transformer : transformers) { // Transformability can be dynamic, i.e. it may have become unusable + // Don't know why we do this test as it has already been done by findTransformers(...) if (transformer.isTransformable(sourceMimetype, sourceSize, targetMimetype, options) == false) { // It is unreliable now. diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java index ea4fb31bf1..05a4de2942 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerRegistryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -236,7 +236,7 @@ public class ContentTransformerRegistryTest extends AbstractContentTransformerTe disable = true; } - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if (disable) { return false; diff --git a/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java b/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java index ff1e445684..ef3e43f7b7 100644 --- a/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java +++ b/source/java/org/alfresco/repo/content/transform/ContentTransformerWorker.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -28,6 +28,7 @@ import org.alfresco.service.cmr.repository.TransformationOptions; * * @author dward */ +// TODO Modify ContentTransformerWorker to understand transformer limits. At the moment no workers use them public interface ContentTransformerWorker { /** diff --git a/source/java/org/alfresco/repo/content/transform/EMLTransformer.java b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java index 0edee16c5e..d10ae47912 100644 --- a/source/java/org/alfresco/repo/content/transform/EMLTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/EMLTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -46,7 +46,8 @@ import org.alfresco.service.cmr.repository.TransformationOptions; */ public class EMLTransformer extends AbstractContentTransformer2 { - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if (!MimetypeMap.MIMETYPE_RFC822.equals(sourceMimetype) || !MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype)) { diff --git a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java index ac6ada5789..290aed391f 100644 --- a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -79,37 +79,65 @@ public class FailoverContentTransformer extends AbstractContentTransformer2 impl } /** - * - * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, long sourceSize, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) - */ - public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) - { - // For this transformer to be considered operational, there must be at least one transformer - // in the chain that can perform for us. - boolean result = false; - - for (ContentTransformer ct : this.transformers) - { - if (ct.isTransformable(sourceMimetype, sourceSize, targetMimetype, options)) - { - // There may be size limits on this transformer as well as those it contains. - result = isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); - break; - } - } - - return result; - } - - /** - * @deprecated This method should no longer be called as the overloaded method - * that calls it has the overridden. + * Overrides super class method to avoid calling + * {@link #isTransformableMimetype(String, String, TransformationOptions)} + * twice on each transformer in the list, as + * {@link #isTransformableSize(String, long, String, TransformationOptions)} + * in this class must check the mimetype too. */ @Override - public boolean isTransformable(String sourceMimetype, String targetMimetype, - TransformationOptions options) + public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) { - return false; + return isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); + } + + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) + { + return isTransformableMimetypeAndSize(sourceMimetype, -1, targetMimetype, options); + } + + @Override + public boolean isTransformableSize(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + { + return (sourceSize < 0) || isTransformableMimetypeAndSize(sourceMimetype, sourceSize, targetMimetype, options); + } + + private boolean isTransformableMimetypeAndSize(String sourceMimetype, long sourceSize, + String targetMimetype, TransformationOptions options) + { + boolean result = false; + if (super.isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options)) + { + for (ContentTransformer ct : this.transformers) + { + transformerDebug.pushIsTransformableSize(this); + if (ct.isTransformableMimetype(sourceMimetype, targetMimetype, options)) + { + if (sourceSize == -1) + { + result = true; + break; + } + else + { + try + { + if (ct.isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options)) + { + result = true; + break; + } + } + finally + { + transformerDebug.popIsTransformableSize(); + } + } + } + } + } + return result; } public boolean isExplicitTransformation(String sourceMimetype, String targetMimetype, TransformationOptions options) diff --git a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java index f0faf92015..f55e64aaea 100644 --- a/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java +++ b/source/java/org/alfresco/repo/content/transform/FailoverContentTransformerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -118,7 +118,8 @@ class DummyTestContentTransformer extends AbstractContentTransformer2 implements } } - public boolean isTransformable(String sourceMimetype, + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { // We'll arbitrarily claim to be able to transform PDF to PNG diff --git a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java index deeda64c1e..6685b2ba96 100644 --- a/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/HtmlParserContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -50,7 +50,8 @@ public class HtmlParserContentTransformer extends AbstractContentTransformer2 /** * Only support HTML to TEXT. */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if (!MimetypeMap.MIMETYPE_HTML.equals(sourceMimetype) || !MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(targetMimetype)) diff --git a/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java b/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java index 07d632cb11..183ae521ad 100644 --- a/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/MediaWikiContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -76,7 +76,9 @@ public class MediaWikiContentTransformer extends AbstractContentTransformer2 * Only transform from mediawiki to html * * @see org.alfresco.repo.content.transform.ContentTransformer#isTransformable(java.lang.String, java.lang.String, org.alfresco.service.cmr.repository.TransformationOptions) - */public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + */ + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if (!MimetypeMap.MIMETYPE_TEXT_MEDIAWIKI.equals(sourceMimetype) || !MimetypeMap.MIMETYPE_HTML.equals(targetMimetype)) diff --git a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java index d904a66a65..39736b8826 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PdfBoxPdfToImageContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -53,7 +53,8 @@ public class PdfBoxPdfToImageContentTransformer extends AbstractContentTransform private static final String PDF_DEFAULT_PASSWORD = ""; private static Log logger = LogFactory.getLog(PdfBoxPdfToImageContentTransformer.class); - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { // only support PDF -> PNG OR Adobe Illustrator -> PNG. // Recent .ai file format is a .pdf file. diff --git a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java index 4cf9a442ac..6acb28be1e 100644 --- a/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PdfToImageContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -53,7 +53,8 @@ public class PdfToImageContentTransformer extends AbstractContentTransformer2 /** * Currently the only transformation performed is that of text extraction from PDF documents. */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { // only support PDF -> PNG OR Adobe Illustrator -> PNG. // .ai is really just a .pdf file anyway diff --git a/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java b/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java index 8963fd5343..b8783cf999 100644 --- a/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/PoiHssfContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -72,28 +72,17 @@ public class PoiHssfContentTransformer extends TikaPoweredContentTransformer * We support transforming to HTML, XML, Text or CSV */ @Override - public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if(sourceMimeTypes.contains(sourceMimetype) && MimetypeMap.MIMETYPE_TEXT_CSV.equals(targetMimetype)) { - // Special case for CSV - return isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); + // Special case for CSV + return true; } // Otherwise fall back on the default Tika rules - return super.isTransformable(sourceMimetype, sourceSize, targetMimetype, options); - } - - /** - * @deprecated This method should no longer be called as the overloaded method - * that calls it has been overridden. - */ - @Override - public boolean isTransformable(String sourceMimetype, String targetMimetype, - TransformationOptions options) - { - return isTransformable(sourceMimetype, -1, targetMimetype, options); + return super.isTransformableMimetype(sourceMimetype, targetMimetype, options); } /** @@ -112,7 +101,7 @@ public class PoiHssfContentTransformer extends TikaPoweredContentTransformer } // Otherwise fall back on the default Tika rules - return super.isTransformable(sourceMimetype, targetMimetype, options); + return super.isTransformableMimetype(sourceMimetype, targetMimetype, options); } @Override diff --git a/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java b/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java index 071a8a6dc6..4d937cc98c 100644 --- a/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/ProxyContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -58,7 +58,8 @@ public class ProxyContentTransformer extends AbstractContentTransformer2 /** * @see DocumentFormatRegistry */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { return this.worker.isTransformable(sourceMimetype, targetMimetype, options); } diff --git a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java index 742c36d21e..facab76cab 100644 --- a/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/StringExtractingContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -50,7 +50,8 @@ public class StringExtractingContentTransformer extends AbstractContentTransform * <p> * Extraction of text from binary data is wholly unreliable. */ - public boolean isTransformable(String sourceMimetype, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if (!targetMimetype.equals(MimetypeMap.MIMETYPE_TEXT_PLAIN)) { diff --git a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java index 8c3269bdbc..32676ace1a 100644 --- a/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/TextToPdfContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -100,7 +100,8 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 /** * Only supports Text to PDF */ - public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if ( (!MimetypeMap.MIMETYPE_TEXT_PLAIN.equals(sourceMimetype) && !MimetypeMap.MIMETYPE_TEXT_CSV.equals(sourceMimetype) && @@ -112,21 +113,10 @@ public class TextToPdfContentTransformer extends AbstractContentTransformer2 } else { - return isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); + return true; } } - /** - * @deprecated This method should no longer be called as the overloaded method - * that calls it has the overridden. - */ - @Override - public boolean isTransformable(String sourceMimetype, String targetMimetype, - TransformationOptions options) - { - return false; - } - @Override protected void transformInternal( ContentReader reader, diff --git a/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java b/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java index d9d135975f..34c2c9d25d 100644 --- a/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java +++ b/source/java/org/alfresco/repo/content/transform/TikaPoweredContentTransformer.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -87,7 +87,8 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans * Can we do the requested transformation via Tika? * We support transforming to HTML, XML or Text */ - public boolean isTransformable(String sourceMimetype, long sourceSize, String targetMimetype, TransformationOptions options) + @Override + public boolean isTransformableMimetype(String sourceMimetype, String targetMimetype, TransformationOptions options) { if(! sourceMimeTypes.contains(sourceMimetype)) { @@ -101,8 +102,7 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans MimetypeMap.MIMETYPE_XML.equals(targetMimetype)) { // We can output to this - // But there may be size limits on this transformer. - return isTransformableSize(sourceMimetype, sourceSize, targetMimetype, options); + return true; } else { @@ -110,17 +110,6 @@ public abstract class TikaPoweredContentTransformer extends AbstractContentTrans return false; } } - - /** - * @deprecated This method should no longer be called as the overloaded method - * that calls it has been overridden. - */ - @Override - public boolean isTransformable(String sourceMimetype, String targetMimetype, - TransformationOptions options) - { - return isTransformable(sourceMimetype, -1, targetMimetype, options); - } /** * Returns an appropriate Tika ContentHandler for the diff --git a/source/java/org/alfresco/repo/content/transform/TransformerDebug.java b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java index 80b2761014..183c023e17 100644 --- a/source/java/org/alfresco/repo/content/transform/TransformerDebug.java +++ b/source/java/org/alfresco/repo/content/transform/TransformerDebug.java @@ -18,10 +18,9 @@ */ package org.alfresco.repo.content.transform; -import java.text.DecimalFormat; +import java.text.NumberFormat; import java.util.ArrayDeque; import java.util.Deque; -import java.util.Formatter; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -74,6 +73,7 @@ public class TransformerDebug }; private final Deque<Frame> stack = new ArrayDeque<Frame>(); + private final Deque<String> isTransformableStack = new ArrayDeque<String>(); private boolean debugOutput = true; public static Deque<Frame> getStack() @@ -85,6 +85,11 @@ public class TransformerDebug { return threadInfo.get().debugOutput; } + + public static Deque<String> getIsTransformableStack() + { + return threadInfo.get().isTransformableStack; + } public static boolean setDebugOutput(boolean debugOutput) { @@ -103,6 +108,7 @@ public class TransformerDebug private final String fromUrl; private final String sourceMimetype; private final String targetMimetype; + private final boolean origDebugOutput; private final long start; private Call callType; @@ -111,13 +117,14 @@ public class TransformerDebug // See debug(String, Throwable) as to why this is commented out // private Throwable lastThrowable; - private Frame(Frame parent, String fromUrl, String sourceMimetype, String targetMimetype, Call pushCall) + private Frame(Frame parent, String fromUrl, String sourceMimetype, String targetMimetype, Call pushCall, boolean origDebugOutput) { this.id = parent == null ? uniqueId.getAndIncrement() : ++parent.childId; this.fromUrl = fromUrl; this.sourceMimetype = sourceMimetype; this.targetMimetype = targetMimetype; this.callType = pushCall; + this.origDebugOutput = origDebugOutput; start = System.currentTimeMillis(); } } @@ -196,6 +203,17 @@ public class TransformerDebug } } + /** + * Called prior to calling a nested isTransformable. + */ + public void pushIsTransformableSize(ContentTransformer transformer) + { + if (isEnabled()) + { + ThreadInfo.getIsTransformableStack().push(getName(transformer)); + } + } + private void push(String name, String fromUrl, String sourceMimetype, String targetMimetype, long sourceSize, Call callType) { Deque<Frame> ourStack = ThreadInfo.getStack(); @@ -207,7 +225,9 @@ public class TransformerDebug } else { - frame = new Frame(frame, fromUrl, sourceMimetype, targetMimetype, callType); + // Create a new frame. Logging level is set to trace if the file size is 0 + boolean origDebugOutput = ThreadInfo.setDebugOutput(ThreadInfo.getDebug() && sourceSize != 0); + frame = new Frame(frame, fromUrl, sourceMimetype, targetMimetype, callType, origDebugOutput); ourStack.push(frame); if (callType == Call.TRANSFORM) @@ -231,8 +251,11 @@ public class TransformerDebug if (frame != null) { - String name = getName(transformer); - String reason = String.format("> %,dK", maxSourceSizeKBytes); + Deque<String> isTransformableStack = ThreadInfo.getIsTransformableStack(); + String name = (!isTransformableStack.isEmpty()) + ? isTransformableStack.getFirst() + : getName(transformer); + String reason = "> "+fileSize(maxSourceSizeKBytes*1024); boolean debug = (maxSourceSizeKBytes != 0); if (ourStack.size() == 1) { @@ -273,7 +296,7 @@ public class TransformerDebug String name = getName(trans); int pad = longestNameLength - name.length(); log((c == 'a' ? "**" : " ") + (c++) + ") " + - name + spaces(pad+1) + trans.getTransformationTime() + " ms"); + name + spaces(pad+1) + ms(trans.getTransformationTime())); } if (frame.unavailableTransformers != null) { @@ -317,7 +340,8 @@ public class TransformerDebug log(frame.fromUrl, firstLevel); } - log(getMimetypeExt(frame.sourceMimetype)+getMimetypeExt(frame.targetMimetype) + String.format("%,dK ", (sourceSize/1024)) + message); + log(getMimetypeExt(frame.sourceMimetype)+getMimetypeExt(frame.targetMimetype) + + ((sourceSize >= 0) ? fileSize(sourceSize)+' ' : "") + message); log(frame.sourceMimetype+' '+frame.targetMimetype, false); } @@ -345,6 +369,17 @@ public class TransformerDebug } } + /** + * Called after returning from a nested isTransformable. + */ + public void popIsTransformableSize() + { + if (isEnabled()) + { + ThreadInfo.getIsTransformableStack().pop(); + } + } + private void pop(Call callType) { Deque<Frame> ourStack = ThreadInfo.getStack(); @@ -358,12 +393,13 @@ public class TransformerDebug { boolean topFrame = ourStack.size() == 1; log("Finished in " + - (System.currentTimeMillis() - frame.start) + " ms" + + ms(System.currentTimeMillis() - frame.start) + (frame.callType == Call.AVAILABLE ? " Transformer NOT called" : "") + (topFrame ? "\n" : ""), topFrame); } + setDebugOutput(frame.origDebugOutput); ourStack.pop(); // See debug(String, Throwable) as to why this is commented out @@ -523,7 +559,7 @@ public class TransformerDebug return sb.toString(); } - private String getName(ContentTransformer transformer) + public String getName(ContentTransformer transformer) { return (transformer instanceof AbstractContentTransformer2 @@ -567,4 +603,51 @@ public class TransformerDebug } return sb.toString(); } + + public String ms(long time) + { + return String.format("%,d ms", time); + } + + public String fileSize(long size) + { + if (size < 0) + { + return "unlimited"; + } + if (size == 1) + { + return "1 byte"; + } + final String[] units = new String[] { "bytes", "KB", "MB", "GB", "TB" }; + long divider = 1; + for(int i = 0; i < units.length-1; i++) + { + long nextDivider = divider * 1024; + if(size < nextDivider) + { + return fileSizeFormat(size, divider, units[i]); + } + divider = nextDivider; + } + return fileSizeFormat(size, divider, units[units.length-1]); + } + + private String fileSizeFormat(long size, long divider, String unit) + { + size = size * 10 / divider; + int decimalPoint = (int) size % 10; + + StringBuilder sb = new StringBuilder(); + sb.append(size/10); + if (decimalPoint != 0) + { + sb.append("."); + sb.append(decimalPoint); + } + sb.append(' '); + sb.append(unit); + + return sb.toString(); + } } diff --git a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java index dce9a1588e..b93e4405bf 100644 --- a/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/node/AbstractNodeDAOImpl.java @@ -1437,6 +1437,32 @@ public abstract class AbstractNodeDAOImpl implements NodeDAO, BatchingDAO return updateNodeImpl(oldNode, nodeUpdate, null); } + + @Override + public int touchNodes(Long txnId, List<Long> nodeIds) + { + // limit in clause to 1000 node ids + int batchSize = 1000; + + int touched = 0; + ArrayList<Long> batch = new ArrayList<Long>(batchSize); + for(Long nodeId : nodeIds) + { + invalidateNodeCaches(nodeId); + batch.add(nodeId); + if(batch.size() % batchSize == 0) + { + touched += updateNodes(txnId, batch); + batch.clear(); + } + } + if(batch.size() > 0) + { + touched += updateNodes(txnId, batch); + } + return touched; + } + /** * Updates the node's transaction and <b>cm:auditable</b> properties while * providing a convenient method to control cache entry invalidation. diff --git a/source/java/org/alfresco/repo/domain/node/NodeDAO.java b/source/java/org/alfresco/repo/domain/node/NodeDAO.java index d7f1003580..003f674401 100644 --- a/source/java/org/alfresco/repo/domain/node/NodeDAO.java +++ b/source/java/org/alfresco/repo/domain/node/NodeDAO.java @@ -202,6 +202,14 @@ public interface NodeDAO extends NodeBulkLoader QName assocTypeQName, QName assocQName); + /** + * Update the transaction associated with a lust of nodes + * @param txnId - the tx id to set + * @param nodeIds - the nodes to update + * @return the number of nodes touched + */ + public int touchNodes(Long txnId, List<Long> nodeIds); + /** * @param nodeTypeQName the new type QName for the node or <tt>null</tt> to keep the existing one * @param nodeLocale the new locale for the node or <tt>null</tt> to keep the existing one diff --git a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java index 392d66f8df..13e0dd062f 100644 --- a/source/java/org/alfresco/repo/domain/patch/PatchDAO.java +++ b/source/java/org/alfresco/repo/domain/patch/PatchDAO.java @@ -244,4 +244,41 @@ public interface PatchDAO * @return Returns a count of the number of nodes that have either of the aspects */ public long getCountNodesWithAspects(Set<QName> qnames); + + /** + * Find all the nodes ids with the given type + * @param typeQNameId - the id of the type qname + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return + */ + public List<Long> getNodesByTypeQNameId(Long typeQNameId, Long minNodeId, Long maxNodeId); + + /** + * Find all the nodes ids with the given type uri + * @param uriId - the id of the type qname uri + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return + */ + public List<Long> getNodesByTypeUriId(Long uriId, Long minNodeId, Long maxNodeId); + + /** + * Find all the nodes ids with the given aspect + * @param aspectQNameId - the id of the aspect qname + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return + */ + public List<Long> getNodesByAspectQNameId(Long aspectQNameId, Long minNodeId, Long maxNodeId); + + /** + * Find all the nodes ids with the given content property set with the given mimetype + * @param mimetypeId - the id of the content data mimetype + * @param minNodeId - min node id in the result set - inclusive + * @param maxNodeId - max node id in the result set - exclusive + * @return + */ + public List<Long> getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId); + } diff --git a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java index 4f95f18723..e314abe414 100644 --- a/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java +++ b/source/java/org/alfresco/repo/domain/patch/ibatis/PatchDAOImpl.java @@ -103,6 +103,11 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl private static final String SELECT_SHARED_ACLS_THAT_DO_NOT_INHERIT_CORRECTLY_FROM_THEIR_DEFINING_ACL = "alfresco.patch.select_sharedAclsThatDoNotInheritCorrectlyFromTheirDefiningAcl"; private static final String SELECT_COUNT_NODES_WITH_ASPECTS = "alfresco.patch.select_CountNodesWithAspectIds"; + + private static final String SELECT_NODES_BY_TYPE_QNAME = "alfresco.patch.select_NodesByTypeQName"; + private static final String SELECT_NODES_BY_TYPE_URI = "alfresco.patch.select_NodesByTypeUriId"; + private static final String SELECT_NODES_BY_ASPECT_QNAME = "alfresco.patch.select_NodesByAspectQName"; + private static final String SELECT_NODES_BY_CONTENT_MIMETYPE = "alfresco.patch.select_NodesByContentMimetype"; private LocaleDAO localeDAO; @@ -653,4 +658,49 @@ public class PatchDAOImpl extends AbstractPatchDAOImpl return count; } } + + + @SuppressWarnings("unchecked") + @Override + public List<Long> getNodesByTypeQNameId(Long typeQNameId, Long minNodeId, Long maxNodeId) + { + Map<String, Object> params = new HashMap<String, Object>(); + params.put("qnameId", typeQNameId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List<Long>) template.selectList(SELECT_NODES_BY_TYPE_QNAME, params); + } + + @SuppressWarnings("unchecked") + @Override + public List<Long> getNodesByTypeUriId(Long nsId, Long minNodeId, Long maxNodeId) + { + Map<String, Object> params = new HashMap<String, Object>(); + params.put("nsId", nsId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List<Long>) template.selectList(SELECT_NODES_BY_TYPE_URI, params); + } + + @SuppressWarnings("unchecked") + @Override + public List<Long> getNodesByAspectQNameId(Long aspectQNameId, Long minNodeId, Long maxNodeId) + { + Map<String, Object> params = new HashMap<String, Object>(); + params.put("qnameId", aspectQNameId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List<Long>) template.selectList(SELECT_NODES_BY_ASPECT_QNAME, params); + } + + @SuppressWarnings("unchecked") + @Override + public List<Long> getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId) + { + Map<String, Object> params = new HashMap<String, Object>(); + params.put("mimetypeId", mimetypeId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List<Long>) template.selectList(SELECT_NODES_BY_CONTENT_MIMETYPE, params); + } } diff --git a/source/java/org/alfresco/repo/jscript/ApplicationScriptUtils.java b/source/java/org/alfresco/repo/jscript/ApplicationScriptUtils.java index c1bdac1606..c9c3ce8d5d 100644 --- a/source/java/org/alfresco/repo/jscript/ApplicationScriptUtils.java +++ b/source/java/org/alfresco/repo/jscript/ApplicationScriptUtils.java @@ -1,298 +1,96 @@ -/* - * Copyright (C) 2005-2011 Alfresco Software Limited. - * - * This file is part of Alfresco - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. - */ -package org.alfresco.repo.jscript; - -import org.alfresco.model.ContentModel; -import org.alfresco.repo.jscript.app.PropertyDecorator; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.AccessStatus; -import org.alfresco.service.namespace.NamespaceException; -import org.alfresco.service.namespace.QName; -import org.alfresco.util.ISO8601DateFormat; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONException; -import org.json.JSONObject; -import org.springframework.extensions.surf.util.URLEncoder; - -import java.io.Serializable; -import java.text.MessageFormat; -import java.util.*; - -/** - * Utility functions specifically for external application use. - * - * @author Mike Hatfield - */ - -public final class ApplicationScriptUtils extends BaseScopableProcessorExtension -{ - private static Log logger = LogFactory.getLog(ApplicationScriptUtils.class); - - /** Repository Service Registry */ - private ServiceRegistry services; - private NodeService nodeService = null; - private Map<String, Object> decoratedProperties; - private String[] userPermissions; - - private final static String CONTENT_DOWNLOAD_API_URL = "/api/node/content/{0}/{1}/{2}/{3}"; - - /** - * Set the service registry - * - * @param serviceRegistry the service registry - */ - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.services = serviceRegistry; - this.nodeService = services.getNodeService(); - } - - /** - * Set the properties that require decorator beans - * - * @param decoratedProperties - */ - public void setDecoratedProperties(Map<String, Object> decoratedProperties) - { - this.decoratedProperties = decoratedProperties; - } - - /** - * Define the list of user permissions to return in the JSON body - * - * @param userPermissions - */ - public void setUserPermissions(String[] userPermissions) - { - this.userPermissions = userPermissions; - } - - /** - * Returns the JSON representation of a node. Long-form QNames are used in the - * result. - * - * @param node the node to convert to JSON representation. - * @return The JSON representation of this node - */ - public String toJSON(ScriptNode node) - { - return this.toJSON(node, false); - } - - /** - * Returns the JSON representation of this node. - * - * @param node the node to convert to JSON representation. - * @param useShortQNames if true short-form qnames will be returned, else long-form. - * @return The JSON representation of this node - */ - public String toJSON(ScriptNode node, boolean useShortQNames) - { - return this.toJSONObj(node, useShortQNames).toString(); - } - - /** - * Returns a JSON object representing the node. - * - * @param node the node to convert to JSON representation. - * @param useShortQNames if true short-form qnames will be returned, else long-form. - * @return The JSON representation of this node - */ - protected Object toJSONObj(ScriptNode node, boolean useShortQNames) - { - NodeRef nodeRef = node.getNodeRef(); - JSONObject json = new JSONObject(); - - if (this.nodeService.exists(nodeRef)) - { - if (this.services.getPublicServiceAccessService().hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", nodeRef) == AccessStatus.ALLOWED) - { - try - { - String typeString = useShortQNames ? this.getShortQName(node.getQNameType()) : node.getType(); - boolean isLink = node.getIsLinkToContainer() || node.getIsLinkToDocument(); - - json.put("nodeRef", nodeRef.toString()); - json.put("type", typeString); - json.put("isContainer", node.getIsContainer() || node.getIsLinkToContainer()); - json.put("isLink", isLink); - json.put("isLocked", node.getIsLocked()); - - if (node.getIsDocument()) - { - json.put("contentURL", this.getDownloadAPIUrl(node)); - json.put("mimetype", node.getMimetype()); - json.put("size", node.getSize()); - } - - // permissions - Map<String, Serializable> permissionsJSON = new LinkedHashMap<String, Serializable>(3); - if (node.hasPermission("ReadPermissions")) - { - permissionsJSON.put("roles", node.retrieveAllSetPermissions(false, true)); - } - permissionsJSON.put("inherited", node.inheritsPermissions()); - Map<String, Serializable> userPermissionJSON = new LinkedHashMap<String, Serializable>(this.userPermissions.length); - for (String userPermission : this.userPermissions) - { - userPermissionJSON.put(userPermission, node.hasPermission(userPermission)); - } - permissionsJSON.put("user", (Serializable) userPermissionJSON); - json.put("permissions", permissionsJSON); - - // add properties - Map<QName, Serializable> nodeProperties = this.nodeService.getProperties(nodeRef); - json.put("properties", this.parseToJSON(nodeRef, nodeProperties, useShortQNames)); - - // add aspects as an array - Set<QName> nodeAspects = this.nodeService.getAspects(nodeRef); - if (useShortQNames) - { - Set<String> nodeAspectsShortQNames = new LinkedHashSet<String>(nodeAspects.size()); - for (QName nextLongQName : nodeAspects) - { - String nextShortQName = this.getShortQName(nextLongQName); - nodeAspectsShortQNames.add(nextShortQName); - } - json.put("aspects", nodeAspectsShortQNames); - } - else - { - json.put("aspects", nodeAspects); - } - - // link to document or folder? - if (isLink) - { - NodeRef targetNodeRef = (NodeRef) nodeProperties.get(ContentModel.PROP_LINK_DESTINATION); - if (targetNodeRef != null) - { - json.put("linkedNode", this.toJSONObj(new ScriptNode(targetNodeRef, this.services, node.scope), useShortQNames)); - } - } - } - catch (JSONException error) - { - error.printStackTrace(); - } - } - } - - return json; - } - - /** - * Given a long-form QName, this method uses the namespace service to create a - * short-form QName string. - * - * @param longQName - * @return the short form of the QName string, e.g. "cm:content" - */ - protected String getShortQName(QName longQName) - { - return longQName.toPrefixString(this.services.getNamespaceService()); - } - - /** - * Converts a map of node properties to a format suitable for JSON output - * - * @param nodeRef - * @param properties - * @param useShortQNames - * @return a decorated map of properties suitable for JSON output - */ - protected Map<String, Serializable> parseToJSON(NodeRef nodeRef, Map<QName, Serializable> properties, boolean useShortQNames) - { - Map<String, Serializable> json = new LinkedHashMap<String, Serializable>(properties.size()); - - for (QName nextLongQName : properties.keySet()) - { - try - { - String shortQName = this.getShortQName(nextLongQName); - String key = useShortQNames ? shortQName : nextLongQName.toString(); - Serializable value = properties.get(nextLongQName); - - if (value != null) - { - // Has a decorator has been registered for this property? - if (this.decoratedProperties.containsKey(shortQName)) - { - json.put(key, ((PropertyDecorator) this.decoratedProperties.get(shortQName)).decorate(nodeRef, shortQName, value)); - } - else - { - // Built-in data type processing - if (value instanceof Date) - { - Map<String, Serializable> dateObj = new LinkedHashMap<String, Serializable>(1); - dateObj.put("value", value); - dateObj.put("iso8601", ISO8601DateFormat.format((Date)value)); - json.put(key, (Serializable)dateObj); - } - else - { - json.put(key, value); - } - } - } - else - { - json.put(key, null); - } - } - catch (NamespaceException ne) - { - // ignore properties that do not have a registered namespace - if (logger.isDebugEnabled()) - logger.debug("Ignoring property '" + nextLongQName + "' as its namespace is not registered"); - } - } - - return json; - } - - /** - * @param node the node to construct the download URL for - * @return For a content document, this method returns the URL to the /api/node/content - * API for the default content property - * <p> - * For a container node, this method returns an empty string - * </p> - */ - public String getDownloadAPIUrl(ScriptNode node) - { - if (node.getIsDocument()) - { - return MessageFormat.format(CONTENT_DOWNLOAD_API_URL, new Object[]{ - node.nodeRef.getStoreRef().getProtocol(), - node.nodeRef.getStoreRef().getIdentifier(), - node.nodeRef.getId(), - URLEncoder.encode(node.getName())}); - } - else - { - return ""; - } - } - -} +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. + */ +package org.alfresco.repo.jscript; + +import java.text.MessageFormat; + +import org.alfresco.repo.jscript.app.JSONConversionComponent; +import org.springframework.extensions.surf.util.URLEncoder; + +/** + * Utility functions specifically for external application use. + * + * @author Mike Hatfield + */ + +public final class ApplicationScriptUtils extends BaseScopableProcessorExtension +{ + /** Content download API URL */ + private final static String CONTENT_DOWNLOAD_API_URL = "/api/node/content/{0}/{1}/{2}/{3}"; + + /** JSON conversion component */ + private JSONConversionComponent jsonConversionComponent; + + /** + * @param jsonConversionComponent JSON conversion component + */ + public void setJsonConversionComponent(JSONConversionComponent jsonConversionComponent) + { + this.jsonConversionComponent = jsonConversionComponent; + } + + /** + * Returns the JSON representation of a node. Long-form QNames are used in the + * result. + * + * @param node the node to convert to JSON representation. + * @return The JSON representation of this node + */ + public String toJSON(ScriptNode node) + { + return this.toJSON(node, false); + } + + /** + * Returns the JSON representation of this node. + * + * @param node the node to convert to JSON representation. + * @param useShortQNames if true short-form qnames will be returned, else long-form. + * @return The JSON representation of this node + */ + public String toJSON(ScriptNode node, boolean useShortQNames) + { + return jsonConversionComponent.toJSON(node.getNodeRef(), useShortQNames); + } + + /** + * @param node the node to construct the download URL for + * @return For a content document, this method returns the URL to the /api/node/content + * API for the default content property + * <p> + * For a container node, this method returns an empty string + * </p> + */ + public String getDownloadAPIUrl(ScriptNode node) + { + if (node.getIsDocument()) + { + return MessageFormat.format(CONTENT_DOWNLOAD_API_URL, new Object[]{ + node.nodeRef.getStoreRef().getProtocol(), + node.nodeRef.getStoreRef().getIdentifier(), + node.nodeRef.getId(), + URLEncoder.encode(node.getName())}); + } + else + { + return ""; + } + } + +} diff --git a/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java b/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java index 33e5894091..6fb9ce0e05 100644 --- a/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java +++ b/source/java/org/alfresco/repo/jscript/RhinoScriptProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -65,6 +65,7 @@ import org.springframework.util.FileCopyUtils; public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcessor, ScriptResourceLoader, InitializingBean { private static final Log logger = LogFactory.getLog(RhinoScriptProcessor.class); + private static final Log callLogger = LogFactory.getLog(RhinoScriptProcessor.class.getName()+".calls"); private static final String PATH_CLASSPATH = "classpath:"; @@ -188,7 +189,13 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess } } - return executeScriptImpl(script, model, location.isSecure()); + String debugScriptName = null; + if (callLogger.isDebugEnabled()) + { + int i = path.lastIndexOf('/'); + debugScriptName = (i != -1) ? path.substring(i+1) : path; + } + return executeScriptImpl(script, model, location.isSecure(), debugScriptName); } catch (Throwable err) { @@ -238,7 +245,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess Context.exit(); } - return executeScriptImpl(script, model, false); + return executeScriptImpl(script, model, false, nodeRef.toString()); } catch (Throwable err) { @@ -264,7 +271,7 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess { Context.exit(); } - return executeScriptImpl(script, model, true); + return executeScriptImpl(script, model, true, "string script"); } catch (Throwable err) { @@ -413,17 +420,19 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess * @param script The script to execute. * @param model Data model containing objects to be added to the root scope. * @param secure True if the script is considered secure and may access java.* libs directly + * @param debugScriptName To identify the script in debug messages. * * @return result of the script execution, can be null. * * @throws AlfrescoRuntimeException */ - private Object executeScriptImpl(Script script, Map<String, Object> model, boolean secure) + private Object executeScriptImpl(Script script, Map<String, Object> model, boolean secure, String debugScriptName) throws AlfrescoRuntimeException { long startTime = 0; - if (logger.isDebugEnabled()) + if (callLogger.isDebugEnabled()) { + callLogger.debug(debugScriptName+" Start"); startTime = System.nanoTime(); } @@ -487,6 +496,10 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess } catch (WrappedException w) { + if (callLogger.isDebugEnabled()) + { + callLogger.debug(debugScriptName+" Exception", w); + } Throwable err = w.getWrappedException(); if (err instanceof RuntimeException) { @@ -496,16 +509,20 @@ public class RhinoScriptProcessor extends BaseProcessor implements ScriptProcess } catch (Throwable err) { + if (callLogger.isDebugEnabled()) + { + callLogger.debug(debugScriptName+" Exception", err); + } throw new AlfrescoRuntimeException(err.getMessage(), err); } finally { Context.exit(); - if (logger.isDebugEnabled()) + if (callLogger.isDebugEnabled()) { long endTime = System.nanoTime(); - logger.debug("Time to execute script: " + (endTime - startTime)/1000000f + "ms"); + callLogger.debug(debugScriptName+" End " + (endTime - startTime)/1000000 + " ms"); } } } diff --git a/source/java/org/alfresco/repo/jscript/ScriptNode.java b/source/java/org/alfresco/repo/jscript/ScriptNode.java index f455265e40..c5d3a9c830 100644 --- a/source/java/org/alfresco/repo/jscript/ScriptNode.java +++ b/source/java/org/alfresco/repo/jscript/ScriptNode.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -2828,7 +2828,7 @@ public class ScriptNode implements Scopeable, NamespacePrefixResolverProvider if (contentReader != null) { String mimetype = contentReader.getMimetype(); - List<ThumbnailDefinition> thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentReader.getContentUrl(), mimetype, contentReader.getSize()); + List<ThumbnailDefinition> thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(mimetype, contentReader.getSize()); for (ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions) { result.add(thumbnailDefinition.getName()); diff --git a/source/java/org/alfresco/repo/jscript/app/BasePropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/BasePropertyDecorator.java new file mode 100644 index 0000000000..9ce41b2530 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/app/BasePropertyDecorator.java @@ -0,0 +1,75 @@ +/** + * + */ +package org.alfresco.repo.jscript.app; + +import java.util.HashSet; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; + +/** + * @author Roy Wetherall + */ +public abstract class BasePropertyDecorator implements PropertyDecorator +{ + protected Set<QName> propertyNames; + + protected NodeService nodeService; + + protected NamespaceService namespaceService; + + protected PermissionService permissionService; + + protected JSONConversionComponent jsonConversionComponent; + + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + public void setJsonConversionComponent(JSONConversionComponent jsonConversionComponent) + { + this.jsonConversionComponent = jsonConversionComponent; + } + + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + public void init() + { + jsonConversionComponent.registerPropertyDecorator(this); + } + + @Override + public Set<QName> getPropertyNames() + { + return propertyNames; + } + + public void setPropertyName(String propertyName) + { + propertyNames = new HashSet<QName>(1); + propertyNames.add(QName.createQName(propertyName, namespaceService)); + } + + public void setPropertyNames(Set<String> propertyNames) + { + this.propertyNames = new HashSet<QName>(propertyNames.size()); + for (String propertyName : propertyNames) + { + this.propertyNames.add(QName.createQName(propertyName, namespaceService)); + } + } + +} diff --git a/source/java/org/alfresco/repo/jscript/app/CategoryPropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/CategoryPropertyDecorator.java index 5abce11f0b..dd6b852556 100644 --- a/source/java/org/alfresco/repo/jscript/app/CategoryPropertyDecorator.java +++ b/source/java/org/alfresco/repo/jscript/app/CategoryPropertyDecorator.java @@ -18,55 +18,46 @@ */ package org.alfresco.repo.jscript.app; -import org.alfresco.model.ContentModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.security.PermissionService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import java.io.Serializable; import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +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; +import org.json.simple.JSONArray; +import org.json.simple.JSONAware; +import org.json.simple.JSONObject; /** * Category property decorator class. * * @author Mike Hatfield */ -public class CategoryPropertyDecorator implements PropertyDecorator +public class CategoryPropertyDecorator extends BasePropertyDecorator { private static Log logger = LogFactory.getLog(CategoryPropertyDecorator.class); - - private ServiceRegistry services; - private NodeService nodeService = null; - private PermissionService permissionService = null; - - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.services = serviceRegistry; - this.nodeService = serviceRegistry.getNodeService(); - this.permissionService = serviceRegistry.getPermissionService(); - } - - public Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value) + + /** + * @see org.alfresco.repo.jscript.app.PropertyDecorator#decorate(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, java.io.Serializable) + */ + @SuppressWarnings("unchecked") + public JSONAware decorate(QName propertyName, NodeRef nodeRef, Serializable value) { Collection<NodeRef> collection = (Collection<NodeRef>)value; - Object[] array = new Object[collection.size()]; - int index = 0; + JSONArray array = new JSONArray(); for (NodeRef obj : collection) { try { - Map<String, Serializable> jsonObj = new LinkedHashMap<String, Serializable>(4); + JSONObject jsonObj = new JSONObject(); jsonObj.put("name", this.nodeService.getProperty(obj, ContentModel.PROP_NAME)); jsonObj.put("path", this.getPath(obj)); jsonObj.put("nodeRef", obj.toString()); - array[index++] = jsonObj; + array.add(jsonObj); } catch (InvalidNodeRefException e) { diff --git a/source/java/org/alfresco/repo/jscript/app/IgnorePropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/IgnorePropertyDecorator.java new file mode 100644 index 0000000000..f51175fe16 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/app/IgnorePropertyDecorator.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2005-2011 Alfresco Software Limited. + * + * This file is part of Alfresco + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see <http://www.gnu.org/licenses/>. + */ +package org.alfresco.repo.jscript.app; + +import java.io.Serializable; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.json.simple.JSONAware; + +/** + * Ignores a given property and doesn't output anything in the decoration. This means the property will not appear in the + * resulting JSON. + * + * @author Roy Wetherall + */ +public class IgnorePropertyDecorator extends BasePropertyDecorator +{ + /** + * @see org.alfresco.repo.jscript.app.PropertyDecorator#decorate(org.alfresco.service.cmr.repository.NodeRef, java.io.Serializable) + */ + public JSONAware decorate(QName propertyName, NodeRef nodeRef, Serializable value) + { + return null; + } +} diff --git a/source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java b/source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java new file mode 100644 index 0000000000..32ec674257 --- /dev/null +++ b/source/java/org/alfresco/repo/jscript/app/JSONConversionComponent.java @@ -0,0 +1,413 @@ +/** + * + */ +package org.alfresco.repo.jscript.app; + +import java.io.Serializable; +import java.text.MessageFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.ServiceRegistry; +import org.alfresco.service.cmr.lock.LockService; +import org.alfresco.service.cmr.lock.LockStatus; +import org.alfresco.service.cmr.model.FileFolderService; +import org.alfresco.service.cmr.model.FileInfo; +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.cmr.security.AccessPermission; +import org.alfresco.service.cmr.security.AccessStatus; +import org.alfresco.service.cmr.security.PermissionService; +import org.alfresco.service.cmr.security.PublicServiceAccessService; +import org.alfresco.service.namespace.NamespaceException; +import org.alfresco.service.namespace.NamespaceService; +import org.alfresco.service.namespace.QName; +import org.alfresco.util.ISO8601DateFormat; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.simple.JSONArray; +import org.json.simple.JSONAware; +import org.json.simple.JSONObject; +import org.springframework.extensions.surf.util.URLEncoder; + +/** + * JSON Conversion Component + * + * @author Roy Wetherall + */ +public class JSONConversionComponent +{ + /** Content download API URL template */ + private final static String CONTENT_DOWNLOAD_API_URL = "/api/node/content/{0}/{1}/{2}/{3}"; + + /** Logger */ + private static Log logger = LogFactory.getLog(JSONConversionComponent.class); + + /** Registered decorators */ + protected Map<QName, PropertyDecorator> propertyDecorators = new HashMap<QName, PropertyDecorator>(3); + + /** User permissions */ + protected String[] userPermissions; + + /** Services */ + protected NodeService nodeService; + protected PublicServiceAccessService publicServiceAccessService; + protected NamespaceService namespaceService; + protected FileFolderService fileFolderService; + protected LockService lockService; + protected ContentService contentService; + protected PermissionService permissionService; + + /** + * @param nodeService node service + */ + public void setNodeService(NodeService nodeService) + { + this.nodeService = nodeService; + } + + /** + * @param publicServiceAccessService public service access service + */ + public void setPublicServiceAccessService(PublicServiceAccessService publicServiceAccessService) + { + this.publicServiceAccessService = publicServiceAccessService; + } + + /** + * @param namespaceService namespace service + */ + public void setNamespaceService(NamespaceService namespaceService) + { + this.namespaceService = namespaceService; + } + + /** + * @param fileFolderService file folder service + */ + public void setFileFolderService(FileFolderService fileFolderService) + { + this.fileFolderService = fileFolderService; + } + + /** + * @param lockService lock service + */ + public void setLockService(LockService lockService) + { + this.lockService = lockService; + } + + /** + * @param permissionService permission service + */ + public void setPermissionService(PermissionService permissionService) + { + this.permissionService = permissionService; + } + + /** + * @param userPermissions user permissions + */ + public void setUserPermissions(String[] userPermissions) + { + this.userPermissions = userPermissions; + } + + /** + * @param contentService content service + */ + public void setContentService(ContentService contentService) + { + this.contentService = contentService; + } + + /** + * Register a property decorator; + * + * @param propertyDecorator + */ + public void registerPropertyDecorator(PropertyDecorator propertyDecorator) + { + for (QName propertyName : propertyDecorator.getPropertyNames()) + { + propertyDecorators.put(propertyName, propertyDecorator); + } + } + + /** + * Convert a node reference to a JSON string. Selects the correct converter based on selection + * implementation. + */ + @SuppressWarnings("unchecked") + public String toJSON(NodeRef nodeRef, boolean useShortQNames) + { + JSONObject json = new JSONObject(); + + if (this.nodeService.exists(nodeRef) == true) + { + if (publicServiceAccessService.hasAccess(ServiceRegistry.NODE_SERVICE.getLocalName(), "getProperties", nodeRef) == AccessStatus.ALLOWED) + { + // Get node info + FileInfo nodeInfo = fileFolderService.getFileInfo(nodeRef); + + // Set root values + setRootValues(nodeInfo, json, useShortQNames); + + // add permissions + json.put("permissions", permissionsToJSON(nodeRef)); + + // add properties + json.put("properties", propertiesToJSON(nodeRef, useShortQNames)); + + // add aspects + json.put("aspects", apsectsToJSON(nodeRef, useShortQNames)); + } + } + + return json.toJSONString(); + } + + /** + * + * @param nodeInfo + * @param rootJSONObject + * @param useShortQNames + * @throws JSONException + */ + @SuppressWarnings("unchecked") + protected void setRootValues(FileInfo nodeInfo, JSONObject rootJSONObject, boolean useShortQNames) + { + NodeRef nodeRef = nodeInfo.getNodeRef(); + + rootJSONObject.put("nodeRef", nodeInfo.getNodeRef().toString()); + rootJSONObject.put("type", nameToString(nodeInfo.getType(), useShortQNames)); + rootJSONObject.put("isContainer", nodeInfo.isFolder()); //node.getIsContainer() || node.getIsLinkToContainer()); + rootJSONObject.put("isLocked", isLocked(nodeInfo.getNodeRef())); + + rootJSONObject.put("isLink", nodeInfo.isLink()); + if (nodeInfo.isLink() == true) + { + NodeRef targetNodeRef = nodeInfo.getLinkNodeRef(); + if (targetNodeRef != null) + { + rootJSONObject.put("linkedNode", toJSON(targetNodeRef, useShortQNames)); + } + } + + // TODO should this be moved to the property output since we may have more than one content property + // or a non-standard content property + + if (nodeInfo.isFolder() == false) + { + ContentReader reader = contentService.getReader(nodeRef, ContentModel.PROP_CONTENT); + + if (reader != null) + { + String contentURL = MessageFormat.format( + CONTENT_DOWNLOAD_API_URL, new Object[]{ + nodeRef.getStoreRef().getProtocol(), + nodeRef.getStoreRef().getIdentifier(), + nodeRef.getId(), + URLEncoder.encode(nodeInfo.getName())}); + + rootJSONObject.put("contentURL", contentURL); + rootJSONObject.put("mimetype", reader.getMimetype()); + rootJSONObject.put("encoding", reader.getEncoding()); + rootJSONObject.put("size", reader.getSize()); + } + } + } + + /** + * + * @param nodeRef + * @return + * @throws JSONException + */ + @SuppressWarnings("unchecked") + protected JSONObject permissionsToJSON(NodeRef nodeRef) + { + JSONObject permissionsJSON = new JSONObject(); + if (AccessStatus.ALLOWED.equals(permissionService.hasPermission(nodeRef, PermissionService.READ_PERMISSIONS)) == true) + { + permissionsJSON.put("inherited", permissionService.getInheritParentPermissions(nodeRef)); + permissionsJSON.put("roles", allSetPermissionsToJSON(nodeRef)); + permissionsJSON.put("user", userPermissionsToJSON(nodeRef)); + } + return permissionsJSON; + } + + /** + * + * @param nodeRef + * @return + */ + @SuppressWarnings("unchecked") + protected JSONObject userPermissionsToJSON(NodeRef nodeRef) + { + JSONObject userPermissionJSON = new JSONObject(); + for (String userPermission : this.userPermissions) + { + boolean hasPermission = AccessStatus.ALLOWED.equals(permissionService.hasPermission(nodeRef, userPermission)); + userPermissionJSON.put(userPermission, hasPermission); + } + return userPermissionJSON; + } + + /** + * + * @param nodeRef + * @param useShortQNames + * @return + * @throws JSONException + */ + @SuppressWarnings("unchecked") + protected JSONObject propertiesToJSON(NodeRef nodeRef, boolean useShortQNames) + { + JSONObject propertiesJSON = new JSONObject(); + + Map<QName, Serializable> properties = nodeService.getProperties(nodeRef); + for (QName propertyName : properties.keySet()) + { + try + { + String key = nameToString(propertyName, useShortQNames); + Serializable value = properties.get(propertyName); + + if (value != null) + { + // Has a decorator has been registered for this property? + if (propertyDecorators.containsKey(propertyName) == true) + { + JSONAware jsonAware = propertyDecorators.get(propertyName).decorate(propertyName, nodeRef, value); + if (jsonAware != null) + { + propertiesJSON.put(key, jsonAware); + } + } + else + { + // Built-in data type processing + if (value instanceof Date) + { + JSONObject dateObj = new JSONObject(); + dateObj.put("value", JSONObject.escape(value.toString())); + dateObj.put("iso8601", JSONObject.escape(ISO8601DateFormat.format((Date)value))); + propertiesJSON.put(key, dateObj); + } + else + { + propertiesJSON.put(key, value.toString()); + } + } + } + else + { + propertiesJSON.put(key, null); + } + } + catch (NamespaceException ne) + { + // ignore properties that do not have a registered namespace + if (logger.isDebugEnabled() == true) + { + logger.debug("Ignoring property '" + propertyName + "' as its namespace is not registered"); + } + } + } + + return propertiesJSON; + } + + /** + * + * @param nodeRef + * @param useShortQNames + * @return + * @throws JSONException + */ + @SuppressWarnings("unchecked") + protected JSONArray apsectsToJSON(NodeRef nodeRef, boolean useShortQNames) + { + JSONArray aspectsJSON = new JSONArray(); + + Set<QName> aspects = this.nodeService.getAspects(nodeRef); + for (QName aspect : aspects) + { + aspectsJSON.add(nameToString(aspect, useShortQNames)); + } + + return aspectsJSON; + } + + /** + * + * @param nodeRef + * @return + */ + @SuppressWarnings("unchecked") + protected JSONArray allSetPermissionsToJSON(NodeRef nodeRef) + { + Set<AccessPermission> acls = permissionService.getAllSetPermissions(nodeRef); + JSONArray permissions = new JSONArray(); + for (AccessPermission permission : acls) + { + StringBuilder buf = new StringBuilder(64); + buf.append(permission.getAccessStatus()) + .append(';') + .append(permission.getAuthority()) + .append(';') + .append(permission.getPermission()) + .append(';').append(permission.isSetDirectly() ? "DIRECT" : "INHERITED"); + permissions.add(buf.toString()); + } + return permissions; + } + + /** + * + * @param qname + * @param isShortName + * @return + */ + private String nameToString(QName qname, boolean isShortName) + { + String result = null; + if (isShortName == true) + { + result = qname.toPrefixString(namespaceService); + } + else + { + result = qname.toString(); + } + return result; + } + + /** + * + * @param nodeRef + * @return + */ + private boolean isLocked(NodeRef nodeRef) + { + boolean locked = false; + + if (nodeService.hasAspect(nodeRef, ContentModel.ASPECT_LOCKABLE) == true) + { + LockStatus lockStatus = lockService.getLockStatus(nodeRef); + if (lockStatus == LockStatus.LOCKED || lockStatus == LockStatus.LOCK_OWNER) + { + locked = true; + } + } + + return locked; + } +} diff --git a/source/java/org/alfresco/repo/jscript/app/PropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/PropertyDecorator.java index 60a33542c5..b492536624 100644 --- a/source/java/org/alfresco/repo/jscript/app/PropertyDecorator.java +++ b/source/java/org/alfresco/repo/jscript/app/PropertyDecorator.java @@ -18,8 +18,12 @@ */ package org.alfresco.repo.jscript.app; -import org.alfresco.service.cmr.repository.NodeRef; import java.io.Serializable; +import java.util.Set; + +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.namespace.QName; +import org.json.simple.JSONAware; /** * Interface for property decorators used by ApplicationScriptUtils.toJSON() @@ -28,5 +32,7 @@ import java.io.Serializable; */ public interface PropertyDecorator { - Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value); + Set<QName> getPropertyNames(); + + JSONAware decorate(QName propertyName, NodeRef nodeRef, Serializable value); } diff --git a/source/java/org/alfresco/repo/jscript/app/TagPropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/TagPropertyDecorator.java index c2b12b69fb..28497df579 100644 --- a/source/java/org/alfresco/repo/jscript/app/TagPropertyDecorator.java +++ b/source/java/org/alfresco/repo/jscript/app/TagPropertyDecorator.java @@ -18,51 +18,45 @@ */ package org.alfresco.repo.jscript.app; -import org.alfresco.model.ContentModel; -import org.alfresco.service.ServiceRegistry; -import org.alfresco.service.cmr.repository.InvalidNodeRefException; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import java.io.Serializable; import java.util.Collection; -import java.util.LinkedHashMap; -import java.util.Map; + +import org.alfresco.model.ContentModel; +import org.alfresco.service.cmr.repository.InvalidNodeRefException; +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; +import org.json.simple.JSONArray; +import org.json.simple.JSONAware; +import org.json.simple.JSONObject; /** * Tag property decorator class. * * @author Mike Hatfield */ -public class TagPropertyDecorator implements PropertyDecorator +public class TagPropertyDecorator extends BasePropertyDecorator { private static Log logger = LogFactory.getLog(TagPropertyDecorator.class); - private ServiceRegistry services; - private NodeService nodeService = null; - - public void setServiceRegistry(ServiceRegistry serviceRegistry) - { - this.services = serviceRegistry; - this.nodeService = serviceRegistry.getNodeService(); - } - - public Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value) + /** + * @see org.alfresco.repo.jscript.app.PropertyDecorator#decorate(org.alfresco.service.namespace.QName, org.alfresco.service.cmr.repository.NodeRef, java.io.Serializable) + */ + @SuppressWarnings("unchecked") + public JSONAware decorate(QName propertyName, NodeRef nodeRef, Serializable value) { Collection<NodeRef> collection = (Collection<NodeRef>)value; - Object[] array = new Object[collection.size()]; - int index = 0; + JSONArray array = new JSONArray(); for (NodeRef obj : collection) { try { - Map<String, Serializable> jsonObj = new LinkedHashMap<String, Serializable>(2); + JSONObject jsonObj = new JSONObject(); jsonObj.put("name", this.nodeService.getProperty(obj, ContentModel.PROP_NAME)); jsonObj.put("nodeRef", obj.toString()); - array[index++] = jsonObj; + array.add(jsonObj); } catch (InvalidNodeRefException e) { diff --git a/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java b/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java index 6cddb58b53..680c94281c 100644 --- a/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java +++ b/source/java/org/alfresco/repo/jscript/app/UsernamePropertyDecorator.java @@ -19,40 +19,43 @@ package org.alfresco.repo.jscript.app; import java.io.Serializable; -import java.util.LinkedHashMap; import java.util.Map; import org.alfresco.model.ContentModel; -import org.alfresco.service.ServiceRegistry; import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; import org.alfresco.service.cmr.security.PersonService; import org.alfresco.service.namespace.QName; +import org.json.simple.JSONAware; +import org.json.simple.JSONObject; /** * Username property decorator class. * * @author Mike Hatfield */ -public class UsernamePropertyDecorator implements PropertyDecorator +public class UsernamePropertyDecorator extends BasePropertyDecorator { - private ServiceRegistry services; - private NodeService nodeService = null; + /** Person service */ private PersonService personService = null; - - public void setServiceRegistry(ServiceRegistry serviceRegistry) + + /** + * @param personService person service + */ + public void setPersonService(PersonService personService) { - this.services = serviceRegistry; - this.nodeService = serviceRegistry.getNodeService(); - this.personService = serviceRegistry.getPersonService(); + this.personService = personService; } - public Serializable decorate(NodeRef nodeRef, String propertyName, Serializable value) + /** + * @see org.alfresco.repo.jscript.app.PropertyDecorator#decorate(org.alfresco.service.cmr.repository.NodeRef, java.io.Serializable) + */ + @SuppressWarnings("unchecked") + public JSONAware decorate(QName propertyName, NodeRef nodeRef, Serializable value) { String username = value.toString(); String firstName = null; String lastName = null; - Map<String, Serializable> map = new LinkedHashMap<String, Serializable>(4); + JSONObject map = new JSONObject(); map.put("userName", username); if (this.personService.personExists(username)) @@ -70,12 +73,12 @@ public class UsernamePropertyDecorator implements PropertyDecorator else { map.put("isDeleted", true); - return (Serializable)map; + return map; } map.put("firstName", firstName); map.put("lastName", lastName); map.put("displayName", (firstName != null ? firstName + " " : "" + lastName != null ? lastName : "").replaceAll("^\\s+|\\s+$", "")); - return (Serializable)map; + return map; } } diff --git a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java index c0378416e5..4b35b0aaac 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/index/IndexInfo.java @@ -3945,7 +3945,7 @@ public class IndexInfo implements IndexMonitor Searcher searcher = new IndexSearcher(reader); try { - for (String stringRef : deletions) + for (String stringRef : containerDeletions) { TermQuery query = new TermQuery(new Term("ANCESTOR", stringRef)); Hits hits = searcher.search(query); diff --git a/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java b/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java index af39196908..a8ac7b6967 100644 --- a/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java +++ b/source/java/org/alfresco/repo/thumbnail/SimpleThumbnailer.java @@ -157,7 +157,7 @@ public class SimpleThumbnailer extends TransactionListenerAdapter implements Serializable value = this.nodeService.getProperty(nodeRef, ContentModel.PROP_CONTENT); ContentData contentData = DefaultTypeConverter.INSTANCE.convert(ContentData.class, value); List<ThumbnailDefinition> thumbnailDefinitions = this.thumbnailService.getThumbnailRegistry() - .getThumbnailDefinitions(contentData.getContentUrl(), contentData.getMimetype(), contentData.getSize()); + .getThumbnailDefinitions(contentData.getMimetype(), contentData.getSize()); for (final ThumbnailDefinition thumbnailDefinition : thumbnailDefinitions) { final NodeRef existingThumbnail = this.thumbnailService.getThumbnailByName(nodeRef, diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java index 71018cb1bb..a144a11e76 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailRegistry.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -66,8 +66,8 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi /** Map of thumbnail definition */ private Map<String, ThumbnailDefinition> thumbnailDefinitions = new HashMap<String, ThumbnailDefinition>(); - /** Cache to store mimetype to thumbnailDefinition mapping */ - private Map<String, List<ThumbnailDefinition>> mimetypeMap = new HashMap<String, List<ThumbnailDefinition>>(17); + /** Cache to store mimetype to thumbnailDefinition mapping with max size limit */ + private Map<String, List<ThumbnailDefinitionLimits>> mimetypeMap = new HashMap<String, List<ThumbnailDefinitionLimits>>(17); private ThumbnailRenditionConvertor thumbnailRenditionConvertor; @@ -196,23 +196,24 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi */ public List<ThumbnailDefinition> getThumbnailDefinitions(String mimetype) { - return getThumbnailDefinitions(null, mimetype, -1); + return getThumbnailDefinitions(mimetype, -1); } - public List<ThumbnailDefinition> getThumbnailDefinitions(String sourceUrl, String mimetype, long sourceSize) + public List<ThumbnailDefinition> getThumbnailDefinitions(String mimetype, long sourceSize) { - List<ThumbnailDefinition> result = this.mimetypeMap.get(mimetype); + List<ThumbnailDefinitionLimits> thumbnailDefinitionsLimitsForMimetype = this.mimetypeMap.get(mimetype); - if (result == null) + if (thumbnailDefinitionsLimitsForMimetype == null) { boolean foundAtLeastOneTransformer = false; - result = new ArrayList<ThumbnailDefinition>(7); + thumbnailDefinitionsLimitsForMimetype = new ArrayList<ThumbnailDefinitionLimits>(7); for (ThumbnailDefinition thumbnailDefinition : this.thumbnailDefinitions.values()) { - if (isThumbnailDefinitionAvailable(sourceUrl, mimetype, sourceSize, thumbnailDefinition)) + long maxSourceSizeBytes = getMaxSourceSizeBytes(mimetype, thumbnailDefinition); + if (maxSourceSizeBytes != 0) { - result.add(thumbnailDefinition); + thumbnailDefinitionsLimitsForMimetype.add(new ThumbnailDefinitionLimits(thumbnailDefinition, maxSourceSizeBytes)); foundAtLeastOneTransformer = true; } } @@ -229,7 +230,18 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi // been launched and that new transformers are available. if (foundAtLeastOneTransformer) { - this.mimetypeMap.put(mimetype, result); + this.mimetypeMap.put(mimetype, thumbnailDefinitionsLimitsForMimetype); + } + } + + // Only return ThumbnailDefinition for this specific source - may be limited on size. + List<ThumbnailDefinition> result = new ArrayList<ThumbnailDefinition>(thumbnailDefinitionsLimitsForMimetype.size()); + for (ThumbnailDefinitionLimits thumbnailDefinitionLimits: thumbnailDefinitionsLimitsForMimetype) + { + long maxSourceSizeBytes = thumbnailDefinitionLimits.getMaxSourceSizeBytes(); + if (sourceSize <= 0 || maxSourceSizeBytes < 0 || maxSourceSizeBytes >= sourceSize) + { + result.add(thumbnailDefinitionLimits.getThumbnailDefinition()); } } @@ -253,11 +265,11 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi * is able to thumbnail the source mimetype. Typically used with Thumbnail Definitions * retrieved by name, and/or when dealing with transient {@link ContentTransformer}s. * @param sourceUrl The URL of the source (optional) - * @param sourceMimeType The source mimetype + * @param sourceMimetype The source mimetype * @param sourceSize the size (in bytes) of the source. Use -1 if unknown. * @param thumbnailDefinition The {@link ThumbnailDefinition} to check for */ - public boolean isThumbnailDefinitionAvailable(String sourceUrl, String sourceMimeType, long sourceSize, ThumbnailDefinition thumbnailDefinition) + public boolean isThumbnailDefinitionAvailable(String sourceUrl, String sourceMimetype, long sourceSize, ThumbnailDefinition thumbnailDefinition) { // Log the following getTransform() as trace so we can see the wood for the trees boolean orig = TransformerDebug.setDebugOutput(false); @@ -265,7 +277,7 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi { return this.contentService.getTransformer( sourceUrl, - sourceMimeType, + sourceMimetype, sourceSize, thumbnailDefinition.getMimetype(), thumbnailDefinition.getTransformationOptions() ) != null; @@ -276,6 +288,28 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi } } + /** + * Returns the maximum source size of any content that may transformed between the supplied + * sourceMimetype and thumbnailDefinition's targetMimetype using its transformation options. + * @param sourceMimetype + * @param thumbnailDefinition + * @return 0 if there are no transformers, -1 if there is no limit or if positive the size in bytes. + */ + public long getMaxSourceSizeBytes(String sourceMimetype, ThumbnailDefinition thumbnailDefinition) + { + // Log the following getTransform() as trace so we can see the wood for the trees + boolean orig = TransformerDebug.setDebugOutput(false); + try + { + return contentService.getMaxSourceSizeBytes(sourceMimetype, + thumbnailDefinition.getMimetype(), thumbnailDefinition.getTransformationOptions()); + } + finally + { + TransformerDebug.setDebugOutput(orig); + } + } + /** * Add a thumbnail details * @@ -344,4 +378,30 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi // Intentionally empty } } + + /** + * Links transformer limits (such as maximum size) to a ThumbnailDefinition. + * + */ + private class ThumbnailDefinitionLimits + { + private ThumbnailDefinition thumbnailDefinition; + private long maxSourceSizeBytes; + + public ThumbnailDefinitionLimits(ThumbnailDefinition thumbnailDefinition, long maxSourceSizeBytes) + { + this.thumbnailDefinition = thumbnailDefinition; + this.maxSourceSizeBytes = maxSourceSizeBytes; + } + + public ThumbnailDefinition getThumbnailDefinition() + { + return thumbnailDefinition; + } + + public long getMaxSourceSizeBytes() + { + return maxSourceSizeBytes; + } + } } diff --git a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java index be2ff0c000..ec95256c2b 100644 --- a/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java +++ b/source/java/org/alfresco/repo/thumbnail/ThumbnailServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -724,7 +724,7 @@ public class ThumbnailServiceImplTest extends BaseAlfrescoSpringTest public void testRegistry() { ThumbnailRegistry thumbnailRegistry = this.thumbnailService.getThumbnailRegistry(); - List<ThumbnailDefinition> defs = thumbnailRegistry.getThumbnailDefinitions(null, MimetypeMap.MIMETYPE_HTML, -1); + List<ThumbnailDefinition> defs = thumbnailRegistry.getThumbnailDefinitions(MimetypeMap.MIMETYPE_HTML, -1); System.out.println("Definitions ..."); for (ThumbnailDefinition def : defs) { diff --git a/source/java/org/alfresco/repo/version/ContentServiceImplTest.java b/source/java/org/alfresco/repo/version/ContentServiceImplTest.java index d6a1ca9687..241f0824f9 100644 --- a/source/java/org/alfresco/repo/version/ContentServiceImplTest.java +++ b/source/java/org/alfresco/repo/version/ContentServiceImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2010 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -19,10 +19,12 @@ package org.alfresco.repo.version; import org.alfresco.model.ContentModel; +import org.alfresco.repo.content.transform.ContentTransformer; 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.TransformationOptions; import org.alfresco.service.cmr.version.Version; /** @@ -109,4 +111,39 @@ public class ContentServiceImplTest extends BaseVersionStoreTest // An exception should be raised } } + + public void testGetTransformer0() + { + ContentTransformer transformer = contentService.getTransformer("test", "application/vnd.ms-excel", 0, + "application/x-shockwave-flash", new TransformationOptions()); + assertTrue("Found have found a transformer for 0 bytes", transformer != null); + } + + public void testGetTransformer10K() + { + ContentTransformer transformer = contentService.getTransformer("test", "application/vnd.ms-excel", 1024*10, + "application/x-shockwave-flash", new TransformationOptions()); + assertTrue("Found have found a transformer for 10 K", transformer != null); + } + + public void testGetTransformer1M() + { + ContentTransformer transformer = contentService.getTransformer("test", "application/vnd.ms-excel", 1024*1024, + "application/x-shockwave-flash", new TransformationOptions()); + assertTrue("Found have found a transformer for 1M", transformer != null); + } + + public void testGetTransformer10M() + { + ContentTransformer transformer = contentService.getTransformer("test", "application/vnd.ms-excel", 1024*1024*10, + "application/x-shockwave-flash", new TransformationOptions()); + assertTrue("Found NOT have found a transformer for 10M as the is a 1M limit on xsl mimetype", transformer == null); + } + + public void testGetMaxSourceSizeByes() + { + long maxSourceSizeBytes = contentService.getMaxSourceSizeBytes("application/vnd.ms-excel", + "application/x-shockwave-flash", new TransformationOptions()); + assertEquals("Found have found a transformer that can handle 1M", 1024*1024, maxSourceSizeBytes); + } } diff --git a/source/java/org/alfresco/service/cmr/repository/ContentService.java b/source/java/org/alfresco/service/cmr/repository/ContentService.java index dc1388a9d3..50f517e2ff 100644 --- a/source/java/org/alfresco/service/cmr/repository/ContentService.java +++ b/source/java/org/alfresco/service/cmr/repository/ContentService.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2005-2011 Alfresco Software Limited. + * Copyright (C) 2005-2012 Alfresco Software Limited. * * This file is part of Alfresco * @@ -249,6 +249,17 @@ public interface ContentService */ public ContentTransformer getTransformer(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** + * Returns the maximum source size of any content that may transformed between the supplied + * mimetypes using the supplied options. + * @param sourceMimetype + * @param targetMimetype + * @param options + * @return 0 if there are no transformers, -1 if there is no limit or if positive number the size in bytes. + */ + @Auditable(parameters = {"sourceMimetype", "targetMimetype", "options"}) + public long getMaxSourceSizeBytes(String sourceMimetype, String targetMimetype, TransformationOptions options); + /** * Fetch all the transformers that are capable of transforming the content in the * given source mimetype to the given target mimetype with the provided transformation