/*
* Copyright (c) 2009-2010 Lockheed Martin Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.eurekastreams.server.persistence;
import java.util.Calendar;
import java.util.GregorianCalendar;
import javax.persistence.Query;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.eurekastreams.commons.hibernate.QueryOptimizer;
import org.eurekastreams.server.domain.Tab;
import org.eurekastreams.server.domain.TabGroup;
import org.eurekastreams.server.domain.TabTemplate;
import org.eurekastreams.server.persistence.exceptions.TabDeletionException;
import org.eurekastreams.server.persistence.exceptions.TabUndeletionException;
/**
* This class provides the mapper functionality for TabGroup entities.
*/
public class TabGroupMapper extends DomainEntityMapper<TabGroup>
{
/**
* Constructor.
*
* @param inQueryOptimizer
* the QueryOptimizer to use for specialized functions.
*/
public TabGroupMapper(final QueryOptimizer inQueryOptimizer)
{
super(inQueryOptimizer);
}
/**
* logger.
*/
private static Log logger = LogFactory.getLog(TabGroupMapper.class);
/**
* Default value for undeleteTabWindowInMinutes.
*/
private final int defaultUndeleteTabWindowInMinutes = 20;
/**
* The number of minutes to allow a tab to be undeleted in, defaults to 20 minutes.
*/
private int undeleteTabWindowInMinutes = defaultUndeleteTabWindowInMinutes;
/**
* Set the number of minutes to allow a tab to be undeleted in.
*
* @param undeleteWindowInMinutes
* the number of minutes to allow a tab to be undeleted in.
*/
public void setUndeleteTabWindowInMinutes(final int undeleteWindowInMinutes)
{
this.undeleteTabWindowInMinutes = undeleteWindowInMinutes;
}
/**
* Get the name of the entity for JpaDomainEntityMapper.
*
* @return The DomainEntityName.
*/
@Override
protected String getDomainEntityName()
{
return "TabGroup";
}
/**
* Mark the input tab as deleted so that it may be undeleted later on.
*
* @param tab
* The tab to delete.
* @throws TabDeletionException
* thrown when the caller tries to delete a Tab from a TabGroup that doesn't own the input Tab.
*/
public void deleteTab(final Tab tab) throws TabDeletionException
{
logger.debug("Attempting to delete the tab with id=" + tab.getId());
// make sure the tab exists in the input TabGroup
TabGroup tabGroup = null;
try
{
tabGroup = getTabGroupByTabId(tab.getId(), false);
}
catch (Exception ex)
{
throw new TabDeletionException("Could not find either the specified Tab or TabGroup", ex, tab);
}
// remove the tab from the collection to rearrange the tabIndexes of the
// other tabs
if (tabGroup.getTabs().size() > 1)
{
tabGroup.getTabs().remove(tab);
}
// mark it as deleted, and re-attach it to the tabGroup, because the
// previous statement detatched it.
markTabAsDeleted(tabGroup, tab);
// clean up all previously deleted tabs that are no longer in the
// undelete time frame.
cleanUpDeletedTabs();
}
/**
* Implementation of the undelete method from the TabGroupMapper interface.
*
* @param tabId
* id of the Tab to be undeleted.
* @return Tab object represented by the Tab id.
* @throws TabUndeletionException
* thrown when error undeleting a Tab.
*/
public Tab undeleteTab(final long tabId) throws TabUndeletionException
{
// make sure the Tab exists in the input tabGroup
TabGroup tabGroup = null;
long start;
try
{
start = System.currentTimeMillis();
logger.debug("***Getting tab group by tab Id");
tabGroup = getTabGroupByTabId(tabId, true);
logger.debug("***Done getting tab group by tab Id (" + (System.currentTimeMillis() - start) + "ms");
}
catch (Exception ex)
{
throw new TabUndeletionException("Could not find either the specified Tab or tabGroup for TabId=" + tabId,
tabId);
}
start = System.currentTimeMillis();
logger.debug("***Getting tab to undelete by tab Id and status");
/* get the deleted Tab from the tab group */
Tab tabToUndelete = (Tab) getEntityManager().createQuery("from Tab where id = :TabId and deleted = true")
.setParameter("TabId", tabId).getSingleResult();
logger.debug("***Done Getting tab to undelete by tab Id and status (" + (System.currentTimeMillis() - start)
+ "ms");
if (tabToUndelete == null)
{
throw new TabUndeletionException("Failure when trying to get Tab with id=" + tabId, tabId);
}
try
{
/*
* re-insert the undeleted tab into the collection
*/
tabGroup.getTabs().add(tabToUndelete.getTabIndex(), tabToUndelete);
/* update the status of the undeleted Tab in the database */
start = System.currentTimeMillis();
logger.debug("***Update tab status");
getEntityManager()
.createQuery(
"update versioned Tab set deleted = false, "
+ "dateDeleted = null, tabGroupId = :tabGroupId " + "where id = :TabId")
.setParameter("TabId", tabToUndelete.getId()).setParameter("tabGroupId", tabGroup.getId())
.executeUpdate();
logger.debug("***Done Update tab status (" + (System.currentTimeMillis() - start) + "ms)");
logger.debug("Un-deleted the tab with id=" + tabToUndelete.getId());
/* update the status of the template of the undeleted Tab */
start = System.currentTimeMillis();
logger.debug("***Update tab template status");
getEntityManager()
.createQuery(
"update versioned TabTemplate set deleted = false, " + "dateDeleted = null "
+ "where id = :TabTemplateId")
.setParameter("TabTemplateId", tabToUndelete.getTemplate().getId()).executeUpdate();
logger.debug("***Done Update tab template status (" + (System.currentTimeMillis() - start) + "ms)");
logger.debug("Un-deleted the tab template with id=" + tabToUndelete.getTemplate().getId());
/* update the status of the template's gadgets of the undeleted Tab */
start = System.currentTimeMillis();
logger.debug("***Update tab template's gadgets status");
getEntityManager()
.createQuery(
"update versioned Gadget set deleted = false, " + "dateDeleted = null "
+ "where template.id = :TabTemplateId")
.setParameter("TabTemplateId", tabToUndelete.getTemplate().getId()).executeUpdate();
logger.debug(//
"***Done Update tab template's gadgets status (" + (System.currentTimeMillis() - start) + "ms)");
logger.debug("Un-deleted gadgets belonging to tab template with id=" + tabToUndelete.getTemplate().getId());
// refresh the restored tab to reload previously deleted components
start = System.currentTimeMillis();
logger.debug("***entity manager flush");
// NOTE: DO NOT use entitymanager.refresh! It's far far faster to throw away object and re-query for it.
// getEntityManager().refresh(tabToUndelete);
getEntityManager().flush();
getEntityManager().clear();
logger.debug("***Done entity manager flush (" + (System.currentTimeMillis() - start) + "ms)");
start = System.currentTimeMillis();
logger.debug("***Getting tab to return by tab Id and status");
/* get the deleted Tab from the tab group */
tabToUndelete = (Tab) getEntityManager()
.createQuery("from Tab t left join fetch t.template where t.id = :tabId and t.deleted = 'false'")
.setParameter("tabId", tabId).getSingleResult();
// Touch the gadgets so that they will be eagerly loaded.
tabToUndelete.getGadgets().size();
logger.debug("***Done Getting tab to return by tab Id and status (" + (System.currentTimeMillis() - start)
+ "ms)");
return tabToUndelete;
}
catch (Exception ex)
{
throw new TabUndeletionException("An error occurred while trying to undelete the Tab with TabId=" + tabId,
tabId);
}
}
/**
* Get the TabGroup by Tab id.
*
* @param tabId
* The id of the tab to find the TabGroup for.
*
* @param isDeleted
* whether to look for deleted or undeleted Tab.
*
* @return the TabGroup that owns the input tabId.
*/
public TabGroup getTabGroupByTabId(final long tabId, final boolean isDeleted)
{
logger.debug("Looking for the tab group that contains the " + (isDeleted ? "deleted" : "active")
+ " tab with tabId=" + tabId);
Query q = getEntityManager()
.createQuery("select t.tabGroup from Tab t where t.id = :tabId and t.deleted = :isDeleted")
.setParameter("tabId", tabId).setParameter("isDeleted", isDeleted);
return (TabGroup) q.getSingleResult();
}
/**
* Mark the input tab as deleted so that it's no longer returned in queries but can be undeleted later on. The tab
* would have just been removed from the TabGroup, so we need to set the tabGroupId back to the Tab, and the
* tabIndex=null so that it's ignored by the collection.
*
* Conditionally mark the tab template as deleted, if this tab is the last one so you can undelete the tab template
* with it
*
* @param tabGroup
* The tab group that contains tab.
* @param tab
* The tab to mark as deleted.
*/
private void markTabAsDeleted(final TabGroup tabGroup, final Tab tab)
{
GregorianCalendar currentDateTime = new GregorianCalendar();
long count = getTabCountForTemplate(tab.getTemplate());
// if you only have one tab left, mark the template and its gadgets
// deleted too so you can undelete them
if (count == 1)
{
getEntityManager()
.createQuery(
"update versioned Gadget set deleted = true, " + "dateDeleted = :deletedTimestamp "
+ "where template.id = :tabTemplateId")
.setParameter("deletedTimestamp", currentDateTime.getTime())
.setParameter("tabTemplateId", tab.getTemplate().getId()).executeUpdate();
getEntityManager()
.createQuery(
"update versioned TabTemplate set deleted = true, " + "dateDeleted = :deletedTimestamp "
+ "where id = :tabTemplateId")
.setParameter("deletedTimestamp", currentDateTime.getTime())
.setParameter("tabTemplateId", tab.getTemplate().getId()).executeUpdate();
}
// still mark the tab deleted
getEntityManager()
.createQuery(
"update versioned Tab set deleted = true, " + "dateDeleted = :deletedTimestamp, "
+ "tabIndex = :tabIndex, " + "tabGroupId = :tabGroupId " + "where id = :tabId")
.setParameter("deletedTimestamp", currentDateTime.getTime()).setParameter("tabId", tab.getId())
.setParameter("tabGroupId", tabGroup.getId()).setParameter("tabIndex", tab.getTabIndex())
.executeUpdate();
}
/**
* Returns number of Tabs associated with a TabTemplate.
*
* @param template
* The Template to check.
* @return Number of Tabs associated with a TabTemplate.
*/
public long getTabCountForTemplate(final TabTemplate template)
{
String query = "select count( t ) from Tab t where t.template.id = :templateId ";
Query countQuery = getEntityManager().createQuery(query);
countQuery.setParameter("templateId", template.getId());
long count = ((Long) countQuery.getSingleResult()).longValue();
return count;
}
/**
* Clean up deleted tabs here using the expired date set earlier. Currently this is hard-coded to be at least 20
* (configurable) minutes since the tab was originally deleted, but could be much longer because it is dependent on
* the next tab that is deleted. If one tab is deleted on Jan 1st and the next tab is deleted on March 1st, the 1st
* tab will remain flagged as deleted in the database until March 1st so we definitely need a full timestamp for
* this object.
*/
private void cleanUpDeletedTabs()
{
GregorianCalendar expiredDateTime = new GregorianCalendar();
expiredDateTime.add(Calendar.MINUTE, -undeleteTabWindowInMinutes);
getEntityManager()
.createQuery(
"delete from Gadget gd where gd.deleted = true " + "and gd.dateDeleted < :expiredTimestamp")
.setParameter("expiredTimestamp", expiredDateTime.getTime()).executeUpdate();
getEntityManager()
.createQuery("delete from Tab de where de.deleted = true " + "and de.dateDeleted < :expiredTimestamp")
.setParameter("expiredTimestamp", expiredDateTime.getTime()).executeUpdate();
try
{
getEntityManager()
.createQuery(
"delete from TabTemplate de where de.deleted = true "
+ "and de.dateDeleted < :expiredTimestamp")
.setParameter("expiredTimestamp", expiredDateTime.getTime()).executeUpdate();
}
catch (Exception e)
{
// This should never happen because a tab template is not marked as deleted unless
// there are no reference to it by active tabs
logger.debug("Unable to delete a tab template because there is still a tab that references it");
}
}
}