/*
* 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 java.util.List;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
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.Gadget;
import org.eurekastreams.server.domain.Tab;
import org.eurekastreams.server.domain.TabTemplate;
import org.eurekastreams.server.persistence.exceptions.GadgetDeletionException;
import org.eurekastreams.server.persistence.exceptions.GadgetUndeletionException;
/**
* This class provides the mapper functionality for Tab entities.
*/
public class TabMapper
{
/**
* Constructor.
*
* @param inQueryOptimizer
* the QueryOptimizer to use for specialized functions.
*/
public TabMapper(final QueryOptimizer inQueryOptimizer)
{
queryOptimizer = inQueryOptimizer;
}
/**
* The QueryOptimizer to use for specialized functions.
*/
private QueryOptimizer queryOptimizer;
/**
* The entity manager to use for all ORM operations.
*/
private EntityManager entityManager;
/**
* Default value for undeleteGadgetWindowInMinutes.
*/
private final int defaultUndeleteGadgetWindowInMinutes = 20;
/**
* The number of minutes to allow a gadget to be undeleted in, defaults to 20 minutes.
*/
private int undeleteGadgetWindowInMinutes = defaultUndeleteGadgetWindowInMinutes;
/**
* Logger instance for this class.
*/
private Log logger = LogFactory.getLog(TabMapper.class);
/**
* Temp Method to get templates based on type. Note: this method only returns a single result, but queries all tab
* templates. There is no way for the query to distinguish which tab template comes back.
*
* @param type
* The TabType.
* @return the TabTemplate.
*/
public TabTemplate getTabTemplate(final String type)
{
Query q = entityManager.createQuery("FROM TabTemplate t where t.type=:tabTemplateType").setParameter(
"tabTemplateType", type);
return (TabTemplate) q.getSingleResult();
}
/**
* Set the number of minutes to allow a gadget to be undeleted in.
*
* @param undeleteWindowInMinutes
* the number of minutes to allow a tab to be undeleted in.
*/
public void setUndeleteGadgetWindowInMinutes(final int undeleteWindowInMinutes)
{
this.undeleteGadgetWindowInMinutes = undeleteWindowInMinutes;
}
/**
* Set the entity manager to use for all ORM operations.
*
* @param inEntityManager
* the entity manager to use for all ORM operations.
*/
@PersistenceContext
public void setEntityManager(final EntityManager inEntityManager)
{
this.entityManager = inEntityManager;
}
/**
* Find the Tab by id and eagerly load the gadget collection.
*
* @param tabId
* ID of the Tab to look up.
*
* @return the entity with the input
*/
public Tab findById(final Long tabId)
{
Query q = entityManager.createQuery(
"from Tab t left join fetch t.template where t.id = :tabId and t.deleted = 'false'").setParameter(
"tabId", tabId);
Tab tab = (Tab) q.getSingleResult();
// Touch the gadgets so that they will be eagerly loaded.
tab.getGadgets().size();
return tab;
}
/**
* Find the parent Tab of the input Gadget id.
*
* @param gadgetId
* the Gadget id to find the parent Tab for.
* @return the parent Tab
*/
public TabTemplate findByGadgetId(final Long gadgetId)
{
return getTabTemplateByGadgetId(gadgetId, false);
}
/**
* This method retrieves a tab with only a gadget id to start with.
*
* @param gadgetId
* - id of the gadget used to retrieve the container tab.
* @return - container tab of the gadget id passed in.
*/
public Tab findTabByGadgetId(final Long gadgetId)
{
Query q = entityManager.createQuery(
"SELECT t FROM Tab t, Gadget g " + "WHERE g.id = :gadgetId " + "AND g.template.id = t.template.id")
.setParameter("gadgetId", gadgetId);
Tab tab = (Tab) q.getSingleResult();
// Touch the gadgets so that they will be eagerly loaded.
tab.getGadgets().size();
return tab;
}
/**
* Find the Tab by id.
*
* @param tabId
* ID of the Tab to look up
* @return the entity with the input
*/
public Tab findById(final Integer tabId)
{
return findById(tabId.longValue());
}
/**
* Update all entities that have changed since they were loaded within the same context.
*/
public void flush()
{
entityManager.flush();
}
/**
* Mark the input gadget as deleted so that it may be undeleted later on.
*
* @param gadgetToDelete
* The gadget to delete.
* @throws GadgetDeletionException
* thrown when the caller tries to delete a gadget from a tab that doesn't own the input gadget.
*/
public void deleteGadget(final Gadget gadgetToDelete) throws GadgetDeletionException
{
// find the tab to which the input gadget belongs
TabTemplate tab = null;
try
{
tab = getTabTemplateByGadgetId(gadgetToDelete.getId(), false);
}
catch (Exception ex)
{
throw new GadgetDeletionException("Could not find either the specified Gadget or Tab", gadgetToDelete
.getId());
}
// remove the gadget from the collection
if (tab.getGadgets().size() > 1)
{
tab.getGadgets().remove(gadgetToDelete);
tab.getGadgets().add(gadgetToDelete);
}
// rearrange the gadgetIndexes of the other gadgets
for (Gadget currentGadget : tab.getGadgets())
{
if (currentGadget.getZoneNumber() == gadgetToDelete.getZoneNumber()
&& currentGadget.getZoneIndex() > gadgetToDelete.getZoneIndex())
{
currentGadget.setZoneIndex(currentGadget.getZoneIndex() - 1);
}
}
// mark gadget as deleted, and re-attach it to the tab, because the
// previous statement detatched it.
markGadgetAsDeleted(tab, gadgetToDelete);
// clean up all previously deleted gadgets that are no longer in the
// undelete time frame.
cleanUpDeletedGadgets();
}
/**
* Implementation of the undelete method from the TabMapper interface.
*
* @param gadgetId
* id of the gadget to be undeleted.
* @return gadget object represented by the gadget id.
* @throws GadgetUndeletionException
* thrown when error undeleting a gadget.
*/
public Gadget undeleteGadget(final long gadgetId) throws GadgetUndeletionException
{
// make sure the gadget exists in the input tab
TabTemplate template = null;
try
{
template = getTabTemplateByGadgetId(gadgetId, true);
}
catch (Exception ex)
{
throw new GadgetUndeletionException("Could not find either the specified gadget or tab for gadgetId="
+ gadgetId, gadgetId);
}
/* get the deleted gadget from the database */
Gadget gadgetToUndelete = (Gadget) entityManager.createQuery(
"from Gadget where id = :gadgetId and deleted = true").setParameter("gadgetId", gadgetId)
.getSingleResult();
if (gadgetToUndelete == null)
{
throw new GadgetUndeletionException("Failure when trying to get gadget with id=" + gadgetId, gadgetId);
}
try
{
/*
* bump up the zone index of each gadget in the same zone as the gadget to be undeleted
*/
for (Gadget currentGadget : template.getGadgets())
{
if (currentGadget.getZoneNumber() == gadgetToUndelete.getZoneNumber()
&& currentGadget.getZoneIndex() >= gadgetToUndelete.getZoneIndex())
{
currentGadget.setZoneIndex(currentGadget.getZoneIndex() + 1);
}
}
/* add the gadget back into the collection */
template.getGadgets().add(gadgetToUndelete);
/* update the status of the undeleted gadget in the database */
entityManager.createQuery(
"update versioned Gadget set deleted = false, " + "dateDeleted = null, tabTemplateId = :tabId "
+ "where id = :gadgetId").setParameter("gadgetId", gadgetToUndelete.getId()).setParameter(
"tabId", template.getId()).executeUpdate();
return gadgetToUndelete;
}
catch (Exception ex)
{
throw new GadgetUndeletionException("An error occurred while trying to undelete the gadget with gadgetId="
+ gadgetId, gadgetId);
}
}
/**
* Get the tab by gadget id.
*
* @param gadgetId
* The id of the gadget to find the tab for.
*
* @param isDeleted
* whether to look for deleted or undeleted gadget.
*
* @return the TabGroup that owns the input tabId.
*/
private TabTemplate getTabTemplateByGadgetId(final long gadgetId, final boolean isDeleted)
{
return (TabTemplate) entityManager.createQuery(
"select g.template from Gadget g where g.id = :gadgetId and g.deleted = :isDeleted").setParameter(
"gadgetId", gadgetId).setParameter("isDeleted", isDeleted).getSingleResult();
}
/**
* Mark the input gadget as deleted so that it's no longer returned in queries but can be undeleted later on. The
* gadget would have just been removed from the gadget, so we need to set the tabId back to the gadget so that it's
* ignored by the collection.
*
* @param template
* The TabTemplate that contains the gadget.
* @param gadget
* The gadget to mark as deleted.
*/
private void markGadgetAsDeleted(final TabTemplate template, final Gadget gadget)
{
GregorianCalendar currentDateTime = new GregorianCalendar();
entityManager.createQuery(
"update versioned Gadget set deleted = true, "
+ "dateDeleted = :deletedTimestamp, tabTemplateId = :tabTemplateId " + "where id = :gadgetId")
.setParameter("deletedTimestamp", currentDateTime.getTime()).setParameter("gadgetId", gadget.getId())
.setParameter("tabTemplateId", template.getId()).executeUpdate();
}
/**
* Clean up deleted gadgets here using the expired date set earlier. Currently this is hard-coded to be at least 20
* (configurable) minutes since the gadget was originally deleted, but could be much longer because it is dependent
* on the next gadget that is deleted. If one gadget is deleted on Jan 1st and the next gadget is deleted on March
* 1st, the 1st gadget will remain flagged as deleted in the database until March 1st so we definitely need a full
* timestamp for this object.
*/
private void cleanUpDeletedGadgets()
{
GregorianCalendar expiredDateTime = new GregorianCalendar();
expiredDateTime.add(Calendar.MINUTE, -undeleteGadgetWindowInMinutes);
entityManager.createQuery(
"delete from Gadget de where de.deleted = true " + "and de.dateDeleted < :expiredTimestamp")
.setParameter("expiredTimestamp", expiredDateTime.getTime()).executeUpdate();
}
/**
* This method is responsible for moving a gadget from one location to another on the any gadget page.
*
* @param gadgetId
* - id of the gadget that is being moved.
* @param sourceTabTemplateId
* - id of the tab template where the gadget is moving from.
* @param sourceZoneIndex
* - index of the position in the zone that the gadget is moving from.
* @param sourceZoneNumber
* - number of the zone that the gadget is moving from.
* @param targetTabTemplateId
* - id of the tab template where the gadget is moving to.
* @param targetZoneIndex
* - index of the position in the zone that the gadget is moving to.
* @param targetZoneNumber
* - number of the zone that the gadget is moving to.
*/
@SuppressWarnings("unchecked")
public void moveGadget(final Long gadgetId, final Long sourceTabTemplateId, final Integer sourceZoneIndex,
final Integer sourceZoneNumber, final Long targetTabTemplateId, final Integer targetZoneIndex,
final Integer targetZoneNumber)
{
logger.debug("Moving gadget: " + gadgetId + " from tab templateid: " + sourceTabTemplateId + " zoneNumber: "
+ sourceZoneNumber + " zoneIndex: " + sourceZoneIndex + " To " + " tab templateid: "
+ targetTabTemplateId + " zoneNumber: " + targetZoneNumber + " zoneIndex: " + targetZoneIndex);
// Source
List<Gadget> sourceGadgets = (List<Gadget>) entityManager.createQuery(
"FROM Gadget g " + "WHERE g.template.id =:sourceTabTemplateId "
+ "AND g.zoneNumber =:sourceZoneNumber AND g.deleted = false ORDER BY g.zoneIndex")
.setParameter("sourceTabTemplateId", sourceTabTemplateId).setParameter("sourceZoneNumber",
sourceZoneNumber).getResultList();
Gadget movingGadget = null;
for (int i = 0; i < sourceGadgets.size(); i++)
{
if (sourceGadgets.get(i).getId() == gadgetId)
{
movingGadget = sourceGadgets.get(i);
sourceGadgets.remove(i);
}
}
// Target
List<Gadget> targetGadgets = (List<Gadget>) entityManager.createQuery(
"FROM Gadget g " + "WHERE g.template.id =:targetTabTemplateId "
+ "AND g.zoneNumber =:targetZoneNumber AND g.deleted = false ORDER BY g.zoneIndex")
.setParameter("targetTabTemplateId", targetTabTemplateId).setParameter("targetZoneNumber",
targetZoneNumber).getResultList();
// Remove from target as well
for (int i = 0; i < targetGadgets.size(); i++)
{
if (targetGadgets.get(i).getId() == gadgetId)
{
targetGadgets.remove(i);
}
}
if (targetZoneIndex < targetGadgets.size())
{
targetGadgets.add(targetZoneIndex, movingGadget);
}
else
{
targetGadgets.add(movingGadget);
}
// Renumber Source gadgets
for (int i = 0; i < sourceGadgets.size(); i++)
{
Gadget g = sourceGadgets.get(i);
entityManager.createQuery(
"UPDATE versioned Gadget g SET g.zoneIndex = :newZoneIndex WHERE g.id = :gadgetId").setParameter(
"newZoneIndex", i).setParameter("gadgetId", g.getId()).executeUpdate();
}
// Renumber target gadgets
for (int i = 0; i < targetGadgets.size(); i++)
{
Gadget g = targetGadgets.get(i);
entityManager.createQuery(
"UPDATE versioned Gadget g SET g.zoneIndex = :newZoneIndex, "
+ "g.template.id =:targetTabTemplateId, g.zoneNumber =:targetZoneNumber "
+ "WHERE g.id = :gadgetId").setParameter("newZoneIndex", i).setParameter("gadgetId",
g.getId()).setParameter("targetTabTemplateId", targetTabTemplateId).setParameter(
"targetZoneNumber", targetZoneNumber).executeUpdate();
}
}
}