Java tutorial
/* * $Header: /var/chroot/cvs/cvs/factsheetDesigner/extern/jakarta-slide-server-src-2.1-iPlus Edit/src/webdav/server/org/apache/slide/webdav/event/NotificationTrigger.java,v 1.2 2006-01-22 22:55:20 peter-cvs Exp $ * $Revision: 1.2 $ * $Date: 2006-01-22 22:55:20 $ * * ==================================================================== * * Copyright 2004 The Apache Software Foundation * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.apache.slide.webdav.event; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.net.DatagramPacket; import java.net.DatagramSocket; import java.net.InetAddress; import java.net.SocketException; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Timer; import java.util.TimerTask; import org.apache.commons.httpclient.HttpConnection; import org.apache.commons.httpclient.HttpState; import org.apache.slide.common.Domain; import org.apache.slide.event.ContentEvent; import org.apache.slide.event.EventCollection; import org.apache.slide.event.EventCollectionFilter; import org.apache.slide.event.EventCollectionListener; import org.apache.slide.event.ResourceEvent; import org.apache.slide.event.VetoException; import org.apache.slide.util.conf.Configurable; import org.apache.slide.util.conf.Configuration; import org.apache.slide.util.conf.ConfigurationException; import org.apache.slide.util.logger.Logger; import org.apache.slide.webdav.util.NotificationConstants; import org.xml.sax.InputSource; import org.xml.sax.helpers.AttributesImpl; import de.zeigermann.xml.XMLEncode; import de.zeigermann.xml.XMLOutputStreamWriter; import de.zeigermann.xml.XMLWriter; import de.zeigermann.xml.simpleImporter.ConversionHelpers; import de.zeigermann.xml.simpleImporter.DefaultSimpleImportHandler; import de.zeigermann.xml.simpleImporter.SimpleImporter; import de.zeigermann.xml.simpleImporter.SimplePath; /** * @version $Revision: 1.2 $ */ public class NotificationTrigger implements NotificationConstants, EventCollectionListener, Configurable { protected static final String LOG_CHANNEL = NotificationTrigger.class.getName(); private final static String A_INCLUDE_EVENTS = "include-events"; private final static String A_FILENAME = "filename"; private final static String TCP_PROTOCOL = "http://"; private final static String UDP_PROTOCOL = "httpu://"; private final static String E_SUBSCRIPTIONS = "subscriptions"; private final static String E_SUBSCRIPTION = "subscription"; private final static String A_ID = "id"; private final static String E_URI = "uri"; private final static String E_DEPTH = "depth"; private final static String E_NOTIFICATION_DELAY = "notification-delay"; private final static String E_NOTIFICATION_TYPE = "notification-type"; private final static String E_CALLBACK = "callback"; private final static String E_SUBSCRIPTION_END = "subscription-end"; protected static final Timer timer = new Timer(); protected List subscribers = new ArrayList(); protected int subscriberId = 0; protected boolean includeEvents = false; protected DatagramSocket socket; protected String filename = null; private static NotificationTrigger notificationTrigger = new NotificationTrigger(); private NotificationTrigger() { Domain.log("Creating notification trigger", LOG_CHANNEL, Logger.INFO); try { socket = new DatagramSocket(); } catch (SocketException exception) { Domain.log("Server socket creation failed, no UDP notifications available", LOG_CHANNEL, Logger.ERROR); socket = null; } } public static NotificationTrigger getInstance() { return notificationTrigger; } public int addSubscriber(Subscriber subscriber) { Domain.log("Adding subscriber", LOG_CHANNEL, Logger.INFO); subscriberId++; subscriber.setId(subscriberId); subscribers.add(subscriber); refreshSubscriber(subscriber, true); return subscriberId; } public boolean removeSubscriber(Subscriber subscriber) { Domain.log("Removing subscriber with ID: " + subscriber.getId(), LOG_CHANNEL, Logger.INFO); subscriber.getLifetime().cancel(); saveSubscribers(); return subscribers.remove(subscriber); } public void refreshSubscriber(final Subscriber subscriber, boolean persist) { TimerTask lifetimeTask = subscriber.getLifetime(); if (lifetimeTask != null) lifetimeTask.cancel(); if (subscriber.getSubscriptionLifetime() > 0) { Domain.log("Refreshing subscriber with ID: " + subscriber.getId(), LOG_CHANNEL, Logger.INFO); TimerTask lifetime = new TimerTask() { public void run() { Domain.log("Removing subscriber with ID: " + subscriber.getId(), LOG_CHANNEL, Logger.INFO); refreshSubscriber(subscriber, true); } }; subscriber.setLifetime(lifetime); timer.schedule(lifetime, subscriber.getSubscriptionLifetime() * 1000); } if (persist) saveSubscribers(); } public List getSubscribers() { return subscribers; } public Subscriber getSubscriber(int id) { for (Iterator i = subscribers.iterator(); i.hasNext();) { Subscriber subsciber = (Subscriber) i.next(); if (subsciber.getId() == id) { return subsciber; } } return null; } public void vetoableCollected(EventCollection collection) throws VetoException { } public void collected(EventCollection collection) { notifySubscribers(collection); } private void notifySubscribers(EventCollection collection) { Map subscriberEnumerations = new HashMap(); List matchingSubscribers = new ArrayList(); // get subscribers with matching notification types // (and remember events) ContentEvent[] update = EventCollectionFilter.getChangedContents(collection); for (int i = 0; i < update.length; i++) { matchingSubscribers.addAll(getSubscribers(Subscriber.UPDATE, update[i])); } ContentEvent[] create = EventCollectionFilter.getCreatedContents(collection); for (int i = 0; i < create.length; i++) { matchingSubscribers.addAll(getSubscribers(Subscriber.NEW_MEMBER, create[i])); } ContentEvent[] delete = EventCollectionFilter.getRemovedContents(collection); for (int i = 0; i < delete.length; i++) { matchingSubscribers.addAll(getSubscribers(Subscriber.DELETE, delete[i])); } // FIXME: Add methods for MOVE, and NEW_MAIL (??) to get full exchange notification compliance // notifiy subscribers for (Iterator i = matchingSubscribers.iterator(); i.hasNext();) { final Subscriber subscriber = (Subscriber) i.next(); // skip subscribers that has no callback (we can't notify them) if (!subscriber.hasCallback()) continue; if (subscriber.getNotificationDelay() == 0) { // send notification without delay List idList = (List) subscriberEnumerations.get(subscriber.getCallback()); if (idList == null) { idList = new ArrayList(); subscriberEnumerations.put(subscriber.getCallback(), idList); } Integer subscriberId = new Integer(subscriber.getId()); if (!idList.contains(subscriberId)) { idList.add(subscriberId); } } else { // send delayed notification TimerTask notifyTask = subscriber.getNotify(); if (notifyTask == null) { Domain.log("Starting notification delay: " + subscriber.getNotificationDelay(), LOG_CHANNEL, Logger.INFO); notifyTask = new TimerTask() { public void run() { notifySubscriber(subscriber.getCallback(), String.valueOf(subscriber.getId())); subscriber.setNotify(null); } }; subscriber.setNotify(notifyTask); timer.schedule(notifyTask, subscriber.getNotificationDelay() * 1000); } } } for (Iterator i = subscriberEnumerations.entrySet().iterator(); i.hasNext();) { Map.Entry entry = (Map.Entry) i.next(); String callBack = (String) entry.getKey(); List idList = (List) entry.getValue(); StringBuffer subscriberBuffer = new StringBuffer(128); boolean firstSubscriber = true; for (Iterator j = idList.iterator(); j.hasNext();) { Integer id = (Integer) j.next(); if (!firstSubscriber) { subscriberBuffer.append(", "); } firstSubscriber = false; subscriberBuffer.append(id); } if (!firstSubscriber) { notifySubscriber(callBack, subscriberBuffer.toString()); } } } protected void notifySubscriber(String callback, String subscribers) { if (callback.startsWith(TCP_PROTOCOL)) { Domain.log("Notify subscribers with adress='" + callback + "' via TCP with id's " + subscribers, LOG_CHANNEL, Logger.INFO); NotifyMethod notifyMethod = new NotifyMethod(callback.toString()); notifyMethod.addRequestHeader(H_SUBSCRIPTION_ID_RESPONSE, subscribers); try { URL url = new URL(callback); notifyMethod.execute(new HttpState(), new HttpConnection(url.getHost(), url.getPort() != -1 ? url.getPort() : 80)); } catch (IOException e) { Domain.log("Notification of subscriber '" + callback.toString() + "' failed!"); } } else if (callback.startsWith(UDP_PROTOCOL) && socket != null) { Domain.log("Notify subscribers with adress='" + callback + "' via UDP with id's " + subscribers + "\n", LOG_CHANNEL, Logger.INFO); try { URL url = new URL(TCP_PROTOCOL + callback.substring(UDP_PROTOCOL.length())); String notification = "NOTIFY " + callback + " HTTP/1.1\nSubscription-id: " + subscribers; byte[] buf = notification.getBytes(); InetAddress address = InetAddress.getByName(url.getHost()); DatagramPacket packet = new DatagramPacket(buf, buf.length, address, url.getPort() != -1 ? url.getPort() : 80); socket.send(packet); } catch (IOException e) { Domain.log("Notification of subscriber '" + callback.toString() + "' failed!", LOG_CHANNEL, Logger.ERROR); } } } private List getSubscribers(String type, ResourceEvent event) { List matchingSubscribers = new ArrayList(); for (Iterator i = subscribers.iterator(); i.hasNext();) { Subscriber subscriber = (Subscriber) i.next(); if (subscriber.matches(type, event)) { matchingSubscribers.add(subscriber); // remember this event for later poll method call subscriber.addEvent(event); } } return matchingSubscribers; } public void configure(Configuration configuration) throws ConfigurationException { Configuration notification = configuration.getConfiguration("notification"); includeEvents = notification.getAttributeAsBoolean(A_INCLUDE_EVENTS, false); Configuration persistSubscriptions = configuration.getConfiguration("persist-subscriptions"); if (persistSubscriptions != null) { filename = persistSubscriptions.getAttribute(A_FILENAME); } loadSubscribers(); } private void loadSubscribers() { if (filename != null) { synchronized (subscribers) { File file = new File(filename); if (file.exists()) { try { FileInputStream inputStream = new FileInputStream(filename); SimpleImporter importer = new SimpleImporter(); importer.addSimpleImportHandler(new DefaultSimpleImportHandler() { String callback, notificationType, uri; int depth, notificationDelay, subscriptionLifetime, id; List events = new ArrayList(); public void startElement(SimplePath path, String name, AttributesImpl attributes, String leadingCDdata) { if (path.matches(E_SUBSCRIPTION)) { id = ConversionHelpers.getInt(attributes.getValue(A_ID)); } else if (path.matches(E_URI)) { uri = leadingCDdata; } else if (path.matches(E_DEPTH)) { depth = Integer.valueOf(leadingCDdata).intValue(); } else if (path.matches(E_CALLBACK)) { callback = leadingCDdata; } else if (path.matches(E_NOTIFICATION_DELAY)) { notificationDelay = Integer.valueOf(leadingCDdata).intValue(); } else if (path.matches(E_NOTIFICATION_TYPE)) { notificationType = leadingCDdata; } else if (path.matches(E_SUBSCRIPTION_END)) { subscriptionLifetime = (int) (Long.valueOf(leadingCDdata).longValue() - System.currentTimeMillis()); } } public void endElement(SimplePath path, String name) { if (path.matches(E_SUBSCRIPTION)) { Subscriber subscriber = new Subscriber(uri, callback, notificationType, notificationDelay, subscriptionLifetime, depth); subscribers.add(subscriber); refreshSubscriber(subscriber, false); } } }); importer.parse(new InputSource(inputStream)); } catch (Exception e) { Domain.log("Exception while restoring subscriptions. Skipping..."); } } } } } private void saveSubscribers() { if (filename != null) { synchronized (subscribers) { try { FileOutputStream outputStream = new FileOutputStream(filename); XMLOutputStreamWriter writer = new XMLOutputStreamWriter(outputStream); writer.writeXMLDeclaration(); writer.writeStartTag(XMLWriter.createStartTag(E_SUBSCRIPTIONS)); for (Iterator i = subscribers.iterator(); i.hasNext();) { Subscriber subscriber = (Subscriber) i.next(); writer.writeStartTag(XMLWriter.createStartTag(E_SUBSCRIPTION, new String[][] { { A_ID, String.valueOf(subscriber.getId()) } })); writer.writeElementWithPCData(XMLWriter.createStartTag(E_URI), XMLEncode.xmlEncodeText(subscriber.getUri()), XMLWriter.createEndTag(E_URI)); writer.writeElementWithPCData(XMLWriter.createStartTag(E_DEPTH), String.valueOf(subscriber.getDepth()), XMLWriter.createEndTag(E_DEPTH)); writer.writeElementWithPCData(XMLWriter.createStartTag(E_CALLBACK), XMLEncode.xmlEncodeText(subscriber.getCallback()), XMLWriter.createEndTag(E_CALLBACK)); writer.writeElementWithPCData(XMLWriter.createStartTag(E_NOTIFICATION_TYPE), XMLEncode.xmlEncodeText(subscriber.getNotificationType()), XMLWriter.createEndTag(E_NOTIFICATION_TYPE)); writer.writeElementWithPCData(XMLWriter.createStartTag(E_NOTIFICATION_DELAY), String.valueOf(subscriber.getNotificationDelay()), XMLWriter.createEndTag(E_NOTIFICATION_DELAY)); writer.writeElementWithPCData(XMLWriter.createStartTag(E_SUBSCRIPTION_END), String.valueOf(subscriber.getSubscriptionEnd()), XMLWriter.createEndTag(E_SUBSCRIPTION_END)); writer.writeEndTag(XMLWriter.createEndTag(E_SUBSCRIPTION)); } writer.writeEndTag(XMLWriter.createEndTag(E_SUBSCRIPTIONS)); outputStream.close(); } catch (Exception e) { Domain.log(e); } } } } }