org.apache.slide.webdav.event.NotificationTrigger.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.slide.webdav.event.NotificationTrigger.java

Source

/*
 * $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);
                }
            }
        }
    }
}