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 @@ + + + + + + + + + + + + + + + + + + @@ -469,6 +487,60 @@ #{item} + + + + + + + + + + + + + + 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 @@ - - + + - - + + + + + @@ -1777,11 +1780,14 @@ 0 4004 4005 - - + + - - + + + + + @@ -1920,8 +1926,11 @@ - - + + + + + @@ -2301,8 +2310,11 @@ - - + + + + + @@ -2847,8 +2859,11 @@ - - + + + + + @@ -2906,8 +2921,11 @@ - - + + + + + @@ -2942,8 +2960,11 @@ - - + + + + + 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 @@ - - - - - - - - - - - - - - + + + - - - appUtils - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + CancelCheckOut @@ -279,7 +244,51 @@ Delete Write - + + + + + + + + + + + + + + + + + + cm:creator + cm:modifier + cm:workingCopyOwner + cm:lockOwner + cm:owner + + + + + + + + cm:content + sys:locale + sys:node-uuid + sys:store-protocol + sys:node-dbid + sys:store-identifier + + + + + + + + + + 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 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 { - 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 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 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 storeRefsList = nodeService.getStores(); - Set storeRefs = new HashSet(); - 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 element : mimetypeMappings.entrySet()) { String oldMimetype = element.getKey(); String newMimetype = element.getValue(); - + // First check if the mimetype is used at all Pair 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 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 { - 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 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 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 { - 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 nodeIds = patchDAO.getNodesByTypeQNameId(qnameId, lower, lower + BATCH_SIZE); + nodeDAO.touchNodes(nodeDAO.getCurrentTransactionId(true), nodeIds); + return nodeIds.size(); } + else if ("ASPECT".equals(reindexClass)) + { + List 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 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 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}

* @@ -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 transformerIterator = transformers.iterator(); Iterator 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 *

* 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 stack = new ArrayDeque(); + private final Deque isTransformableStack = new ArrayDeque(); private boolean debugOutput = true; public static Deque getStack() @@ -85,6 +85,11 @@ public class TransformerDebug { return threadInfo.get().debugOutput; } + + public static Deque 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 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 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 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 nodeIds) + { + // limit in clause to 1000 node ids + int batchSize = 1000; + + int touched = 0; + ArrayList batch = new ArrayList(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 cm:auditable 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 nodeIds); + /** * @param nodeTypeQName the new type QName for the node or null to keep the existing one * @param nodeLocale the new locale for the node or null 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 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 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 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 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 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 getNodesByTypeQNameId(Long typeQNameId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("qnameId", typeQNameId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List) template.selectList(SELECT_NODES_BY_TYPE_QNAME, params); + } + + @SuppressWarnings("unchecked") + @Override + public List getNodesByTypeUriId(Long nsId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("nsId", nsId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List) template.selectList(SELECT_NODES_BY_TYPE_URI, params); + } + + @SuppressWarnings("unchecked") + @Override + public List getNodesByAspectQNameId(Long aspectQNameId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("qnameId", aspectQNameId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List) template.selectList(SELECT_NODES_BY_ASPECT_QNAME, params); + } + + @SuppressWarnings("unchecked") + @Override + public List getNodesByContentPropertyMimetypeId(Long mimetypeId, Long minNodeId, Long maxNodeId) + { + Map params = new HashMap(); + params.put("mimetypeId", mimetypeId); + params.put("minNodeId", minNodeId); + params.put("maxNodeId", maxNodeId); + return (List) 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 . - */ -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 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 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 permissionsJSON = new LinkedHashMap(3); - if (node.hasPermission("ReadPermissions")) - { - permissionsJSON.put("roles", node.retrieveAllSetPermissions(false, true)); - } - permissionsJSON.put("inherited", node.inheritsPermissions()); - Map userPermissionJSON = new LinkedHashMap(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 nodeProperties = this.nodeService.getProperties(nodeRef); - json.put("properties", this.parseToJSON(nodeRef, nodeProperties, useShortQNames)); - - // add aspects as an array - Set nodeAspects = this.nodeService.getAspects(nodeRef); - if (useShortQNames) - { - Set nodeAspectsShortQNames = new LinkedHashSet(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 parseToJSON(NodeRef nodeRef, Map properties, boolean useShortQNames) - { - Map json = new LinkedHashMap(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 dateObj = new LinkedHashMap(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 - *

- * For a container node, this method returns an empty string - *

- */ - 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 . + */ +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 + *

+ * For a container node, this method returns an empty string + *

+ */ + 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 model, boolean secure) + private Object executeScriptImpl(Script script, Map 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 thumbnailDefinitions = thumbnailService.getThumbnailRegistry().getThumbnailDefinitions(contentReader.getContentUrl(), mimetype, contentReader.getSize()); + List 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 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 getPropertyNames() + { + return propertyNames; + } + + public void setPropertyName(String propertyName) + { + propertyNames = new HashSet(1); + propertyNames.add(QName.createQName(propertyName, namespaceService)); + } + + public void setPropertyNames(Set propertyNames) + { + this.propertyNames = new HashSet(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 collection = (Collection)value; - Object[] array = new Object[collection.size()]; - int index = 0; + JSONArray array = new JSONArray(); for (NodeRef obj : collection) { try { - Map jsonObj = new LinkedHashMap(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 . + */ +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 propertyDecorators = new HashMap(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 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 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 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 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 collection = (Collection)value; - Object[] array = new Object[collection.size()]; - int index = 0; + JSONArray array = new JSONArray(); for (NodeRef obj : collection) { try { - Map jsonObj = new LinkedHashMap(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 map = new LinkedHashMap(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 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 thumbnailDefinitions = new HashMap(); - /** Cache to store mimetype to thumbnailDefinition mapping */ - private Map> mimetypeMap = new HashMap>(17); + /** Cache to store mimetype to thumbnailDefinition mapping with max size limit */ + private Map> mimetypeMap = new HashMap>(17); private ThumbnailRenditionConvertor thumbnailRenditionConvertor; @@ -196,23 +196,24 @@ public class ThumbnailRegistry implements ApplicationContextAware, ApplicationLi */ public List getThumbnailDefinitions(String mimetype) { - return getThumbnailDefinitions(null, mimetype, -1); + return getThumbnailDefinitions(mimetype, -1); } - public List getThumbnailDefinitions(String sourceUrl, String mimetype, long sourceSize) + public List getThumbnailDefinitions(String mimetype, long sourceSize) { - List result = this.mimetypeMap.get(mimetype); + List thumbnailDefinitionsLimitsForMimetype = this.mimetypeMap.get(mimetype); - if (result == null) + if (thumbnailDefinitionsLimitsForMimetype == null) { boolean foundAtLeastOneTransformer = false; - result = new ArrayList(7); + thumbnailDefinitionsLimitsForMimetype = new ArrayList(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 result = new ArrayList(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 defs = thumbnailRegistry.getThumbnailDefinitions(null, MimetypeMap.MIMETYPE_HTML, -1); + List 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