001 /** 002 * Copyright 2005-2012 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 package org.kuali.rice.ken.util; 017 018 import org.apache.commons.lang.StringUtils; 019 import org.apache.log4j.Logger; 020 import org.kuali.rice.core.api.config.property.ConfigContext; 021 import org.kuali.rice.core.framework.persistence.dao.GenericDao; 022 import org.kuali.rice.ken.bo.NotificationBo; 023 import org.kuali.rice.ken.bo.NotificationChannelBo; 024 import org.kuali.rice.ken.bo.NotificationContentTypeBo; 025 import org.kuali.rice.ken.bo.NotificationPriorityBo; 026 import org.kuali.rice.ken.bo.NotificationProducerBo; 027 import org.kuali.rice.ken.bo.NotificationRecipientBo; 028 import org.kuali.rice.ken.bo.NotificationSenderBo; 029 import org.kuali.rice.ken.service.NotificationContentTypeService; 030 import org.w3c.dom.Document; 031 import org.w3c.dom.Element; 032 import org.w3c.dom.Node; 033 import org.w3c.dom.NodeList; 034 import org.xml.sax.EntityResolver; 035 import org.xml.sax.ErrorHandler; 036 import org.xml.sax.InputSource; 037 import org.xml.sax.SAXException; 038 import org.xml.sax.SAXParseException; 039 040 import javax.xml.namespace.NamespaceContext; 041 import javax.xml.parsers.DocumentBuilder; 042 import javax.xml.parsers.DocumentBuilderFactory; 043 import javax.xml.parsers.ParserConfigurationException; 044 import javax.xml.transform.stream.StreamSource; 045 import java.io.IOException; 046 import java.sql.Timestamp; 047 import java.text.DateFormat; 048 import java.text.ParseException; 049 import java.text.SimpleDateFormat; 050 import java.util.ArrayList; 051 import java.util.Collections; 052 import java.util.Date; 053 import java.util.HashMap; 054 import java.util.Map; 055 import java.util.TimeZone; 056 057 /** 058 * A general Utility class for the Notification system. 059 * @author Kuali Rice Team (rice.collab@kuali.org) 060 */ 061 public final class Util { 062 private static final Logger LOG = Logger.getLogger(Util.class); 063 064 public static final java.lang.String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage"; 065 public static final java.lang.String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema"; 066 067 public static final NamespaceContext NOTIFICATION_NAMESPACE_CONTEXT 068 = new ConfiguredNamespaceContext(Collections.singletonMap("nreq", "ns:notification/NotificationRequest")); 069 070 private static final String ZULU_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; 071 private static final TimeZone ZULU_TZ = TimeZone.getTimeZone("UTC"); 072 073 private static final String CURR_TZ_FORMAT = "MM/dd/yyyy hh:mm a"; 074 075 private Util() { 076 throw new UnsupportedOperationException("do not call"); 077 } 078 079 /** 080 * @return the name of the user configured to be the Notification system user 081 */ 082 public static String getNotificationSystemUser() { 083 String system_user = ConfigContext.getCurrentContextConfig().getProperty(NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_PARAM); 084 if (system_user == null) { 085 system_user = NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER; 086 } 087 return system_user; 088 } 089 090 /** 091 * Parses a date/time string under XSD dateTime type syntax 092 * @see #ZULU_FORMAT 093 * @param dateTimeString an XSD dateTime-formatted String 094 * @return a Date representing the time value of the String parameter 095 * @throws ParseException if an error occurs during parsing 096 */ 097 public static Date parseXSDDateTime(String dateTimeString) throws ParseException { 098 return createZulu().parse(dateTimeString); 099 } 100 101 /** 102 * Formats a Date into XSD dateTime format 103 * @param d the date value to format 104 * @return date value formatted into XSD dateTime format 105 */ 106 public static String toXSDDateTimeString(Date d) { 107 return createZulu().format(d); 108 } 109 110 /** 111 * Returns the current date formatted for the UI 112 * @return the current date formatted for the UI 113 */ 114 public static String getCurrentDateTime() { 115 return toUIDateTimeString(new Date()); 116 } 117 118 /** 119 * Returns the specified date formatted for the UI 120 * @return the specified date formatted for the UI 121 */ 122 public static String toUIDateTimeString(Date d) { 123 return createCurrTz().format(d); 124 } 125 126 /** 127 * Parses the string in UI date time format 128 * @return the date parsed from UI date time format 129 */ 130 public static Date parseUIDateTime(String s) throws ParseException { 131 return createCurrTz().parse(s); 132 } 133 134 /** 135 * Returns a compound NamespaceContext that defers to the preconfigured notification namespace context 136 * first, then delegates to the document prefix/namespace definitions second. 137 * @param doc the Document to use for prefix/namespace resolution 138 * @return compound NamespaceContext 139 */ 140 public static NamespaceContext getNotificationNamespaceContext(Document doc) { 141 return new CompoundNamespaceContext(NOTIFICATION_NAMESPACE_CONTEXT, new DocumentNamespaceContext(doc)); 142 } 143 144 /** 145 * Returns an EntityResolver to resolve XML entities (namely schema resources) in the notification system 146 * @param notificationContentTypeService the NotificationContentTypeService 147 * @return an EntityResolver to resolve XML entities (namely schema resources) in the notification system 148 */ 149 public static EntityResolver getNotificationEntityResolver(NotificationContentTypeService notificationContentTypeService) { 150 return new CompoundEntityResolver(new ClassLoaderEntityResolver("schema", "notification"), 151 new ContentTypeEntityResolver(notificationContentTypeService)); 152 } 153 154 /** 155 * transformContent - transforms xml content in notification to a string 156 * using the xsl in the datastore for a given documentType 157 * @param notification 158 * @return 159 */ 160 public static String transformContent(NotificationBo notification) { 161 NotificationContentTypeBo contentType = notification.getContentType(); 162 String xsl = contentType.getXsl(); 163 164 LOG.debug("xsl: "+xsl); 165 166 XslSourceResolver xslresolver = new XslSourceResolver(); 167 //StreamSource xslsource = xslresolver.resolveXslFromFile(xslpath); 168 StreamSource xslsource = xslresolver.resolveXslFromString(xsl); 169 String content = notification.getContent(); 170 LOG.debug("xslsource:"+xslsource.toString()); 171 172 String contenthtml = new String(); 173 try { 174 ContentTransformer transformer = new ContentTransformer(xslsource); 175 contenthtml = transformer.transform(content); 176 LOG.debug("html: "+contenthtml); 177 } catch (IOException ex) { 178 LOG.error("IOException transforming document",ex); 179 } catch (Exception ex) { 180 LOG.error("Exception transforming document",ex); 181 } 182 return contenthtml; 183 } 184 185 /** 186 * This method uses DOM to parse the input source of XML. 187 * @param source the input source 188 * @param validate whether to turn on validation 189 * @param namespaceAware whether to turn on namespace awareness 190 * @return Document the parsed (possibly validated) document 191 * @throws ParserConfigurationException 192 * @throws IOException 193 * @throws SAXException 194 */ 195 public static Document parse(final InputSource source, boolean validate, boolean namespaceAware, EntityResolver entityResolver) throws ParserConfigurationException, IOException, SAXException { 196 // TODO: optimize this 197 DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 198 dbf.setValidating(validate); 199 dbf.setNamespaceAware(namespaceAware); 200 dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA); 201 DocumentBuilder db = dbf.newDocumentBuilder(); 202 if (entityResolver != null) { 203 db.setEntityResolver(entityResolver); 204 } 205 db.setErrorHandler(new ErrorHandler() { 206 public void warning(SAXParseException se) { 207 LOG.warn("Warning parsing xml doc " + source, se); 208 } 209 public void error(SAXParseException se) throws SAXException { 210 LOG.error("Error parsing xml doc " + source, se); 211 throw se; 212 } 213 public void fatalError(SAXParseException se) throws SAXException { 214 LOG.error("Fatal error parsing xml doc " + source, se); 215 throw se; 216 } 217 }); 218 return db.parse(source); 219 } 220 221 /** 222 * This method uses DOM to parse the input source of XML, supplying a notification-system-specific 223 * entity resolver. 224 * @param source the input source 225 * @param validate whether to turn on validation 226 * @param namespaceAware whether to turn on namespace awareness 227 * @return Document the parsed (possibly validated) document 228 * @throws ParserConfigurationException 229 * @throws IOException 230 * @throws SAXException 231 */ 232 public static Document parseWithNotificationEntityResolver(final InputSource source, boolean validate, boolean namespaceAware, NotificationContentTypeService notificationContentTypeService) throws ParserConfigurationException, IOException, SAXException { 233 return parse(source, validate, namespaceAware, getNotificationEntityResolver(notificationContentTypeService)); 234 } 235 236 /** 237 * Returns a node child with the specified tag name of the specified parent node, 238 * or null if no such child node is found. 239 * @param parent the parent node 240 * @param name the name of the child node 241 * @return child node if found, null otherwise 242 */ 243 public static Element getChildElement(Node parent, String name) { 244 NodeList childList = parent.getChildNodes(); 245 for (int i = 0; i < childList.getLength(); i++) { 246 Node node = childList.item(i); 247 // we must test against NodeName, not just LocalName 248 // LocalName seems to be null - I am guessing this is because 249 // the DocumentBuilderFactory is not "namespace aware" 250 // although I would have expected LocalName to default to 251 // NodeName 252 if (node.getNodeType() == Node.ELEMENT_NODE 253 && (name.equals(node.getLocalName()) 254 || name.equals(node.getNodeName()))) { 255 return (Element) node; 256 } 257 } 258 return null; 259 } 260 261 /** 262 * This method will clone a given Notification object, one level deep, returning a fresh new instance 263 * without any references. 264 * @param notification the object to clone 265 * @return Notification a fresh instance 266 */ 267 public static final NotificationBo cloneNotificationWithoutObjectReferences(NotificationBo notification) { 268 NotificationBo clone = new NotificationBo(); 269 270 // handle simple data types first 271 if(notification.getCreationDateTime() != null) { 272 clone.setCreationDateTimeValue(new Timestamp(notification.getCreationDateTimeValue().getTime())); 273 } 274 if(notification.getAutoRemoveDateTime() != null) { 275 clone.setAutoRemoveDateTimeValue(new Timestamp(notification.getAutoRemoveDateTimeValue().getTime())); 276 } 277 clone.setContent(new String(notification.getContent())); 278 clone.setDeliveryType(new String(notification.getDeliveryType())); 279 if(notification.getId() != null) { 280 clone.setId(new Long(notification.getId())); 281 } 282 clone.setProcessingFlag(new String(notification.getProcessingFlag())); 283 if(notification.getSendDateTimeValue() != null) { 284 clone.setSendDateTimeValue(new Timestamp(notification.getSendDateTimeValue().getTime())); 285 } 286 287 clone.setTitle(notification.getTitle()); 288 289 // now take care of the channel 290 NotificationChannelBo channel = new NotificationChannelBo(); 291 channel.setId(new Long(notification.getChannel().getId())); 292 channel.setName(new String(notification.getChannel().getName())); 293 channel.setDescription(new String(notification.getChannel().getDescription())); 294 channel.setSubscribable(new Boolean(notification.getChannel().isSubscribable()).booleanValue()); 295 clone.setChannel(channel); 296 297 // handle the content type 298 NotificationContentTypeBo contentType = new NotificationContentTypeBo(); 299 contentType.setId(new Long(notification.getContentType().getId())); 300 contentType.setDescription(new String(notification.getContentType().getDescription())); 301 contentType.setName(new String(notification.getContentType().getName())); 302 contentType.setNamespace(new String(notification.getContentType().getNamespace())); 303 clone.setContentType(contentType); 304 305 // take care of the prioirity 306 NotificationPriorityBo priority = new NotificationPriorityBo(); 307 priority.setDescription(new String(notification.getPriority().getDescription())); 308 priority.setId(new Long(notification.getPriority().getId())); 309 priority.setName(new String(notification.getPriority().getName())); 310 priority.setOrder(new Integer(notification.getPriority().getOrder())); 311 clone.setPriority(priority); 312 313 // take care of the producer 314 NotificationProducerBo producer = new NotificationProducerBo(); 315 producer.setDescription(new String(notification.getProducer().getDescription())); 316 producer.setId(new Long(notification.getProducer().getId())); 317 producer.setName(new String(notification.getProducer().getName())); 318 producer.setContactInfo(new String(notification.getProducer().getContactInfo())); 319 clone.setProducer(producer); 320 321 // process the list of recipients now 322 ArrayList<NotificationRecipientBo> recipients = new ArrayList<NotificationRecipientBo>(); 323 for(int i = 0; i < notification.getRecipients().size(); i++) { 324 NotificationRecipientBo recipient = notification.getRecipient(i); 325 NotificationRecipientBo cloneRecipient = new NotificationRecipientBo(); 326 cloneRecipient.setRecipientId(new String(recipient.getRecipientId())); 327 cloneRecipient.setRecipientType(new String(recipient.getRecipientType())); 328 329 recipients.add(cloneRecipient); 330 } 331 clone.setRecipients(recipients); 332 333 // process the list of senders now 334 ArrayList<NotificationSenderBo> senders = new ArrayList<NotificationSenderBo>(); 335 for(int i = 0; i < notification.getSenders().size(); i++) { 336 NotificationSenderBo sender = notification.getSender(i); 337 NotificationSenderBo cloneSender = new NotificationSenderBo(); 338 cloneSender.setSenderName(new String(sender.getSenderName())); 339 340 senders.add(cloneSender); 341 } 342 clone.setSenders(senders); 343 344 return clone; 345 } 346 347 /** 348 * This method generically retrieves a reference to foreign key objects that are part of the content, to get 349 * at the reference objects' pk fields so that those values can be used to store the notification with proper 350 * foreign key relationships in the database. 351 * @param <T> 352 * @param fieldName 353 * @param keyName 354 * @param keyValue 355 * @param clazz 356 * @param boDao 357 * @return T 358 * @throws IllegalArgumentException 359 */ 360 public static <T> T retrieveFieldReference(String fieldName, String keyName, String keyValue, Class clazz, GenericDao boDao) throws IllegalArgumentException { 361 LOG.debug(fieldName + " key value: " + keyValue); 362 if (StringUtils.isBlank(keyValue)) { 363 throw new IllegalArgumentException(fieldName + " must be specified in notification"); 364 } 365 Map<String, Object> keys = new HashMap<String, Object>(1); 366 keys.put(keyName, keyValue); 367 T reference = (T) boDao.findByPrimaryKey(clazz, keys); 368 if (reference == null) { 369 throw new IllegalArgumentException(fieldName + " '" + keyValue + "' not found"); 370 } 371 return reference; 372 } 373 374 /** date formats are not thread safe so creating a new one each time it is needed. */ 375 private static DateFormat createZulu() { 376 final DateFormat df = new SimpleDateFormat(ZULU_FORMAT); 377 df.setTimeZone(ZULU_TZ); 378 return df; 379 } 380 381 /** date formats are not thread safe so creating a new one each time it is needed. */ 382 private static DateFormat createCurrTz() { 383 return new SimpleDateFormat(CURR_TZ_FORMAT); 384 } 385 }