View Javadoc

1   /**
2    * Copyright 2005-2015 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.ken.util;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.apache.log4j.Logger;
20  import org.kuali.rice.core.api.config.property.ConfigContext;
21  import org.kuali.rice.core.framework.persistence.dao.GenericDao;
22  import org.kuali.rice.ken.bo.NotificationBo;
23  import org.kuali.rice.ken.bo.NotificationChannelBo;
24  import org.kuali.rice.ken.bo.NotificationContentTypeBo;
25  import org.kuali.rice.ken.bo.NotificationPriorityBo;
26  import org.kuali.rice.ken.bo.NotificationProducerBo;
27  import org.kuali.rice.ken.bo.NotificationRecipientBo;
28  import org.kuali.rice.ken.bo.NotificationSenderBo;
29  import org.kuali.rice.ken.service.NotificationContentTypeService;
30  import org.w3c.dom.Document;
31  import org.w3c.dom.Element;
32  import org.w3c.dom.Node;
33  import org.w3c.dom.NodeList;
34  import org.xml.sax.EntityResolver;
35  import org.xml.sax.ErrorHandler;
36  import org.xml.sax.InputSource;
37  import org.xml.sax.SAXException;
38  import org.xml.sax.SAXParseException;
39  
40  import javax.xml.namespace.NamespaceContext;
41  import javax.xml.parsers.DocumentBuilder;
42  import javax.xml.parsers.DocumentBuilderFactory;
43  import javax.xml.parsers.ParserConfigurationException;
44  import javax.xml.transform.stream.StreamSource;
45  import java.io.IOException;
46  import java.sql.Timestamp;
47  import java.text.DateFormat;
48  import java.text.ParseException;
49  import java.text.SimpleDateFormat;
50  import java.util.ArrayList;
51  import java.util.Collections;
52  import java.util.Date;
53  import java.util.HashMap;
54  import java.util.Map;
55  import java.util.TimeZone;
56  
57  /**
58   * A general Utility class for the Notification system.
59   * @author Kuali Rice Team (rice.collab@kuali.org)
60   */
61  public final class Util {
62      private static final Logger LOG = Logger.getLogger(Util.class);
63      
64      public static final java.lang.String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
65      public static final java.lang.String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
66  
67      public static final NamespaceContext NOTIFICATION_NAMESPACE_CONTEXT
68          = new ConfiguredNamespaceContext(Collections.singletonMap("nreq", "ns:notification/NotificationRequest"));
69  
70      private static final String ZULU_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
71      private static final TimeZone ZULU_TZ = TimeZone.getTimeZone("UTC");
72  
73      private static final String CURR_TZ_FORMAT = "MM/dd/yyyy hh:mm a";
74      
75  	private Util() {
76  		throw new UnsupportedOperationException("do not call");
77  	}
78  
79      /**
80       * @return the name of the user configured to be the Notification system user
81       */
82      public static String getNotificationSystemUser() {
83          String system_user = ConfigContext.getCurrentContextConfig().getProperty(NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_PARAM);
84          if (system_user == null) {
85              system_user = NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER;
86          }
87          return system_user;
88      }
89  
90      /**
91       * Parses a date/time string under XSD dateTime type syntax
92       * @see #ZULU_FORMAT
93       * @param dateTimeString an XSD dateTime-formatted String
94       * @return a Date representing the time value of the String parameter 
95       * @throws ParseException if an error occurs during parsing 
96       */
97      public static Date parseXSDDateTime(String dateTimeString) throws ParseException {
98              return createZulu().parse(dateTimeString);
99      }
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 }