/*
* Data Hub Service (DHuS) - For Space data distribution.
* Copyright (C) 2013,2014,2015,2016 GAEL Systems
*
* This file is part of DHuS software sources.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package fr.gael.dhus.database.dao;
import fr.gael.dhus.database.dao.interfaces.HibernateDao;
import fr.gael.dhus.database.object.Collection;
import fr.gael.dhus.database.object.MetadataIndex;
import fr.gael.dhus.database.object.Product;
import fr.gael.dhus.database.object.User;
import java.math.BigInteger;
import java.net.URL;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.hibernate.HibernateException;
import org.hibernate.Query;
import org.hibernate.SQLQuery;
import org.hibernate.Session;
import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Restrictions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.support.DataAccessUtils;
import org.springframework.orm.hibernate3.HibernateCallback;
import org.springframework.stereotype.Repository;
/**
* Product Data Access Object provides interface to Product Table into the
* database.
*/
@Repository
public class ProductDao extends HibernateDao<Product, Long>
{
private static final Logger LOGGER = LogManager.getLogger(ProductDao.class);
@Autowired
private CollectionDao collectionDao;
@Autowired
private ProductCartDao productCartDao;
@Autowired
private UserDao userDao;
@Autowired
private EvictionDao evictionDao;
private final static Integer MAX_PRODUCT_PAGE_SIZE=
Integer.getInteger("max.product.page.size",100);
/**
* Checks if the passed number as a number of product is acceptable
* according to the current configuration
* @param n the number of product to retrieve.
* @throws UnsupportedOperationException if the passed number cannot be
* handled.
*/
static void checkProductNumber (int n)
{
if (n>MAX_PRODUCT_PAGE_SIZE)
{
throw new UnsupportedOperationException (
"Product page size exceeds the authorized size (" +
MAX_PRODUCT_PAGE_SIZE + ").");
}
}
/**
* Returns the maximum number of product that a page of request can handled.
* @return the max number of products.
*/
public static int getMaxPageSize ()
{
return MAX_PRODUCT_PAGE_SIZE;
}
public Product getProductByPath (final URL path)
{
if (path == null)
return null;
Product p = (Product)DataAccessUtils.uniqueResult(getHibernateTemplate().
find("from Product where path=? AND processed=true",path));
return p;
}
@Override
public List<Product> listCriteria(DetachedCriteria detached, int skip,
int top)
{
checkProductNumber(top);
return super.listCriteria(detached, skip, top);
}
/**
* Retrieve a list of products to their ids. returned list is not controlled
* with user rights nor processing completion.
* @param ids the list of ids to retrieve
* @return the list of products
*/
@SuppressWarnings ("unchecked")
public List<Product> read(List<Long>ids)
{
if ((ids == null)|| ids.isEmpty ())
{
return Collections.emptyList();
}
String facet = "";
String logic_op="";
for (Long id: ids)
{
facet += " " + logic_op + " p.id=" + id;
logic_op = "or";
}
return (List<Product>) find (
"from " + entityClass.getName () +
" p WHERE " + facet);
}
/**
* Does the product corresponding to the given url exist in the database ?
* Processed or not.
*/
public boolean exists (URL url)
{
if (url == null)
return false;
Product p = (Product)DataAccessUtils.uniqueResult(getHibernateTemplate().
find("from Product where path=?", url));
return p != null;
}
/**
* Override Hibernate scroll to add the page size limitation.
* @see {@link HibernateDao#scroll(String, int, int)}
*/
@Override
public List<Product> scroll(String clauses, int skip, int n)
{
checkProductNumber (n);
if (n<0) n=ProductDao.getMaxPageSize();
return super.scroll(clauses, skip, n);
}
/**
* Paginated scroll.
* @param filter substring of Product.Identifier, may be null.
* @param collection_id may be null.
* @param skip number of rows to skip from the result set.
* @return result set as a paginated iterator.
*/
public Iterator<Product> scrollFiltered(String filter, final Long collection_id, int skip)
{
StringBuilder sb = new StringBuilder("SELECT p ");
if (collection_id != null)
{
sb.append("FROM Collection c LEFT OUTER JOIN c.products p ");
sb.append("WHERE c.id=").append(collection_id).append(" AND ");
}
else
{
sb.append("FROM ").append(entityClass.getName()).append(" p WHERE ");
}
if (filter != null && !filter.isEmpty())
{
sb.append("p.identifier LIKE '%").append(filter.toUpperCase()).append("%' AND ");
}
sb.append("p.processed=true");
return new PagedIterator<>(this, sb.toString(), skip);
}
/**
* Returns the number of Product records whose `processed` field is `true`.
* @param filter an optional additionnal `where` clause (without the "where" token).
* @param collection_uuid an optional parent collection ID.
* @return a number of rows in table `PRODUCTS`.
*/
public int count (String filter, final String collection_uuid)
{
StringBuilder sb = new StringBuilder("select count(*) ");
if (collection_uuid != null)
{
sb.append("from Collection c left outer join c.products p ");
sb.append("where c.uuid='").append(collection_uuid).append("' and ");
}
else
{
sb.append("from Product p where ");
}
sb.append("p.processed=true");
if (filter != null && !filter.isEmpty())
{
sb.append(" and ").append(filter);
}
return DataAccessUtils.intResult(find(sb.toString()));
}
@Override
public void deleteAll()
{
Iterator<Product> it = getAllProducts ();
while (it.hasNext ())
{
it.next ();
it.remove ();
}
}
@Override
public void delete (Product product)
{
Product p = read (product.getId ());
List<Collection>cls = collectionDao.getCollectionsOfProduct (p.getId ());
// Remove collection references
// Must use rootUser to remove every reference of this product
// (or maybe a new non usable user ?)
User user = userDao.getRootUser();
if (cls!=null)
{
for (Collection c: cls)
{
LOGGER.info("deconnect product from collection " + c.getName ());
collectionDao.removeProduct (c.getUUID (), p.getId (), user);
}
}
// Remove references
productCartDao.deleteProductReferences(p);
setIndexes (p.getId (), null);
evictionDao.removeProduct (p);
super.delete (p);
}
/**
* Manage replacing existing lazy index into persistent structure.
* @param product the product to modify.
* @param indexes the index to set.
*/
public void setIndexes(Product product, List<MetadataIndex>indexes)
{
product.setIndexes (indexes);
update (product);
}
public void setIndexes(Long id, List<MetadataIndex>indexes)
{
setIndexes (read(id), indexes);
}
/**
* Retrieve products ordered by it date of ingestion, updated.
* The list of product is returned prior to the passed date argument.
* Currently processed and locked products are not returned.
* @param max_date maximum date to retrieve products. More recent
* product will not be returned.
* @return a scrollable list of the products.
*/
public Iterator<Product> getProductsByIngestionDate (Date max_date)
{
SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSS");
String date = sdf.format (max_date);
String query = "FROM " + entityClass.getName () + " " +
"WHERE created < '" + date + "' AND processed=true AND " +
"locked=false ORDER BY created ASC, updated ASC";
return new PagedIterator<> (this, query);
}
/**
* Retrieve the list of product ordered by lowest access.
* The list of product is returned prior to the passed date argument.
* Currently processed and locked products are not returned.
* @return the ordered list of products.
*/
public Iterator<Product>getProductsLowerAccess (Date max_date)
{
SimpleDateFormat sdf = new SimpleDateFormat ("yyyy-MM-dd HH:mm:ss.SSS");
String date = sdf.format (max_date);
String query = "FROM" + entityClass.getName () +
"WHERE created < '" + date + "' AND processed=true AND " +
"locked=false ORDER BY updated ASC, created ASC";
return new PagedIterator<> (this, query);
}
public static String getPathFromProduct (Product product)
{
return getPathFromURL(product.getPath ());
}
public static String getPathFromURL (URL product)
{
return product.toString();
}
/**
* THIS METHOD IS NOT SAFE: IT MUST BE REMOVED.
* TODO: manage access by page.
* @param user_uuid
* @return
*/
@SuppressWarnings ("unchecked")
public List<Long> getAuthorizedProducts (String user_uuid)
{
return (List<Long>) find (
"select id FROM " + entityClass.getName () +
" p WHERE p.processed=true");
}
@SuppressWarnings ("unchecked")
public Product getProductByDownloadableFilename (final String filename,
final Collection collection)
{
List<Product>products=null;
if (collection == null)
{
products = (List<Product>)find(
"from Product where download.path LIKE '%" + filename +
"' AND processed = true");
}
else
{
products = (List<Product>)getHibernateTemplate ().find (
"select p from Collection c left outer join c.products p " +
"where c=? AND" +
" p.download.path LIKE ? AND" +
" processed=true", collection, "%"+filename +"%");
}
if ((products!=null) && (products.size ()>0))
return products.iterator ().next ();
return null;
}
public Product getProductByUuid (String uuid)
{
@SuppressWarnings ("unchecked")
Product product = (Product) DataAccessUtils.uniqueResult (
find ("from Product p where p.uuid='" + uuid +
"' AND p.processed=true"));
return product;
}
/**
* TODO: manage access by page.
* @param user
* @return
*/
public List<Product> getNoCollectionProducts (User user)
{
ArrayList<Product> products = new ArrayList<> ();
StringBuilder sqlBuilder = new StringBuilder ();
sqlBuilder.append ("SELECT p.ID ");
sqlBuilder.append ("FROM PRODUCTS p ");
sqlBuilder.append ("LEFT OUTER JOIN COLLECTION_PRODUCT cp ")
.append ("ON p.ID = cp.PRODUCTS_ID ");
sqlBuilder.append ("WHERE cp.COLLECTIONS_UUID IS NULL");
final String sql = sqlBuilder.toString ();
List<BigInteger> queryResult =
getHibernateTemplate ().execute (
new HibernateCallback<List<BigInteger>> ()
{
@Override
@SuppressWarnings ("unchecked")
public List<BigInteger> doInHibernate (Session session)
throws HibernateException, SQLException
{
SQLQuery query = session.createSQLQuery (sql);
return query.list ();
}
});
for (BigInteger pid : queryResult)
{
Product p = read (pid.longValue ());
if (p == null)
{
throw new IllegalStateException (
"Existing product is null ! product id = " + pid.longValue ());
}
products.add (p);
}
return products;
}
public List<Product> scrollUploadedProducts (final User user, final int skip,
final int top)
{
checkProductNumber (top);
return getHibernateTemplate ().execute (
new HibernateCallback<List<Product>>()
{
@Override
@SuppressWarnings ("unchecked")
public List<Product> doInHibernate (Session session)
throws HibernateException, SQLException
{
String hql = "SELECT p FROM Product p, User u" +
" WHERE p.owner = u and u.uuid like ? AND p.processed = true";
Query query = session.createQuery (hql);
query.setString (0, user.getUUID ());
query.setFirstResult (skip);
query.setMaxResults (top);
return (List<Product>) query.list ();
}
});
}
@SuppressWarnings ("unchecked")
public List<Product> getUploadedProducts (final User user)
{
return (List<Product>) getHibernateTemplate ().find (
"FROM Product WHERE owner = ? AND processed = true", user);
}
public User getOwnerOfProduct (final Product product)
{
return (User)DataAccessUtils.uniqueResult(getHibernateTemplate().find(
"select p.owner from Product p where p=?", product));
}
public boolean isAuthorized (final String user_uuid, final long product_id)
{
if(userDao.read (user_uuid) == null || read (product_id) == null)
{
return false;
}
return true;
}
public Iterator<Product> getUnprocessedProducts ()
{
String query = "FROM " + entityClass.getName ()
+ " WHERE processed is false";
return new PagedIterator<> (this, query);
}
public Iterator<Product> getAllProducts ()
{
String query = "FROM " + entityClass.getName ();
return new PagedIterator<> (this, query);
}
public Product getProductByIdentifier (String identifier)
{
DetachedCriteria criteria = DetachedCriteria.forClass (Product.class);
criteria.add (Restrictions.eq ("identifier", identifier));
criteria.add (Restrictions.eq ("processed", true));
return uniqueResult (criteria);
}
}