package edu.harvard.iq.dataverse;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import static javax.ejb.TransactionAttributeType.REQUIRES_NEW;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.NonUniqueResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;
import org.apache.commons.lang.StringUtils;
import org.ocpsoft.common.util.Strings;
/**
* Your goto bean for everything {@link DvObject}, that's not tied to any
* concrete subclass.
*
* @author michael
*/
@Stateless
@Named
public class DvObjectServiceBean implements java.io.Serializable {
@PersistenceContext(unitName = "VDCNet-ejbPU")
private EntityManager em;
private static final Logger logger = Logger.getLogger(DvObjectServiceBean.class.getCanonicalName());
/**
* @param dvoc The object we check
* @return {@code true} iff the passed object is the owner of any
* {@link DvObject}.
*/
public boolean hasData(DvObjectContainer dvoc) {
return em.createNamedQuery("DvObject.ownedObjectsById", Long.class)
.setParameter("id", dvoc.getId())
.getSingleResult() > 0;
}
public DvObject findDvObject(Long id) {
try {
return em.createNamedQuery("DvObject.findById", DvObject.class)
.setParameter("id", id)
.getSingleResult();
} catch (NoResultException | NonUniqueResultException ex) {
return null;
}
}
public List<DvObject> findAll() {
return em.createNamedQuery("DvObject.findAll", DvObject.class).getResultList();
}
public DvObject updateContentIndexTime(DvObject dvObject) {
/**
* @todo to avoid a possible OptimisticLockException, should we merge
* dvObject before we try to setIndexTime? See
* https://github.com/IQSS/dataverse/commit/6ad0ebb272c8cb46368cb76784b55dbf33eea947
*/
DvObject dvObjectToModify = findDvObject(dvObject.getId());
dvObjectToModify.setIndexTime(new Timestamp(new Date().getTime()));
DvObject savedDvObject = em.merge(dvObjectToModify);
return savedDvObject;
}
/**
* @todo DRY! Perhaps we should merge this with the older
* updateContentIndexTime method.
*/
public DvObject updatePermissionIndexTime(DvObject dvObject) {
/**
* @todo to avoid a possible OptimisticLockException, should we merge
* dvObject before we try to set this timestamp? See
* https://github.com/IQSS/dataverse/commit/6ad0ebb272c8cb46368cb76784b55dbf33eea947
*/
Long dvObjectId = dvObject.getId();
DvObject dvObjectToModify = findDvObject(dvObjectId);
if (dvObjectToModify == null) {
logger.fine("Unable to update permission index time on DvObject with id of " + dvObjectId);
return dvObject;
}
dvObjectToModify.setPermissionIndexTime(new Timestamp(new Date().getTime()));
DvObject savedDvObject = em.merge(dvObjectToModify);
logger.fine("Updated permission index time for DvObject id " + dvObjectId);
return savedDvObject;
}
@TransactionAttribute(REQUIRES_NEW)
public int clearAllIndexTimes() {
Query clearIndexTimes = em.createQuery("UPDATE DvObject o SET o.indexTime = NULL, o.permissionIndexTime = NULL");
int numRowsUpdated = clearIndexTimes.executeUpdate();
return numRowsUpdated;
}
public int clearIndexTimes(long dvObjectId) {
Query clearIndexTimes = em.createQuery("UPDATE DvObject o SET o.indexTime = NULL, o.permissionIndexTime = NULL WHERE o.id =:dvObjectId");
clearIndexTimes.setParameter("dvObjectId", dvObjectId);
int numRowsUpdated = clearIndexTimes.executeUpdate();
return numRowsUpdated;
}
private String getDvObjectIdListClause(List<Long> dvObjectIdList){
if (dvObjectIdList == null){
return null;
}
List<String> outputList = new ArrayList<>();
for(Long id : dvObjectIdList){
if (id != null){
outputList.add(id.toString());
}
}
if (outputList.isEmpty()){
return null;
}
return " (" + StringUtils.join(outputList, ",") + ")";
}
public List<Object[]> getDvObjectInfoForMyData(List<Long> dvObjectIdList){
//msgt("getAssigneeAndRoleIdListFor");
String dvObjectClause = getDvObjectIdListClause(dvObjectIdList);
if (dvObjectClause==null){
return null;
}
String qstr = "SELECT dv.id, dv.dtype, dv.owner_id"; // dv.modificationtime,
qstr += " FROM dvobject dv";
qstr += " WHERE dv.id IN " + dvObjectClause;
qstr += ";";
return em.createNativeQuery(qstr).getResultList();
}
/**
* Used for retrieving DvObject based on a list of parent Ids
* MyData use case: The Dataverse has file permissions and we want to know
* the Datasets under that Dataverse (and subsequently query files by
* their parent id--but in solr)
*
* @param dvObjectParentIdList
* @return
*/
public List<Object[]> getDvObjectInfoByParentIdForMyData(List<Long> dvObjectParentIdList){
//msgt("getAssigneeAndRoleIdListFor");
String dvObjectClause = getDvObjectIdListClause(dvObjectParentIdList);
if (dvObjectClause==null){
return null;
}
String qstr = "SELECT dv.id, dv.dtype, dv.owner_id"; // dv.modificationtime,
qstr += " FROM dvobject dv";
qstr += " WHERE dv.owner_id IN " + dvObjectClause;
qstr += ";";
return em.createNativeQuery(qstr).getResultList();
}
/**
* Used to exclude Harvested Data from the Mydata page
*
* @return
*/
public List<Long> getAllHarvestedDataverseIds(){
String qstr = "SELECT h.dataverse_id FROM harvestingclient h;";
return em.createNativeQuery(qstr)
.getResultList();
}
/**
* Used to calculate the dvObject tree paths for the search results on the
* dataverse page. (In order to determine if "linked" or not).
* *done in recursive 1 query!*
*
* @return
*/
public Map<Long, String> getObjectPathsByIds(Set<Long> objectIds){
if (objectIds == null || objectIds.size() < 1) {
return null;
}
String datasetIdStr = Strings.join(objectIds, ", ");
String qstr = "WITH RECURSIVE path_elements AS ((" +
" SELECT id, owner_id FROM dvobject WHERE id in (" + datasetIdStr + "))" +
" UNION\n" +
" SELECT o.id, o.owner_id FROM path_elements p, dvobject o WHERE o.id = p.owner_id) " +
"SELECT id, owner_id FROM path_elements WHERE owner_id IS NOT NULL;"; // ORDER by id ASC;";
List<Object[]> searchResults = null;
try {
searchResults = em.createNativeQuery(qstr).getResultList();
} catch (Exception ex) {
searchResults = null;
}
if (searchResults == null || searchResults.size() < 1) {
return null;
}
Map<Long, Long> treeMap = new HashMap<>();
for (Object[] result : searchResults) {
Long objectId = null;
Long ownerId = null;
if (result[0] != null) {
try {
objectId = ((Integer)result[0]).longValue();
} catch (Exception ex) {
logger.warning("OBJECT PATH: could not cast result[0] (dvobject id) to Integer!");
objectId = null;
}
if (objectId == null) {
continue;
}
ownerId = (Long)result[1];
logger.fine("OBJECT PATH: id: "+objectId+", owner: "+ownerId);
if (ownerId != null && (ownerId.longValue() != 1L)) {
treeMap.put(objectId, ownerId);
}
}
}
Map<Long, String> ret = new HashMap<>();
for (Long objectId : objectIds) {
String treePath = "/" + objectId;
Long treePosition = treeMap.get(objectId);
while (treePosition != null) {
treePath = "/" + treePosition + treePath;
treePosition = treeMap.get(treePosition);
}
logger.fine("OBJECT PATH: returning "+treePath+" for "+objectId);
ret.put(objectId, treePath);
}
treeMap = null;
return ret;
}
}