/*
* Data Hub Service (DHuS) - For Space data distribution.
* Copyright (C) 2013,2014,2015 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.service.job;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.commons.mail.EmailException;
import org.apache.commons.mail.HtmlEmail;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import fr.gael.dhus.DHuS;
import fr.gael.dhus.database.dao.UserDao;
import fr.gael.dhus.database.object.MetadataIndex;
import fr.gael.dhus.database.object.Product;
import fr.gael.dhus.database.object.Role;
import fr.gael.dhus.database.object.Search;
import fr.gael.dhus.database.object.User;
import fr.gael.dhus.messaging.mail.MailServer;
import fr.gael.dhus.service.ProductService;
import fr.gael.dhus.service.SearchService;
import fr.gael.dhus.system.config.ConfigurationManager;
/**
* Autowired by {@link AutowiringJobFactory}
*/
@Component
public class SearchesJob extends AbstractJob
{
private static final Logger LOGGER = LogManager.getLogger(SearchesJob.class);
@Autowired
private UserDao userDao;
@Autowired
private ProductService productService;
@Autowired
private MailServer mailServer;
@Autowired
private SearchService searchService;
@Autowired
private ConfigurationManager configurationManager;
@Override
public String getCronExpression ()
{
return configurationManager.getSearchesCronConfiguration ().getSchedule();
}
@Override
protected void executeInternal (JobExecutionContext arg0)
throws JobExecutionException
{
if (!configurationManager.getSearchesCronConfiguration ().isActive ())
return;
long time_start = System.currentTimeMillis ();
LOGGER.info("SCHEDULER : User searches mailings.");
if (!DHuS.isStarted ())
{
LOGGER.warn("SCHEDULER : Not run while system not fully initialized.");
return;
}
Map <String,String>cids= new HashMap<String, String> ();
for (User user:userDao.readNotDeleted ())
{
List<Search>searches = userDao.getUserSearches (user);
if (searches == null || (searches.size ()==0))
{
LOGGER.debug("No saved search for user \"" + user.getUsername () +
"\".");
continue;
}
if (user.getEmail () == null)
{
LOGGER.error("User \"" + user.getUsername () +
"\" email not configured to send search notifications.");
continue;
}
HtmlEmail he = new HtmlEmail();
cids.clear ();
int maxResult = searches.size () >= 10 ? 5 : 10;
String message = "<html><style>" +
"a { font-weight: bold; color: #205887; " +
"text-decoration: none; }\n" +
"a:hover { font-weight:bold; color: #FF790B" +
"; text-decoration: none; }\na img { border-style: none; }\n" +
"</style><body style=\"font-family: Trebuchet MS, Helvetica, " +
"sans-serif; font-size: 14px;\">Dear " + getUserWelcome (user) +
",<p/>\n\n";
message += "You requested periodic notification for the following " +
"searches. Here are the top "+maxResult+" results for " +
"each search:<p/>";
message +="<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\" " +
"style=\"width: 100%;font-family: Trebuchet MS, Helvetica, " +
"sans-serif; font-size: 14px;\"><tbody>";
boolean atLeastOneResult = false;
for (Search search:searches)
{
if (search.isNotify ())
{
message += "<tr><td colspan=\"3\" style=\"font-size: 13px; " +
"font-weight: bold; color: white; background-color: " +
"#205887; text-align: center;\"><b>";
message += search.getValue ();
message += "</b></td></tr>\n";
Map<String, String> advanceds = search.getAdvanced ();
if (advanceds != null && !advanceds.isEmpty ())
{
message +="<tr><td style=\"font-size: 13px; padding:0px; " +
"margin:0px; font-weight:normal; background-color: " +
"#799BB7; text-align: center; border-left: 1px solid " +
"#205887; border-right: 1px solid #205887; " +
"border-bottom: 1px solid #205887;\">";
boolean first = true;
List<String> keys = new ArrayList<String> (
advanceds.keySet ());
Collections.sort (keys);
String lastKey = "";
for (String key : keys)
{
if ((lastKey+"End").equals(key))
{
message += " to "+advanceds.get (key);
}
else
{
if (key.endsWith ("End"))
{
message += (first?"":", ") +
key.substring (0, key.length ()-3) +
": * to "+advanceds.get (key);
}
else
{
message += (first?"":", ") + key+": " +
advanceds.get (key);
}
}
first = false;
lastKey = key;
}
message +="</td></tr>";
}
Iterator<Product> results;
try
{
results = searchService.search(search.getComplete());
}
catch (Exception e)
{
message += "<tr><td colspan=\"3\" style=\"" +
"text-align: center; border-left: 1px solid #205887; " +
"border-right: 1px solid #205887;\">" +
"No result found</td></tr>";
LOGGER.debug("There was an error when executing query : \"" +
e.getMessage () + "\"");
continue;
}
if (!results.hasNext())
{
message += "<tr><td colspan=\"3\" style=\"" +
"text-align: center; border-left: 1px solid #205887; " +
"border-right: 1px solid #205887;\">" +
"No result found</td></tr>";
LOGGER.debug("No result matches query : \"" +
search.getComplete () + "\"");
}
boolean first = true;
int searchIndex = 0;
while (results.hasNext () && searchIndex < maxResult)
{
if (!first)
{
message += "<tr><td colspan=\"3\" style=\"" +
"background-color: #205887; height:1px;\" /></tr>";
}
first = false;
Product product = results.next();
// WARNING : must implement to schedule fix of this issue...
if (product==null) continue;
atLeastOneResult = true;
searchIndex++;
LOGGER.debug("Result found: " + product.getIdentifier());
String purl = configurationManager.getServerConfiguration ()
.getExternalUrl () + "odata/v1/Products('" +
product.getUuid () + "')";
// EMBEDED THUMBNAIL
String cid=null;
if (product.getThumbnailFlag ())
{
File thumbnail = new File (product.getThumbnailPath ());
String thumbname = thumbnail.getName ();
if (cids.containsKey (thumbname))
{
cid=cids.get (thumbname);
}
else
{
try
{
cid = he.embed (thumbnail);
cids.put (thumbname, cid);
}
catch (Exception e)
{
LOGGER.warn("Cannot embed image \"" + purl +
"/Products('Quicklook')/$value\" :"+
e.getMessage ());
cid=null;
}
}
}
boolean downloadRight = user.getRoles ().contains (
Role.DOWNLOAD);
String link = downloadRight?"(<a target=\"_blank\" href=\"" +
purl + "/$value\">download</a>)" : "";
message += " <tr><td colspan=\"3\" style=\"" +
"font-size: 14px; text-align: center; " +
"border-left: 1px solid #205887; border-right: 1px " +
"solid #205887;\"><a target=\"_blank\" href=\"" +
purl + "/$value\">" + product.getIdentifier () +
"</a> "+link+"</td>\n</tr>\n";
if (cid != null)
{
message += " <tr><td rowspan=\"8\" style=\"" +
"text-align: center; vertical-align: middle;" +
" border-left: 1px solid #205887;\">" +
"<a target=\"_blank\" href=\"" + purl +
"/Products('Quicklook')/$value\"><img src=cid:" +
cid + " style=\"max-height: 64px; max-width:" +
" 64px;\"></a></td>\n";
}
// Displays metadata
List<MetadataIndex>indexes = new ArrayList<> (
productService.getIndexes (product.getId ()));
Collections.sort (indexes, new Comparator<MetadataIndex>()
{
@Override
public int compare (MetadataIndex o1, MetadataIndex o2)
{
if ((o1.getCategory () == null) ||
o1.getCategory ().equals (o2.getCategory ()))
return o1.getName ().compareTo (o2.getName ());
return o1.getCategory ().compareTo (o2.getCategory ());
}
});
int i = 0;
for (MetadataIndex index:indexes)
{
String queryable = index.getQueryable ();
String name = index.getName ();
String value = index.getValue ();
if (value.length ()>50) continue;
if (queryable != null) name += "(" + queryable + ")";
if (i != 0)
{
message += "<tr>";
}
String start = "<td";
if (cid == null || i >= 8)
{
start += " style=\"width: 120px;" +
" border-left: 1px solid #205887;\"><td";
}
i++;
message += start+">" + name + "</td>" +
"<td style=\"border-right: 1px solid #205887;\">" +
value + "</td>";
message += "</tr>";
}
if (indexes == null || indexes.size () == 0)
{
message += "</tr>";
}
}
}
}
// No result: next user, no mail.
if (!atLeastOneResult) continue;
message += "<tr><td colspan=\"3\" style=\"background-color: #205887;" +
" height:1px;\" /></tr>";
message +="</tbody></table><p/>\n";
message +="You can configure which searches are sent by mail in the "+
"<i>saved searches</i> tab in "+ configurationManager
.getNameConfiguration ().getShortName () +
" system at <a target=\"_blank\" href=\"" +
configurationManager.getServerConfiguration ().getExternalUrl () +
"\">" +
configurationManager.getServerConfiguration ().getExternalUrl () +
"</a><br/>To stop receiving this message, just disable " +
"all searches.<p/>";
message += "Thanks for using " +
configurationManager.getNameConfiguration ().getShortName () +
",<br/>" +
configurationManager.getSupportConfiguration ().getName ();
message += "</body></html>";
LOGGER.info("Sending search results to " + user.getEmail());
LOGGER.debug(message);
try
{
he.setHtmlMsg (message);
mailServer.send (he, user.getEmail (), null, null,
"Saved searches notifications");
}
catch (EmailException e)
{
LOGGER.error("Cannot send mail to \"" +
user.getEmail () + "\" :" + e.getMessage ());
}
}
LOGGER.info("SCHEDULER : User searches mailings done - " +
(System.currentTimeMillis ()-time_start) + "ms");
}
private String getUserWelcome (User u)
{
String firstname = u.getUsername ();
String lastname = "";
if (u.getFirstname () != null && !u.getFirstname().trim ().isEmpty ())
{
firstname = u.getFirstname ();
if (u.getLastname () != null && !u.getLastname().trim ().isEmpty ())
lastname = " " + u.getLastname ();
}
return firstname + lastname;
}
}