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

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

There is a newer version: 6.0.2
Show newest version
/*******************************************************************************
 * 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
 *******************************************************************************/

// @(#) $Id: ComponentServer.java 2089 2007-11-28 13:56:13Z s3460 $
package at.spardat.xma.component;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;

import at.spardat.enterprise.exc.AppException;
import at.spardat.enterprise.exc.BaseException;
import at.spardat.enterprise.exc.SysException;
import at.spardat.properties.XProperties;
import at.spardat.xma.exception.Codes;
import at.spardat.xma.monitoring.TimeingEvent;
import at.spardat.xma.page.Page;
import at.spardat.xma.page.PageServer;
import at.spardat.xma.rpc.RemoteCall;
import at.spardat.xma.rpc.RemoteReply;
import at.spardat.xma.serializer.XmaInput;
import at.spardat.xma.serializer.XmaOutput;
import at.spardat.xma.session.XMASessionServer;

/**
 * Representation of a server side component
 *
 * @author YSD, 01.05.2003 21:30:12
 */
public abstract class ComponentServer extends Component {

    /**
     * Constructor. Registers this with the session.
     *
     * @param session     the session this component will belong to.
     * @param isStateless defines whether this component is stateless or not.
     * @param id          the id of the component as transmitted from the client.
     */
    public ComponentServer (XMASessionServer session, boolean isStateless, short id) {
        super (session, isStateless, true);
        setId (id);
        session.registerComponent(this);
    }

    /**
     * This constructor is for test-purpose. It is not intended to be called from
     * other classes except JUnit-Tests.
     */
    public ComponentServer () {
    }

    /**
     * Destroys this component and deregisters it with the session.
     */
    public void dispose () {
        XMASessionServer        session = getSession();
        if (session != null) session.removeComponent(getId());
    }

    /**
     * This method produces the delta of the server model changes cumulated in the course
     * of executing a server side event. This information will be read by {@link ComponentClient#internalize}. 

* * The forceFull parameter is ignored since we always produce deltas. * * @see at.spardat.xma.mdl.Synchronization#externalize(at.spardat.xma.serializer.XmaOutput, boolean) */ public void externalize (XmaOutput xo, boolean forceFull) throws IOException { // externalize properties externalizeProperties(xo, forceFull); // pages HashSet pages = new HashSet(); pages.addAll(pageModels.values()); externalizePageSet (pages, xo, false); } /** * This is the partner operation of {@link ComponentClient#externalize}. The output stream * produces in externalize is processed and applied here to make this server side component look like * the client side component. * * @see at.spardat.xma.mdl.Synchronization#internalize(at.spardat.xma.serializer.XmaInput) */ public void internalize (XmaInput in) throws IOException, ClassNotFoundException { // read properties internalizeProperties(in); boolean full = in.readBoolean(); // all models or just the changed ones? if (full) internalizeFull (in); else internalizeDeltas (in); // correct parent child relationships adjustParentChildRelationships (); } /** * The client gives us all he has. */ private void internalizeFull (XmaInput in) throws IOException, ClassNotFoundException { /** * destroy all server alive pages */ super.pageModels.clear(); /** * store the set of client alive pages in clientPages and construct * all corresponding server pages */ HashMap clientPages = new HashMap(); short pCount = in.readShort(); for (int j=pCount; j>0; j--) { ClientPageInfo pi = new ClientPageInfo(); pi.id_ = in.readShort(); pi.parentId_ = in.readShort(); pi.typeId_ = in.readShort(); clientPages.put(new Short(pi.id_), pi); // construct the page PageServer p = (PageServer)newPageModel (pi.typeId_); registerServerPage(p, pi.id_); p.setParentId(pi.parentId_); } // read the data of the transmitted pages internalizePageSet (in); } /** * The client sends deltas that must be applied to this component */ private void internalizeDeltas (XmaInput in) throws IOException, ClassNotFoundException { // clientPages stores information about client alive pages. The key is the // page id of type Short, the value is a ClientPageInfo HashMap clientPages = new HashMap(); short oldStatelessCount = in.readShort(); // pages which are stateless at the client, but not new for (int j=oldStatelessCount; j>0; j--) { ClientPageInfo pi = new ClientPageInfo(); pi.newAtClient_ = false; pi.id_ = in.readShort(); pi.parentId_ = in.readShort(); pi.typeId_ = in.readShort(); clientPages.put(new Short(pi.id_), pi); } short oldStatefulCount = in.readShort(); // pages which are stateful, but not new for (int j=oldStatefulCount; j>0; j--) { ClientPageInfo pi = new ClientPageInfo(); pi.newAtClient_ = false; pi.id_ = in.readShort(); clientPages.put(new Short(pi.id_), pi); } // some pages at the client are new (its the first server side event they are participating in). short newCount = in.readShort(); for (int j=newCount; j>0; j--) { ClientPageInfo pi = new ClientPageInfo(); pi.newAtClient_ = true; pi.id_ = in.readShort(); pi.parentId_ = in.readShort(); pi.typeId_ = in.readShort(); clientPages.put(new Short(pi.id_), pi); } // update the server side set of alive pages in order to match its set with the client set adjustPageSet (clientPages); // read the data of the transmitted pages internalizePageSet (in); } /** * Creates and destroys server pages so that the set of alive pages after this method * finishes equals the pages specified in clientPages. * * @param clientPages specifies the set of alive client pages. Keys are Shorts (page ids), * values are ClientPageInfo objects. */ private void adjustPageSet (HashMap clientPages) { /** * first step: Remove all server alive pages whose ids are not in the set clientPages. * Obviously they have been removed at the client since the last server side event. */ ArrayList toRemove = new ArrayList(); Iterator serverAlive = getPageModels(); while (serverAlive.hasNext()) { Page p = (Page) serverAlive.next(); if (!clientPages.containsKey(new Short(p.getId()))) toRemove.add(new Short(p.getId())); } for (int i=toRemove.size()-1; i>=0; i--) removePageModel(((Short)toRemove.get(i)).shortValue()); /** * second step: Construct pages which are alive at the client but not constructed at * the server yet. */ Iterator clientAlives = clientPages.values().iterator(); while (clientAlives.hasNext()) { ClientPageInfo cpi = (ClientPageInfo) clientAlives.next(); if (!isPageModelAlive(cpi.id_)) { // construct the page model PageServer p = (PageServer)newPageModel(cpi.typeId_); registerServerPage(p, cpi.id_); p.setParentId(cpi.parentId_); } } } /** * Must be called after pages have been transferred from the client in order to * update the parent child relationships. */ private void adjustParentChildRelationships () { Iterator iter = getPageModels(); while (iter.hasNext()) { PageServer p = (PageServer) iter.next(); PageServer parent = p.getParent(); if (parent != null) parent.setChild(p); } } /** * Returns the Session this belongs to (you can rely on the returned object not to be null). */ public XMASessionServer getSession () { return (XMASessionServer) session_; } /** * Temporarely used to store information about client alive pages as read from the * input stream in internalize. * * @author YSD, 01.05.2003 21:58:45 */ private static class ClientPageInfo { public short id_; public boolean newAtClient_; public short parentId_; // filled if newAtClient_ public short typeId_; // filled if newAtClient_ } /** * This method is called for every server side event on a page in this component * or on the component itself. The default implementation is to call the * method executeRemoteCallImpl, which actually calls the right * method in the page or component. You may overwrite this method in order * to correct things like error handling or other stuff you want to do generically * at the component level.

* * Warning: This method must not be called from outside the framework. It may just * be overwritten. * * @param call the RemoteCall objects as sent from the client. * @param reply the RemoteReply object that will sent back to the client. * @param targetPage if the RemoteCall has been launched in a page, this * page is the server page, that will execute the call. * If the RemoteCall has been launched from a component, * targetPage is null. * @param targetMethod the executing method of the page or component. */ public void executeRemoteCall (RemoteCall call, RemoteReply reply, PageServer targetPage, Method targetMethod) { executeRemoteCallImpl (call, reply, targetPage, targetMethod); } /** * See {@link #executeRemoteCall}. This method must not be called or overwritten * outside the framework. */ protected void executeRemoteCallImpl (RemoteCall call, RemoteReply reply, PageServer targetPage, Method targetMethod) { /** * Measurement for imcmonitor. A server event methods execution is considered to be a failure * if it throws an Exception which is not an AppException. */ String contextPath = getSession().getContextPath(); if (contextPath.length() == 0) contextPath = "xma"; TimeingEvent detailevent = null; XProperties node = XProperties.getNodeOfPackage("at.spardat.xma"); if("true".equalsIgnoreCase(node.get("rpcDetailStatistics","false"))) { if(targetPage != null) detailevent = new TimeingEvent ("app<"+contextPath+">:rpc<"+targetPage.getClass().getName()+"."+targetMethod.getName()+">"); else detailevent = new TimeingEvent ("app<"+contextPath+">:rpc<"+this.getClass().getName()+"."+targetMethod.getName()+">"); } TimeingEvent ev = new TimeingEvent ("app<"+contextPath+">:rpcServerMethod"); try { if (targetPage != null) { targetMethod.invoke(targetPage, new Object[]{call,reply}); } else { targetMethod.invoke(this, new Object[]{call,reply}); } if(detailevent!=null) detailevent.success(); ev.success(); } catch (InvocationTargetException ex) { /** * The most common case is that the method is successfully invoked, but throws an exception * in the business logic. Then we try to unwrap the InvocationTargetException that * does not add useful information. */ Throwable detail = ex.getTargetException(); if(detail != null){ detail = convertToBaseException(detail); } if (detail != null && detail instanceof BaseException) { if (detail instanceof AppException) { if(detailevent!=null) detailevent.success(); ev.success(); } /** * The exception from the method is already a BaseException. No need to wrap here. */ throw (BaseException)detail; } if (detail == null) detail = ex; throw new SysException (detail, "exception in server event method \"" + targetMethod.getName() + "\"").setCode (Codes.SERVER_METHOD_INVOKE); } catch (Exception ex) { throw new SysException (ex, "exception in server event method \"" + targetMethod.getName() + "\"").setCode (Codes.SERVER_METHOD_INVOKE); } finally { if(detailevent!=null) detailevent.failure(); ev.failure(); } } /** * This method is always called when an Exception occurred in a server side RPC implementation. * The default behauvior is that the argument is simply returned. * This method can be overridden to implement project specific Exception type handling. * Typically these are conversions from an Exception (as it was thrown) to a BaseException (with the possible additional setting of a code or a message etc.) * The Exception returned by this method will be handeled by the XMA framework. A BaseException is transfered to the client, others are converted to a SysException and then transfered. * @param detail the Exception to convert * @return a converted Exception, it really only makes sense to convert to a BaseException. * @since version_number * @author s3460 */ public Throwable convertToBaseException(Throwable detail) { return detail; } /** * Removes all stateless pages from this component.

* * This method must not be called from outside the framework. It also must * not be overwritten. */ public void cleanUpAfterServerEvent () { Iterator iter = getPageModels(); while (iter.hasNext()) { PageServer p = (PageServer)iter.next(); if (p.isStateless()) { removePageModel(p.getId()); } else { p.cleanUpAfterServerEvent (); } } } /** * Estimates the number of bytes this object consumes in memory. */ public int estimateMemory () { int numBytes = 0; Iterator iter = getPageModels(); while (iter.hasNext()) { PageServer element = (PageServer) iter.next(); numBytes += element.estimateMemory(); } return numBytes; } /** * Constructs a page for a given type id */ public PageServer newPageModel (short typeId) { String className = getModelClass(typeId); try { Class modelClass = Class.forName(className); Constructor constructor = modelClass.getConstructor(new Class[]{ComponentServer.class}); return (PageServer) constructor.newInstance(new Object[]{this}); } catch (InvocationTargetException ex) { /** * The most common case is that the method is successfully invoked, but throws an exception * in the business logic. Then we try to unwrap the InvocationTargetException that * does not add useful information. */ Throwable detail = ex.getTargetException(); if (detail != null && detail instanceof BaseException) { /** * The exception from the method is already a BaseException. No need to wrap here. */ throw (BaseException)detail; } if (detail == null) detail = ex; throw new SysException (detail, "exception in construction of server side page \"" + className + "\""); } catch (Exception ex) { throw new SysException (ex, "exception in construction of server side page \"" + className + "\""); } } /** * Registers a Page with the Component where the caller provides an id. * Is used at the server, when pages are transmitted from the client * where the server knows the id. * * @param page the Page to register. * @param id the id to assign * @exception IllegalStateException a page with the given id is already registered. */ public void registerServerPage (PageServer page, short id) { if(pageModels.get(new Integer(id))!=null) { throw new IllegalStateException("PageModel allready allocated"); } page.setId (id); pageModels.put(new Integer(id), page); } /** * Removes a page model with a given id. Extends the superclass-remove * by deleting the parent child relationship. */ protected boolean removePageModel (short idPageModel) { PageServer p = (PageServer) getPageModel(idPageModel); if (p != null) { PageServer parent = p.getParent(); if (parent != null) parent.removeChild(p); } return super.removePageModel(idPageModel); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy