at.spardat.xma.component.ComponentServer 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
*******************************************************************************/
// @(#) $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);
}
}