View Javadoc
1   /**
2    * Copyright 2005-2014 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.api.criteria.Predicate;
22  import org.kuali.rice.core.api.criteria.QueryByCriteria;
23  import org.kuali.rice.core.framework.persistence.dao.GenericDao;
24  import org.kuali.rice.ken.bo.NotificationBo;
25  import org.kuali.rice.ken.bo.NotificationChannelBo;
26  import org.kuali.rice.ken.bo.NotificationContentTypeBo;
27  import org.kuali.rice.ken.bo.NotificationPriorityBo;
28  import org.kuali.rice.ken.bo.NotificationProducerBo;
29  import org.kuali.rice.ken.bo.NotificationRecipientBo;
30  import org.kuali.rice.ken.bo.NotificationSenderBo;
31  import org.kuali.rice.ken.service.NotificationContentTypeService;
32  import org.kuali.rice.krad.data.DataObjectService;
33  import org.w3c.dom.Document;
34  import org.w3c.dom.Element;
35  import org.w3c.dom.Node;
36  import org.w3c.dom.NodeList;
37  import org.xml.sax.EntityResolver;
38  import org.xml.sax.ErrorHandler;
39  import org.xml.sax.InputSource;
40  import org.xml.sax.SAXException;
41  import org.xml.sax.SAXParseException;
42  
43  import javax.xml.namespace.NamespaceContext;
44  import javax.xml.parsers.DocumentBuilder;
45  import javax.xml.parsers.DocumentBuilderFactory;
46  import javax.xml.parsers.ParserConfigurationException;
47  import javax.xml.transform.stream.StreamSource;
48  import java.io.IOException;
49  import java.sql.Timestamp;
50  import java.text.DateFormat;
51  import java.text.ParseException;
52  import java.text.SimpleDateFormat;
53  import java.util.ArrayList;
54  import java.util.Collections;
55  import java.util.Date;
56  import java.util.HashMap;
57  import java.util.List;
58  import java.util.Map;
59  import java.util.TimeZone;
60  
61  import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
62  
63  /**
64   * A general Utility class for the Notification system.
65   * @author Kuali Rice Team (rice.collab@kuali.org)
66   */
67  public final class Util {
68      private static final Logger LOG = Logger.getLogger(Util.class);
69      
70      public static final java.lang.String JAXP_SCHEMA_LANGUAGE = "http://java.sun.com/xml/jaxp/properties/schemaLanguage";
71      public static final java.lang.String W3C_XML_SCHEMA = "http://www.w3.org/2001/XMLSchema";
72  
73      public static final NamespaceContext NOTIFICATION_NAMESPACE_CONTEXT
74          = new ConfiguredNamespaceContext(Collections.singletonMap("nreq", "ns:notification/NotificationRequest"));
75  
76      private static final String ZULU_FORMAT = "yyyy-MM-dd'T'HH:mm:ss";
77      private static final TimeZone ZULU_TZ = TimeZone.getTimeZone("UTC");
78  
79      private static final String CURR_TZ_FORMAT = "MM/dd/yyyy hh:mm a";
80      
81  	private Util() {
82  		throw new UnsupportedOperationException("do not call");
83  	}
84  
85      /**
86       * @return the name of the user configured to be the Notification system user
87       */
88      public static String getNotificationSystemUser() {
89          String system_user = ConfigContext.getCurrentContextConfig().getProperty(NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER_PARAM);
90          if (system_user == null) {
91              system_user = NotificationConstants.KEW_CONSTANTS.NOTIFICATION_SYSTEM_USER;
92          }
93          return system_user;
94      }
95  
96      /**
97       * Parses a date/time string under XSD dateTime type syntax
98       * @see #ZULU_FORMAT
99       * @param dateTimeString an XSD dateTime-formatted String
100      * @return a Date representing the time value of the String parameter 
101      * @throws ParseException if an error occurs during parsing 
102      */
103     public static Date parseXSDDateTime(String dateTimeString) throws ParseException {
104             return createZulu().parse(dateTimeString);
105     }
106 
107     /**
108      * Formats a Date into XSD dateTime format
109      * @param d the date value to format
110      * @return date value formatted into XSD dateTime format
111      */
112     public static String toXSDDateTimeString(Date d) {
113         return createZulu().format(d);
114     }
115     
116     /**
117      * Returns the current date formatted for the UI
118      * @return the current date formatted for the UI
119      */
120     public static String getCurrentDateTime() {
121         return toUIDateTimeString(new Date());
122     }
123     
124     /**
125      * Returns the specified date formatted for the UI
126      * @return the specified date formatted for the UI
127      */
128     public static String toUIDateTimeString(Date d) {
129         return createCurrTz().format(d);
130     }
131 
132     /**
133      * Parses the string in UI date time format
134      * @return the date parsed from UI date time format
135      */
136     public static Date parseUIDateTime(String s) throws ParseException {
137         return createCurrTz().parse(s);
138     }
139 
140     /**
141      * Returns a compound NamespaceContext that defers to the preconfigured notification namespace context
142      * first, then delegates to the document prefix/namespace definitions second.
143      * @param doc the Document to use for prefix/namespace resolution
144      * @return  compound NamespaceContext
145      */
146     public static NamespaceContext getNotificationNamespaceContext(Document doc) {
147         return new CompoundNamespaceContext(NOTIFICATION_NAMESPACE_CONTEXT, new DocumentNamespaceContext(doc));
148     }
149 
150     /**
151      * Returns an EntityResolver to resolve XML entities (namely schema resources) in the notification system
152      * @param notificationContentTypeService the NotificationContentTypeService
153      * @return an EntityResolver to resolve XML entities (namely schema resources) in the notification system
154      */
155     public static EntityResolver getNotificationEntityResolver(NotificationContentTypeService notificationContentTypeService) {
156         return new CompoundEntityResolver(new ClassLoaderEntityResolver("schema", "notification"),
157                                           new ContentTypeEntityResolver(notificationContentTypeService));
158     }
159 
160     /**
161      * transformContent - transforms xml content in notification to a string
162      * using the xsl in the datastore for a given documentType
163      * @param notification
164      * @return
165      */
166     public static String transformContent(NotificationBo notification) {
167         NotificationContentTypeBo contentType = notification.getContentType();
168         String xsl = contentType.getXsl();
169         
170         LOG.debug("xsl: "+xsl);
171         
172         XslSourceResolver xslresolver = new XslSourceResolver();
173         //StreamSource xslsource = xslresolver.resolveXslFromFile(xslpath);
174         StreamSource xslsource = xslresolver.resolveXslFromString(xsl);
175         String content = notification.getContent();
176         LOG.debug("xslsource:"+xslsource.toString());
177         
178         String contenthtml = new String();
179         try {
180           ContentTransformer transformer = new ContentTransformer(xslsource);
181           contenthtml = transformer.transform(content);
182           LOG.debug("html: "+contenthtml);
183         } catch (IOException ex) {
184             LOG.error("IOException transforming document",ex);
185         } catch (Exception ex) {
186             LOG.error("Exception transforming document",ex);
187         } 
188         return contenthtml;
189     }
190 
191     /**
192      * This method uses DOM to parse the input source of XML.
193      * @param source the input source
194      * @param validate whether to turn on validation
195      * @param namespaceAware whether to turn on namespace awareness
196      * @return Document the parsed (possibly validated) document
197      * @throws ParserConfigurationException
198      * @throws IOException
199      * @throws SAXException
200      */
201     public static Document parse(final InputSource source, boolean validate, boolean namespaceAware, EntityResolver entityResolver) throws ParserConfigurationException, IOException, SAXException {
202         // TODO: optimize this
203         DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
204         dbf.setValidating(validate);
205         dbf.setNamespaceAware(namespaceAware);
206         dbf.setAttribute(JAXP_SCHEMA_LANGUAGE, W3C_XML_SCHEMA);
207         DocumentBuilder db = dbf.newDocumentBuilder();
208         if (entityResolver != null) {
209             db.setEntityResolver(entityResolver);
210         }
211         db.setErrorHandler(new ErrorHandler() {
212             public void warning(SAXParseException se) {
213                 LOG.warn("Warning parsing xml doc " + source, se);
214             }
215             public void error(SAXParseException se) throws SAXException {
216                 LOG.error("Error parsing xml doc " + source, se);
217                 throw se;
218             }
219             public void fatalError(SAXParseException se) throws SAXException {
220                 LOG.error("Fatal error parsing xml doc " + source, se);
221                 throw se;
222             }
223         });
224         return db.parse(source);
225     }
226 
227     /**
228      * This method uses DOM to parse the input source of XML, supplying a notification-system-specific
229      * entity resolver.
230      * @param source the input source
231      * @param validate whether to turn on validation
232      * @param namespaceAware whether to turn on namespace awareness
233      * @return Document the parsed (possibly validated) document
234      * @throws ParserConfigurationException
235      * @throws IOException
236      * @throws SAXException
237      */
238     public static Document parseWithNotificationEntityResolver(final InputSource source, boolean validate, boolean namespaceAware, NotificationContentTypeService notificationContentTypeService) throws ParserConfigurationException, IOException, SAXException {
239         return parse(source, validate, namespaceAware, getNotificationEntityResolver(notificationContentTypeService));
240     }
241 
242     /**
243      * Returns a node child with the specified tag name of the specified parent node,
244      * or null if no such child node is found. 
245      * @param parent the parent node
246      * @param name the name of the child node
247      * @return child node if found, null otherwise
248      */
249     public static Element getChildElement(Node parent, String name) {
250         NodeList childList = parent.getChildNodes();
251         for (int i = 0; i < childList.getLength(); i++) {
252             Node node = childList.item(i);
253             // we must test against NodeName, not just LocalName
254             // LocalName seems to be null - I am guessing this is because
255             // the DocumentBuilderFactory is not "namespace aware"
256             // although I would have expected LocalName to default to
257             // NodeName
258             if (node.getNodeType() == Node.ELEMENT_NODE
259                 && (name.equals(node.getLocalName())
260                    || name.equals(node.getNodeName()))) {
261                 return (Element) node;
262             }
263         }
264         return null;
265     }
266     
267     /**
268      * This method will clone a given Notification object, one level deep, returning a fresh new instance 
269      * without any references.
270      * @param notification the object to clone
271      * @return Notification a fresh instance
272      */
273     public static final NotificationBo cloneNotificationWithoutObjectReferences(NotificationBo notification) {
274 	NotificationBo clone = new NotificationBo();
275 	
276 	// handle simple data types first
277         if(notification.getCreationDateTime() != null) {
278             clone.setCreationDateTimeValue(new Timestamp(notification.getCreationDateTimeValue().getTime()));
279         }
280 	if(notification.getAutoRemoveDateTime() != null) {
281 	    clone.setAutoRemoveDateTimeValue(new Timestamp(notification.getAutoRemoveDateTimeValue().getTime()));
282 	}
283 	clone.setContent(new String(notification.getContent()));
284 	clone.setDeliveryType(new String(notification.getDeliveryType()));
285 	if(notification.getId() != null) {
286 	    clone.setId(new Long(notification.getId()));
287 	}
288 	clone.setProcessingFlag(new String(notification.getProcessingFlag()));
289 	if(notification.getSendDateTimeValue() != null) {
290 	    clone.setSendDateTimeValue(new Timestamp(notification.getSendDateTimeValue().getTime()));
291 	}
292 	
293         clone.setTitle(notification.getTitle());
294         
295 	// now take care of the channel
296 	NotificationChannelBo channel = new NotificationChannelBo();
297 	channel.setId(new Long(notification.getChannel().getId()));
298 	channel.setName(new String(notification.getChannel().getName()));
299 	channel.setDescription(new String(notification.getChannel().getDescription()));
300 	channel.setSubscribable(new Boolean(notification.getChannel().isSubscribable()).booleanValue());
301 	clone.setChannel(channel);
302 	
303 	// handle the content type
304 	NotificationContentTypeBo contentType = new NotificationContentTypeBo();
305 	contentType.setId(new Long(notification.getContentType().getId()));
306 	contentType.setDescription(new String(notification.getContentType().getDescription()));
307 	contentType.setName(new String(notification.getContentType().getName()));
308 	contentType.setNamespace(new String(notification.getContentType().getNamespace()));
309 	clone.setContentType(contentType);
310 	
311 	// take care of the prioirity
312 	NotificationPriorityBo priority = new NotificationPriorityBo();
313 	priority.setDescription(new String(notification.getPriority().getDescription()));
314 	priority.setId(new Long(notification.getPriority().getId()));
315 	priority.setName(new String(notification.getPriority().getName()));
316 	priority.setOrder(new Integer(notification.getPriority().getOrder()));
317 	clone.setPriority(priority);
318 	
319 	// take care of the producer
320 	NotificationProducerBo producer = new NotificationProducerBo();
321 	producer.setDescription(new String(notification.getProducer().getDescription()));
322 	producer.setId(new Long(notification.getProducer().getId()));
323 	producer.setName(new String(notification.getProducer().getName()));
324 	producer.setContactInfo(new String(notification.getProducer().getContactInfo()));
325 	clone.setProducer(producer);
326 	
327 	// process the list of recipients now
328 	ArrayList<NotificationRecipientBo> recipients = new ArrayList<NotificationRecipientBo>();
329 	for(int i = 0; i < notification.getRecipients().size(); i++) {
330 	    NotificationRecipientBo recipient = notification.getRecipient(i);
331 	    NotificationRecipientBo cloneRecipient = new NotificationRecipientBo();
332 	    cloneRecipient.setRecipientId(new String(recipient.getRecipientId()));
333 	    cloneRecipient.setRecipientType(new String(recipient.getRecipientType()));
334 	    
335 	    recipients.add(cloneRecipient);
336 	}
337 	clone.setRecipients(recipients);
338 	
339 	// process the list of senders now
340 	ArrayList<NotificationSenderBo> senders = new ArrayList<NotificationSenderBo>();
341 	for(int i = 0; i < notification.getSenders().size(); i++) {
342 	    NotificationSenderBo sender = notification.getSender(i);
343 	    NotificationSenderBo cloneSender = new NotificationSenderBo();
344 	    cloneSender.setSenderName(new String(sender.getSenderName()));
345 	    
346 	    senders.add(cloneSender);
347 	}
348 	clone.setSenders(senders);
349 	
350 	return clone;
351     }
352     
353     /**
354      * This method generically retrieves a reference to foreign key objects that are part of the content, to get 
355      * at the reference objects' pk fields so that those values can be used to store the notification with proper 
356      * foreign key relationships in the database.
357      * @param <T>
358      * @param fieldName
359      * @param keyName
360      * @param keyValue
361      * @param clazz
362      * @param dataObjectService
363      * @return T
364      * @throws IllegalArgumentException
365      */
366     public static <T> T retrieveFieldReference(String fieldName, String keyName, String keyValue, Class clazz,
367             DataObjectService dataObjectService, Boolean searchCurrentField) throws RuntimeException {
368 
369         LOG.debug(fieldName + " key value: " + keyValue);
370         if (StringUtils.isBlank(keyValue)) {
371             throw new IllegalArgumentException(fieldName + " must be specified in notification");
372         }
373 
374         List<Predicate> predicates = new ArrayList<Predicate>();
375         predicates.add(equal(keyName, keyValue));
376         if (searchCurrentField) {
377             predicates.add(equal("current", Boolean.TRUE));
378         }
379         QueryByCriteria.Builder criteria = QueryByCriteria.Builder.create();
380         criteria.setPredicates(predicates.toArray(new Predicate[predicates.size()]));
381         List<T> references = dataObjectService.findMatching(clazz, criteria.build()).getResults();
382 
383         if (references.isEmpty()) {
384             throw new IllegalArgumentException(fieldName + " '" + keyValue + "' not found");
385         }
386         if (references.size() > 1) {
387             throw new RuntimeException("More than one item found for the given value: " + keyValue);
388         }
389 
390         return references.get(0);
391     }
392 
393     public static <T> T retrieveFieldReference(String fieldName, String keyName, String keyValue, Class clazz, DataObjectService dataObjectService) throws RuntimeException {
394         return retrieveFieldReference(fieldName, keyName, keyValue, clazz, dataObjectService, false);
395     }
396     /** date formats are not thread safe so creating a new one each time it is needed. */
397     private static DateFormat createZulu() {
398         final DateFormat df = new SimpleDateFormat(ZULU_FORMAT);
399         df.setTimeZone(ZULU_TZ);
400         return df;
401     }
402 
403     /** date formats are not thread safe so creating a new one each time it is needed. */
404     private static DateFormat createCurrTz() {
405         return new SimpleDateFormat(CURR_TZ_FORMAT);
406     }
407 }