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 }