Java tutorial
/*************************************************************************** * * * Organization: Lawrence Livermore National Lab (LLNL) * * Directorate: Computation * * Department: Computing Applications and Research * * Division: S&T Global Security * * Matrix: Atmospheric, Earth and Energy Division * * Program: PCMDI * * Project: Earth Systems Grid Federation (ESGF) Data Node Software * * First Author: Gavin M. Bell (gavin@llnl.gov) * * * **************************************************************************** * * * Copyright (c) 2009, Lawrence Livermore National Security, LLC. * * Produced at the Lawrence Livermore National Laboratory * * Written by: Gavin M. Bell (gavin@llnl.gov) * * LLNL-CODE-420962 * * * * All rights reserved. This file is part of the: * * Earth System Grid Federation (ESGF) Data Node Software Stack * * * * For details, see http://esgf.org/esg-node/ * * Please also read this link * * http://esgf.org/LICENSE * * * * * Redistribution and use in source and binary forms, with or * * without modification, are permitted provided that the following * * conditions are met: * * * * * Redistributions of source code must retain the above copyright * * notice, this list of conditions and the disclaimer below. * * * * * Redistributions in binary form must reproduce the above copyright * * notice, this list of conditions and the disclaimer (as noted below) * * in the documentation and/or other materials provided with the * * distribution. * * * * Neither the name of the LLNS/LLNL nor the names of its contributors * * may be used to endorse or promote products derived from this * * software without specific prior written permission. * * * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL LAWRENCE * * LIVERMORE NATIONAL SECURITY, LLC, THE U.S. DEPARTMENT OF ENERGY OR * * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT * * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF * * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT * * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * * SUCH DAMAGE. * * * ***************************************************************************/ /** Description: Perform sql query to find out all the people who Return Tuple of info needed (dataset_id, recipients/(user), names of updated files) **/ package esg.node.components.notification; import java.util.List; import java.util.Vector; import java.util.Set; import java.util.HashSet; import java.util.Map; import java.util.HashMap; import java.util.Iterator; import java.util.Calendar; import java.io.Serializable; import java.sql.ResultSet; import java.sql.SQLException; import javax.sql.DataSource; import org.apache.commons.dbutils.QueryRunner; import org.apache.commons.dbutils.ResultSetHandler; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.commons.logging.impl.*; import esg.common.Utils; import esg.common.ESGInvalidObjectStateException; //import esg.security.OpenId2EmailAddressResolution; public class NotificationDAO implements Serializable { private static final String notificationQuery = "SELECT DISTINCT d.user_id, d.email, ds.name, d.url, fv.mod_time FROM dataset as ds, file as f, esgf_node_manager.access_logging as d, file_version as fv WHERE ds.id=f.dataset_id and fv.file_id=f.id and d.url=fv.url and fv.mod_time>d.date_fetched AND d.date_fetched > (SELECT distinct MAX(notify_time) FROM esgf_node_manager.notification_run_log where id = ? ) ORDER BY d.user_id"; private static final String markTimeQuery = "UPDATE esgf_node_manager.notification_run_log SET notify_time = ? WHERE id = ?"; private static final String regCheckEntryQuery = "SELECT COUNT(*) FROM esgf_node_manager.notification_run_log WHERE id = ?"; private static final String regAddEntryQuery = "INSERT INTO esgf_node_manager.notification_run_log (id, notify_time) VALUES ( ? , ? )"; private static final Log log = LogFactory.getLog(NotificationDAO.class); private DataSource dataSource = null; private QueryRunner queryRunner = null; private ResultSetHandler<List<NotificationDAO.NotificationRecipientInfo>> handler = null; private String nodeID = null; private Map<String, String> emailResolverCache = null; public NotificationDAO(DataSource dataSource, String nodeID) { this.setDataSource(dataSource); this.setNodeID(nodeID); init(); } /** Not preferred constructor. Uses default node id value... */ public NotificationDAO(DataSource dataSource) { this(dataSource, Utils.getNodeID()); } /** Not preferred constructor but here for serialization requirement. */ public NotificationDAO() { this(null, null); } //Initialize result set handlers... public void init() { //TODO: Put a *bound* on cache this to some reasonable length so //it doesn't get crazy large, or even better yet use something //like EHCache, perhaps? (maybe overkill) emailResolverCache = new HashMap<String, String>(); //openid2Email = new OpenId2EmailAddressResolution(); //openid2Email.init(attributeQueryIssuer,yadisPropertiesFilename,attributeServiceClientPropertiesFilename); log.trace("Setting up result handler"); handler = new ResultSetHandler<List<NotificationDAO.NotificationRecipientInfo>>() { public List<NotificationDAO.NotificationRecipientInfo> handle(ResultSet rs) throws SQLException { List<NotificationDAO.NotificationRecipientInfo> nris = new Vector<NotificationDAO.NotificationRecipientInfo>(); NotificationDAO.NotificationRecipientInfo nri = new NotificationDAO.NotificationRecipientInfo(); String lastUserid = null; String lastUserAddress = null; if (!rs.next()) { return null; } do { String userid = rs.getString(1); String userAddress = rs.getString(2); //Do the email resolution only if email is not available and not already cached //if( (null != userid) && (null == userAddress) && (null == (userAddress = emailResolverCache.get(userid)) ) ) { // try{ // userAddress = openid2email.resolve(userid); //phil's resolver (uses SAML) // emailResolverCache.put(userid,userAddress); // }catch (Throwable e) { // log.warn("Cannot perform openid2email resolution: "+e.getMessage()); // log.error(e); // } //} //Create a new object PER NEW EMAIL ADDRESS... if ((lastUserAddress == null) || (!lastUserAddress.equals(userAddress))) { //NOTE - Memory Optimization: See comment section of ESGNotifier... //(memory optimization place to put callback to notifier.generateNotification(lastUserid,lastUserAddress,nri);) //(and then remove the call to nris.add(nri)) //(also would have to make this object take a [final] 'notifier' obj, prob via the call getNotificationInfo) nri = new NotificationDAO.NotificationRecipientInfo(); nri.withValues(userid, userAddress); } //NOTE: As a refinement take a look at using a //"BeanListHandler" I think it would provide //flexibility with ordering of columns, but has //the additional stipulation of having to have to //name attributes the same as column headings or //alias the result column names from the query. //At the moment I am going with what I //know. (K.I.S.S.) //For more info on bean handling in dbUtil see: http://commons.apache.org/dbutils/examples.html //For reference on jdbc/sql type mapping: //http://java.sun.com/j2se/1.3/docs/guide/jdbc/getstart/mapping.html nri.withDatasetInfo(rs.getString(3), rs.getString(4), rs.getLong(5)); nris.add(nri); lastUserid = userid; lastUserAddress = userAddress; } while (rs.next()); return nris; } }; //inserts entry into table for this node registerWithNotificationRunLog(); } public void setDataSource(DataSource dataSource) { log.trace("Setting Up Notification DAO's Pooled Data Source"); this.dataSource = dataSource; this.queryRunner = new QueryRunner(dataSource); } //NOTE: I made this private because I can't think of any //circumstance where an instance of a DAO would need to change //it's nodeID. The nodeID is a intrinsic identifying //characteristic of a DAO that qualifies many of the queries it //performs. It is especially critical when nodes may use existing //postgres installations that is shared by other nodes. (just //thinking ahead into the state preserving cloud-ish model of //operation. private void setNodeID(String nodeID) { log.trace("Notification DAO's nodeID: " + nodeID); this.nodeID = nodeID; } private String getNodeID() { if (nodeID == null) throw new ESGInvalidObjectStateException("NodeID cannot be NULL!"); return nodeID; } public List<NotificationRecipientInfo> getNotificationRecipientInfo() { if (this.dataSource == null) { log.error("The datasource [" + dataSource + "] is not valid, Please call setDataSource(...) first!!!"); return null; } //log.trace("Getting Notification Recipient Info... \n Query = "+notificationQuery); List<NotificationRecipientInfo> nris = null; try { nris = queryRunner.query(notificationQuery, handler, getNodeID()); } catch (SQLException ex) { log.error(ex); } return nris; } public int markLastCompletionTime() { int ret = -1; try { long now = System.currentTimeMillis() / 1000; //Calendar cal = Calendar.getInstance(); //cal.setTimeInMillis(now*1000); //log.trace("marking completion time: "+now+" : "+cal.getTime()); ret = queryRunner.update(markTimeQuery, now, getNodeID()); } catch (SQLException ex) { log.error(ex); } return ret; } private int registerWithNotificationRunLog() { int ret = -1; try { log.trace("Registering this node [" + getNodeID() + "] into database"); int count = queryRunner.query(regCheckEntryQuery, new ResultSetHandler<Integer>() { public Integer handle(ResultSet rs) throws SQLException { if (!rs.next()) { return -1; } return rs.getInt(1); } }, NotificationDAO.this.getNodeID()); if (count > 0) { log.info("Yes, " + NotificationDAO.this.getNodeID() + " exists in notification run log table"); } else { log.info("No, " + NotificationDAO.this.getNodeID() + " does NOT exist in notification run log table"); ret = queryRunner.update(regAddEntryQuery, NotificationDAO.this.getNodeID(), System.currentTimeMillis() / 1000); } } catch (SQLException ex) { log.error(ex); } return ret; } //---------------------------------------------- //Result data holder objects.... //---------------------------------------------- public static class NotificationRecipientInfo { String userid = null; String userAddress = null; Map<String, Set<FileInfo>> datasets = new HashMap<String, Set<FileInfo>>(); //Using a more "fluent" model of setting vars: convention is to use "withXXX" public NotificationRecipientInfo withValues(String userid, String userAddress) { this.userid = userid; this.userAddress = userAddress; return this; } public NotificationRecipientInfo withDatasetInfo(String datasetName, String fileUrl, long modTime) { Set<FileInfo> fileInfoSet = datasets.get(datasetName); if (fileInfoSet == null) { datasets.put(datasetName, fileInfoSet = new HashSet<FileInfo>()); } fileInfoSet.add(new FileInfo(fileUrl, modTime)); return this; } public String toString() { StringBuilder sb = new StringBuilder(); String datasetName = null; for (Iterator<String> dsIt = datasets.keySet().iterator(); dsIt.hasNext();) { datasetName = dsIt.next(); sb.append("Dataset: " + datasetName + "\n"); for (Iterator<FileInfo> fiIt = datasets.get(datasetName).iterator(); fiIt.hasNext();) { sb.append(" File URL: " + fiIt.next()); } } return sb.toString(); } } public static class FileInfo { Calendar cal = Calendar.getInstance(); String url = null; long modTime = 0L; FileInfo(String url, long modTime) { this.url = url; this.modTime = modTime; } //FileInfos are equal if the urls are the same //(Not counting mod time) public boolean equals(Object obj) { return this.url.equals(((FileInfo) obj).url); } public String toString() { cal.setTimeInMillis(modTime * 1000); return url + " : " + cal.getTime(); //TODO: turn modTime into a human readable date } } //---------------------------------------------- public String toString() { StringBuilder out = new StringBuilder(); out.append("DAO:(1)[" + this.getClass().getName() + "] - [Q:" + notificationQuery + "] " + ((dataSource == null) ? "[OK]" : "[INVALID]\n")); out.append("DAO:(1)[" + this.getClass().getName() + "] - [Q:" + regCheckEntryQuery + "] " + ((dataSource == null) ? "[OK]" : "[INVALID]\n")); out.append("DAO:(1)[" + this.getClass().getName() + "] - [Q:" + regAddEntryQuery + "] " + ((dataSource == null) ? "[OK]" : "[INVALID]\n")); out.append("DAO:(1)[" + this.getClass().getName() + "] - [Q:" + markTimeQuery + "] " + ((dataSource == null) ? "[OK]" : "[INVALID]")); return out.toString(); } }