All Downloads are FREE. Search and download functionalities are using the official Maven repository.

at.spardat.xma.component.Component Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright (c) 2003, 2007 s IT Solutions AT Spardat GmbH .
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     s IT Solutions AT Spardat GmbH - initial API and implementation
 *******************************************************************************/

package at.spardat.xma.component;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import at.spardat.xma.mdl.Atom;
import at.spardat.xma.mdl.Synchronization;
import at.spardat.xma.mdl.util.DNode;
import at.spardat.xma.mdl.util.Descriptive;
import at.spardat.xma.mdl.util.TransAtomTable;
import at.spardat.xma.page.Page;
import at.spardat.xma.serializer.XmaInput;
import at.spardat.xma.serializer.XmaOutput;
import at.spardat.xma.session.XMASession;

/**
 * This class implements the comon base for Components on the client
 * side and on the server side. This is mostly the live-cycle-management
 * of the PageModels.
 * 
 * @author s2877
 */
public abstract class Component implements Synchronization, Descriptive {
    
    /** 
     * contains all active PageModels of the Component 
     */
    HashMap                         pageModels = new HashMap();
    
    /**
     * Component properties are stored in instance-variables in generated
     * subclasses. For the purpose of synchronizing properties between
     * client and server, this transactional one-column table is used.
     * In RPCs, properties are copied from the propModel to instance
     * variables and vice versa usind the methods prop2model
     * and model2props.
     */
    private TransAtomTable          propModel = new TransAtomTable(1);

    /** 
     * counter for assigning unique IDs to PageModels 
     */
    short                           lastPageId = 0;    
    
    /**
     * Indicates whether this Component is stateless or not. A Component may 
     * only be stateless if all its pages are stateless. Stateless Components 
     * do not consume space at the server before and after a server side event. 
     * This also means that the programmer cannot store information in the class 
     * derived from ComponentServer that survives server side events.
     * This property is defined at UI design time and may not be changed at 
     * programming time or later.
     */
    private boolean                 isStateless_; 
    
    /**
     * A dynamically (at runtime) assigned id which is unique amongst all living
     * component instances within a Session. The id is created at the client side
     * of XMA, since Components are also created there.
     */
    private short                   id_;
    
    /**
     * The session this component belongs to. Never null.
     */
    protected XMASession            session_;
    
    /**
     * This instance variable applies to stateful components only.
     * Synchronizing component state between client and server is based
     * on exchanging delta information for bandwith efficiency. This
     * requires that client and server side state are correct with respect
     * to each other in order to successfully apply deltas. 

* * There are certain types of communication failures where the client * cannot make any assumptions about the state of the server component, * i.e., the client does not know whether the changes in the last * server side event have been successfully applied or not.

* * The purpose of the serverChangeNumber_ (SCN) is to detect this * kind of failures. The server component increments the counter * once if it has successfully integrated changes he's got from the client * in the course of a server side event. The server component increments * the number a second time if changes made by the programmer on the * models are committed.

* * The number is transmitted back to the client and retransmitted in * the next server side event to make sure that the server components * state is exactly the same the client component thinks it is.

* * If there are communication failures, the client may guess what the * server did: Either *

    *
  • the server did nothing, then SCN would not have changed. *
  • the server integrated the changes, then SCN would have increased by one. *
  • the server successfully did the business logic, then SCN increased by two. *
      * Whatever the client guesses, if he is wrong, the component states are * inconsistent with respect to each other. In this case, the user * should be forced to cancel the component since there is no easy * way out. Period.

      * * A value of -1 means that the number is unknown. This is the case * with stateless components, where the mechanism is not used. Or if * the component is new at the client, where there has been none defined * yet. */ private short serverChangeNumber_ = -1; /** * Constructor * * @param session the XMASession this component belongs to * @param isStateless defines if this component is stateless * @param isAtServer indicates if this component executes at the server side */ public Component (XMASession session, boolean isStateless, boolean isAtServer) { isStateless_ = isStateless; if (session == null) { throw new IllegalArgumentException("session must not be null!"); } session_ = session; } /** * This is a noop-constructor for test-purpose. */ public Component () { } /** * Returns true if this Component is stateless. Stateless Components * do not consume space at the server before and after a server side event. */ public boolean isStateless () { return isStateless_; } /** * Registers the given Page in the Component and assignes a unique ID to it. * A page must be registered at the time its models are created. * After registration, the Page will be syncronized between client and server * every time a serverEvent occures until the page is removed.

      * * The assigned id is also stored in the page (via Page.setId()). * * @param model the Page to register. * @throws IllegalStateExecption if the page is already registered. */ public void registerPageModel (Page model) { if(pageModels.get(new Integer(model.getId()))!=null) { throw new IllegalStateException("PageModel allready allocated"); } model.setId (++lastPageId); pageModels.put(new Integer(lastPageId), model); } /** * Gets the Page with the given ID. * The Page exists, if it has been registered and not removed yet. * * @param idPage the ID of the desired Page. * @return the Page with the given ID or null if there exists no Page * for the given ID. */ public Page getPageModel (short idPage) { return (Page) pageModels.get(new Integer(idPage)); } /** * Indicates if there exists an active Page with the given ID. * The Page is alive, if it has been registered and not removed yet. * * @param idPageModel the ID of the Page * @return true if the Page with the given ID is alive * false otherwise. */ public boolean isPageModelAlive(short idPageModel) { return pageModels.containsKey(new Integer(idPageModel)); } /** * Removes a page model with a given id. The model aspect of this * page effectively is dead afterwards. The data stored in widget * models on this page is lost. * * @param idPageModel the id of the model to remove * @return true if successfully removed, false if there does not * exist a page model with the provided id. */ protected boolean removePageModel (short idPageModel) { return pageModels.remove (new Integer (idPageModel)) != null; } /** * Gets the number of registered PageModels in this Component. * * @return the number of PageModels registered but not removed yet. */ public int getNumPageModels() { return pageModels.size(); } /** * Gets an Iterator to iterate over all living PageModels. * * @return an Iterator to iterate over all living PageModels. */ public Iterator getPageModels() { return pageModels.values().iterator(); } /** * Gets the full classname for the given typeId of * a page in the component. This method will be implemented * by the generated class for each component. * * @param typeId the unique typeId * @return the full classname */ abstract public String getModelClass(short typeId); /** * Writes a set of pages to the output stream. * * @param pages the set of pages * @param xo the output stream * @param forceFull if true, all pages with all widget models are written. If false, * only changed pages and within these pages only changed widget * models are written. */ protected void externalizePageSet (HashSet pages, XmaOutput xo, boolean forceFull) throws IOException { Iterator iter = pages.iterator(); while (iter.hasNext()) { Page p = (Page) iter.next(); // write the page if forceFull mandates it or the page changed. boolean writePage = forceFull || p.changed(); // a stateless page is always transferred from client to server if (!xo.isAtServer() && p.isStateless()) writePage = true; if (writePage) { // write true in front of the page which indicates that a page follows xo.writeBoolean("pageFollows", true); // write id xo.writeShort("id", p.getId()); // write the page; boolean forceFullOnPage = forceFull; // from client to server stateless pages are always fully written, even // if forceFull is false. if (!xo.isAtServer() && p.isStateless()) forceFullOnPage = true; p.externalize (xo, forceFullOnPage); } } // write false at the end to signal the reader to stop right after here xo.writeBoolean("pageFollows", false); } /** * Reads the information produces via externalizePageSet and applies * the changes to the models.

      * * This method requires that all pages whose information will be read out * of the stream are already created.

      * * @param xi the object input stream * @throws IOException on serialization errors * @throws ClassNotFoundException on serialization errors */ protected void internalizePageSet (XmaInput xi) throws IOException, ClassNotFoundException { // read pages as long as we read a true-boolean boolean morePages = xi.readBoolean(); while (morePages) { short pageId = xi.readShort(); // retrieve the page Page p = getPageModel(pageId); // p == null clearly is a programming error if (p == null) throw new RuntimeException ("Page " + pageId + " does not exist"); p.internalize(xi); morePages = xi.readBoolean(); } } /** * Writes the property model to xout. * * @param xo output-stream * @param forceFull ignore deltas */ protected void externalizeProperties (XmaOutput xo, boolean forceFull) throws IOException { // write the pageModel if forceFull mandates it or the propModel changed. boolean write = forceFull || propModel.changed(); xo.writeBoolean ("propWrite", write); if (write) { propModel.externalize (xo, forceFull); } } /** * modifies the propModel so that the value of the cell (i,0) equals * the ith property of this component.

      * * This method must be called from outside the XMA-framework. */ public void props2model () { ComponentProperty [] cps = getPropDes(); int numProps = (cps == null ? 0 : cps.length); for (int i=0; i * * This method must not be called from outside the XMA-framework. */ public void model2props () { ComponentProperty [] cps = getPropDes(); int numProps = (cps == null ? 0 : cps.length); for (int i=0; iindex to value. * Expands the model to contain at least index+1 rows if necessary. * Does not modify the model if the old value equals the provided one. * * @param index index of the property * @param value new value which may be null */ private void setPropModelValue (int index, String value) { // expand model if necessary while (propModel.size() < index+1) propModel.add (String.valueOf(propModel.size()), new Atom[]{null}); // get old value String oldValue = getPropModelValue (index); if (!eqString (value, oldValue)) { if (value == null) propModel.replace(index, 0, null); else propModel.replace(index, 0, new Atom (value)); } } /** * Returns the value of the property at index. */ private String getPropModelValue (int index) { if (index >= propModel.size()) return null; Atom valueAsAtom = propModel.get(index, 0); return (valueAsAtom == null ? null : valueAsAtom.toString()); } // checks if two strings are equal, including the treatment of null Strings. // two null Strings are considered equal. A null String and a non null String // are not equal. private static boolean eqString (String s1, String s2) { if (s1 == null) return s2 == null; else return s1.equals(s2); } /** * Restores property model externalized via externalizeProperties. * * @param xi input stream */ protected void internalizeProperties (XmaInput xi) throws IOException, ClassNotFoundException { boolean read = xi.readBoolean(); if (read) { propModel.internalize(xi); } } /** * @see at.spardat.xma.mdl.util.Descriptive#describe(at.spardat.xma.mdl.util.DNode) */ public void describe (DNode n) { n.app("[").app(id_).app("] "); n.appShortClass(this).app(": "); n.app("stateless", isStateless_); Iterator iter = getPageModels(); while (iter.hasNext()) { Page pm = (Page) iter.next(); DNode son = new DNode(n); pm.describe(son); } } /** * @see java.lang.Object#toString() */ public String toString () { return DNode.toString(this); } /** * Returns the id of this component. */ public short getId () { return id_; } /** * Sets the id of this component. This method must not be called from outside * the XMA-framework. */ public void setId (short s) { id_ = s; } /** * Returns the short name of this component. The short name uniquely identifies * a component type within an XMA application. Furthermore, the shortname equals * the last component in the components fully qualified class name. */ public String getName () { String className = getClass().getName(); int lastPoint = className.lastIndexOf('.'); return lastPoint != -1 ? className.substring(lastPoint+1) : className; } /** * Returns the server change number. */ public short getSCN () { return serverChangeNumber_; } /** * Sets the server change number. */ public void setSCN (short s) { serverChangeNumber_ = s; } /** * Increments the server change number. */ public void incrementSCN () { serverChangeNumber_++; } /** * Calls commit on every page in this component. This method must not be called * from outside the framework, nor overwritten. */ public void commit () { Iterator iter = getPageModels(); while (iter.hasNext()) { Page p = (Page) iter.next(); p.commit(); } // and the property model propModel.commit(); } /** * Calls rollback on every page in this component. This method must not be called * from outside the framework, nor overwritten. */ public void rollback () { Iterator iter = getPageModels(); while (iter.hasNext()) { Page p = (Page) iter.next(); p.rollback(); } // and the property model propModel.rollback(); } /** * @return the descriptors of all properties of this component. * null if no properties are defined for the component in the guidesigner . */ protected ComponentProperty[] getPropDes() { return null; } /** * Gets the descriptor of a given property * Only properties defined in the guidesigner for this component * are found by this method. * @param name the name of the property to find * @return the descriptor of the property with the given name */ protected ComponentProperty getPropDes(String name) { ComponentProperty[] allprops = getPropDes(); if(allprops!=null) { for(int i=0;i





© 2015 - 2024 Weber Informatics LLC | Privacy Policy