at.spardat.xma.datasource.XMATabularDataSourceServer 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: XMATabularDataSourceServer.java 4042 2009-08-27 09:10:15Z gub $
package at.spardat.xma.datasource;
import java.util.HashMap;
import at.spardat.enterprise.cache.DefaultCache;
import at.spardat.enterprise.cache.ICacheDescriptor;
//import at.spardat.enterprise.dom.CacheFilter;
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.security.XMAContext;
import at.spardat.xma.session.XMASession;
import at.spardat.xma.session.XMASessionServer;
/**
* The purpose of this class if twofold. First, it implements {@link ITabularDataSource} at
* the server side of XMA. Second, it is responsible for querying the origin data source
* at the server side (database, legacy system or something else).
*
* If a XMA application defines additional data source types, it must subclass this
* class and call the method {@link #addTableProvider(String, ITableProvider)} in the
* derived class's constructor to add additional handler objects that must implement {@link ITableProvider}.
* Basically, ITableProvider is responsible for querying tables for
* a particular type and has the methods
* provideTable, getExpireDurationClientSecs and
* getExpireDurationServerSecs. The first method actually gathers the data
* and the other control caching behaviour.
*
* getExpireDurationClientSecs defines how long the returned
* table may stay in the XMA client cache without querying the XMA server again.
* getExpireDurationServerSecs defines how long the table may reside in
* the server cache without calling provideTable again. Values of zero
* indicate that caches effectively are ignored.
*
* Note that the expiration mechanism effectively avoids requests to next higher
* cache for the specified amount of time, even if the ressource has changed
* at the origin server in the meantime. Therefore, in the worst case, a XMA client might see
* a ressource that has been changed getExpireDurationClientSecs + getExpireDurationServerSecs
* seconds ago. A XMA server might see a ressource that has been changed
* getExpireDurationServerSecs seconds ago in the worst case.
*
* If a table is considered to be expired at the client, the client validates the table
* by issuing an HTTP conditional get. The recipient of the HTTP get, the XMA server,
* itself looks up its cache. If the table is also expired in the server cache,
* provideTable is called.
*
* This class registers only an ITableProvider for type 'rsc', i.e. resource bundles.
*
* @author YSD, 16.06.2003 18:29:08
*/
public class XMATabularDataSourceServer implements ITabularDataSource, ITableProvider {
/**
* Constant for a timestamp that is unknown. May be used to specify unknown
* last modified timestamps.
*/
public static final long UNKNOWN_TIMESTAMP = Long.MIN_VALUE;
/**
* Manages a set of caches, one cache per type of data source. The key
* in this map is a String, the type of the data source. Values
* are of type TypeCache.
*/
private HashMap typeCaches_ = new HashMap();
/**
* Holds a set of predefined {@link ITableProvider} objects for predefined
* data source types. Keys are data source types, values are ITableProvider
* objects.
*/
private HashMap providers_ = new HashMap();
/**
* Constructs and installs predefined table providers.
*/
public XMATabularDataSourceServer () {
/**
* Install the predefined providers
*/
addTableProvider ("rsc", new RessourceBundleProviderServer());
}
/**
* Installs a ITableProvider for a given type. If requests for
* tabular data sources are made and the provided type is specified,
* the call is delegated to the given provider.
*/
public void addTableProvider (String type, ITableProvider provider) {
providers_.put(type, provider);
}
/**
* This method is not indented to be overwritten.
*
* @see at.spardat.xma.datasource.ITabularDataSource#getTable(java.lang.String, at.spardat.xma.session.XMASession)
*/
public ITabularData getTable (String spec, XMASession session) {
TableSpec tableSpec = getTableSpec(spec, session);
ProviderResultServer result = getTableFromServerCache(session, tableSpec, ITabularDataSource.UNKNOWN_TIMESTAMP);
return result.table_;
}
/**
* Creates a TableSpec from the String parameter spec.
* If spec is not valid a sysException is thrown.
* @param spec
* @param session
* @return
* @since version_number
* @author s3460
*/
private TableSpec getTableSpec(String spec, XMASession session){
TableSpec tableSpec = new TableSpec (spec);
if (!tableSpec.isValid())
throw new SysException ("invalid table spec " + spec).setCode(Codes.DS_INVALID_TABLE_SPEC);
tableSpec.addContextParams(session);
return tableSpec;
}
/**
* @see at.spardat.xma.datasource.ITabularDataSource#getDomTable(java.lang.String, at.spardat.xma.session.XMASession)
*/
public ITabularDomData getDomTable(String spec, XMASession session) {
ITabularData table = getTable (spec, session);
if (!(table instanceof ITabularDomData)) throw new IllegalStateException (spec + " is not a domain table");
return (ITabularDomData)table;
}
/**
* Removes the resource specified by spec from memory.
* The resource is removed from a cache hold by TabularDataSourceServer
* or by TableManager.
* @param spec
* @author s3460
*/
public void invalidate(String spec){
TableSpec tableSpec = new TableSpec (spec);
if (!tableSpec.isValid()){
throw new SysException ("invalid table spec " + spec).setCode(Codes.DS_INVALID_TABLE_SPEC);
}
String type = tableSpec.getType();
clearCacheHaving(spec,type);
}
/**
* Removes the resource specified by spec from the cache
* which holds the resources of type.
* @param spec
* @param type
* @since version_number
* @author s3460
*/
private void clearCacheHaving(String spec, String type){
TypeCache cache = null;
synchronized (typeCaches_) {
cache = (TypeCache) typeCaches_.get(type);
if (cache != null) {
//cache.cache_.print(); //DEBUG
//Open Source change: now use at.spardat.xma.datasource.CacheFilter instead of at.spardat.enterprise.dom.CacheFilter
cache.cache_.removeAllHaving(new CacheFilter(spec));
//cache.cache_.print(); //DEBUG
}
}
}
/**
* Requests a table by looking up the server cache (if any). If the server cache
* lookup is not successful, {@link #provideTable(XMASession, TableSpec, long)} is called.
*
* @param session the active XMASession
* @param spec a valid table spec (not checked here for validity, caller must
* do that). Spec must include context params as done by {@link TableSpec#addContextParams(XMAContext)}.
* @param lastModified the last modified timestamp, if the caller knows one
* or {@link ITabularDataSource#UNKNOWN_TIMESTAMP} otherwise.
* @return null if lastModified is known and the table did not
* change. Otherwise not null.
*/
ProviderResultServer getTableFromServerCache (XMASession session, TableSpec spec, long lastModified) {
ProviderResultServer result = null;
String type = spec.getType();
/**
* first, determine if entries of this type are cached at all.
*/
int ageServer = getExpireDurationServerSecs(type);
if (ageServer > 0) {
/**
* The cache must be used. First, get the right cache for the provided type
*/
TypeCache cache = null;
synchronized (typeCaches_) {
cache = (TypeCache) typeCaches_.get(type);
if (cache == null) {
cache = new TypeCache (type, ageServer);
typeCaches_.put(type, cache);
}
}
/**
* variable cache holds the right cache, either retrieved or newly created.
* Look up the cache for spec
*/
String specAsString = spec.toString();
result = (ProviderResultServer) cache.cache_.lookup(specAsString);
if (result == null) {
/**
* The hard part: The entry is not in the cache. We must call
* provideTable and insert it into the cache.
*/
result = provideTableSafe (session, spec, UNKNOWN_TIMESTAMP);
cache.cache_.insert(specAsString, result);
}
String contextPath = ((XMASessionServer)session).getContextPath();
if(contextPath.length() == 0) contextPath = "xma";
TimeingEvent.success("app<"+contextPath+">:tabularCache<"+type+">:size", cache.cache_.getCurrentSize());
// TimeingEvent.success("app<"+contextPath+">:tabularCache<"+type+">:access", (int)cache.cache_.getCntAccess());
// TimeingEvent.success("app<"+contextPath+">:tabularCache<"+type+">:hitratio", (int)((cache.cache_.getCntHit()/(double)cache.cache_.getCntAccess())*100));
} else {
// no server cache is used
result = provideTableSafe (session, spec, lastModified);
}
/**
* if the lastModified timestamp was provided and did not change, we end up
* with the result null.
*/
if (result != null && lastModified != UNKNOWN_TIMESTAMP && result.lastModified_ == lastModified) {
result = null;
}
return result;
}
/**
* This method is a wrapper around {@link #provideTable()} which does error checking
* on the result. Additionally, if the returned last modified timestamp is unknown,
* it computes an artificial one.
*/
private ProviderResultServer provideTableSafe (XMASession session, TableSpec spec, long lastModified) {
ProviderResultServer result = provideTable (session, spec, lastModified);
if (lastModified == ITabularDataSource.UNKNOWN_TIMESTAMP && result == null) {
throw new SysException ("provideTable called for datasource " + spec.toString() + " returned null")
.setCode(Codes.DS_PROVIDE_TABLE_RETURNS_NULL);
}
if (result != null && result.table_ == null) {
throw new SysException ("provideTable called for datasource " + spec.toString() + " returned a null table")
.setCode(Codes.DS_PROVIDE_TABLE_RETURNS_NULL_TABLE);
}
/**
* compute last modified timestamp
*/
if (result != null && result.lastModified_ == UNKNOWN_TIMESTAMP)
result.lastModified_ = tableHash2randomTimeStamp(result.table_.hashCode());
return result;
}
/**
* The default implemententation is to consult the installed table providers.
*
* @see at.spardat.xma.datasource.ITableProvider#provideTable(XMASession, at.spardat.xma.datasource.TableSpec, long)
* @exception SysException if no provider for the type can be found.
*/
public ProviderResultServer provideTable (XMASession session, TableSpec spec, long lastModified) {
return getTableProviderSafe (spec.getType()).provideTable(session, spec, lastModified);
}
/**
* The default implemententation is to consult the installed table providers.
*
* @see at.spardat.xma.datasource.ITableProvider#getExpireDurationClientSecs(java.lang.String)
* @exception SysException if no provider for the type can be found.
*/
public int getExpireDurationClientSecs (String type) {
return getTableProviderSafe(type).getExpireDurationClientSecs(type);
}
/**
* The default implemententation is to consult the installed table providers.
*
* @see at.spardat.xma.datasource.ITableProvider#getExpireDurationServerSecs(java.lang.String)
* @exception SysException if no provider for the type can be found.
*/
public int getExpireDurationServerSecs (String type) {
return getTableProviderSafe(type).getExpireDurationServerSecs(type);
}
/**
* Returns the ITableProvider for a given tabular datasource type.
*
* @param type the requested type
* @return ITableProvider never null.
* @exception SysException if there is no provider for type.
*/
protected ITableProvider getTableProviderSafe (String type) {
ITableProvider provider = (ITableProvider) providers_.get(type);
if (provider == null)
throw new SysException ("no table provider for type " + type + " installed.")
.setCode(Codes.DS_NO_TABLE_PROVIDER);
return provider;
}
// long value of the date 1.7.2003 00:00:00.000 GMT
private static final long time_1_7_2003 = 1057017600000L;
/**
* Maps a given hash code to a timestamp ranging from 1.1.1970 up to 1.7.2003. The
* purpose of this method is to generate an artificial lastModified timestamp for
* tabular datas which are not able to provide one.
*
* @param hashCode the input hashCode
* @return a long timestamp (milliseconds since 1.1.1970 UTC).
*/
protected static long tableHash2randomTimeStamp (int hashCode) {
long hash = (((long)hashCode)-(long)Integer.MIN_VALUE) * 1000;
return hash % time_1_7_2003;
}
private int getMaxServerCacheSize(String type) {
XProperties node = XProperties.getNodeOfPackage("at.spardat.xma.datasource."+type);
String size = node.get("ServerMemCacheMaxSize",null);
if(size!=null) {
return Integer.parseInt(size);
} else {
node = XProperties.getNodeOfPackage("at.spardat.xma.datasource");
size = node.get("ServerMemCacheMaxSize",null);
if(size!=null) {
return Integer.parseInt(size);
} else {
return -1;
}
}
}
/**
* Encapsulates the cache and its descriptor
*/
private class TypeCache {
TypeCache (String type, int expirationDurationSecs) {
String name = "TabularData of type " + type;
descriptor_ = new CacheDescriptor (name, expirationDurationSecs, getMaxServerCacheSize(type));
cache_ = new DefaultCache (descriptor_);
}
DefaultCache cache_;
ICacheDescriptor descriptor_;
}
/**
* Descriptor necessary for a cache
*/
private class CacheDescriptor extends ICacheDescriptor {
String name_;
int expirationDurationSecs_;
int maxSize_;
/**
* Construcs a CacheDescriptor that is necessaray for a cache
*
* @param name the name of the cache
* @param expirationDurationSecs the max age of entries in the cache
*/
public CacheDescriptor (String name, int expirationDurationSecs, int maxSize) {
name_ = name;
expirationDurationSecs_ = expirationDurationSecs;
maxSize_=maxSize;
}
/**
* @see at.spardat.enterprise.cache.ICacheDescriptor#getMaxAgeMillis()
*/
public long getMaxAgeMillis() {
return (long)(expirationDurationSecs_)*1000L;
}
/**
* Spread is beeing set to 10 percent.
*
* @see at.spardat.enterprise.cache.ICacheDescriptor#getMaxAgeSpreadPct()
*/
public int getMaxAgeSpreadPct() {
return 10;
}
/**
* @see at.spardat.enterprise.cache.ICacheDescriptor#getMaxSize()
*/
public int getMaxSize() {
return maxSize_;
}
/**
* @see at.spardat.enterprise.cache.ICacheDescriptor#getName()
*/
public String getName() {
return name_;
}
/**
* @see at.spardat.enterprise.cache.ICacheDescriptor#isTransparent()
*/
public boolean isTransparent() {
return false;
}
/**
* @see at.spardat.enterprise.cache.ICacheDescriptor#load(java.lang.Object)
*/
public Object load(Object key) {
throw new IllegalStateException();
}
}
// public static void main(String[] args) throws Exception {
// SimpleDateFormat df = new SimpleDateFormat("dd.MM.yyyy HH:mm:ss.SSS z");
// SimpleDateFormat df2 = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
// java.util.Date d0 = df.parse("1.1.1970 00:00:00.000 GMT");
// java.util.Date d1 = df.parse("1.7.2003 00:00:00.000 GMT");
//
// System.out.println ("d0: " + d0.getTime());
// System.out.println ("d1: " + d1.getTime());
//
// for (int i=0; i<10000; i++) {
// long time = tableHash2randomTimeStamp(TestUtil.randomString(50).hashCode());
// System.out.println (df2.format(new java.util.Date (time)) + " " + time);
// }
// }
}