KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > nextapp > echo2 > webrender > service > SynchronizeService


1 /*
2  * This file is part of the Echo Web Application Framework (hereinafter "Echo").
3  * Copyright (C) 2002-2005 NextApp, Inc.
4  *
5  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
6  *
7  * The contents of this file are subject to the Mozilla Public License Version
8  * 1.1 (the "License"); you may not use this file except in compliance with
9  * the License. You may obtain a copy of the License at
10  * http://www.mozilla.org/MPL/
11  *
12  * Software distributed under the License is distributed on an "AS IS" basis,
13  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
14  * for the specific language governing rights and limitations under the
15  * License.
16  *
17  * Alternatively, the contents of this file may be used under the terms of
18  * either the GNU General Public License Version 2 or later (the "GPL"), or
19  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
20  * in which case the provisions of the GPL or the LGPL are applicable instead
21  * of those above. If you wish to allow use of your version of this file only
22  * under the terms of either the GPL or the LGPL, and not to allow others to
23  * use your version of this file under the terms of the MPL, indicate your
24  * decision by deleting the provisions above and replace them with the notice
25  * and other provisions required by the GPL or the LGPL. If you do not delete
26  * the provisions above, a recipient may use your version of this file under
27  * the terms of any one of the MPL, the GPL or the LGPL.
28  */

29
30 package nextapp.echo2.webrender.service;
31
32 import java.io.ByteArrayInputStream JavaDoc;
33 import java.io.ByteArrayOutputStream JavaDoc;
34 import java.io.IOException JavaDoc;
35 import java.io.InputStream JavaDoc;
36 import java.util.HashMap JavaDoc;
37 import java.util.Map JavaDoc;
38
39 import javax.servlet.http.HttpServletRequest JavaDoc;
40
41 import org.w3c.dom.Document JavaDoc;
42 import org.w3c.dom.Element JavaDoc;
43 import org.xml.sax.SAXException JavaDoc;
44
45 import nextapp.echo2.webrender.ClientAnalyzerProcessor;
46 import nextapp.echo2.webrender.Connection;
47 import nextapp.echo2.webrender.ContentType;
48 import nextapp.echo2.webrender.ServerMessage;
49 import nextapp.echo2.webrender.Service;
50 import nextapp.echo2.webrender.UserInstance;
51 import nextapp.echo2.webrender.UserInstanceUpdateManager;
52 import nextapp.echo2.webrender.servermessage.ClientConfigurationUpdate;
53 import nextapp.echo2.webrender.servermessage.ClientPropertiesStore;
54 import nextapp.echo2.webrender.servermessage.ServerDelayMessageUpdate;
55 import nextapp.echo2.webrender.util.DomUtil;
56
57 /**
58  * A service which synchronizes the state of the client with that of the server.
59  * Requests made to this service are in the form of "ClientMessage" XML
60  * documents which describe the user's actions since the last synchronization,
61  * e.g., the input typed into text fields and the action taken (e.g., a button
62  * press) which caused the server interaction. The service parses this XML input
63  * from the client and performs updates to the server state of the application.
64  * Once the input has been processed by the server application, an output
65  * "ServerMessage" containing instructions to update the client state is
66  * generated as a response.
67  */

68 public abstract class SynchronizeService
69 implements Service {
70     
71     /**
72      * An interface describing a ClientMessage MessagePart Processor.
73      * Implementations registered with the
74      * <code>registerClientMessagePartProcessor()</code> method will have
75      * their <code>process()</code> methods invoked when a matching
76      * message part is provided in a ClientMessage.
77      */

78     public static interface ClientMessagePartProcessor {
79         
80         /**
81          * Returns the name of the <code>ClientMessagePartProcessor</code>.
82          * The processor will be invoked when a message part with its name
83          * is found within the ClientMessage.
84          *
85          * @return the name of the processor
86          */

87         public String JavaDoc getName();
88         
89         /**
90          * Processes a MessagePart of a ClientMessage
91          *
92          * @param userInstance the relevant <code>UserInstance</code>
93          * @param messagePartElement the <code>message part</code> element
94          * to process
95          */

96         public void process(UserInstance userInstance, Element JavaDoc messagePartElement);
97     }
98
99     /**
100      * <code>Service</code> identifier.
101      */

102     public static final String JavaDoc SERVICE_ID = "Echo.Synchronize";
103
104     /**
105      * Map containing registered <code>ClientMessagePartProcessor</code>s.
106      */

107     private Map JavaDoc clientMessagePartProcessorMap = new HashMap JavaDoc();
108     
109     /**
110      * Creates a new <code>SynchronizeService</code>.
111      */

112     public SynchronizeService() {
113         super();
114         registerClientMessagePartProcessor(new ClientAnalyzerProcessor());
115     }
116     
117     /**
118      * Trims an XML <code>InputStream</code> to work around the issue
119      * of the XML parser crashing on trailing whitespace. This issue is present
120      * with requests from Konqueror/KHTML browsers.
121      *
122      * @param in the <code>InputStream</code>
123      * @param characterEncoding the character encoding of the stream
124      * @return a cleaned version of the stream, as a
125      * <code>ByteArrayInputStream</code>.
126      */

127     private InputStream JavaDoc cleanXmlInputStream(InputStream JavaDoc in, String JavaDoc characterEncoding)
128     throws IOException JavaDoc{
129         ByteArrayOutputStream JavaDoc byteOut = new ByteArrayOutputStream JavaDoc();
130         
131         byte[] buffer = new byte[4096];
132         int bytesRead = 0;
133         
134         try {
135             do {
136                 bytesRead = in.read(buffer);
137                 if (bytesRead > 0) {
138                     byteOut.write(buffer, 0, bytesRead);
139                 }
140             } while (bytesRead > 0);
141         } finally {
142             if (in != null) { try { in.close(); } catch (IOException JavaDoc ex) { } }
143         }
144         
145         in.close();
146         
147         byte[] data = byteOut.toByteArray();
148         data = new String JavaDoc(data, characterEncoding).trim().getBytes(characterEncoding);
149         
150         return new ByteArrayInputStream JavaDoc(data);
151     }
152     
153     /**
154      * @see nextapp.echo2.webrender.Service#getId()
155      */

156     public String JavaDoc getId() {
157         return SERVICE_ID;
158     }
159     
160     /**
161      * @see nextapp.echo2.webrender.Service#getVersion()
162      */

163     public int getVersion() {
164         return DO_NOT_CACHE;
165     }
166     
167     /**
168      * Generates a DOM representation of the XML input POSTed to this service.
169      *
170      * @param conn the relevant <code>Connection</code>
171      * @return a DOM representation of the POSTed XML input
172      * @throws IOException if the input is invalid
173      */

174     private Document JavaDoc parseRequestDocument(Connection conn)
175     throws IOException JavaDoc {
176         HttpServletRequest JavaDoc request = conn.getRequest();
177         InputStream JavaDoc in = null;
178         try {
179             String JavaDoc userAgent = conn.getRequest().getHeader("user-agent");
180             if (userAgent != null && userAgent.indexOf("onqueror") != -1) {
181                 // Invoke XML 'cleaner', but only for user agents that contain the string "onqueror",
182
// such as Konqueror, for example.
183
in = cleanXmlInputStream(request.getInputStream(), conn.getUserInstance().getCharacterEncoding());
184             } else {
185                 in = request.getInputStream();
186             }
187             return DomUtil.getDocumentBuilder().parse(in);
188         } catch (SAXException JavaDoc ex) {
189             throw new IOException JavaDoc("Provided InputStream cannot be parsed: " + ex);
190         } catch (IOException JavaDoc ex) {
191             throw new IOException JavaDoc("Provided InputStream cannot be parsed: " + ex);
192         } finally {
193             if (in != null) { try { in.close(); } catch (IOException JavaDoc ex) { } }
194         }
195     }
196
197     /**
198      * Processes a "ClientMessage" XML document containing application UI state
199      * change information from the client. This method will parse the
200      * message parts of the ClientMessage and invoke the
201      * <code>ClientMessagePartProcessor</code>s registered to process them.
202      *
203      * @param conn the relevant <code>Connection</code>
204      * @param clientMessageDocument the ClientMessage XML document to process
205      * @see ClientMessagePartProcessor
206      */

207     protected void processClientMessage(Connection conn, Document JavaDoc clientMessageDocument) {
208         UserInstance userInstance = conn.getUserInstance();
209         Element JavaDoc[] messageParts = DomUtil.getChildElementsByTagName(clientMessageDocument.getDocumentElement(),
210                 "message-part");
211         for (int i = 0; i < messageParts.length; ++i) {
212             ClientMessagePartProcessor processor =
213                     (ClientMessagePartProcessor) clientMessagePartProcessorMap.get(messageParts[i].getAttribute("processor"));
214             if (processor == null) {
215                 throw new RuntimeException JavaDoc("Invalid processor name \"" + messageParts[i].getAttribute("processor") + "\".");
216             }
217             processor.process(userInstance, messageParts[i]);
218         }
219     }
220     
221     /**
222      * Registers a <code>ClientMessagePartProcessor</code> to handle a
223      * specific type of message part.
224      *
225      * @param processor the <code>ClientMessagePartProcessor</code> to
226      * register
227      * @throws IllegalStateException if a processor with the same name is
228      * already registered
229      */

230     protected void registerClientMessagePartProcessor(ClientMessagePartProcessor processor) {
231         if (clientMessagePartProcessorMap.containsKey(processor.getName())) {
232             throw new IllegalStateException JavaDoc("Processor already registered with name \"" + processor.getName() + "\".");
233         }
234         clientMessagePartProcessorMap.put(processor.getName(), processor);
235     }
236     
237     /**
238      * Renders a <code>ServerMessage</code> in response to the initial
239      * synchronization.
240      *
241      * @param conn the relevant <code>Connection</code>
242      * @param clientMessageDocument the ClientMessage XML document
243      * @return the generated <code>ServerMessage</code>
244      */

245     protected abstract ServerMessage renderInit(Connection conn, Document JavaDoc clientMessageDocument);
246     
247     /**
248      * Renders a <code>ServerMessage</code> in response to a synchronization
249      * other than the initial synchronization.
250      *
251      * @param conn the relevant <code>Connection</code>
252      * @param clientMessageDocument the ClientMessage XML document
253      * @return the generated <code>ServerMessage</code>
254      */

255     protected abstract ServerMessage renderUpdate(Connection conn, Document JavaDoc clientMessageDocument);
256     
257     /**
258      * @see nextapp.echo2.webrender.Service#service(nextapp.echo2.webrender.Connection)
259      */

260     public void service(Connection conn)
261     throws IOException JavaDoc {
262         UserInstance userInstance = conn.getUserInstance();
263         synchronized(userInstance) {
264             Document JavaDoc clientMessageDocument = parseRequestDocument(conn);
265             String JavaDoc messageType = clientMessageDocument.getDocumentElement().getAttribute("type");
266             ServerMessage serverMessage;
267             
268             if ("initialize".equals(messageType)) {
269                 serverMessage = renderInit(conn, clientMessageDocument);
270                 ClientPropertiesStore.renderStoreDirective(serverMessage, userInstance.getClientProperties());
271                 ClientConfigurationUpdate.renderUpdateDirective(serverMessage, userInstance.getClientConfiguration());
272                 ServerDelayMessageUpdate.renderUpdateDirective(serverMessage, userInstance.getServerDelayMessage());
273                 
274                 // Add "test attribute" used by ClientEngine to determine if browser is correctly (un)escaping
275
// attribute values. Safari does not do this correctly and a workaround is thus employed if such
276
// bugs are detected.
277
serverMessage.getDocument().getDocumentElement().setAttribute("xml-attr-test", "x&y");
278             } else {
279                 serverMessage = renderUpdate(conn, clientMessageDocument);
280                 processUserInstanceUpdates(userInstance, serverMessage);
281             }
282             serverMessage.setTransactionId(userInstance.getNextTransactionId());
283             conn.setContentType(ContentType.TEXT_XML);
284             serverMessage.render(conn.getWriter());
285         }
286     }
287     
288     /**
289      * Renders updates to <code>UserInstance</code> properties.
290      *
291      * @param userInstance the relevant <code>UserInstance</code>
292      * @param serverMessage the <code>ServerMessage</code> containing the updates
293      */

294     private void processUserInstanceUpdates(UserInstance userInstance, ServerMessage serverMessage) {
295         UserInstanceUpdateManager updateManager = userInstance.getUserInstanceUpdateManager();
296         String JavaDoc[] updatedPropertyNames = updateManager.getPropertyUpdateNames();
297         for (int i = 0; i < updatedPropertyNames.length; ++i) {
298             if (UserInstance.PROPERTY_CLIENT_CONFIGURATION.equals(updatedPropertyNames[i])) {
299                 ClientConfigurationUpdate.renderUpdateDirective(serverMessage, userInstance.getClientConfiguration());
300             } else if (UserInstance.PROPERTY_SERVER_DELAY_MESSAGE.equals(updatedPropertyNames[i])) {
301                 ServerDelayMessageUpdate.renderUpdateDirective(serverMessage, userInstance.getServerDelayMessage());
302             }
303         }
304         updateManager.purge();
305     }
306 }
307
Popular Tags