urls = makeUrls(1000);
- // Delete them
- dao.deleteContentUrls(urls);
- // Now iterate over them in the same transaction
- ContentUrlDAO.ContentUrlHandler handler = new ContentUrlDAO.ContentUrlHandler()
- {
- public void handle(String contentUrl)
- {
- urls.remove(contentUrl);
- }
- };
- dao.getAllContentUrls(handler);
- // All the URLs previously deleted will not have been removed from the Set
- assertEquals("Specific content URLs were not deleted", 1000, urls.size());
-
- txn.commit();
- }
- catch (Throwable e)
- {
- try { txn.rollback(); } catch (Throwable ee) {}
- throw e;
- }
- }
-
- public void testDeleteAllContentUrls() throws Throwable
- {
- UserTransaction txn = transactionService.getUserTransaction();
- try
- {
- txn.begin();
-
- makeUrls(1000);
- // Delete them
- dao.deleteAllContentUrls();
- // Check that there are none left
-
- // Now iterate over them in the same transaction
- ContentUrlDAO.ContentUrlHandler handler = new ContentUrlDAO.ContentUrlHandler()
- {
- public void handle(String contentUrl)
- {
- fail("There should not be any URLs remaining.");
- }
- };
- dao.getAllContentUrls(handler);
-
- txn.commit();
- }
- catch (Throwable e)
- {
- try { txn.rollback(); } catch (Throwable ee) {}
- throw e;
- }
- }
-}
diff --git a/source/java/org/alfresco/repo/domain/NodePropertyValue.java b/source/java/org/alfresco/repo/domain/NodePropertyValue.java
index f1860d51d3..b69ffbf8f0 100644
--- a/source/java/org/alfresco/repo/domain/NodePropertyValue.java
+++ b/source/java/org/alfresco/repo/domain/NodePropertyValue.java
@@ -266,13 +266,27 @@ public class NodePropertyValue implements Cloneable, Serializable
@Override
protected ValueType getPersistedType(Serializable value)
{
- return ValueType.STRING;
+ if (value instanceof Long)
+ {
+ return ValueType.LONG;
+ }
+ else
+ {
+ return ValueType.STRING;
+ }
}
@Override
Serializable convert(Serializable value)
{
- return DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
+ if (value instanceof Long)
+ {
+ return value;
+ }
+ else
+ {
+ return DefaultTypeConverter.INSTANCE.convert(ContentData.class, value);
+ }
}
},
NODEREF
diff --git a/source/java/org/alfresco/repo/domain/contentclean/ContentCleanDAO.java b/source/java/org/alfresco/repo/domain/contentclean/ContentCleanDAO.java
new file mode 100644
index 0000000000..a92ace1e36
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentclean/ContentCleanDAO.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentclean;
+
+
+/**
+ * DAO services for alf_contentclean table.
+ * This DAO is geared towards bulk processing of content URLs.
+ *
+ * Content URLs are lowercased and CRC'ed
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public interface ContentCleanDAO
+{
+ /**
+ * Interface callback for putting and getting content URL values
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+ public interface ContentUrlBatchProcessor
+ {
+ void start();
+ void processContentUrl(String contentUrl);
+ void end();
+ }
+
+ void cleanUp();
+
+ ContentUrlBatchProcessor getUrlInserter();
+
+ ContentUrlBatchProcessor getUrlRemover();
+
+ void listAllUrls(ContentUrlBatchProcessor batchProcessor);
+}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/ContentUrlImpl.java b/source/java/org/alfresco/repo/domain/contentclean/ContentCleanEntity.java
similarity index 52%
rename from source/java/org/alfresco/repo/domain/hibernate/ContentUrlImpl.java
rename to source/java/org/alfresco/repo/domain/contentclean/ContentCleanEntity.java
index 5335b3cc8e..2d42359376 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/ContentUrlImpl.java
+++ b/source/java/org/alfresco/repo/domain/contentclean/ContentCleanEntity.java
@@ -1,86 +1,89 @@
-/*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
-
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- * As a special exception to the terms and conditions of version 2.0 of
- * the GPL, you may redistribute this Program in connection with Free/Libre
- * and Open Source Software ("FLOSS") applications as described in Alfresco's
- * FLOSS exception. You should have recieved a copy of the text describing
- * the FLOSS exception, and it is also available here:
- * http://www.alfresco.com/legal/licensing
- */
-package org.alfresco.repo.domain.hibernate;
-
-import java.io.Serializable;
-
-import org.alfresco.repo.domain.ContentUrl;
-
-/**
- * Bean containing all the persistence data representing a Content Url.
- *
- * This implementation of the {@link org.alfresco.repo.domain.Node Node} interface is
- * Hibernate specific.
- *
- * @author Derek Hulley
- * @since 2.0
- */
-public class ContentUrlImpl extends LifecycleAdapter implements ContentUrl, Serializable
-{
- private static final long serialVersionUID = -7368859912728834288L;
-
- private Long id;
- private String contentUrl;
-// private boolean isOrphaned;
-
- public ContentUrlImpl()
- {
-// isOrphaned = false;
- }
-
- public Long getId()
- {
- return id;
- }
-
- /**
- * For Hibernate Use
- */
- @SuppressWarnings("unused")
- private void setId(Long id)
- {
- this.id = id;
- }
-
- public String getContentUrl()
- {
- return contentUrl;
- }
-
- public void setContentUrl(String contentUrl)
- {
- this.contentUrl = contentUrl;
- }
-//
-// public boolean isOrphaned()
-// {
-// return isOrphaned;
-// }
-//
-// public void setOrphaned(boolean isOrphaned)
-// {
-// this.isOrphaned = isOrphaned;
-// }
-}
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentclean;
+
+import org.alfresco.util.EqualsHelper;
+
+/**
+ * Entity bean for alf_content_url table.
+ *
+ * These are unique (see {@link #equals(Object) equals} and {@link #hashCode() hashCode}) based
+ * on the {@link #getContentUrl() content URL} value.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class ContentCleanEntity
+{
+ private String contentUrl;
+
+ public ContentCleanEntity()
+ {
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (contentUrl == null ? 0 : contentUrl.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof ContentCleanEntity)
+ {
+ ContentCleanEntity that = (ContentCleanEntity) obj;
+ return EqualsHelper.nullSafeEquals(this.contentUrl, that.contentUrl);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(512);
+ sb.append("ContentCleanEntity")
+ .append("[ contentUrl=").append(contentUrl)
+ .append("]");
+ return sb.toString();
+ }
+
+ public String getContentUrl()
+ {
+ return contentUrl;
+ }
+
+ public void setContentUrl(String contentUrl)
+ {
+ this.contentUrl = contentUrl;
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/contentclean/ibatis/ContentCleanDAOImpl.java b/source/java/org/alfresco/repo/domain/contentclean/ibatis/ContentCleanDAOImpl.java
new file mode 100644
index 0000000000..94e617fa8b
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentclean/ibatis/ContentCleanDAOImpl.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentclean.ibatis;
+
+import java.sql.SQLException;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.domain.contentclean.ContentCleanDAO;
+import org.alfresco.repo.domain.contentclean.ContentCleanEntity;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.orm.ibatis.SqlMapClientTemplate;
+
+import com.ibatis.sqlmap.client.SqlMapClient;
+import com.ibatis.sqlmap.client.event.RowHandler;
+
+/**
+ * iBatis-specific implementation of the Content Cleaner DAO.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class ContentCleanDAOImpl implements ContentCleanDAO
+{
+ private static Log logger = LogFactory.getLog(ContentCleanDAOImpl.class);
+
+ private static final int DEFAULT_BATCH_SIZE = 50;
+
+ private static final String INSERT_CONTENT_CLEAN = "insert.ContentCleanUrl";
+ private static final String SELECT_CONTENT_CLEAN_URLS = "select.ContentCleanUrls";
+ private static final String DELETE_CONTENT_CLEAN_BY_URL = "delete.ContentCleanUrl";
+ private static final String DELETE_CONTENT_CLEAN = "delete.ContentCleanUrls";
+
+ private SqlMapClientTemplate template;
+
+ public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate)
+ {
+ this.template = sqlMapClientTemplate;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ContentUrlBatchProcessor getUrlInserter()
+ {
+ final SqlMapClient sqlMapClient = template.getSqlMapClient();
+ ContentUrlBatchProcessor processor = new ContentUrlBatchProcessor()
+ {
+ private int count = 0;
+ private int total = 0;
+
+ public void start()
+ {
+ try
+ {
+ sqlMapClient.startBatch();
+ count = 0;
+ }
+ catch (SQLException e)
+ {
+ // Batches not supported, so don't do batching
+ count = -1;
+ }
+ }
+ public void processContentUrl(String contentUrl)
+ {
+ ContentCleanEntity contentCleanEntity = new ContentCleanEntity();
+ contentCleanEntity.setContentUrl(contentUrl);
+ template.insert(INSERT_CONTENT_CLEAN, contentCleanEntity);
+ // Write the batch
+ executeBatch();
+ total++;
+ }
+ public void end()
+ {
+ // Write the batch
+ executeBatch();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" Inserted " + total + " content URLs (FINISHED)");
+ }
+ }
+ private void executeBatch()
+ {
+ // Are we batching?
+ if (count > -1)
+ {
+ // Write the batch, if required
+ if (++count >= DEFAULT_BATCH_SIZE)
+ {
+ try
+ {
+ sqlMapClient.executeBatch();
+ sqlMapClient.startBatch();
+ }
+ catch (SQLException e)
+ {
+ throw new AlfrescoRuntimeException("Failed to execute batch", e);
+ }
+ count = 0;
+ }
+ }
+ if (logger.isDebugEnabled() && (total == 0 || (total % 1000 == 0) ))
+ {
+ logger.debug(" Inserted " + total + " content URLs");
+ }
+ }
+ };
+ // Done
+ return processor;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public ContentUrlBatchProcessor getUrlRemover()
+ {
+ final SqlMapClient sqlMapClient = template.getSqlMapClient();
+ ContentUrlBatchProcessor processor = new ContentUrlBatchProcessor()
+ {
+ private int count = 0;
+ private int total = 0;
+
+ public void start()
+ {
+ try
+ {
+ sqlMapClient.startBatch();
+ count = 0;
+ }
+ catch (SQLException e)
+ {
+ // Batches not supported, so don't do batching
+ count = -1;
+ }
+ }
+ public void processContentUrl(String contentUrl)
+ {
+ ContentCleanEntity contentCleanEntity = new ContentCleanEntity();
+ contentCleanEntity.setContentUrl(contentUrl);
+ template.delete(DELETE_CONTENT_CLEAN_BY_URL, contentCleanEntity);
+ // Write the batch
+ executeBatch();
+ total++;
+ }
+ public void end()
+ {
+ // Write the batch
+ executeBatch();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" Removed " + total + " content URLs (FINISHED)");
+ }
+ }
+ private void executeBatch()
+ {
+ // Are we batching?
+ if (count > -1)
+ {
+ // Write the batch, if required
+ if (++count >= DEFAULT_BATCH_SIZE)
+ {
+ try
+ {
+ sqlMapClient.executeBatch();
+ sqlMapClient.startBatch();
+ }
+ catch (SQLException e)
+ {
+ throw new AlfrescoRuntimeException("Failed to execute batch", e);
+ }
+ count = 0;
+ }
+ }
+ if (logger.isDebugEnabled() && (total == 0 || (total % 1000 == 0) ))
+ {
+ logger.debug(" Removed " + total + " content URLs");
+ }
+ }
+ };
+ // Done
+ return processor;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void listAllUrls(ContentUrlBatchProcessor batchProcessor)
+ {
+ ListAllRowHandler rowHandler = new ListAllRowHandler(batchProcessor);
+
+ batchProcessor.start();
+ template.queryWithRowHandler(SELECT_CONTENT_CLEAN_URLS, rowHandler);
+ batchProcessor.end();
+ if (logger.isDebugEnabled())
+ {
+ logger.debug(" Listed " + rowHandler.total + " content URLs");
+ }
+ }
+
+ /**
+ * Row handler for listing all content clean URLs
+ * @author Derek Hulley
+ * @since 3.2
+ */
+ private static class ListAllRowHandler implements RowHandler
+ {
+ private final ContentUrlBatchProcessor batchProcessor;
+ private int total = 0;
+ private ListAllRowHandler(ContentUrlBatchProcessor batchProcessor)
+ {
+ this.batchProcessor = batchProcessor;
+ }
+ public void handleRow(Object valueObject)
+ {
+ batchProcessor.processContentUrl((String)valueObject);
+ total++;
+ if (logger.isDebugEnabled() && (total == 0 || (total % 1000 == 0) ))
+ {
+ logger.debug(" Listed " + total + " content URLs");
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void cleanUp()
+ {
+ template.delete(DELETE_CONTENT_CLEAN);
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/contentdata/AbstractContentDataDAOImpl.java b/source/java/org/alfresco/repo/domain/contentdata/AbstractContentDataDAOImpl.java
new file mode 100644
index 0000000000..5f05f0772f
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentdata/AbstractContentDataDAOImpl.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentdata;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+import org.alfresco.repo.cache.SimpleCache;
+import org.alfresco.repo.content.cleanup.EagerContentStoreCleaner;
+import org.alfresco.repo.domain.LocaleDAO;
+import org.alfresco.repo.domain.encoding.EncodingDAO;
+import org.alfresco.repo.domain.mimetype.MimetypeDAO;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.util.Pair;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.springframework.dao.ConcurrencyFailureException;
+
+/**
+ * Abstract implementation for ContentData DAO.
+ *
+ * This provides basic services such as caching, but defers to the underlying implementation
+ * for CRUD operations.
+ *
+ * The DAO deals in {@link ContentData} instances. The cache is primarily present to decode
+ * IDs into ContentData
instances.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public abstract class AbstractContentDataDAOImpl implements ContentDataDAO
+{
+ private static Log logger = LogFactory.getLog(AbstractContentDataDAOImpl.class);
+
+ private static final Long CACHE_NULL_LONG = Long.MIN_VALUE;
+
+ private MimetypeDAO mimetypeDAO;
+ private EncodingDAO encodingDAO;
+ private LocaleDAO localeDAO;
+ private EagerContentStoreCleaner contentStoreCleaner;
+ private SimpleCache contentDataCache;
+
+ public void setMimetypeDAO(MimetypeDAO mimetypeDAO)
+ {
+ this.mimetypeDAO = mimetypeDAO;
+ }
+
+ public void setEncodingDAO(EncodingDAO encodingDAO)
+ {
+ this.encodingDAO = encodingDAO;
+ }
+
+ public void setLocaleDAO(LocaleDAO localeDAO)
+ {
+ this.localeDAO = localeDAO;
+ }
+
+ /**
+ * Set this property to enable eager cleanup of orphaned content.
+ *
+ * @param contentStoreCleaner an eager cleaner (may be null)
+ */
+ public void setContentStoreCleaner(EagerContentStoreCleaner contentStoreCleaner)
+ {
+ this.contentStoreCleaner = contentStoreCleaner;
+ }
+
+ /**
+ * @param contentDataCache the cache of IDs to ContentData and vice versa
+ */
+ public void setContentDataCache(SimpleCache contentDataCache)
+ {
+ this.contentDataCache = contentDataCache;
+ }
+
+ /**
+ * Register new content for post-rollback handling
+ */
+ protected void registerNewContentUrl(String contentUrl)
+ {
+ if (contentStoreCleaner != null)
+ {
+ contentStoreCleaner.registerNewContentUrl(contentUrl);
+ }
+ }
+
+ /**
+ * Register orphaned content for post-commit handling
+ */
+ protected void registerOrphanedContentUrl(String contentUrl)
+ {
+ if (contentStoreCleaner != null)
+ {
+ contentStoreCleaner.registerOrphanedContentUrl(contentUrl);
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Pair createContentData(ContentData contentData)
+ {
+ /*
+ * TODO: Cache
+ */
+ ContentDataEntity contentDataEntity = createContentDataEntity(contentData);
+ // Done
+ return new Pair(contentDataEntity.getId(), contentData);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public Pair getContentData(Long id)
+ {
+ /*
+ * TODO: Cache
+ */
+ ContentDataEntity contentDataEntity = getContentDataEntity(id);
+ if (contentDataEntity == null)
+ {
+ return null;
+ }
+ // Convert back to ContentData
+ ContentData contentData = makeContentData(contentDataEntity);
+ // Done
+ return new Pair(id, contentData);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ public void deleteContentData(Long id)
+ {
+ int deleted = deleteContentDataEntity(id);
+ if (deleted < 1)
+ {
+ throw new ConcurrencyFailureException("ContetntData with ID " + id + " no longer exists");
+ }
+ return;
+ }
+
+ /**
+ * Translates this instance into an externally-usable ContentData
instance.
+ */
+ private ContentData makeContentData(ContentDataEntity contentDataEntity)
+ {
+ // Decode content URL
+ String contentUrl = contentDataEntity.getContentUrl();
+ long size = contentDataEntity.getSize() == null ? 0L : contentDataEntity.getSize().longValue();
+ // Decode mimetype
+ Long mimetypeId = contentDataEntity.getMimetypeId();
+ String mimetype = null;
+ if (mimetypeId != null)
+ {
+ mimetype = mimetypeDAO.getMimetype(mimetypeId).getSecond();
+ }
+ // Decode encoding
+ Long encodingId = contentDataEntity.getEncodingId();
+ String encoding = null;
+ if (encodingId != null)
+ {
+ encoding = encodingDAO.getEncoding(encodingId).getSecond();
+ }
+ // Decode locale
+ Long localeId = contentDataEntity.getLocaleId();
+ Locale locale = null;
+ if (localeId != null)
+ {
+ locale = localeDAO.getLocalePair(localeId).getSecond();
+ }
+ // Build the ContentData
+ ContentData contentData = new ContentData(contentUrl, mimetype, size, encoding, locale);
+ // Done
+ return contentData;
+ }
+
+ /**
+ * Translates the {@link ContentData} into persistable values using the helper DAOs
+ */
+ private ContentDataEntity createContentDataEntity(ContentData contentData)
+ {
+ // Resolve the content URL
+ Long contentUrlId = null;
+ String contentUrl = contentData.getContentUrl();
+ long size = contentData.getSize();
+ if (contentUrl != null)
+ {
+ // We must find or create the ContentUrlEntity
+ contentUrlId = getOrCreateContentUrlEntity(contentUrl, size).getId();
+ }
+ // Resolve the mimetype
+ Long mimetypeId = null;
+ String mimetype = contentData.getMimetype();
+ if (mimetype != null)
+ {
+ mimetypeId = mimetypeDAO.getOrCreateMimetype(mimetype).getFirst();
+ }
+ // Resolve the encoding
+ Long encodingId = null;
+ String encoding = contentData.getEncoding();
+ if (encoding != null)
+ {
+ encodingId = encodingDAO.getOrCreateEncoding(encoding).getFirst();
+ }
+ // Resolve the locale
+ Long localeId = null;
+ Locale locale = contentData.getLocale();
+ if (locale != null)
+ {
+ localeId = localeDAO.getOrCreateLocalePair(locale).getFirst();
+ }
+
+ // Create ContentDataEntity
+ ContentDataEntity contentDataEntity = createContentDataEntity(contentUrlId, mimetypeId, encodingId, localeId);
+ // Done
+ return contentDataEntity;
+ }
+
+ /**
+ * Caching method that creates an entity for content_url_entity.
+ */
+ private ContentUrlEntity getOrCreateContentUrlEntity(String contentUrl, long size)
+ {
+ /*
+ * TODO: Check for cache requirements
+ */
+ // Create the content URL entity
+ ContentUrlEntity contentUrlEntity = getContentUrlEntity(contentUrl);
+ // If it exists, then we can just re-use it, but check that the size is consistent
+ if (contentUrlEntity != null)
+ {
+ // Reuse it
+ long existingSize = contentUrlEntity.getSize();
+ if (size != existingSize)
+ {
+ logger.warn(
+ "Re-using Content URL, but size is mismatched: \n" +
+ " Inbound: " + contentUrl + "\n" +
+ " Existing: " + contentUrlEntity);
+ }
+ }
+ else
+ {
+ // Create it
+ contentUrlEntity = createContentUrlEntity(contentUrl, size);
+ }
+ // Done
+ return contentUrlEntity;
+ }
+
+ /**
+ * @param contentUrl the content URL to create or search for
+ */
+ protected abstract ContentUrlEntity createContentUrlEntity(String contentUrl, long size);
+
+ /**
+ * @param id the ID of the content url entity
+ * @return Return the entity or null if it doesn't exist
+ */
+ protected abstract ContentUrlEntity getContentUrlEntity(Long id);
+
+ /**
+ * @param contentUrl the URL of the content url entity
+ * @return Return the entity or null if it doesn't exist
+ */
+ protected abstract ContentUrlEntity getContentUrlEntity(String contentUrl);
+
+ /**
+ * Delete the entity with the given ID
+ * @return Returns the number of rows deleted
+ */
+ protected abstract int deleteContentUrlEntity(Long id);
+
+ /**
+ * Create the row for the alf_content_data
+ */
+ protected abstract ContentDataEntity createContentDataEntity(
+ Long contentUrlId,
+ Long mimetypeId,
+ Long encodingId,
+ Long localeId);
+
+ /**
+ * @param id the entity ID
+ * @return Returns the entity or null if it doesn't exist
+ */
+ protected abstract ContentDataEntity getContentDataEntity(Long id);
+
+ /**
+ * Delete the entity with the given ID
+ *
+ * @return Returns the number of rows deleted
+ */
+ protected abstract int deleteContentDataEntity(Long id);
+}
diff --git a/source/java/org/alfresco/repo/domain/contentdata/ContentDataDAO.java b/source/java/org/alfresco/repo/domain/contentdata/ContentDataDAO.java
new file mode 100644
index 0000000000..f9ee71b818
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentdata/ContentDataDAO.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentdata;
+
+import java.util.Set;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.util.Pair;
+import org.springframework.dao.ConcurrencyFailureException;
+
+/**
+ * DAO services for alf_content_data table
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public interface ContentDataDAO
+{
+ /**
+ * Create a new ContentData instance.
+ *
+ * @param contentData the ContentData details
+ * @return the ContentData pair (id, ContentData) (never null)
+ */
+ Pair createContentData(ContentData contentData);
+
+ /**
+ * @param id the unique ID of the entity
+ * @return the ContentData pair (id, ContentData) or null if it doesn't exist
+ * @throws AlfrescoRuntimeException if the ID provided is invalid
+ */
+ Pair getContentData(Long id);
+
+ /**
+ * Delete an instance of content.
+ * @param id the unique ID of the entity
+ * @throws ConcurrencyFailureException if the ID does not exist
+ */
+ void deleteContentData(Long id);
+
+ /**
+ * Deletes all alf_content_data rows that are referenced by the given node
+ *
+ * @param nodeId the node ID
+ * @param qnameIds the content properties to target
+ */
+ void deleteContentDataForNode(Long nodeId, Set qnameIds);
+
+ /**
+ * Interface for callbacks during content URL enumeration
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+ public static interface ContentUrlHandler
+ {
+ void handle(String contentUrl);
+ }
+
+ /**
+ * Enumerate all available content URLs
+ *
+ * @param contentUrlHandler
+ */
+ void getAllContentUrls(ContentUrlHandler contentUrlHandler);
+}
diff --git a/source/java/org/alfresco/repo/domain/contentdata/ContentDataDAOTest.java b/source/java/org/alfresco/repo/domain/contentdata/ContentDataDAOTest.java
new file mode 100644
index 0000000000..6fcf399a9b
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentdata/ContentDataDAOTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentdata;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+
+import junit.framework.TestCase;
+
+import org.alfresco.repo.content.ContentContext;
+import org.alfresco.repo.content.ContentStore;
+import org.alfresco.repo.content.MimetypeMap;
+import org.alfresco.repo.content.filestore.FileContentStore;
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.cmr.repository.ContentData;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.Pair;
+import org.alfresco.util.TempFileProvider;
+import org.springframework.context.ConfigurableApplicationContext;
+
+/**
+ * @see ContentDataDAO
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class ContentDataDAOTest extends TestCase
+{
+ private ConfigurableApplicationContext ctx = (ConfigurableApplicationContext) ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private RetryingTransactionHelper txnHelper;
+ private ContentDataDAO contentDataDAO;
+ private ContentStore contentStore;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ transactionService = serviceRegistry.getTransactionService();
+ txnHelper = transactionService.getRetryingTransactionHelper();
+
+ contentDataDAO = (ContentDataDAO) ctx.getBean("contentDataDAO");
+ contentStore = new FileContentStore(ctx, TempFileProvider.getTempDir());
+ }
+
+ private Pair create(final ContentData contentData)
+ {
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public Pair execute() throws Throwable
+ {
+ Pair contentDataPair = contentDataDAO.createContentData(contentData);
+ return contentDataPair;
+ }
+ };
+ return txnHelper.doInTransaction(callback, false, false);
+ }
+
+ /**
+ * Retrieves and checks the ContentData for equality
+ */
+ private void getAndCheck(final Long contentDataId, ContentData checkContentData)
+ {
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public Pair execute() throws Throwable
+ {
+ Pair contentDataPair = contentDataDAO.getContentData(contentDataId);
+ return contentDataPair;
+ }
+ };
+ Pair resultPair = txnHelper.doInTransaction(callback, true, false);
+ assertNotNull("Failed to find result for ID " + contentDataId, resultPair);
+ assertEquals("ContentData retrieved not the same as persisted: ", checkContentData, resultPair.getSecond());
+ }
+
+ private ContentData getContentData()
+ {
+ ContentContext contentCtx = new ContentContext(null, null);
+ String contentUrl = contentStore.getWriter(contentCtx).getContentUrl();
+ ContentData contentData = new ContentData(
+ contentUrl,
+ MimetypeMap.MIMETYPE_TEXT_PLAIN,
+ 12335L,
+ "UTF-8",
+ Locale.FRENCH);
+ return contentData;
+ }
+
+ public void testGetWithInvalidId()
+ {
+ assertNull("Expected null for invalid ID", contentDataDAO.getContentData(-1L));
+ }
+
+ /**
+ * Check that the ContentData
is decoded and persisted correctly.
+ */
+ public void testCreateContentDataSimple() throws Exception
+ {
+ ContentData contentData = getContentData();
+
+ Pair resultPair = create(contentData);
+ getAndCheck(resultPair.getFirst(), contentData);
+ }
+
+ /**
+ * Check that the ContentData
is decoded and persisted correctly.
+ */
+ public void testCreateContentDataNulls() throws Exception
+ {
+ ContentData contentData = new ContentData(null, null, 0L, null, null);
+
+ Pair resultPair = create(contentData);
+ getAndCheck(resultPair.getFirst(), contentData);
+ }
+
+ /**
+ * Ensure that upper and lowercase URLs don't clash
+ * @throws Exception
+ */
+ public void testEnsureCaseSensitiveStorage() throws Exception
+ {
+ ContentData contentData = getContentData();
+ String contentUrlUpper = contentData.getContentUrl().toUpperCase();
+ ContentData contentDataUpper = new ContentData(
+ contentUrlUpper, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, "UTF-8", new Locale("FR"));
+ String contentUrlLower = contentData.getContentUrl().toLowerCase();
+ ContentData contentDataLower = new ContentData(
+ contentUrlLower, MimetypeMap.MIMETYPE_TEXT_PLAIN, 0L, "utf-8", new Locale("fr"));
+
+ Pair resultPairUpper = create(contentDataUpper);
+ getAndCheck(resultPairUpper.getFirst(), contentDataUpper);
+
+ Pair resultPairLower = create(contentDataLower);
+ getAndCheck(resultPairLower.getFirst(), contentDataLower);
+ }
+
+ public void testDelete() throws Exception
+ {
+ ContentData contentData = getContentData();
+
+ Pair resultPair = create(contentData);
+ getAndCheck(resultPair.getFirst(), contentData);
+ contentDataDAO.deleteContentData(resultPair.getFirst());
+ try
+ {
+ getAndCheck(resultPair.getFirst(), contentData);
+ fail("Entity still exists");
+ }
+ catch (Throwable e)
+ {
+ // Expected
+ }
+ }
+
+ private static final String[] MIMETYPES = new String[]
+ {
+ MimetypeMap.MIMETYPE_ACP,
+ MimetypeMap.MIMETYPE_EXCEL,
+ MimetypeMap.MIMETYPE_IMAGE_JPEG,
+ MimetypeMap.MIMETYPE_JAVASCRIPT,
+ MimetypeMap.MIMETYPE_RSS
+ };
+ private static final String[] ENCODINGS = new String[]
+ {
+ "utf-8",
+ "ascii",
+ "latin1",
+ "wibbles",
+ "iso-whatever"
+ };
+ private static final Locale[] LOCALES = new Locale[]
+ {
+ Locale.FRENCH,
+ Locale.CHINESE,
+ Locale.ITALIAN,
+ Locale.JAPANESE,
+ Locale.ENGLISH
+ };
+
+ private List> speedTestWrite(String name, int total)
+ {
+ System.out.println("Starting write speed test: " + name);
+ long start = System.nanoTime();
+ List> pairs = new ArrayList>(100000);
+ // Loop and check for performance degradation
+ for (int i = 0; i < (total / 200 / 5); i++)
+ {
+ for (int j = 0; j < 200; j++)
+ {
+ for (int k = 0; k < 5; k++)
+ {
+ ContentData contentData = getContentData();
+ String contentUrl = contentData.getContentUrl();
+ contentData = new ContentData(
+ contentUrl,
+ MIMETYPES[k],
+ (long) j*k,
+ ENCODINGS[k],
+ LOCALES[k]);
+ Pair pair = create(contentData);
+ pairs.add(pair);
+ }
+ }
+ // That's 1000
+ long now = System.nanoTime();
+ double diffMs = (double) (now - start) / 1E6;
+ double aveMs = diffMs / (double) pairs.size();
+ String msg = String.format(
+ " Wrote %7d rows; average is %5.2f ms per row or %5.2f rows per second",
+ pairs.size(),
+ aveMs,
+ 1000.0 / aveMs);
+ System.out.println(msg);
+ }
+ // Done
+ return pairs;
+ }
+
+ private void speedTestRead(String name, List> pairs)
+ {
+ System.out.println("Starting read speed test: " + name);
+ long start = System.nanoTime();
+ // Loop and check for performance degradation
+ int num = 1;
+ for (Pair pair : pairs)
+ {
+ Long id = pair.getFirst();
+ ContentData contentData = pair.getSecond();
+ // Retrieve it
+ getAndCheck(id, contentData);
+ // Report
+ if (num % 1000 == 0)
+ {
+ long now = System.nanoTime();
+ double diffMs = (double) (now - start) / 1E6;
+ double aveMs = diffMs / (double) num;
+ String msg = String.format(
+ " Read %7d rows; average is %5.2f ms per row or %5.2f rows per second",
+ num,
+ aveMs,
+ 1000.0 / aveMs);
+ System.out.println(msg);
+ }
+ num++;
+ }
+ // Done
+ }
+
+ public void testCreateSpeedIndividualTxns()
+ {
+ List> pairs = speedTestWrite(getName(), 2000);
+ speedTestRead(getName(), pairs);
+ }
+
+ public void testCreateSpeedSingleTxn()
+ {
+ RetryingTransactionCallback>> writeCallback = new RetryingTransactionCallback>>()
+ {
+ public List> execute() throws Throwable
+ {
+ return speedTestWrite(getName(), 10000);
+ }
+ };
+ final List> pairs = txnHelper.doInTransaction(writeCallback, false, false);
+ RetryingTransactionCallback readCallback = new RetryingTransactionCallback()
+ {
+ public Void execute() throws Throwable
+ {
+ speedTestRead(getName(), pairs);
+ return null;
+ }
+ };
+ txnHelper.doInTransaction(readCallback, false, false);
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/contentdata/ContentDataEntity.java b/source/java/org/alfresco/repo/domain/contentdata/ContentDataEntity.java
new file mode 100644
index 0000000000..61b2c7c637
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentdata/ContentDataEntity.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentdata;
+
+import org.alfresco.util.EqualsHelper;
+
+/**
+ * Entity bean for alf_content_data table.
+ *
+ * These are unique (see {@link #equals(Object) equals} and {@link #hashCode() hashCode}) based
+ * on the {@link #getContentUrl() content URL} value.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class ContentDataEntity
+{
+ public static final Long CONST_LONG_ZERO = new Long(0L);
+
+ private Long id;
+ private Long version;
+ private Long contentUrlId;
+ private String contentUrl;
+ private Long size;
+ private Long mimetypeId;
+ private Long encodingId;
+ private Long localeId;
+
+ public ContentDataEntity()
+ {
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (id == null ? 0 : id.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof ContentDataEntity)
+ {
+ ContentDataEntity that = (ContentDataEntity) obj;
+ return EqualsHelper.nullSafeEquals(this.id, that.id);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(512);
+ sb.append("ContentDataEntity")
+ .append("[ ID=").append(id)
+ .append(", contentUrlId=").append(contentUrlId)
+ .append(", contentUrl=").append(contentUrl)
+ .append(", size=").append(size)
+ .append(", mimetype=").append(mimetypeId)
+ .append(", encoding=").append(encodingId)
+ .append(", locale=").append(localeId)
+ .append("]");
+ return sb.toString();
+ }
+
+ public Long getId()
+ {
+ return id;
+ }
+
+ public void setId(Long id)
+ {
+ this.id = id;
+ }
+
+ public Long getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion(Long version)
+ {
+ this.version = version;
+ }
+
+ public Long getContentUrlId()
+ {
+ return contentUrlId;
+ }
+
+ public void setContentUrlId(Long contentUrlId)
+ {
+ this.contentUrlId = contentUrlId;
+ }
+
+ public String getContentUrl()
+ {
+ return contentUrl;
+ }
+
+ public void setContentUrl(String contentUrl)
+ {
+ this.contentUrl = contentUrl;
+ }
+
+ public Long getSize()
+ {
+ return size;
+ }
+
+ public void setSize(Long size)
+ {
+ this.size = size;
+ }
+
+ public Long getMimetypeId()
+ {
+ return mimetypeId;
+ }
+
+ public void setMimetypeId(Long mimetypeId)
+ {
+ this.mimetypeId = mimetypeId;
+ }
+
+ public Long getEncodingId()
+ {
+ return encodingId;
+ }
+
+ public void setEncodingId(Long encodingId)
+ {
+ this.encodingId = encodingId;
+ }
+
+ public Long getLocaleId()
+ {
+ return localeId;
+ }
+
+ public void setLocaleId(Long localeId)
+ {
+ this.localeId = localeId;
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/contentdata/ContentUrlEntity.java b/source/java/org/alfresco/repo/domain/contentdata/ContentUrlEntity.java
new file mode 100644
index 0000000000..3dbcd77429
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentdata/ContentUrlEntity.java
@@ -0,0 +1,245 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentdata;
+
+import java.io.UnsupportedEncodingException;
+import java.util.zip.CRC32;
+
+import org.alfresco.util.EqualsHelper;
+import org.alfresco.util.Pair;
+
+/**
+ * Entity bean for alf_content_url table.
+ *
+ * These are unique (see {@link #equals(Object) equals} and {@link #hashCode() hashCode}) based
+ * on the {@link #getContentUrl() content URL} value.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class ContentUrlEntity
+{
+ public static final Long CONST_LONG_ZERO = new Long(0L);
+ public static final String EMPTY_URL = "empty";
+
+ private Long id;
+ private Long version;
+ private String contentUrl;
+ private String contentUrlShort;
+ private long contentUrlCrc;
+ private long size;
+
+ public ContentUrlEntity()
+ {
+ this.size = 0L;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return (contentUrl == null ? 0 : contentUrl.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof ContentUrlEntity)
+ {
+ ContentUrlEntity that = (ContentUrlEntity) obj;
+ return EqualsHelper.nullSafeEquals(this.contentUrl, that.contentUrl);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(512);
+ sb.append("ContentUrlEntity")
+ .append("[ ID=").append(id)
+ .append(", contentUrl=").append(contentUrl)
+ .append(", size=").append(size)
+ .append("]");
+ return sb.toString();
+ }
+
+ /**
+ * @param
+ * @return Returns a pair of the short (12 chars lowercase) URL and the CRC value
+ */
+ private static Pair getContentUrlCrcPair(String internalContentUrl)
+ {
+ if (internalContentUrl == null)
+ {
+ return new Pair(null, null);
+ }
+
+ // Calculate the CRC value
+ CRC32 crc = new CRC32();
+ try
+ {
+ crc.update(internalContentUrl.getBytes("UTF-8"));
+ }
+ catch (UnsupportedEncodingException e)
+ {
+ throw new RuntimeException("UTF-8 encoding is not supported");
+ }
+ // Get the short name (case-insensitive)
+ String contentUrlShort = null;
+ int contentUrlLen = internalContentUrl.length();
+ if (contentUrlLen < 12)
+ {
+ contentUrlShort = internalContentUrl.toLowerCase();
+ }
+ else
+ {
+ contentUrlShort = internalContentUrl.toLowerCase().substring(contentUrlLen - 12);
+ }
+ // Done
+ return new Pair(contentUrlShort, crc.getValue());
+ }
+
+ private static String getInternalUrl(String contentUrl)
+ {
+ if (contentUrl == null)
+ {
+ return null;
+ }
+ // Deal with Oracle's NULL-EMPTY confusion
+ if (contentUrl.length() == 0)
+ {
+ return EMPTY_URL;
+ }
+ else
+ {
+ return contentUrl;
+ }
+ }
+
+ /**
+ * @return Returns the originally-set content URL
+ */
+ private static String getExternalUrl(String contentUrl)
+ {
+ if (contentUrl == null)
+ {
+ return null;
+ }
+ // Decode Oracle's NULL-EMPTY confusion
+ if (contentUrl.equals(EMPTY_URL))
+ {
+ return "";
+ }
+ else
+ {
+ return contentUrl;
+ }
+ }
+
+ public Long getId()
+ {
+ return id;
+ }
+
+ public void setId(Long id)
+ {
+ this.id = id;
+ }
+
+ public Long getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion(Long version)
+ {
+ this.version = version;
+ }
+
+ public String getContentUrl()
+ {
+ // Convert the persisted content URL to an external value
+ return ContentUrlEntity.getExternalUrl(contentUrl);
+ }
+
+ public void setContentUrl(String contentUrl)
+ {
+ this.contentUrl = contentUrl;
+ // Convert the URL to a persistable value
+ String internalContentUrl = ContentUrlEntity.getInternalUrl(contentUrl);
+ Pair contentUrlPair = ContentUrlEntity.getContentUrlCrcPair(internalContentUrl);
+ this.contentUrlShort = contentUrlPair.getFirst();
+ this.contentUrlCrc = contentUrlPair.getSecond();
+ }
+
+ /**
+ * For persistence use
+ */
+ public String getContentUrlShort()
+ {
+ return contentUrlShort;
+ }
+
+ /**
+ * For persistence use
+ */
+ public void setContentUrlShort(String contentUrlShort)
+ {
+ this.contentUrlShort = contentUrlShort;
+ }
+
+ /**
+ * For persistence use
+ */
+ public long getContentUrlCrc()
+ {
+ return contentUrlCrc;
+ }
+
+ /**
+ * For persistence use
+ */
+ public void setContentUrlCrc(long contentUrlCrc)
+ {
+ this.contentUrlCrc = contentUrlCrc;
+ }
+
+ public long getSize()
+ {
+ return size;
+ }
+
+ public void setSize(long size)
+ {
+ this.size = size;
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/contentdata/ibatis/ContentDataDAOImpl.java b/source/java/org/alfresco/repo/domain/contentdata/ibatis/ContentDataDAOImpl.java
new file mode 100644
index 0000000000..5076fd857e
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/contentdata/ibatis/ContentDataDAOImpl.java
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.contentdata.ibatis;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.alfresco.repo.domain.contentdata.AbstractContentDataDAOImpl;
+import org.alfresco.repo.domain.contentdata.ContentDataEntity;
+import org.alfresco.repo.domain.contentdata.ContentUrlEntity;
+import org.springframework.orm.ibatis.SqlMapClientTemplate;
+
+import com.ibatis.sqlmap.client.event.RowHandler;
+
+/**
+ * iBatis-specific implementation of the ContentData DAO.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class ContentDataDAOImpl extends AbstractContentDataDAOImpl
+{
+ private static final String SELECT_CONTENT_URL_BY_ID = "select.ContentUrlById";
+ private static final String SELECT_CONTENT_URL_BY_KEY = "select.ContentUrlByKey";
+ private static final String SELECT_CONTENT_URLS = "select.ContentUrls";
+ private static final String SELECT_CONTENT_DATA_BY_ID = "select.ContentDataById";
+ private static final String SELECT_CONTENT_DATA_BY_NODE_AND_QNAME = "select.ContentDataByNodeAndQName";
+ private static final String SELECT_CONTENT_DATA_BY_URL_ID = "select.ContentDataByContentUrlId";
+ private static final String INSERT_CONTENT_URL = "insert.ContentUrl";
+ private static final String INSERT_CONTENT_DATA = "insert.ContentData";
+ private static final String DELETE_CONTENT_DATA = "delete.ContentData";
+ private static final String DELETE_CONTENT_URL = "delete.ContentUrl";
+
+ private SqlMapClientTemplate template;
+
+ public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate)
+ {
+ this.template = sqlMapClientTemplate;
+ }
+
+ @Override
+ protected ContentUrlEntity createContentUrlEntity(String contentUrl, long size)
+ {
+ ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
+ contentUrlEntity.setVersion(ContentUrlEntity.CONST_LONG_ZERO);
+ contentUrlEntity.setContentUrl(contentUrl);
+ contentUrlEntity.setSize(size);
+ /* Long id = (Long) */ template.insert(INSERT_CONTENT_URL, contentUrlEntity);
+ /*contentUrlEntity.setId(id);*/
+ // Register the url as new
+ registerNewContentUrl(contentUrl);
+ // Done
+ return contentUrlEntity;
+ }
+
+ @Override
+ protected ContentUrlEntity getContentUrlEntity(Long id)
+ {
+ ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
+ contentUrlEntity.setId(id);
+ contentUrlEntity = (ContentUrlEntity) template.queryForObject(SELECT_CONTENT_URL_BY_ID, contentUrlEntity);
+ // Done
+ return contentUrlEntity;
+ }
+
+ @Override
+ protected ContentUrlEntity getContentUrlEntity(String contentUrl)
+ {
+ ContentUrlEntity contentUrlEntity = new ContentUrlEntity();
+ contentUrlEntity.setContentUrl(contentUrl);
+ contentUrlEntity = (ContentUrlEntity) template.queryForObject(SELECT_CONTENT_URL_BY_KEY, contentUrlEntity);
+ // Done
+ return contentUrlEntity;
+ }
+
+ @Override
+ protected int deleteContentUrlEntity(Long id)
+ {
+ Map params = new HashMap(11);
+ params.put("id", id);
+ return template.delete(DELETE_CONTENT_URL, params);
+ }
+
+ @Override
+ protected ContentDataEntity createContentDataEntity(
+ Long contentUrlId,
+ Long mimetypeId,
+ Long encodingId,
+ Long localeId)
+ {
+ ContentDataEntity contentDataEntity = new ContentDataEntity();
+ contentDataEntity.setVersion(ContentDataEntity.CONST_LONG_ZERO);
+ contentDataEntity.setContentUrlId(contentUrlId);
+ contentDataEntity.setMimetypeId(mimetypeId);
+ contentDataEntity.setEncodingId(encodingId);
+ contentDataEntity.setLocaleId(localeId);
+ template.insert(INSERT_CONTENT_DATA, contentDataEntity);
+ // Done
+ return contentDataEntity;
+ }
+
+ @Override
+ protected ContentDataEntity getContentDataEntity(Long id)
+ {
+ Map params = new HashMap(11);
+ params.put("id", id);
+ ContentDataEntity contentDataEntity = (ContentDataEntity) template.queryForObject(SELECT_CONTENT_DATA_BY_ID, params);
+ // Done
+ return contentDataEntity;
+ }
+
+ @Override
+ protected int deleteContentDataEntity(Long id)
+ {
+ Map params = new HashMap(11);
+ params.put("id", id);
+ return template.delete(DELETE_CONTENT_DATA, params);
+ }
+
+ public void deleteContentDataForNode(Long nodeId, Set qnameIds)
+ {
+ /*
+ * TODO: use IN clause in parameters
+ */
+ for (Long qnameId : qnameIds)
+ {
+ // Get the ContentData that matches (may be multiple due to collection properties)
+ Map params = new HashMap(11);
+ params.put("nodeId", nodeId);
+ params.put("qnameId", qnameId);
+ @SuppressWarnings("unchecked")
+ List ids = (List) template.queryForList(SELECT_CONTENT_DATA_BY_NODE_AND_QNAME, params);
+ // Delete each one
+ for (Long id : ids)
+ {
+ // Get the content urls
+ ContentDataEntity contentDataEntity = getContentDataEntity(id);
+ // Only check the content URLs if one is present
+ String contentUrl = contentDataEntity.getContentUrl();
+ Long contentUrlId = contentDataEntity.getContentUrlId();
+ // Delete the ContentData entity
+ deleteContentData(id);
+ // Check if the content URL was orphaned
+ if (contentUrlId != null)
+ {
+ params.clear();
+ params.put("id", contentUrlId);
+ @SuppressWarnings("unchecked")
+ List contentDataEntities = (List) template.queryForList(SELECT_CONTENT_DATA_BY_URL_ID, params);
+ // If there is still ContentData associated with the content URL, then leave it
+ if (contentDataEntities.size() == 0)
+ {
+ // Orphaned
+ registerOrphanedContentUrl(contentUrl);
+ }
+ }
+ }
+ }
+ }
+
+ public void getAllContentUrls(final ContentUrlHandler contentUrlHandler)
+ {
+ RowHandler rowHandler = new RowHandler()
+ {
+ public void handleRow(Object valueObject)
+ {
+ contentUrlHandler.handle((String)valueObject);
+ }
+ };
+ template.queryWithRowHandler(SELECT_CONTENT_URLS, rowHandler);
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/encoding/AbstractEncodingDAOImpl.java b/source/java/org/alfresco/repo/domain/encoding/AbstractEncodingDAOImpl.java
new file mode 100644
index 0000000000..025e89507b
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/encoding/AbstractEncodingDAOImpl.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.encoding;
+
+import java.io.Serializable;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.cache.SimpleCache;
+import org.alfresco.util.Pair;
+import org.alfresco.util.ParameterCheck;
+
+/**
+ * Abstract implementation for Encoding DAO.
+ *
+ * This provides basic services such as caching, but defers to the underlying implementation
+ * for CRUD operations.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public abstract class AbstractEncodingDAOImpl implements EncodingDAO
+{
+ private static final Long CACHE_NULL_LONG = Long.MIN_VALUE;
+ private static final String NULL_SAFE_STRING = ".null";
+ private SimpleCache encodingEntityCache;
+
+ /**
+ *
+ * @param encodingEntityCache the cache of IDs to mimetypes
+ */
+ public void setEncodingEntityCache(SimpleCache encodingEntityCache)
+ {
+ this.encodingEntityCache = encodingEntityCache;
+ }
+
+ public Pair getEncoding(Long id)
+ {
+ // Check the cache
+ String encoding = (String) encodingEntityCache.get(id);
+ if (encoding != null)
+ {
+ return new Pair(id, encoding);
+ }
+ // Get it from the DB
+ EncodingEntity mimetypeEntity = getEncodingEntity(id);
+ if (mimetypeEntity == null)
+ {
+ throw new AlfrescoRuntimeException("The MimetypeEntity ID " + id + " doesn't exist.");
+ }
+ encoding = mimetypeEntity.getEncoding();
+ // Cache it
+ encodingEntityCache.put(encoding, id);
+ encodingEntityCache.put(id, encoding);
+ // Done
+ return new Pair(id, encoding);
+ }
+
+ public Pair getEncoding(String encoding)
+ {
+ ParameterCheck.mandatory("encoding", encoding);
+
+ // Check the cache
+ Long id = (Long) encodingEntityCache.get(encoding);
+ if (id != null)
+ {
+ if (id.equals(CACHE_NULL_LONG))
+ {
+ return null;
+ }
+ else
+ {
+ return new Pair(id, encoding);
+ }
+ }
+ // It's not in the cache, so query
+ EncodingEntity result = getEncodingEntity(encoding);
+ if (result == null)
+ {
+ // Cache it
+ encodingEntityCache.put(encoding, CACHE_NULL_LONG);
+ // Done
+ return null;
+ }
+ else
+ {
+ id = result.getId();
+ // Cache it
+ encodingEntityCache.put(id, encoding);
+ encodingEntityCache.put(encoding, id);
+ // Done
+ return new Pair(id, encoding);
+ }
+ }
+
+ public Pair getOrCreateEncoding(String encoding)
+ {
+ ParameterCheck.mandatory("encoding", encoding);
+
+ Pair result = getEncoding(encoding);
+ if (result == null)
+ {
+ EncodingEntity encodingEntity = createEncodingEntity(encoding);
+ Long id = encodingEntity.getId();
+ result = new Pair(id, encoding);
+ // Cache it
+ encodingEntityCache.put(id, encoding);
+ encodingEntityCache.put(encoding, id);
+ }
+ return result;
+ }
+
+ /**
+ * @param id the ID of the encoding entity
+ * @return Return the entity or null if it doesn't exist
+ */
+ protected abstract EncodingEntity getEncodingEntity(Long id);
+ protected abstract EncodingEntity getEncodingEntity(String encoding);
+ protected abstract EncodingEntity createEncodingEntity(String encoding);
+}
diff --git a/source/java/org/alfresco/repo/domain/ContentUrl.java b/source/java/org/alfresco/repo/domain/encoding/EncodingDAO.java
similarity index 64%
rename from source/java/org/alfresco/repo/domain/ContentUrl.java
rename to source/java/org/alfresco/repo/domain/encoding/EncodingDAO.java
index 949e3a4b0a..86453ed563 100644
--- a/source/java/org/alfresco/repo/domain/ContentUrl.java
+++ b/source/java/org/alfresco/repo/domain/encoding/EncodingDAO.java
@@ -1,49 +1,48 @@
-/*
- * Copyright (C) 2005-2007 Alfresco Software Limited.
- *
- * This program is free software; you can redistribute it and/or
- * modify it under the terms of the GNU General Public License
- * as published by the Free Software Foundation; either version 2
- * of the License, or (at your option) any later version.
-
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
-
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-
- * As a special exception to the terms and conditions of version 2.0 of
- * the GPL, you may redistribute this Program in connection with Free/Libre
- * and Open Source Software ("FLOSS") applications as described in Alfresco's
- * FLOSS exception. You should have recieved a copy of the text describing
- * the FLOSS exception, and it is also available here:
- * http://www.alfresco.com/legal/licensing
- */
-package org.alfresco.repo.domain;
-
-/**
- * Interface for persistent Content URL objects.
- *
- * Instances represent physically stored content.
- *
- * @author Derek Hulley
- * @since 2.0
- */
-public interface ContentUrl
-{
- /**
- * @return Returns the auto-generated ID
- */
- Long getId();
-
- String getContentUrl();
-
- void setContentUrl(String contentUrl);
-//
-// boolean isOrphaned();
-//
-// void setOrphaned(boolean orphaned);
-}
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.encoding;
+
+import org.alfresco.util.Pair;
+
+/**
+ * DAO services for alf_encoding and related tables
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public interface EncodingDAO
+{
+ /**
+ * Get the encoding pair.
+ *
+ * @param encoding the encoding string
+ * @return the ID-encoding pair or null if it doesn't exsit
+ */
+ Pair getEncoding(String encoding);
+
+ Pair getEncoding(Long id);
+
+ Pair getOrCreateEncoding(String encoding);
+}
diff --git a/source/java/org/alfresco/repo/domain/encoding/EncodingDAOTest.java b/source/java/org/alfresco/repo/domain/encoding/EncodingDAOTest.java
new file mode 100644
index 0000000000..04ec30cd36
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/encoding/EncodingDAOTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.encoding;
+
+import junit.framework.TestCase;
+
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
+import org.alfresco.util.Pair;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * @see EncodingDAO
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class EncodingDAOTest extends TestCase
+{
+ private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private RetryingTransactionHelper txnHelper;
+ private EncodingDAO encodingDAO;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ transactionService = serviceRegistry.getTransactionService();
+ txnHelper = transactionService.getRetryingTransactionHelper();
+
+ encodingDAO = (EncodingDAO) ctx.getBean("encodingDAO");
+ }
+
+ private Pair get(final String encoding, final boolean autoCreate, boolean expectSuccess)
+ {
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public Pair execute() throws Throwable
+ {
+ Pair mimetypePair = null;
+ if (autoCreate)
+ {
+ mimetypePair = encodingDAO.getOrCreateEncoding(encoding);
+ }
+ else
+ {
+ mimetypePair = encodingDAO.getEncoding(encoding);
+ }
+ return mimetypePair;
+ }
+ };
+ try
+ {
+ return txnHelper.doInTransaction(callback, !autoCreate, false);
+ }
+ catch (Throwable e)
+ {
+ if (expectSuccess)
+ {
+ // oops
+ throw new RuntimeException("Expected to get encoding '" + encoding + "'.", e);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ public void testCreateWithCommit() throws Exception
+ {
+ // Create an encoding
+ String encoding = GUID.generate();
+ Pair encodingPair = get(encoding, true, true);
+ // Check that it can be retrieved
+ Pair encodingPairCheck = get(encodingPair.getSecond(), false, true);
+ assertEquals("Encoding ID changed", encodingPair.getFirst(), encodingPairCheck.getFirst());
+ }
+
+ public void testCreateWithRollback() throws Exception
+ {
+ final String encoding = GUID.generate();
+ // Create an encoding
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public Pair execute() throws Throwable
+ {
+ get(encoding, true, true);
+ // Now force a rollback
+ throw new RuntimeException("Forced");
+ }
+ };
+ try
+ {
+ txnHelper.doInTransaction(callback);
+ fail("Transaction didn't roll back");
+ }
+ catch (RuntimeException e)
+ {
+ // Expected
+ }
+ // Check that it doesn't exist
+ get(encoding, false, false);
+ }
+
+ public void testCaseInsensitivity() throws Exception
+ {
+ String encoding = "AAA-" + GUID.generate();
+ Pair lowercasePair = get(encoding.toLowerCase(), true, true);
+ // Check that the same pair is retrievable using uppercase
+ Pair uppercasePair = get(encoding.toUpperCase(), true, true);
+ assertNotNull(uppercasePair);
+ assertEquals(
+ "Upper and lowercase encoding instance IDs were not the same",
+ lowercasePair.getFirst(), uppercasePair.getFirst());
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/encoding/EncodingEntity.java b/source/java/org/alfresco/repo/domain/encoding/EncodingEntity.java
new file mode 100644
index 0000000000..5d499b9cbf
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/encoding/EncodingEntity.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.encoding;
+
+import org.alfresco.util.EqualsHelper;
+
+/**
+ * Entity bean for alf_encoding table.
+ *
+ * These are unique (see {@link #equals(Object) equals} and {@link #hashCode() hashCode}) based
+ * on the {@link #getEncoding() encoding} value.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class EncodingEntity
+{
+ public static final Long CONST_LONG_ZERO = new Long(0L);
+
+ private Long id;
+ private Long version;
+ private String encoding;
+
+ @Override
+ public int hashCode()
+ {
+ return (encoding == null ? 0 : encoding.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof EncodingEntity)
+ {
+ EncodingEntity that = (EncodingEntity) obj;
+ return EqualsHelper.nullSafeEquals(this.encoding, that.encoding);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(512);
+ sb.append("EncodingEntity")
+ .append("[ ID=").append(id)
+ .append(", encoding=").append(encoding)
+ .append("]");
+ return sb.toString();
+ }
+
+ public Long getId()
+ {
+ return id;
+ }
+
+ public void setId(Long id)
+ {
+ this.id = id;
+ }
+
+ public Long getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion(Long version)
+ {
+ this.version = version;
+ }
+
+ public String getEncoding()
+ {
+ return encoding;
+ }
+
+ public void setEncoding(String encoding)
+ {
+ this.encoding = encoding;
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/encoding/ibatis/EncodingDAOImpl.java b/source/java/org/alfresco/repo/domain/encoding/ibatis/EncodingDAOImpl.java
new file mode 100644
index 0000000000..60492bff8f
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/encoding/ibatis/EncodingDAOImpl.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.encoding.ibatis;
+
+import org.alfresco.repo.domain.encoding.AbstractEncodingDAOImpl;
+import org.alfresco.repo.domain.encoding.EncodingEntity;
+import org.alfresco.repo.domain.mimetype.MimetypeEntity;
+import org.springframework.orm.ibatis.SqlMapClientTemplate;
+
+/**
+ * iBatis-specific implementation of the Mimetype DAO.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class EncodingDAOImpl extends AbstractEncodingDAOImpl
+{
+ private static final String SELECT_ENCODING_BY_ID = "select.EncodingById";
+ private static final String SELECT_ENCODING_BY_KEY = "select.EncodingByKey";
+ private static final String INSERT_ENCODING = "insert.Encoding";
+
+ private SqlMapClientTemplate template;
+
+ public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate)
+ {
+ this.template = sqlMapClientTemplate;
+ }
+
+ @Override
+ protected EncodingEntity getEncodingEntity(Long id)
+ {
+ EncodingEntity encodingEntity = new EncodingEntity();
+ encodingEntity.setId(id);
+ encodingEntity = (EncodingEntity) template.queryForObject(SELECT_ENCODING_BY_ID, encodingEntity);
+ // Done
+ return encodingEntity;
+ }
+
+ @Override
+ protected EncodingEntity getEncodingEntity(String encoding)
+ {
+ EncodingEntity encodingEntity = new EncodingEntity();
+ encodingEntity.setEncoding(encoding);
+ encodingEntity = (EncodingEntity) template.queryForObject(SELECT_ENCODING_BY_KEY, encodingEntity);
+ // Could be null
+ return encodingEntity;
+ }
+
+ @Override
+ protected EncodingEntity createEncodingEntity(String encoding)
+ {
+ EncodingEntity encodingEntity = new EncodingEntity();
+ encodingEntity.setVersion(MimetypeEntity.CONST_LONG_ZERO);
+ encodingEntity.setEncoding(encoding);
+ Long id = (Long) template.insert(INSERT_ENCODING, encodingEntity);
+ encodingEntity.setId(id);
+ // Done
+ return encodingEntity;
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/ContentUrl.hbm.xml b/source/java/org/alfresco/repo/domain/hibernate/ContentUrl.hbm.xml
deleted file mode 100644
index 5325aaaaa0..0000000000
--- a/source/java/org/alfresco/repo/domain/hibernate/ContentUrl.hbm.xml
+++ /dev/null
@@ -1,56 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- select
- entity.contentUrl
- from
- org.alfresco.repo.domain.hibernate.ContentUrlImpl entity
-
-
-
- delete
- from
- org.alfresco.repo.domain.hibernate.ContentUrlImpl entity
- where
- entity.contentUrl in (:contentUrls)
-
-
-
- delete
- from
- org.alfresco.repo.domain.hibernate.ContentUrlImpl entity
- where
- entity.contentUrl = :contentUrl
-
-
-
- delete
- from
- org.alfresco.repo.domain.hibernate.ContentUrlImpl entity
-
-
-
diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateContentUrlDAOImpl.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateContentUrlDAOImpl.java
deleted file mode 100644
index 9036602157..0000000000
--- a/source/java/org/alfresco/repo/domain/hibernate/HibernateContentUrlDAOImpl.java
+++ /dev/null
@@ -1,164 +0,0 @@
-package org.alfresco.repo.domain.hibernate;
-
-import java.util.Set;
-
-import org.alfresco.repo.domain.ContentUrl;
-import org.alfresco.repo.domain.ContentUrlDAO;
-import org.alfresco.repo.transaction.AlfrescoTransactionSupport;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.hibernate.FlushMode;
-import org.hibernate.Query;
-import org.hibernate.ScrollMode;
-import org.hibernate.ScrollableResults;
-import org.hibernate.Session;
-import org.hibernate.type.TypeFactory;
-import org.springframework.orm.hibernate3.HibernateCallback;
-import org.springframework.orm.hibernate3.support.HibernateDaoSupport;
-
-/**
- * Hibernate-specific implementation of the DAO layer for Content URLs.
- *
- * @author Derek Hulley
- * @since 2.0
- */
-public class HibernateContentUrlDAOImpl extends HibernateDaoSupport implements ContentUrlDAO
-{
- private static final String QUERY_GET_ALL = "contentUrl.GetAll";
- private static final String UPDATE_DELETE_BY_URL = "contentUrl.DeleteByUrl";
- private static final String UPDATE_DELETE_IN_LIST = "contentUrl.DeleteInList";
- private static final String UPDATE_DELETE_ALL = "contentUrl.DeleteAll";
-
- /** Txn resource key to check for required flushes */
- private static final String KEY_REQUIRES_FLUSH = "HibernateContentUrlDAOImpl.requiresFlush";
-
- private static Log logger = LogFactory.getLog(HibernateContentUrlDAOImpl.class);
-
- private void flushIfRequired()
- {
- Boolean requiresFlush = (Boolean) AlfrescoTransactionSupport.getResource(KEY_REQUIRES_FLUSH);
- if (requiresFlush == null)
- {
- requiresFlush = Boolean.FALSE;
- AlfrescoTransactionSupport.bindResource(KEY_REQUIRES_FLUSH, Boolean.FALSE);
- }
- else if (requiresFlush.booleanValue() == true)
- {
- getSession().flush();
- AlfrescoTransactionSupport.bindResource(KEY_REQUIRES_FLUSH, Boolean.FALSE);
- }
- }
-
- public ContentUrl createContentUrl(String contentUrl)
- {
- AlfrescoTransactionSupport.bindResource(KEY_REQUIRES_FLUSH, Boolean.TRUE);
- ContentUrl entity = new ContentUrlImpl();
- entity.setContentUrl(contentUrl);
- getSession().save(entity);
- return entity;
- }
-
- public void getAllContentUrls(final ContentUrlHandler handler)
- {
- // Force a flush if there are pending changes
- flushIfRequired();
-
- HibernateCallback callback = new HibernateCallback()
- {
- public Object doInHibernate(Session session)
- {
- Query query = session
- .getNamedQuery(HibernateContentUrlDAOImpl.QUERY_GET_ALL)
- ;
- return query.scroll(ScrollMode.FORWARD_ONLY);
- }
- };
- ScrollableResults results = null;
- try
- {
- results = (ScrollableResults) getHibernateTemplate().execute(callback);
- while (results.next())
- {
- String contentUrl = results.getText(0);
- handler.handle(contentUrl);
- }
- }
- finally
- {
- if(results != null)
- {
- results.close();
- }
- }
- }
-
- public void deleteContentUrl(final String contentUrl)
- {
- // Force a flush if there are pending changes
- flushIfRequired();
-
- HibernateCallback callback = new HibernateCallback()
- {
- public Object doInHibernate(Session session)
- {
- Query query = session
- .getNamedQuery(HibernateContentUrlDAOImpl.UPDATE_DELETE_BY_URL)
- .setFlushMode(FlushMode.MANUAL)
- .setString("contentUrl", contentUrl);
- return (Integer) query.executeUpdate();
- }
- };
- Integer deletedCount = (Integer) getHibernateTemplate().execute(callback);
- if (logger.isDebugEnabled())
- {
- logger.debug("Deleted " + deletedCount + " ContentUrl entities.");
- }
- }
-
- public void deleteContentUrls(final Set contentUrls)
- {
- // Force a flush if there are pending changes
- flushIfRequired();
-
- HibernateCallback callback = new HibernateCallback()
- {
- public Object doInHibernate(Session session)
- {
- Query query = session
- .getNamedQuery(HibernateContentUrlDAOImpl.UPDATE_DELETE_IN_LIST)
- .setFlushMode(FlushMode.MANUAL)
- .setParameterList("contentUrls", contentUrls, TypeFactory.basic("string"));
- return (Integer) query.executeUpdate();
- }
- };
- Integer deletedCount = (Integer) getHibernateTemplate().execute(callback);
- if (logger.isDebugEnabled())
- {
- logger.debug("Deleted " + deletedCount + " ContentUrl entities.");
- }
- }
-
- public void deleteAllContentUrls()
- {
- // Force a flush if there are pending changes
- flushIfRequired();
-
- HibernateCallback callback = new HibernateCallback()
- {
- public Object doInHibernate(Session session)
- {
- session.flush();
- Query query = session
- .getNamedQuery(HibernateContentUrlDAOImpl.UPDATE_DELETE_ALL)
- .setFlushMode(FlushMode.MANUAL)
- ;
- return (Integer) query.executeUpdate();
- }
- };
- Integer deletedCount = (Integer) getHibernateTemplate().execute(callback);
- if (logger.isDebugEnabled())
- {
- logger.debug("Deleted " + deletedCount + " ContentUrl entities.");
- }
- }
-}
diff --git a/source/java/org/alfresco/repo/domain/hibernate/HibernateLocaleDAOImpl.java b/source/java/org/alfresco/repo/domain/hibernate/HibernateLocaleDAOImpl.java
index d37f9985cc..6c8e51e07b 100644
--- a/source/java/org/alfresco/repo/domain/hibernate/HibernateLocaleDAOImpl.java
+++ b/source/java/org/alfresco/repo/domain/hibernate/HibernateLocaleDAOImpl.java
@@ -120,6 +120,10 @@ public class HibernateLocaleDAOImpl extends HibernateDaoSupport implements Local
// Add the cache entry
localeIdCache.put(id, localeStr);
localeIdCache.put(localeStr, id);
+
+ // Force a flush
+ DirtySessionMethodInterceptor.flushSession(getSession(), true);
+
// Done
if (logger.isDebugEnabled())
{
diff --git a/source/java/org/alfresco/repo/domain/mimetype/AbstractMimetypeDAOImpl.java b/source/java/org/alfresco/repo/domain/mimetype/AbstractMimetypeDAOImpl.java
new file mode 100644
index 0000000000..327a239e53
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/mimetype/AbstractMimetypeDAOImpl.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.mimetype;
+
+import java.io.Serializable;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.repo.cache.SimpleCache;
+import org.alfresco.util.Pair;
+import org.alfresco.util.ParameterCheck;
+
+/**
+ * Abstract implementation for Mimetype DAO.
+ *
+ * This provides basic services such as caching, but defers to the underlying implementation
+ * for CRUD operations.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public abstract class AbstractMimetypeDAOImpl implements MimetypeDAO
+{
+ private static final Long CACHE_NULL_LONG = Long.MIN_VALUE;
+ private static final String NULL_SAFE_STRING = ".null";
+ private SimpleCache mimetypeEntityCache;
+
+ /**
+ *
+ * @param mimetypeEntityCache the cache of IDs to mimetypes
+ */
+ public void setMimetypeEntityCache(SimpleCache mimetypeEntityCache)
+ {
+ this.mimetypeEntityCache = mimetypeEntityCache;
+ }
+
+ public Pair getMimetype(Long id)
+ {
+ // Check the cache
+ String mimetype = (String) mimetypeEntityCache.get(id);
+ if (mimetype != null)
+ {
+ return new Pair(id, mimetype);
+ }
+ // Get it from the DB
+ MimetypeEntity mimetypeEntity = getMimetypeEntity(id);
+ if (mimetypeEntity == null)
+ {
+ throw new AlfrescoRuntimeException("The MimetypeEntity ID " + id + " doesn't exist.");
+ }
+ mimetype = mimetypeEntity.getMimetype();
+ // Cache it
+ mimetypeEntityCache.put(mimetype, id);
+ mimetypeEntityCache.put(id, mimetype);
+ // Done
+ return new Pair(id, mimetype);
+ }
+
+ public Pair getMimetype(String mimetype)
+ {
+ ParameterCheck.mandatory("mimetype", mimetype);
+
+ // Check the cache
+ Long id = (Long) mimetypeEntityCache.get(mimetype);
+ if (id != null)
+ {
+ if (id.equals(CACHE_NULL_LONG))
+ {
+ return null;
+ }
+ else
+ {
+ return new Pair(id, mimetype);
+ }
+ }
+ // It's not in the cache, so query
+ MimetypeEntity result = getMimetypeEntity(mimetype);
+ if (result == null)
+ {
+ // Cache it
+ mimetypeEntityCache.put(mimetype, CACHE_NULL_LONG);
+ // Done
+ return null;
+ }
+ else
+ {
+ id = result.getId();
+ // Cache it
+ mimetypeEntityCache.put(id, mimetype);
+ mimetypeEntityCache.put(mimetype, id);
+ // Done
+ return new Pair(id, mimetype);
+ }
+ }
+
+ public Pair getOrCreateMimetype(String mimetype)
+ {
+ ParameterCheck.mandatory("mimetype", mimetype);
+
+ Pair result = getMimetype(mimetype);
+ if (result == null)
+ {
+ MimetypeEntity mimetypeEntity = createMimetypeEntity(mimetype);
+ Long id = mimetypeEntity.getId();
+ result = new Pair(id, mimetype);
+ // Cache it
+ mimetypeEntityCache.put(id, mimetype);
+ mimetypeEntityCache.put(mimetype, id);
+ }
+ return result;
+ }
+
+ /**
+ * @param id the ID of the mimetype entity
+ * @return Return the entity or null if it doesn't exist
+ */
+ protected abstract MimetypeEntity getMimetypeEntity(Long id);
+ protected abstract MimetypeEntity getMimetypeEntity(String mimetype);
+ protected abstract MimetypeEntity createMimetypeEntity(String mimetype);
+}
diff --git a/source/java/org/alfresco/repo/domain/mimetype/MimetypeDAO.java b/source/java/org/alfresco/repo/domain/mimetype/MimetypeDAO.java
new file mode 100644
index 0000000000..ea01f663f3
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/mimetype/MimetypeDAO.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.mimetype;
+
+import org.alfresco.error.AlfrescoRuntimeException;
+import org.alfresco.util.Pair;
+
+/**
+ * DAO services for alf_mimetype table
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public interface MimetypeDAO
+{
+ /**
+ * @param id the unique ID of the entity
+ * @return the Mimetype pair (id, mimetype) (never null)
+ * @throws AlfrescoRuntimeException if the ID provided is invalid
+ */
+ Pair getMimetype(Long id);
+
+ /**
+ * @param mimetype the Mimetype to query for
+ * @return the Mimetype pair (id, mimetype) or null if it doesn't exist
+ */
+ Pair getMimetype(String mimetype);
+
+ /**
+ * Retrieve an existing mimetype or create a new one if it doesn't exist.
+ *
+ * @param mimetype the Mimetype
+ * @return the Mimetype pair (id, mimetype) (never null)
+ */
+ Pair getOrCreateMimetype(String mimetype);
+}
diff --git a/source/java/org/alfresco/repo/domain/mimetype/MimetypeDAOTest.java b/source/java/org/alfresco/repo/domain/mimetype/MimetypeDAOTest.java
new file mode 100644
index 0000000000..08dc519852
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/mimetype/MimetypeDAOTest.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.mimetype;
+
+import junit.framework.TestCase;
+
+import org.alfresco.repo.transaction.RetryingTransactionHelper;
+import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
+import org.alfresco.service.ServiceRegistry;
+import org.alfresco.service.transaction.TransactionService;
+import org.alfresco.util.ApplicationContextHelper;
+import org.alfresco.util.GUID;
+import org.alfresco.util.Pair;
+import org.springframework.context.ApplicationContext;
+
+/**
+ * @see MimetypeDAO
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class MimetypeDAOTest extends TestCase
+{
+ private ApplicationContext ctx = ApplicationContextHelper.getApplicationContext();
+
+ private TransactionService transactionService;
+ private RetryingTransactionHelper txnHelper;
+ private MimetypeDAO mimetypeDAO;
+
+ @Override
+ public void setUp() throws Exception
+ {
+ ServiceRegistry serviceRegistry = (ServiceRegistry) ctx.getBean(ServiceRegistry.SERVICE_REGISTRY);
+ transactionService = serviceRegistry.getTransactionService();
+ txnHelper = transactionService.getRetryingTransactionHelper();
+
+ mimetypeDAO = (MimetypeDAO) ctx.getBean("mimetypeDAO");
+ }
+
+ private Pair get(final String mimetype, final boolean autoCreate, boolean expectSuccess)
+ {
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public Pair execute() throws Throwable
+ {
+ Pair mimetypePair = null;
+ if (autoCreate)
+ {
+ mimetypePair = mimetypeDAO.getOrCreateMimetype(mimetype);
+ }
+ else
+ {
+ mimetypePair = mimetypeDAO.getMimetype(mimetype);
+ }
+ return mimetypePair;
+ }
+ };
+ try
+ {
+ return txnHelper.doInTransaction(callback, !autoCreate, false);
+ }
+ catch (Throwable e)
+ {
+ if (expectSuccess)
+ {
+ // oops
+ throw new RuntimeException("Expected to get mimetype '" + mimetype + "'.", e);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ public void testCreateWithCommit() throws Exception
+ {
+ // Create a mimetype
+ String mimetype = GUID.generate();
+ Pair mimetypePair = get(mimetype, true, true);
+ // Check that it can be retrieved
+ Pair mimetypePairCheck = get(mimetypePair.getSecond(), false, true);
+ assertEquals("Mimetype ID changed", mimetypePair.getFirst(), mimetypePairCheck.getFirst());
+ }
+
+ public void testCreateWithRollback() throws Exception
+ {
+ final String mimetype = GUID.generate();
+ // Create a mimetype
+ RetryingTransactionCallback> callback = new RetryingTransactionCallback>()
+ {
+ public Pair execute() throws Throwable
+ {
+ get(mimetype, true, true);
+ // Now force a rollback
+ throw new RuntimeException("Forced");
+ }
+ };
+ try
+ {
+ txnHelper.doInTransaction(callback);
+ fail("Transaction didn't roll back");
+ }
+ catch (RuntimeException e)
+ {
+ // Expected
+ }
+ // Check that it doesn't exist
+ get(mimetype, false, false);
+ }
+
+ public void testCaseInsensitivity() throws Exception
+ {
+ String mimetype = "AAA-" + GUID.generate();
+ Pair lowercasePair = get(mimetype.toLowerCase(), true, true);
+ // Check that the same pair is retrievable using uppercase
+ Pair uppercasePair = get(mimetype.toUpperCase(), true, true);
+ assertNotNull(uppercasePair);
+ assertEquals(
+ "Upper and lowercase mimetype instance IDs were not the same",
+ lowercasePair.getFirst(), uppercasePair.getFirst());
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/mimetype/MimetypeEntity.java b/source/java/org/alfresco/repo/domain/mimetype/MimetypeEntity.java
new file mode 100644
index 0000000000..c2b9078dcb
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/mimetype/MimetypeEntity.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.mimetype;
+
+import org.alfresco.util.EqualsHelper;
+
+/**
+ * Entity bean for alf_mimetype table.
+ *
+ * These are unique (see {@link #equals(Object) equals} and {@link #hashCode() hashCode}) based
+ * on the {@link #getMimetype() mimetype} value.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class MimetypeEntity
+{
+ public static final Long CONST_LONG_ZERO = new Long(0L);
+
+ private Long id;
+ private Long version;
+ private String mimetype;
+
+ @Override
+ public int hashCode()
+ {
+ return (mimetype == null ? 0 : mimetype.hashCode());
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ {
+ return true;
+ }
+ else if (obj instanceof MimetypeEntity)
+ {
+ MimetypeEntity that = (MimetypeEntity) obj;
+ return EqualsHelper.nullSafeEquals(this.mimetype, that.mimetype);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ @Override
+ public String toString()
+ {
+ StringBuilder sb = new StringBuilder(512);
+ sb.append("MimetypeEntity")
+ .append("[ ID=").append(id)
+ .append(", mimetype=").append(mimetype)
+ .append("]");
+ return sb.toString();
+ }
+
+ public Long getId()
+ {
+ return id;
+ }
+
+ public void setId(Long id)
+ {
+ this.id = id;
+ }
+
+ public Long getVersion()
+ {
+ return version;
+ }
+
+ public void setVersion(Long version)
+ {
+ this.version = version;
+ }
+
+ public String getMimetype()
+ {
+ return mimetype;
+ }
+
+ public void setMimetype(String mimetype)
+ {
+ this.mimetype = mimetype;
+ }
+}
diff --git a/source/java/org/alfresco/repo/domain/mimetype/ibatis/MimetypeDAOImpl.java b/source/java/org/alfresco/repo/domain/mimetype/ibatis/MimetypeDAOImpl.java
new file mode 100644
index 0000000000..4cb78aa3aa
--- /dev/null
+++ b/source/java/org/alfresco/repo/domain/mimetype/ibatis/MimetypeDAOImpl.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2005-2009 Alfresco Software Limited.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+ * As a special exception to the terms and conditions of version 2.0 of
+ * the GPL, you may redistribute this Program in connection with Free/Libre
+ * and Open Source Software ("FLOSS") applications as described in Alfresco's
+ * FLOSS exception. You should have recieved a copy of the text describing
+ * the FLOSS exception, and it is also available here:
+ * http://www.alfresco.com/legal/licensing"
+ */
+package org.alfresco.repo.domain.mimetype.ibatis;
+
+import org.alfresco.repo.domain.mimetype.AbstractMimetypeDAOImpl;
+import org.alfresco.repo.domain.mimetype.MimetypeEntity;
+import org.springframework.orm.ibatis.SqlMapClientTemplate;
+
+/**
+ * iBatis-specific implementation of the Mimetype DAO.
+ *
+ * @author Derek Hulley
+ * @since 3.2
+ */
+public class MimetypeDAOImpl extends AbstractMimetypeDAOImpl
+{
+ private static final String SELECT_MIMETYPE_BY_ID = "select.MimetypeById";
+ private static final String SELECT_MIMETYPE_BY_KEY = "select.MimetypeByKey";
+ private static final String INSERT_MIMETYPE = "insert.Mimetype";
+
+ private SqlMapClientTemplate template;
+
+ public void setSqlMapClientTemplate(SqlMapClientTemplate sqlMapClientTemplate)
+ {
+ this.template = sqlMapClientTemplate;
+ }
+
+ @Override
+ protected MimetypeEntity getMimetypeEntity(Long id)
+ {
+ MimetypeEntity mimetypeEntity = new MimetypeEntity();
+ mimetypeEntity.setId(id);
+ mimetypeEntity = (MimetypeEntity) template.queryForObject(SELECT_MIMETYPE_BY_ID, mimetypeEntity);
+ // Done
+ return mimetypeEntity;
+ }
+
+ @Override
+ protected MimetypeEntity getMimetypeEntity(String mimetype)
+ {
+ MimetypeEntity mimetypeEntity = new MimetypeEntity();
+ mimetypeEntity.setMimetype(mimetype);
+ mimetypeEntity = (MimetypeEntity) template.queryForObject(SELECT_MIMETYPE_BY_KEY, mimetypeEntity);
+ // Could be null
+ return mimetypeEntity;
+ }
+
+ @Override
+ protected MimetypeEntity createMimetypeEntity(String mimetype)
+ {
+ MimetypeEntity mimetypeEntity = new MimetypeEntity();
+ mimetypeEntity.setVersion(MimetypeEntity.CONST_LONG_ZERO);
+ mimetypeEntity.setMimetype(mimetype);
+ Long id = (Long) template.insert(INSERT_MIMETYPE, mimetypeEntity);
+ mimetypeEntity.setId(id);
+ // Done
+ return mimetypeEntity;
+ }
+}
diff --git a/source/java/org/alfresco/repo/lock/JobLockService.java b/source/java/org/alfresco/repo/lock/JobLockService.java
index 8edc2ecbea..6db55abc34 100644
--- a/source/java/org/alfresco/repo/lock/JobLockService.java
+++ b/source/java/org/alfresco/repo/lock/JobLockService.java
@@ -68,21 +68,63 @@ public interface JobLockService
* @throws LockAcquisitionException if the lock could not be acquired
* @throws IllegalStateException if a transaction is not active
*/
- void getTransacionalLock(QName lockQName, long timeToLive);
+ void getTransactionalLock(QName lockQName, long timeToLive);
/**
- * {@inheritDoc JobLockService#getTransacionalLock(QName, long)}
+ * {@inheritDoc JobLockService#getTransactionalLock(QName, long)}
*
* If the lock cannot be immediately acquired, the process will wait and retry. Note
* that second and subsequent attempts to get the lock during a transaction cannot
* make use of retrying; the lock is actually being refreshed and will therefore never
* become valid if it doesn't refresh directly.
*
- * @param timeToLive the time (in milliseconds) for the lock to remain valid
* @param retryWait the time (in milliseconds) to wait before trying again
* @param retryCount the maximum number of times to attempt the lock acquisition
* @throws LockAcquisitionException if the lock could not be acquired
* @throws IllegalStateException if a transaction is not active
*/
- void getTransacionalLock(QName lockQName, long timeToLive, long retryWait, int retryCount);
+ void getTransactionalLock(QName lockQName, long timeToLive, long retryWait, int retryCount);
+
+ /**
+ * Take a manually-managed lock. The lock current thread or transaction will not be tagged -
+ * the returned lock token must be used for further management of the lock.
+ *
+ * No lock management is provided: the lock must be released manually or will only become
+ * available by expiry. No deadlock management is provided, either.
+ *
+ * @param lockQName the name of the lock to acquire
+ * @param timeToLive the time (in milliseconds) for the lock to remain valid
+ * @return Returns the newly-created lock token
+ * @throws LockAcquisitionException if the lock could not be acquired
+ */
+ String getLock(QName lockQName, long timeToLive);
+
+ /**
+ * {@inheritDoc JobLockService#getLock(QName, long)}
+ *
+ * If the lock cannot be immediately acquired, the process will wait and retry.
+ *
+ * @param retryWait the time (in milliseconds) to wait before trying again
+ * @param retryCount the maximum number of times to attempt the lock acquisition
+ * @throws LockAcquisitionException if the lock could not be acquired
+ */
+ String getLock(QName lockQName, long timeToLive, long retryWait, int retryCount);
+
+ /**
+ * Refresh the lock using a valid lock token.
+ *
+ * @param lockToken the lock token returned when the lock was acquired
+ * @param lockQName the name of the previously-acquired lock
+ * @param timeToLive the time (in milliseconds) for the lock to remain valid
+ * @throws LockAcquisitionException if the lock could not be refreshed or acquired
+ */
+ void refreshLock(String lockToken, QName lockQName, long timeToLive);
+
+ /**
+ * Release the lock using a valid lock token.
+ *
+ * @param lockToken the lock token returned when the lock was acquired
+ * @param lockQName the name of the previously-acquired lock
+ */
+ void releaseLock(String lockToken, QName lockQName);
}
diff --git a/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java b/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java
index 6555a37170..285d000653 100644
--- a/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java
+++ b/source/java/org/alfresco/repo/lock/JobLockServiceImpl.java
@@ -33,6 +33,7 @@ import org.alfresco.repo.transaction.TransactionListenerAdapter;
import org.alfresco.repo.transaction.TransactionalResourceHelper;
import org.alfresco.repo.transaction.RetryingTransactionHelper.RetryingTransactionCallback;
import org.alfresco.service.namespace.QName;
+import org.alfresco.util.GUID;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -103,15 +104,15 @@ public class JobLockServiceImpl implements JobLockService
/**
* {@inheritDoc}
*/
- public void getTransacionalLock(QName lockQName, long timeToLive)
+ public void getTransactionalLock(QName lockQName, long timeToLive)
{
- getTransacionalLock(lockQName, timeToLive, defaultRetryWait, defaultRetryCount);
+ getTransactionalLock(lockQName, timeToLive, defaultRetryWait, defaultRetryCount);
}
/**
* {@inheritDoc}
*/
- public void getTransacionalLock(QName lockQName, long timeToLive, long retryWait, int retryCount)
+ public void getTransactionalLock(QName lockQName, long timeToLive, long retryWait, int retryCount)
{
// Check that transaction is present
final String txnId = AlfrescoTransactionSupport.getTransactionId();
@@ -127,7 +128,7 @@ public class JobLockServiceImpl implements JobLockService
if (!added)
{
// It's a refresh. Ordering is not important here as we already hold the lock.
- refreshLock(lockQName, timeToLive);
+ refreshLock(txnId, lockQName, timeToLive);
}
else
{
@@ -144,7 +145,7 @@ public class JobLockServiceImpl implements JobLockService
}
// If it was last in the set, then the order is correct and we use the
// full retry behaviour.
- getLock(lockQName, timeToLive, retryWait, retryCount);
+ getLockImpl(txnId, lockQName, timeToLive, retryWait, retryCount);
}
else
{
@@ -158,26 +159,47 @@ public class JobLockServiceImpl implements JobLockService
}
// The lock request is made out of natural order.
// Unordered locks do not get any retry behaviour
- getLock(lockQName, timeToLive, retryWait, 1);
+ getLockImpl(txnId, lockQName, timeToLive, retryWait, 1);
}
}
// It went in, so add it to the transactionally-stored set
heldLocks.add(lockQName);
// Done
}
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see #getLock(QName, long, long, int)
+ */
+ public String getLock(QName lockQName, long timeToLive)
+ {
+ return getLock(lockQName, timeToLive, defaultRetryWait, defaultRetryCount);
+ }
/**
+ * {@inheritDoc}
+ */
+ public String getLock(QName lockQName, long timeToLive, long retryWait, int retryCount)
+ {
+ String lockToken = GUID.generate();
+ getLockImpl(lockToken, lockQName, timeToLive, retryWait, retryCount);
+ // Done
+ return lockToken;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @throws LockAcquisitionException on failure
*/
- private void refreshLock(final QName lockQName, final long timeToLive)
+ public void refreshLock(final String lockToken, final QName lockQName, final long timeToLive)
{
- // The lock token is the current transaction ID
- final String txnId = AlfrescoTransactionSupport.getTransactionId();
RetryingTransactionCallback