/* * Copyright 2010 - 2013 Interactive Media Management * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU 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 General Public License for more * details. * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. */ package dk.i2m.converge.ejb.services; import dk.i2m.converge.core.DataNotFoundException; import dk.i2m.converge.core.ConfigurationKey; import dk.i2m.converge.core.content.ContentTag; import dk.i2m.converge.core.newswire.*; import dk.i2m.converge.core.plugin.NewswireDecoder; import dk.i2m.converge.core.plugin.PluginManager; import dk.i2m.converge.core.search.SearchEngineIndexingException; import dk.i2m.converge.core.security.UserAccount; import dk.i2m.converge.core.security.UserRole; import dk.i2m.converge.domain.search.SearchFacet; import dk.i2m.converge.domain.search.SearchResult; import dk.i2m.converge.domain.search.SearchResults; import dk.i2m.converge.ejb.facades.SystemFacadeLocal; import dk.i2m.converge.ejb.facades.UserFacadeLocal; import dk.i2m.converge.ejb.messaging.NewswireDecoderMessageBean; import java.io.IOException; import java.net.MalformedURLException; import java.text.DateFormat; import java.text.MessageFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Resource; import javax.ejb.*; import javax.jms.*; import org.apache.commons.lang.StringUtils; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrRequest.METHOD; import org.apache.solr.client.solrj.SolrServer; import org.apache.solr.client.solrj.SolrServerException; import org.apache.solr.client.solrj.impl.BinaryRequestWriter; import org.apache.solr.client.solrj.impl.CommonsHttpSolrServer; import org.apache.solr.client.solrj.response.FacetField; import org.apache.solr.client.solrj.response.QueryResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; import org.apache.solr.common.SolrInputDocument; /** * Stateless enterprise bean providing access to the external newswire services. * * @author Allan Lykke Christensen */ @Stateless public class NewswireServiceBean implements NewswireServiceLocal { private static final Logger LOG = Logger.getLogger(NewswireServiceBean.class.getName()); private final ResourceBundle msgs = ResourceBundle.getBundle("dk.i2m.converge.i18n.ServiceMessages"); @EJB private ConfigurationServiceLocal cfgService; @EJB private DaoServiceLocal daoService; @EJB private UserFacadeLocal userFacade; @EJB private PluginContextBeanLocal pluginContext; @EJB private NotificationServiceLocal notificationService; @EJB private SystemFacadeLocal systemFacade; @Resource private SessionContext ctx; @Resource(mappedName = "jms/newswireServiceQueue") private Destination destination; @Resource(mappedName = "jms/connectionFactory") private ConnectionFactory jmsConnectionFactory; @Override public NewswireBasket createBasket(NewswireBasket basket) { Calendar now = Calendar.getInstance(); basket.setCreated(now.getTime()); basket.setUpdated(now.getTime()); if (basket.isMailDelivery()) { Calendar next = (Calendar) now.clone(); next.set(Calendar.HOUR_OF_DAY, basket.getHourFirstDelivery()); next.set(Calendar.MINUTE, 0); next.set(Calendar.SECOND, 0); next.set(Calendar.MILLISECOND, 0); while (next.before(now)) { next.add(Calendar.HOUR_OF_DAY, basket.getMailFrequency()); } basket.setNextDelivery(next.getTime()); } return daoService.create(basket); } @Override public NewswireBasket updateBasket(NewswireBasket basket) { Calendar now = Calendar.getInstance(); basket.setUpdated(now.getTime()); if (basket.isMailDelivery()) { Calendar next = (Calendar) now.clone(); next.set(Calendar.HOUR_OF_DAY, basket.getHourFirstDelivery()); while (next.before(now)) { next.add(Calendar.HOUR_OF_DAY, basket.getMailFrequency()); } basket.setNextDelivery(next.getTime()); } return daoService.update(basket); } @Override public void deleteBasket(NewswireBasket basket) { if (basket.getId() != null) { daoService.delete(NewswireBasket.class, basket.getId()); } } /** * Gets all the {@link NewswireBasket}s of a particular user. * * @param userId Unique identifier of the user * @return {@link List} of {@link NewswireAlert}s of the particular user */ @Override public List<NewswireBasket> findBasketsByUser(Long userId) { Map<String, Object> params = QueryBuilder.with("uid", userId).parameters(); return daoService.findWithNamedQuery(NewswireBasket.FIND_BY_USER, params); } /** * {@inheritDoc} */ @Override public List<NewswireItem> getNews() { String currentUserName = ctx.getCallerPrincipal().getName(); try { UserAccount user = userFacade.findById(currentUserName); Map<String, Object> params = QueryBuilder.with("user", user).parameters(); return daoService.findWithNamedQuery(NewswireItem.FIND_BY_USER,params); } catch (DataNotFoundException ex) { LOG.log(Level.FINE, "Current user [{0}] is unknown. {1}", new Object[]{currentUserName, ex.getMessage()}); return Collections.emptyList(); } } /** * {@inheritDoc} */ @Override public List<NewswireItem> getNews(Long newswireServiceId) { try { NewswireService service = daoService.findById(NewswireService.class, newswireServiceId); Map<String, Object> params = QueryBuilder.with("newswireService",service).parameters(); return daoService.findWithNamedQuery(NewswireItem.FIND_BY_SERVICE, params); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, "Requested newswire service [{0}] does not exist. {1}", new Object[]{newswireServiceId, ex.getMessage()}); return Collections.emptyList(); } } /** * {@inheritDoc} */ @Override public List<NewswireService> getNewswireServices() { return daoService.findAll(NewswireService.class); } /** * {@inheritDoc} */ @Override public List<NewswireService> findActiveNewswireServices() { Map<String, Object> params = QueryBuilder.with("active", true). parameters(); return daoService.findWithNamedQuery(NewswireService.FIND_BY_STATUS, params); } /** * {@inheritDoc} */ @Override public List<NewswireService> getNewswireServicesWithSubscribersAndItems() { List<NewswireService> services = getNewswireServices(); for (NewswireService service : services) { try { Long subscribers = daoService.findObjectWithNamedQuery( Long.class, NewswireService.COUNT_SUBSCRIBERS, QueryBuilder.with("id", service.getId()).parameters()); Long items = daoService.findObjectWithNamedQuery(Long.class, NewswireService.COUNT_ITEMS, QueryBuilder.with("id", service.getId()).parameters()); service.setNumberOfSubscribers(subscribers); service.setNumberOfItems(items); } catch (Exception ex) { LOG.log(Level.WARNING, "Unknown response from query.", ex); } } return services; } /** * {@inheritDoc} */ @Override public NewswireService findById(Long id) throws DataNotFoundException { return daoService.findById(NewswireService.class, id); } /** * {@inheritDoc} */ @Override public NewswireService create(NewswireService newsFeed) { return daoService.create(newsFeed); } /** * {@inheritDoc} */ @Override public void update(NewswireService newsFeed) { daoService.update(newsFeed); } /** * {@inheritDoc} */ @Override public void delete(Long id) throws DataNotFoundException { // Remove log entries by the newswire service systemFacade.removeLogEntries(NewswireService.class.getName(), "" + id); // Remove items of the newswire service emptyNewswireService(id); // Delete the newswire service daoService.delete(NewswireService.class, id); } @Override public void purgeNewswires() { SolrServer solrServer = getSolrServer(); List<NewswireService> services = daoService.findAll( NewswireService.class); for (NewswireService service : services) { deleteExpiredItems(service.getId(), solrServer); } } /** * Indexes a {@link NewswireItem} in the database. * * @param item {@link NewswireItem} to index. * @throws SearchEngineIndexingException * If the item could not be indexed */ @Override public void index(NewswireItem item) throws SearchEngineIndexingException { SolrServer solrServer = getSolrServer(); index(item, solrServer); } private void deleteExpiredItems(Long newswireServiceId, SolrServer solrServer) { Long taskId = 0L; try { NewswireService service = daoService.findById(NewswireService.class, newswireServiceId); if (service.getDaysToKeep().intValue() < 1) { // Ignore return; } taskId = systemFacade.createBackgroundTask("Expiring items from newswire service " + service.getSource()); try { solrServer.deleteByQuery("provider-id:" + newswireServiceId + " AND date:[* TO NOW-" + service.getDaysToKeep() + "DAY/DAY]"); Calendar expirationDate = Calendar.getInstance(); expirationDate.add(Calendar.DAY_OF_MONTH, -service.getDaysToKeep()); QueryBuilder qb = QueryBuilder. with("id", newswireServiceId). and("expirationDate", expirationDate); daoService.executeQuery(NewswireService.DELETE_EXPIRED_ITEMS, qb); } catch (SolrServerException ex) { LOG.log(Level.SEVERE, "Could not remove expired newswire items from index. {0} ", new Object[]{ex.getMessage()}); LOG.log(Level.FINEST, "", ex); } catch (IOException ex) { LOG.log(Level.SEVERE, "Could not remove expired newswire items from index. {0} ", new Object[]{ex.getMessage()}); LOG.log(Level.FINEST, "", ex); } } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, ex.getMessage()); LOG.log(Level.FINEST, "", ex); } finally { systemFacade.removeBackgroundTask(taskId); } } private void fetchNewswire(Long newswireServiceId, SolrServer solrServer) { Long taskId = 0L; try { NewswireService service = daoService.findById(NewswireService.class, newswireServiceId); startProcessingNewswireService(newswireServiceId); taskId = systemFacade.createBackgroundTask("Downloading newswire service " + service.getSource()); NewswireDecoder decoder = service.getDecoder(); decoder.decode(pluginContext, service); service.setLastFetch(Calendar.getInstance()); daoService.update(service); stopProcessingNewswireService(newswireServiceId); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, ex.getMessage()); } catch (NewswireDecoderException ex) { LOG.log(Level.SEVERE, null, ex); } finally { systemFacade.removeBackgroundTask(taskId); } } private void index(NewswireItem ni, SolrServer solrServer) throws SearchEngineIndexingException { SolrInputDocument solrDoc = new SolrInputDocument(); try { solrDoc.addField("id", ni.getId(), 1.0f); solrDoc.addField("headline", ni.getTitle(), 1.0f); solrDoc.addField("provider", ni.getNewswireService().getSource()); solrDoc.addField("provider-id", ni.getNewswireService().getId()); solrDoc.addField("story", dk.i2m.converge.core.utils.StringUtils. stripHtml(ni.getContent())); solrDoc.addField("caption", ni.getSummary()); solrDoc.addField("author", ni.getAuthor()); solrDoc.addField("date", ni.getDate().getTime()); if (ni.isThumbnailAvailable()) { solrDoc.addField("thumb-url", ni.getThumbnailUrl()); } for (ContentTag tag : ni.getTags()) { solrDoc.addField("tag", tag.getTag().toLowerCase()); } } catch (NullPointerException ex) { throw new SearchEngineIndexingException( "A value was missing from the content to be indexed. ", ex); } try { solrServer.add(solrDoc); } catch (SolrServerException ex) { throw new SearchEngineIndexingException(ex); } catch (IOException ex) { throw new SearchEngineIndexingException(ex); } } /** * Schedules the download of all active newswire services. */ @Override public void downloadNewswireServices() { List<NewswireService> services = findActiveNewswireServices(); for (NewswireService service : services) { try { downloadNewswireService(service.getId()); } catch (DataNotFoundException ex) { // Unknown newswire } } } /** * Schedules the download of a single {@link NewswireService}. * * @param id Unique identifier of the {@link NewswireService} * @throws DataNotFoundException If a {@link NewswireService} with the given * id does not exist */ @Override public void downloadNewswireService(Long id) throws DataNotFoundException { NewswireService service = findById(id); if (service.isProcessing()) { LOG.log(Level.INFO, "{0} is already being downloaded", service. getSource()); return; } else { startProcessingNewswireService(id); } Connection conn = null; try { conn = jmsConnectionFactory.createConnection(); Session session = conn.createSession(true, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer(destination); MapMessage msg = session.createMapMessage(); msg.setLongProperty(NewswireDecoderMessageBean.NEWSWIRE_SERVICE_ID, id); producer.send(msg); session.close(); conn.close(); } catch (JMSException ex) { LOG.log(Level.SEVERE, null, ex); } finally { if (conn != null) { try { conn.close(); } catch (Exception e) { } } } stopProcessingNewswireService(id); } @Override public SearchResults search(String query, int start, int rows, String sortField, boolean sortOrder, String... filterQueries) { long startTime = System.currentTimeMillis(); SearchResults searchResults = new SearchResults(); final DateFormat ORIGINAL_FORMAT = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'"); final DateFormat NEW_FORMAT = new SimpleDateFormat("MMMM yyyy"); try { SolrQuery solrQuery = new SolrQuery(); solrQuery.setStart(start); solrQuery.setRows(rows); solrQuery.setQuery(query); solrQuery.setFacet(true); if (sortOrder) { solrQuery.setSortField(sortField, SolrQuery.ORDER.asc); } else { solrQuery.setSortField(sortField, SolrQuery.ORDER.desc); } solrQuery.addFacetField("provider"); solrQuery.addFacetField("tag"); solrQuery.addFilterQuery(filterQueries); solrQuery.setFacetMinCount(1); solrQuery.setIncludeScore(true); solrQuery.setHighlight(true).setHighlightSnippets(1); solrQuery.setParam("hl.fl", "headline,story,caption"); solrQuery.setParam("hl.fragsize", "500"); solrQuery.setParam("hl.simple.pre", "<span class=\"searchHighlight\">"); solrQuery.setParam("hl.simple.post", "</span>"); solrQuery.setParam("facet.date", "date"); solrQuery.setParam("facet.date.start", "NOW/YEAR-10YEAR"); solrQuery.setParam("facet.date.end", "NOW"); solrQuery.setParam("facet.date.gap", "+1MONTH"); SolrServer srv = getSolrServer(); // POST is used to support UTF-8 QueryResponse qr = srv.query(solrQuery, METHOD.POST); SolrDocumentList sdl = qr.getResults(); searchResults.setNumberOfResults(sdl.getNumFound()); for (SolrDocument d : sdl) { // Copy all fields to map for easy access HashMap<String, Object> values = new HashMap<String, Object>(); for (Iterator<Map.Entry<String, Object>> i = d.iterator(); i. hasNext();) { Map.Entry<String, Object> e2 = i.next(); values.put(e2.getKey(), e2.getValue()); } SearchResult hit = new SearchResult(); String id = (String) values.get("id"); StringBuilder story = new StringBuilder(); StringBuilder title = new StringBuilder(); StringBuilder note = new StringBuilder(); Map<String, List<String>> highlighting = qr.getHighlighting(). get(id); boolean highlightingExist = highlighting != null; if (highlightingExist && highlighting.get("caption") != null) { for (String hl : highlighting.get("caption")) { story.append(hl); } } else { story.append(StringUtils.abbreviate((String) values.get( "caption"), 500)); } if (highlightingExist && highlighting.get("headline") != null) { for (String hl : qr.getHighlighting().get(id).get("headline")) { title.append(hl); } } else { title.append((String) values.get("headline")); } if (values.containsKey("tag")) { if (values.get("tag") instanceof String) { hit.setTags(new String[]{(String) values.get("tag")}); } else if (values.get("tag") instanceof List) { List<String> tags = (List<String>) values.get("tag"); hit.setTags(tags.toArray(new String[tags.size()])); } else { LOG.warning( "Unexpected (tag) value returned from search engine"); } } note.append(values.get("provider")); if (values.containsKey("author") && values.get("author") != null && !((String) values.get("author")).trim().isEmpty()) { note.append(" - ").append(values.get("author")); } if (title != null) { hit.setTitle(title.toString()); } if (story != null) { hit.setDescription(story.toString()); } if (note != null) { hit.setNote(note.toString()); } hit.setId(Long.valueOf(id)); hit.setLink("{0}/Newswire.xhtml?q=id%3A" + id); hit.setType("Newswire"); if (values.containsKey("thumb-url")) { hit.setPreview(true); hit.setPreviewLink((String) values.get("thumb-url")); } if (values.containsKey("date")) { if (values.get("date") instanceof Date) { hit.addDate((Date) values.get("date")); } else if (values.get("date") instanceof List) { hit.setDates((List<Date>) values.get("date")); } else { LOG.warning( "Unexpected (date) value returned from search engine"); } } hit.setScore((Float) d.getFieldValue("score")); searchResults.getHits().add(hit); } List<FacetField> facets = qr.getFacetFields(); for (FacetField facet : facets) { List<FacetField.Count> facetEntries = facet.getValues(); if (facetEntries != null) { for (FacetField.Count fcount : facetEntries) { if (!searchResults.getFacets().containsKey( facet.getName())) { searchResults.getFacets().put(facet.getName(), new ArrayList<SearchFacet>()); } SearchFacet sf = new SearchFacet(fcount.getName(), fcount.getAsFilterQuery(), fcount.getCount()); // Check if the filter query is already active for (String fq : filterQueries) { if (fq.equals(fcount.getAsFilterQuery())) { sf.setSelected(true); } } // Ensure that the facet is not already there if (!searchResults.getFacets().get(facet.getName()). contains(sf)) { searchResults.getFacets().get(facet.getName()).add( sf); } } } } for (FacetField facet : qr.getFacetDates()) { List<FacetField.Count> facetEntries = facet.getValues(); if (facetEntries != null) { for (FacetField.Count fcount : facetEntries) { if (fcount.getCount() != 0) { if (!searchResults.getFacets().containsKey(facet. getName())) { searchResults.getFacets().put(facet.getName(), new ArrayList<SearchFacet>()); } String facetLabel = ""; try { Date facetDate = ORIGINAL_FORMAT.parse(fcount. getName()); facetLabel = NEW_FORMAT.format(facetDate); } catch (ParseException ex) { LOG.log(Level.SEVERE, null, ex); facetLabel = fcount.getName(); } String realFilterQuery = "date:[" + fcount.getName() + " TO " + fcount.getName() + "+1MONTH]"; SearchFacet sf = new SearchFacet(facetLabel, realFilterQuery, fcount.getCount()); // Check if the filter query is already active for (String fq : filterQueries) { if (fq.equals(realFilterQuery)) { sf.setSelected(true); } } // Ensure that the facet is not already there if (!searchResults.getFacets().get(facet.getName()). contains(sf)) { searchResults.getFacets().get(facet.getName()). add(sf); } } } } } } catch (SolrServerException ex) { LOG.log(Level.SEVERE, null, ex); } long endTime = System.currentTimeMillis(); searchResults.setSearchTime(endTime - startTime); searchResults.setStart(start); searchResults.setResultsPerPage(rows); return searchResults; } /** * Empties the items of a given {@link NewswireService}. * * @param id Unique identifier of the {@link NewswireService} * @return Number of items deleted */ @Override public int emptyNewswireService(Long id) { int affectedRecords = 0; try { NewswireService ns = daoService.findById(NewswireService.class, id); SolrServer solr = getSolrServer(); for (NewswireItem item : ns.getItems()) { try { solr.deleteById(String.valueOf(item.getId())); } catch (SolrServerException ex) { LOG.log(Level.SEVERE, "Could not remove newswire item from SolrServer", ex); } catch (IOException ex) { LOG.log(Level.SEVERE, "Could not remove newswire item from SolrServer", ex); } } return daoService.executeQuery(NewswireItem.DELETE_BY_SERVICE, QueryBuilder.with("newswireService", ns)); } catch (DataNotFoundException ex) { return affectedRecords; } } /** * {@inheritDoc} */ @Override public List<NewswireItem> findByExternalId(String externalId) { Map<String, Object> params = QueryBuilder.with("externalId", externalId). parameters(); return daoService.findWithNamedQuery(NewswireItem.FIND_BY_EXTERNAL_ID, params); } /** * {@inheritDoc} */ @Override public NewswireItem create(NewswireItem item) { return daoService.create(item); } /** * {@inheritDoc} */ @Override public Map<String, NewswireDecoder> getNewswireDecoders() { return PluginManager.getInstance().getNewswireDecoders(); } /** * Gets the instance of the Apache Solr server used for indexing. * * @return Instance of the Apache Solr server * @throws IllegalStateException If the search engine is not properly * configured */ private SolrServer getSolrServer() { try { String url = cfgService.getString( ConfigurationKey.SEARCH_ENGINE_NEWSWIRE_URL); Integer socketTimeout = cfgService.getInteger( ConfigurationKey.SEARCH_ENGINE_SOCKET_TIMEOUT); Integer connectionTimeout = cfgService.getInteger( ConfigurationKey.SEARCH_ENGINE_CONNECTION_TIMEOUT); Integer maxTotalConnectionsPerHost = cfgService.getInteger( ConfigurationKey.SEARCH_ENGINE_MAX_TOTAL_CONNECTIONS_PER_HOST); Integer maxTotalConnections = cfgService.getInteger( ConfigurationKey.SEARCH_ENGINE_MAX_TOTAL_CONNECTIONS); Integer maxRetries = cfgService.getInteger( ConfigurationKey.SEARCH_ENGINE_MAX_RETRIES); Boolean followRedirects = cfgService.getBoolean( ConfigurationKey.SEARCH_ENGINE_FOLLOW_REDIRECTS); Boolean allowCompression = cfgService.getBoolean( ConfigurationKey.SEARCH_ENGINE_ALLOW_COMPRESSION); CommonsHttpSolrServer solrServer = new CommonsHttpSolrServer(url); solrServer.setRequestWriter(new BinaryRequestWriter()); solrServer.setSoTimeout(socketTimeout); solrServer.setConnectionTimeout(connectionTimeout); solrServer.setDefaultMaxConnectionsPerHost( maxTotalConnectionsPerHost); solrServer.setMaxTotalConnections(maxTotalConnections); solrServer.setFollowRedirects(followRedirects); solrServer.setAllowCompression(allowCompression); solrServer.setMaxRetries(maxRetries); return solrServer; } catch (MalformedURLException ex) { LOG.log(Level.SEVERE, "Invalid search engine configuration. {0}", ex.getMessage()); LOG.log(Level.FINE, "", ex); throw new java.lang.IllegalStateException( "Invalid search engine configuration", ex); } } @Override public NewswireItem findNewswireItemById(Long id) throws DataNotFoundException { return daoService.findById(NewswireItem.class, id); } /** * Find a {@link List} of available {@link NewswireService}s for a given * {@link UserAccount}. * * @param id Unique identifier of the {@link UserAccount} * @return {@link List} of available {@link NewswireService}s for the * {@link UserAccount} */ @Override public List<NewswireService> findAvailableNewswireServices(Long id) { List<NewswireService> servicesAvailable = new ArrayList<NewswireService>(); try { UserAccount ua = daoService.findById(UserAccount.class, id); List<NewswireService> services = findActiveNewswireServices(); for (NewswireService s : services) { if (s.isPublic()) { servicesAvailable.add(s); } else { for (UserRole role : ua.getUserRoles()) { if (s.getRestrictedTo().contains(role)) { servicesAvailable.add(s); break; } } } } return servicesAvailable; } catch (DataNotFoundException ex) { return Collections.emptyList(); } } @Override public void dispatchBaskets() { Long taskId = systemFacade.createBackgroundTask(msgs.getString( "NewswireServiceBean_BASKET_DISPATCH_TASK")); List<NewswireBasket> baskets = daoService.findWithNamedQuery( NewswireBasket.FIND_BY_EMAIL_DISPATCH); for (NewswireBasket basket : baskets) { dispatchBasket(basket); } systemFacade.removeBackgroundTask(taskId); } /** * Dispatches a {@link NewswireBasket} via e-mail. * * @param id Unique identifier of the {@link NewswireBasket} * @return {@code true} if the basket has items that was e-mailed, otherwise * {@code false} */ @Override public boolean dispatchBasket(Long id) { try { NewswireBasket b = daoService.findById(NewswireBasket.class, id); return dispatchBasket(b); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, ex.getMessage()); return false; } } private boolean dispatchBasket(NewswireBasket basket) { SimpleDateFormat dateFormat = new SimpleDateFormat(msgs.getString( "Generic_FORMAT_DATE_AND_TIME")); final String NL = System.getProperty("line.separator"); final String SENDER = cfgService.getString( ConfigurationKey.NEWSWIRE_BASKET_MAIL); final String HOME_URL = cfgService.getString( ConfigurationKey.CONVERGE_HOME_URL); final String LBL_READ_MORE = msgs.getString( "NewswireServiceBean_BASKET_MAIL_READ_MORE"); final String LBL_BASKET_SUMMARY = msgs.getString( "NewswireServiceBean_BASKET_MAIL_SUMMARY"); final String LBL_MATCHES = msgs.getString( "NewswireServiceBean_BASKET_MAIL_MATCHES"); final String LBL_SEARCH_TERMS = msgs.getString( "NewswireServiceBean_BASKET_MAIL_SEARCH_TERMS"); final String LBL_TAGS = msgs.getString( "NewswireServiceBean_BASKET_MAIL_TAGS"); final String LBL_SERVICES = msgs.getString( "NewswireServiceBean_BASKET_MAIL_SERVICES"); final String LBL_SERVICES_ALL = msgs.getString( "NewswireServiceBean_BASKET_MAIL_SERVICES_ALL"); final String LBL_BASKET_SUBJECT = msgs.getString( "NewswireServiceBean_BASKET_MAIL_SUBJECT"); Calendar now = Calendar.getInstance(); Calendar lastDelivery = (Calendar) now.clone(); if (basket.getLastDelivery() == null) { lastDelivery.add(Calendar.HOUR_OF_DAY, -24); basket.setLastDelivery(now.getTime()); } else { lastDelivery.setTime(basket.getLastDelivery()); } Calendar next = (Calendar) now.clone(); next.set(Calendar.MINUTE, 0); next.set(Calendar.SECOND, 0); next.set(Calendar.MILLISECOND, 0); next.add(Calendar.HOUR_OF_DAY, basket.getMailFrequency()); basket.setNextDelivery(next.getTime()); StringBuilder query = new StringBuilder(); query.append("date:[NOW-"). append(basket.getMailFrequency()). append("HOURS "). append(" TO NOW]"); if (!basket.getQuery().trim().isEmpty()) { query.append(" && "); query.append(basket.getQuery()); } SearchResults searchResults; try { searchResults = search(query.toString(), 0, 1000, "score", false, new String[]{}); } catch (Exception ex) { LOG.log(Level.SEVERE, "Problem dispatching basket #{0} with query {1}. {2}", new Object[]{basket.getId(), query.toString(), ex.getMessage()}); return false; } if (searchResults.getHits().size() > 0) { dateFormat.setTimeZone(basket.getOwner().getTimeZone()); StringBuilder htmlContent = new StringBuilder(); StringBuilder plainContent = new StringBuilder(); htmlContent.append("<html><head><title>"). append(basket.getTitle()). append("</title>"). append("</head><body><h1>"). append(basket.getTitle()). append("</h1>"); plainContent.append(basket.getTitle().toUpperCase()). append(NL).append(NL); for (SearchResult sr : searchResults.getHits()) { String link = HOME_URL + "/NewswireItem.xhtml?id=" + sr.getId(); htmlContent.append( "<h2 style=\"margin-bottom: 0px; padding-top: 10px;\">"). append(sr.getTitle()). append("</h2>"); htmlContent.append("<p style=\"margin-top: 0px;\">"); htmlContent.append( " <span style=\"font-size: 0.9em; color: #0E774A; font-weight: bold; text-transform: uppercase;\">"). append(sr.getNote()). append("</span>"); htmlContent.append( " <span style=\"font-size: 0.9em; color: #4272DB; text-transform: uppercase;\"> "). append(dateFormat.format(sr.getLatestDate())).append( "</span>"); htmlContent.append("</p>"); htmlContent.append("<p style=\"margin-top: 0px;\">"); if (sr.isPreview()) { htmlContent.append("<a href=\"").append(link).append( "\"><img src=\"").append(sr.getPreviewLink()). append( "\" style=\"border: 1px solid black; margin-right: 5px; margin-top: 5px;\" align=\"left\" alt=\"\" title=\"\" /></a>"); } htmlContent.append(sr.getDescription()).append("</p>"); htmlContent.append("<p><a href=\"").append(link).append( "\">").append(LBL_READ_MORE).append( "</a></p><div style=\"clear: both;\" />"); plainContent.append(sr.getTitle().toUpperCase()).append(NL); plainContent.append(sr.getNote().toUpperCase()).append(" "). append(sr.getLatestDate()).append(NL); plainContent.append(dk.i2m.converge.core.utils.StringUtils. stripHtml(sr.getDescription()).trim()).append(NL). append(NL); plainContent.append(LBL_READ_MORE).append(" ").append(link). append(NL).append("===").append(NL); } htmlContent.append("<hr/><h3>").append(LBL_BASKET_SUMMARY). append("</h3>"); plainContent.append(LBL_BASKET_SUMMARY.toUpperCase()).append(NL); htmlContent.append("<p>").append(LBL_MATCHES).append(searchResults. getNumberOfResults()).append("</p>"); plainContent.append(LBL_MATCHES).append(searchResults. getNumberOfResults()).append(NL); htmlContent.append("<p>").append(LBL_SEARCH_TERMS).append(basket. getSearchTerm()).append("</p>"); plainContent.append(LBL_SEARCH_TERMS).append(searchResults. getNumberOfResults()).append(NL); if (!basket.getTags().isEmpty()) { htmlContent.append("<p>").append(LBL_TAGS).append("<ul>"); plainContent.append(LBL_TAGS).append(NL); for (ContentTag tag : basket.getTags()) { htmlContent.append("<li>").append(tag.getTag()).append( "</li>"); plainContent.append("* ").append(tag.getTag()).append(NL); } htmlContent.append("</ul></p>"); plainContent.append(NL); } htmlContent.append("<p>").append(LBL_SERVICES).append("<ul>"); plainContent.append(LBL_SERVICES).append(NL); if (basket.getAppliesTo().isEmpty()) { htmlContent.append("<li>").append(LBL_SERVICES_ALL).append( "</li>"); plainContent.append("* ").append(LBL_SERVICES_ALL); } else { for (NewswireService service : basket.getAppliesTo()) { htmlContent.append("<li>").append(service.getSource()). append("</li>"); plainContent.append("* ").append(service.getSource()). append(NL); } } htmlContent.append("</ul></p>"); plainContent.append(NL); htmlContent.append("</body></html>"); // Generate subject String subject = compileMsg(LBL_BASKET_SUBJECT, new Object[]{searchResults.getHits().size(), basket.getTitle()}, basket.getOwner().getPreferredLocale()); // Dispatch mail notificationService.dispatchMail(basket.getOwner().getEmail(), SENDER, subject, htmlContent.toString(), plainContent. toString()); return true; } else { return false; } } private String compileMsg(String pattern, Object[] arguments, Locale userLocale) { MessageFormat fmt = new MessageFormat(pattern); if (userLocale != null) { fmt.setLocale(userLocale); } return fmt.format(arguments); } @Override public NewswireItemAttachment findNewswireItemAttachmentById(Long id) throws DataNotFoundException { return daoService.findById(NewswireItemAttachment.class, id); } /** * Removes a given item from the newswire service and the search engine. * * @param id * Unique identifier of the newswire item */ @Override public void removeItem(Long id) { daoService.delete(NewswireItem.class, id); SolrServer solr = getSolrServer(); try { solr.deleteById(String.valueOf(id)); } catch (SolrServerException ex) { LOG.log(Level.SEVERE, "Could not remove newswire item from SolrServer", ex); } catch (IOException ex) { LOG.log(Level.SEVERE, "Could not remove newswire item from SolrServer", ex); } } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) @Override public void startProcessingNewswireService(Long id) { try { NewswireService service = daoService.findById(NewswireService.class, id); service.setProcessing(true); daoService.update(service); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, ex.getMessage()); } } @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW) @Override public void stopProcessingNewswireService(Long id) { try { NewswireService service = daoService.findById(NewswireService.class, id); service.setProcessing(false); daoService.update(service); } catch (DataNotFoundException ex) { LOG.log(Level.WARNING, ex.getMessage()); } } }