View Javadoc

1   /*
2    * jcurl java curling software framework https://JCurl.mro.name Copyright (C)
3    * 2005-2009 M. Rohrmoser
4    * 
5    * This program is free software; you can redistribute it and/or modify it under
6    * the terms of the GNU General Public License as published by the Free Software
7    * Foundation; either version 2 of the License, or (at your option) any later
8    * version.
9    * 
10   * This program is distributed in the hope that it will be useful, but WITHOUT
11   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13   * details.
14   * 
15   * You should have received a copy of the GNU General Public License along with
16   * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
17   * Place, Suite 330, Boston, MA 02111-1307 USA
18   */
19  
20  package org.jcurl.demo.smack;
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.IOException;
25  import java.io.ObjectInputStream;
26  import java.io.ObjectOutputStream;
27  import java.util.concurrent.Executor;
28  import java.util.regex.Matcher;
29  import java.util.regex.Pattern;
30  import java.util.zip.GZIPInputStream;
31  import java.util.zip.GZIPOutputStream;
32  
33  import org.apache.commons.codec.binary.Base64;
34  import org.jcurl.core.ui.ChangeManager;
35  import org.jcurl.core.ui.Memento;
36  import org.jcurl.core.ui.UndoableMemento;
37  import org.jivesoftware.smack.Chat;
38  import org.jivesoftware.smack.ChatManagerListener;
39  import org.jivesoftware.smack.MessageListener;
40  import org.jivesoftware.smack.XMPPException;
41  import org.jivesoftware.smack.packet.Message;
42  import org.jivesoftware.smack.packet.PacketExtension;
43  
44  /**
45   * Could well be a decorator to add Jabber/XMPP remoting to
46   * {@link ChangeManager}s.
47   * 
48   * @author <a href="mailto:JCurl@mro.name">M. Rohrmoser </a>
49   * @version $Id: SmackChangeManager.java 1031 2009-07-23 15:06:05Z mro $
50   */
51  public class SmackChangeManager extends ChangeManager implements
52  		ChatManagerListener {
53  
54  	private class MementoPackageExtension implements PacketExtension {
55  		private final Memento<?> me;
56  		private String xml = null;
57  
58  		public MementoPackageExtension(final Memento<?> me) {
59  			this.me = me;
60  		}
61  
62  		public String getElementName() {
63  			return temporaryElem;
64  		}
65  
66  		public String getNamespace() {
67  			return namespace;
68  		}
69  
70  		public String toXML() {
71  			if (xml != null)
72  				return xml;
73  			return xml = toXml(me);
74  		}
75  	}
76  
77  	private static final String JCURL_GRAPHICS_SYNC = "jcurl graphics sync";
78  	private static final String namespace = "/xep/"
79  			+ SmackChangeManager.class.getName();
80  	static final Pattern pat = Pattern
81  			.compile("<([^ >]+)\\s+xmlns='([^']*)'><([^ >]+)\\s+id='([^']*)'>([^<]*)</([^ >]+)></([^ >]+)>");
82  	private static final String subnode = "base64gz";
83  	private static final String temporaryElem = "temporary";
84  	private static final String US_ASCII = "US-ASCII";
85  
86  	/**
87  	 * Turn a {@link PacketExtension#toXML()} string into a {@link Memento}.
88  	 * 
89  	 * @see #toXml(Memento)
90  	 */
91  	static Memento<?> fromXml(final String xml) {
92  		final Matcher m = pat.matcher(xml);
93  		if (!m.matches())
94  			throw new IllegalStateException("xml doesn't match: " + xml);
95  		if (!m.group(1).equals(m.group(7)) || !m.group(3).equals(m.group(6)))
96  			throw new IllegalStateException("xml not well formed: " + xml);
97  		if (!namespace.equals(m.group(2)))
98  			throw new IllegalStateException("wrong namespace: " + m.group(2));
99  		if (!temporaryElem.equals(m.group(1)))
100 			throw new IllegalStateException("wrong elementName: " + m.group(1));
101 		if (!subnode.equals(m.group(3)))
102 			throw new IllegalStateException("wrong subnode: " + m.group(3));
103 		final String id = m.group(4);
104 		try {
105 			// deserialize the memento from BASE64:
106 			final ByteArrayInputStream bin = new ByteArrayInputStream(Base64
107 					.decodeBase64(m.group(5).getBytes(US_ASCII)));
108 			final GZIPInputStream gin = new GZIPInputStream(bin);
109 			final ObjectInputStream oin = new ObjectInputStream(gin);
110 			try {
111 				final Memento<?> me = (Memento<?>) oin.readObject();
112 				// FIXME id -> context.
113 				me.setContext(null);
114 				return me;
115 			} catch (final ClassNotFoundException e) {
116 				throw new IllegalStateException("Unhandled", e);
117 			} finally {
118 				oin.close();
119 			}
120 		} catch (final IOException e) {
121 			throw new RuntimeException("Unhandled", e);
122 		}
123 	}
124 
125 	/**
126 	 * Turn a {@link Memento} into a {@link PacketExtension#toXML} string.
127 	 * 
128 	 * @see #fromXml(String)
129 	 */
130 	static String toXml(final Memento<?> me) {
131 		final StringBuilder s = new StringBuilder();
132 		s.append('<').append(temporaryElem).append(" xmlns='")
133 				.append(namespace).append("'>");
134 		s.append("<").append(subnode).append(" id='");
135 		{
136 			// FIXME find/create the id.
137 			s.append("TODO id");
138 		}
139 		s.append("'>");
140 		try {
141 			// serialize the memento to BASE64:
142 			final ByteArrayOutputStream bout = new ByteArrayOutputStream();
143 			final GZIPOutputStream gout = new GZIPOutputStream(bout);
144 			final ObjectOutputStream oout = new ObjectOutputStream(gout);
145 			oout.writeObject(me);
146 			oout.close();
147 			s.append(new String(Base64.encodeBase64(bout.toByteArray(), false),
148 					US_ASCII));
149 		} catch (final IOException e) {
150 			throw new RuntimeException("Unhandled", e);
151 		}
152 		s.append("</").append(subnode).append(">");
153 		s.append("</").append(temporaryElem).append('>');
154 		return s.toString();
155 	}
156 
157 	private Chat chat = null;
158 	/** push incoming remote changes to the local data model */
159 	private final MessageListener msg = new MessageListener() {
160 		/** dispatch remote messages to local methods */
161 		public void processMessage(final Chat arg0, final Message arg1) {
162 			final PacketExtension pe = arg1.getExtension(namespace);
163 			if (pe == null)
164 				return;
165 			if (temporaryElem.equals(pe.getElementName()))
166 				superTemporary(fromXml(pe.toXML()));
167 			else if (superUndoable(convert_(arg1)))
168 				;
169 			else
170 				throw new IllegalStateException(pe.getElementName());
171 			return;
172 		}
173 	};
174 
175 	public SmackChangeManager() {
176 		super();
177 	}
178 
179 	public SmackChangeManager(final Executor executor) {
180 		super(executor);
181 	}
182 
183 	public void chatCreated(final Chat chat, final boolean arg1) {
184 		if (this.chat != null)
185 			this.chat.removeMessageListener(msg);
186 		this.chat = chat;
187 		if (this.chat != null)
188 			this.chat.addMessageListener(msg);
189 	}
190 
191 	private Message convert(final UndoableMemento<?> pre) {
192 		// TODO
193 		throw new UnsupportedOperationException();
194 	}
195 
196 	private UndoableMemento<?> convert_(final Message m) {
197 		// TODO
198 		throw new UnsupportedOperationException();
199 	}
200 
201 	private boolean superTemporary(final Memento<?> m) {
202 		if (m == null)
203 			return false;
204 		super.temporary(m);
205 		return true;
206 	}
207 
208 	private boolean superUndoable(final UndoableMemento m) {
209 		if (m == null)
210 			return false;
211 		// return super.undoable(m.pre, m.post);
212 		// TODO
213 		throw new UnsupportedOperationException();
214 	}
215 
216 	/**
217 	 * Push local gui-generated Mementos to the local data model and publish
218 	 * them
219 	 */
220 	@Override
221 	public void temporary(final Memento<?> m) {
222 		super.temporary(m);
223 		if (chat == null)
224 			return;
225 		try {
226 			final Message r = new Message();
227 			r.setBody(JCURL_GRAPHICS_SYNC);
228 			r.addExtension(new MementoPackageExtension(m));
229 			chat.sendMessage(r);
230 		} catch (final XMPPException e) {
231 			throw new RuntimeException("Unhandled", e);
232 		}
233 	}
234 
235 	/**
236 	 * Push local gui-generated Mementos to the local data model and publish
237 	 * them.
238 	 * 
239 	 */
240 	@Override
241 	public <E> UndoableMemento<E> undoable(final Memento<E> pre,
242 			final Memento<E> post) {
243 		final UndoableMemento<E> m = super.undoable(pre, post);
244 		if (m == null)
245 			return m;
246 		if (chat != null)
247 			try {
248 				chat.sendMessage(convert(m));
249 			} catch (final XMPPException e) {
250 				throw new RuntimeException("Unhandled", e);
251 			}
252 		return m;
253 	}
254 }