nextapp.echo2.webrender.ServerMessage Maven / Gradle / Ivy
The newest version!
/*
* This file is part of the Echo Web Application Framework (hereinafter "Echo").
* Copyright (C) 2002-2009 NextApp, Inc.
*
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (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.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*/
package nextapp.echo2.webrender;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import nextapp.echo2.webrender.output.XmlDocument;
/**
* The outgoing XML message which synchronizes the state of the client to that
* of the server.
*/
public class ServerMessage extends XmlDocument {
/**
* Constant for the use with setRootLayoutDirection()
* indicating a left-to-right layout direction.
*/
public static final int LEFT_TO_RIGHT = 0;
/**
* Constant for the use with setRootLayoutDirection()
* indicating a right-to-left layout direction.
*/
public static final int RIGHT_TO_LEFT = 1;
/**
* A hash table associating ItemizedDirectiveLookupKey
s to
* corresponding Element
s in the message.
*/
private Map itemizedDirectivesMap = new HashMap();
/**
* Representation of the information required to look up a suitable Itemized
* Directive Element
. Instances are used as keys in the
* itemizedDirectivesMap
. This class provides a
* getHashCode()
implementation for efficient lookups.
*/
private class ItemizedDirectiveLookupKey {
String groupId;
String processor;
String directiveName;
String[] keyAttributeNames;
String[] keyAttributeValues;
int hashCode;
/**
* Creates an ItemizedDirectiveLookupKey
based on the
* provided description.
*
* @param groupId the identifier of the target message part group
* @param processor the name of the client-side processor object which
* will process the message part containing the directive, e.g.,
* "EchoEventUpdate", or "EchoDomUpdate"
* @param directiveName the name of the directive, e.g., "event-add" or
* "dom-remove".
* @param keyAttributeNames
* @param keyAttributeValues
*/
private ItemizedDirectiveLookupKey(String groupId, String processor, String directiveName, String[] keyAttributeNames,
String[] keyAttributeValues) {
super();
this.groupId = groupId;
this.processor = processor;
this.directiveName = directiveName;
this.keyAttributeNames = keyAttributeNames;
this.keyAttributeValues = keyAttributeValues;
this.hashCode = groupId.hashCode() ^ processor.hashCode() ^ directiveName.hashCode();
for (int i = 0; i < keyAttributeNames.length; ++i) {
this.hashCode ^= keyAttributeNames[i].hashCode() ^ keyAttributeValues[i].hashCode();
}
}
/**
* @see java.lang.Object#equals(java.lang.Object)
*/
public boolean equals(Object o) {
if (!(o instanceof ItemizedDirectiveLookupKey)) {
return false;
}
ItemizedDirectiveLookupKey that = (ItemizedDirectiveLookupKey) o;
if (!this.groupId.equals(that.groupId)) {
return false;
}
if (!this.processor.equals(that.processor)) {
return false;
}
if (!this.directiveName.equals(that.directiveName)) {
return false;
}
if (this.keyAttributeNames.length != that.keyAttributeNames.length) {
return false;
}
for (int i = 0; i < keyAttributeValues.length; ++i) {
if (!(this.keyAttributeValues[i].equals(that.keyAttributeValues[i]))) {
return false;
}
}
for (int i = 0; i < keyAttributeNames.length; ++i) {
if (!(this.keyAttributeNames[i].equals(that.keyAttributeNames[i]))) {
return false;
}
}
return true;
}
/**
* @see java.lang.Object#hashCode()
*/
public int hashCode() {
return hashCode;
}
}
/**
* Constant for the "init" message part group. Message parts in this group are
* processed before the "preremove", "remove" "update", and "postupdate"
* groups.
*/
public static final String GROUP_ID_INIT = "init";
/**
* Constant for the "preremove" message part group. Message parts in this group
* are processed after the "init" group. Message parts in this group are
* processed before the "remove", "update" and "postupdate" groups.
*/
public static final String GROUP_ID_PREREMOVE = "preremove";
/**
* Constant for the "remove" message part group. Message parts in this group
* are processed after the "init" and "preremove" groups. Message parts in
* this group are processed before the "update" and "postupdate" groups.
* This group is used for removing elements from the DOM.
*/
public static final String GROUP_ID_REMOVE = "remove";
/**
* Constant for the "update" message part group. Message parts in this group
* are processed after the "init", "preremove" and "remove" groups.
* Message parts in this group are processed before the "postupdate" group.
* This group is used for adding elements to the DOM.
*/
public static final String GROUP_ID_UPDATE = "update";
/**
* Constant for the "postupdate" message part group. Message parts in this
* group are processed after the "init", "preremove", "remove" and "update"
* groups.
*/
public static final String GROUP_ID_POSTUPDATE = "postupdate";
/** Set of added script libraries. */
private Set addedLibraries;
/**
* DOM libraries
Element to which library
* elements are added to represent individual dynamically loaded JavaScript
* libraries.
*/
private Element librariesElement;
/** Root DOM server-message
element. */
private Element serverMessageElement;
/**
* Creates a new ServerMessage
.
*/
public ServerMessage() {
super("server-message", null, null, "http://www.nextapp.com/products/echo2/svrmsg/servermessage");
Document document = getDocument();
serverMessageElement = document.getDocumentElement();
librariesElement = document.createElement("libraries");
serverMessageElement.appendChild(librariesElement);
// Add basic part groups.
addPartGroup(GROUP_ID_INIT);
addPartGroup(GROUP_ID_PREREMOVE);
addPartGroup(GROUP_ID_REMOVE);
addPartGroup(GROUP_ID_UPDATE);
addPartGroup(GROUP_ID_POSTUPDATE);
}
/**
* Adds a JavaScript library service to be dynamically loaded.
*
* @param serviceId the id of the service to load (the service must return
* JavaScript code with content-type "text/javascript")
*/
public void addLibrary(String serviceId) {
if (addedLibraries == null) {
addedLibraries = new HashSet();
}
if (addedLibraries.contains(serviceId)) {
return;
}
Element libraryElement = getDocument().createElement("library");
libraryElement.setAttribute("service-id", serviceId);
librariesElement.appendChild(libraryElement);
addedLibraries.add(serviceId);
}
/**
* Adds a "group" to the document. Part groups enable certain groups of
* operations, e.g., remove operations, to be performed before others, e.g.,
* add operations.
*
* @param groupId the identifier of the group
* @return the created "message-part-group" element.
*/
public Element addPartGroup(String groupId) {
Element messagePartGroupElement = getDocument().createElement("message-part-group");
messagePartGroupElement.setAttribute("id", groupId);
serverMessageElement.appendChild(messagePartGroupElement);
return messagePartGroupElement;
}
/**
* Retrieves the "message-part-group" element pertaining to a specific group.
*
* @param groupId the id of the group
* @return the "message-part-group" element
*/
public Element getPartGroup(String groupId) {
NodeList groupList = serverMessageElement.getElementsByTagName("message-part-group");
int length = groupList.getLength();
for (int i = 0; i < length; ++i) {
Element groupElement = (Element) groupList.item(i);
if (groupId.equals(groupElement.getAttribute("id"))) {
return groupElement;
}
}
return null;
}
/**
* Adds a "message-part" to the document that will be processed by the
* specified client-side processor object.
*
* @param groupId the id of the group to which the "message-part" element
* should be added
* @param processor the name of the client-side processor object which will
* process the message part, e.g., "EchoEventUpdate", or
* "EchoDomUpdate"
* @return the created "message-part" element
*/
public Element addPart(String groupId, String processor) {
Element messagePartGroupElement = getPartGroup(groupId);
Element messagePartElement = getDocument().createElement("message-part");
messagePartElement.setAttribute("processor", processor);
messagePartGroupElement.appendChild(messagePartElement);
return messagePartElement;
}
/**
* Creates and appends a directive element beneath to a message part.
* Attempts to append the directive to an existing message part if the last
* message part in the specified group happens to have the same processor as
* is specified by the processor
argument. If this is not
* possible, a new "message-part" element is created and the directive is
* added to it.
*
* @param groupId
* @param processor the name of the client-side processor object which will
* process the message part, e.g., "EchoEventUpdate", or
* "EchoDomUpdate"
* @param directiveName the name of the directive, e.g., "event-add" or
* "dom-remove".
* @return the directive element
*/
public Element appendPartDirective(String groupId, String processor, String directiveName) {
Element messagePartElement = null;
Element groupElement = getPartGroup(groupId);
Element lastChild = (Element) groupElement.getLastChild();
if (lastChild != null && processor.equals(lastChild.getAttribute("processor"))) {
messagePartElement = lastChild;
} else {
messagePartElement = addPart(groupId, processor);
}
Element directiveElement = getDocument().createElement(directiveName);
messagePartElement.appendChild(directiveElement);
return directiveElement;
}
/**
* Creates or retrieves a suitable "Itemized Directive" element. Itemized
* Directives may be used to create more bandwidth-efficient ServerMessage
* output in cases where a particular operation may need to be performed on
* a significant number of targets. In the case that the directive must be
* created, it will be added to the message. Repeated invocations of this
* method with equivalent values of all parameters will result in the same
* directive being returned each time.
*
* This method should only be used for adding directives to the
* GROUP_ID_PREREMOVE
and GROUP_ID_POSTUPDATE
* groups as itemized directives will not be executed in-order, which
* will cause problems if they are used for to manipulate the DOM.
*
* @param groupId the identifier of the target message part group,
* either GROUP_ID_PREREMOVE
or
* GROUP_ID_POSTUPDATE
* @param processor the name of the client-side processor object which will
* process the message part containing the directive, e.g.,
* "EchoEventUpdate", or "EchoDomUpdate"
* @param directiveName the name of the directive, e.g., "event-add" or
* "dom-remove"
* @param keyAttributeNames the names of the key attributes
* @param keyAttributeValues the values of the key attributes
* @return the created/retrieved directive element
*/
public Element getItemizedDirective(String groupId, String processor, String directiveName, String[] keyAttributeNames,
String[] keyAttributeValues) {
ItemizedDirectiveLookupKey itemizedDirectiveLookupKey = new ItemizedDirectiveLookupKey(groupId, processor, directiveName,
keyAttributeNames, keyAttributeValues);
Element element = (Element) itemizedDirectivesMap.get(itemizedDirectiveLookupKey);
if (element == null) {
element = appendPartDirective(groupId, processor, directiveName);
for (int i = 0; i < keyAttributeNames.length; ++i) {
element.setAttribute(keyAttributeNames[i], keyAttributeValues[i]);
}
itemizedDirectivesMap.put(itemizedDirectiveLookupKey, element);
}
return element;
}
/**
* Sets the interval between asynchronous requests to the server to check
* for server-pushed updates.
*
* @param newValue the new interval in milliseconds (a negative value will
* disable asynchronous requests)
*/
public void setAsynchronousMonitorInterval(int newValue) {
if (newValue < 0) {
serverMessageElement.setAttribute("async-interval", "disable");
} else {
serverMessageElement.setAttribute("async-interval", Integer.toString(newValue));
}
}
/**
* Sets the element id of the root of the modal context. Only elements
* within the modal context will be enabled. A id
value of
* null will disable the modal context, thus allowing ALL elements to be
* enabled.
*
* @param id the root element id of the modal context
*/
public void setModalContextRootId(String id) {
if (id == null) {
serverMessageElement.setAttribute("modal-id", "");
} else {
serverMessageElement.setAttribute("modal-id", id);
}
}
/**
* Sets the root layout direction of the application, i.e., either
* LEFT_TO_RIGHT
or RIGHT_TO_LEFT
.
*
* @param layoutDirection the new layout direction
*/
public void setRootLayoutDirection(int layoutDirection) {
switch (layoutDirection) {
case LEFT_TO_RIGHT:
serverMessageElement.setAttribute("root-layout-direction", "ltr");
break;
case RIGHT_TO_LEFT:
serverMessageElement.setAttribute("root-layout-direction", "rtl");
break;
default:
throw new IllegalArgumentException("Illegal layout direction.");
}
}
/**
* Sets the numeric identifier for this transaction, which will be returned
* in next client message.
*
* @param transactionId the transaction identifier
*/
public void setTransactionId(long transactionId) {
serverMessageElement.setAttribute("trans-id", Long.toString(transactionId));
}
}