at.spardat.xma.datasource.RessourceBundleProviderServer 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: RessourceBundleProviderServer.java 2089 2007-11-28 13:56:13Z s3460 $
package at.spardat.xma.datasource;
import java.io.IOException;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Locale;
import java.util.ResourceBundle;
import java.util.StringTokenizer;
import java.util.TreeMap;
import at.spardat.enterprise.exc.SysException;
import at.spardat.enterprise.util.DateUtil;
import at.spardat.enterprise.util.Types;
import at.spardat.xma.exception.Codes;
import at.spardat.xma.mdl.Atom;
import at.spardat.xma.session.XMASession;
/**
* Provides tabular data from a RessourceBundle. The specification of these kind of data sources
* must follow the syntax
*
* type=rsc,bundle=at.spardat. ... .MyBundle
*
* i.e., the type must be the reserverd word rsc and a second attribute named
* bundle indicates the name of the ressource bundle where the values are extracted
* from.
*
* The most typical usage are properties-files to implement ressource bundles. The following
* paragraph defines how properties files are mapped to {@link TabularData} objects, which
* are used from the XMA application to programmatically access the tabular data. We start
* with an example. Suppose our table should have the following columns:
*
* column name column type
* ----------------------------------------
* lastName String
* datBirth Date
* ynMarried Boolean
* betIncome Numeric
*
* then a properties file providing the actual data looks like:
*
* COLS = lastName,datBirth,ynMarried,betIncome
* 10 = "Huber",D19720101,BY,N100000.23
* 20 = "Maier",D19661110,BN,N20000.00
* 50 = "Lilly",D20030101,B ,N
*
*
* There must be one line with the key COLS that provides the name of the columns.
* The remaining lines must have a numeric key that indicates the order of the lines (since
* the lines in property files are unordered).
*
* Each value (the part after the equal sign) contains the cells of the table rows, separated
* by comma. String types are delimited with quotes. If the string value should contain a
* quote, praefix it with double backslash (i.e., \\"). Date values start with a D and a date
* followed in the ISO-format, i.e., YYYYMMDD. Booleans start with a B as type
* sign, followed by Y or N. Numeric values start with N, followed
* by a numeric string as defined by {@link java.lang.Double#parseDouble(java.lang.String)}.
*
* Empty Strings must be specified via double quotes, i.e., quotes with no text in between.
* All other empty types must start with the type character without following text, e.g.,
* N with no trailing text denotes an empty numeric.
*
* If the provided table holds domain data, a property file must look like this:
*
* COLS = COD_KEY,SHORT_VALUE,LONG_VALUE,VALID_FROM,VALID_TO
* 10 = "ab", "ab", "abgetreten",D20030101,D20030720
* 20 = "we", "we", "weggetreten",D20030101,D20030719
*
*
* @author YSD, 19.06.2003 23:49:38
*/
public class RessourceBundleProviderServer implements ITableProvider {
/**
* @see at.spardat.xma.datasource.ITableProvider#provideTable(at.spardat.xma.session.XMASession, at.spardat.xma.datasource.TableSpec, long)
*/
public ProviderResultServer provideTable (XMASession session, TableSpec spec, long lastModified) {
String bundleString = spec.getProperty("bundle");
Locale l = spec.getLocale();
if (bundleString == null) {
throw new SysException ("RessourceBundle spec " + spec.toString() + " misses bundle=...")
.setCode (Codes.DS_MISSING_BUNDLE_ATTRIBUTE);
}
ResourceBundle bundle = ResourceBundle.getBundle (bundleString, l);
TabularData table;
TreeMap rankLineMap = new TreeMap(); // keys are Integers, values are Strings
String columns = null;
Enumeration keys = bundle.getKeys();
while (keys.hasMoreElements()) {
String key = (String) keys.nextElement();
String value = bundle.getString(key);
if (value == null) continue;
if (key.equals("COLS")) columns = value;
else rankLineMap.put(new Integer(key), value);
}
if (columns == null) {
throw new SysException ("RessourceBundle " + spec.toString() + ", missing line 'COLS=...' that defines the columns.")
.setCode(Codes.DS_RSC_MISSING_HEADER);
}
/**
* Check if the table is a domain table
*/
StringTokenizer tokizer = new StringTokenizer (columns, ",");
ArrayList colList = new ArrayList();
while (tokizer.hasMoreTokens()) {
colList.add(((String)tokizer.nextToken()).trim());
}
String [] colListAsArray = new String[colList.size()];
colList.toArray(colListAsArray);
if (TabularDomData.isDomainColStructure(colListAsArray)) {
// create TabularDomData
table = new TabularDomData();
} else {
// create TabularData
table = new TabularData();
}
/**
* Add columns to the TabularData
*/
for (int i = 0; i < colListAsArray.length; i++) {
table.addColumn (colListAsArray[i]);
}
/**
* For each entry in rankLineMap, add a TableRow to the table
*/
Iterator iter = rankLineMap.values().iterator();
while (iter.hasNext()) {
String element = (String) iter.next();
addLine (table, element, spec);
}
ProviderResultServer result = new ProviderResultServer();
result.table_ = table;
return result;
}
/**
* adds a line to the table
*/
private void addLine (TabularData table, String line, TableSpec spec) {
try {
StreamTokenizer toki = getTokenizerFor (line);
TabularDataRow row = new TabularDataRow (table);
while (toki.nextToken() != StreamTokenizer.TT_EOF) {
// System.out.println("token: " + toki.ttype);
// System.out.println("string: " + toki.sval);
if (toki.ttype == '"' || toki.ttype == StreamTokenizer.TT_WORD) {
if (toki.ttype == '"') {
if (toki.sval.length() == 0) row.add(Atom.EMPTY_STRING);
else row.add(new Atom(toki.sval));
} else {
// got some type which is not a string
char type = toki.sval.charAt(0);
int sLen = toki.sval.length();
switch (type) {
case 'B':
if (sLen == 1) row.add(Atom.EMPTY_BOOLEAN);
else row.add (new Atom(toki.sval.length() >= 2 && toki.sval.charAt(1) == 'Y'));
break;
case 'D':
if (sLen == 1) row.add(Atom.EMPTY_DATE);
else {
String date = toki.sval.substring(1);
if (date.length() != 8) lineError ("invalid date "+date, line, spec);
if (!DateUtil.isValid(date)) lineError ("invalid date "+date, line, spec);
row.add (new Atom(Types.T_DATE, DateUtil.internal2Gregorian(date).getTime()));
}
break;
case 'N':
if (sLen == 1) row.add(Atom.EMPTY_BCD);
else {
double d = 0.0;
try {
d = Double.parseDouble(toki.sval.substring(1));
} catch (Exception ex) {
lineError ("invalid numeric " + toki.sval, line, spec);
}
row.add(new Atom(d));
}
break;
default:
lineError ("unknown type " + type, line, spec);
}
}
toki.nextToken();
if (toki.ttype == StreamTokenizer.TT_EOF) break;
if (toki.ttype != ',') lineError ("missing comma", line, spec);
} else if (toki.ttype == ',') {
row.add(Atom.EMPTY_STRING);
} else {
lineError ("wrong syntax", line, spec);
}
}
table.addRow(row);
} catch (IOException ex) {
throw new SysException (ex, "unknown line " + line + ", tabular data source: " + spec.toString())
.setCode(Codes.DS_RSC_INVALID_LINE);
}
}
/**
* Throws an SysException indicating a syntax error
*/
private void lineError (String reason, String line, TableSpec spec) {
throw new SysException ("data source: " + spec.toString() + ", line: " + line + ", " + reason)
.setCode(Codes.DS_RSC_INVALID_LINE);
}
private static StreamTokenizer getTokenizerFor (String toParse) {
StreamTokenizer toki = new StreamTokenizer (new StringReader (toParse));
toki.wordChars('a', 'z');
toki.wordChars('A', 'Z');
toki.wordChars('0', '9');
toki.wordChars('.', '.');
toki.wordChars('-', '-');
toki.wordChars('+', '+');
toki.wordChars(128 + 32, 255);
toki.whitespaceChars(0, ' ');
toki.quoteChar('"');
return toki;
}
/**
* Returns 30 days, which de facto means never. However, it requires that some application id
* is included in the cache keys to ignore cached resources if a new version is deployed
* at the server.
*
* @see at.spardat.xma.datasource.ITableProvider#getExpireDurationClientSecs(java.lang.String)
*/
public int getExpireDurationClientSecs(String type) {
return 3600*24*30; // 30 days
}
/**
* Returns 30 days, which de facto means never. As long as the application is running,
* the bundle cannot change.
*
* @see at.spardat.xma.datasource.ITableProvider#getExpireDurationServerSecs(java.lang.String)
*/
public int getExpireDurationServerSecs(String type) {
return 3600*24*30; // 30 days
}
// public static void main(String[] args) throws Exception {
// RessourceBundleProviderServer rp = new RessourceBundleProviderServer();
// TableSpec spec = new TableSpec ("type=rsc,bundle=at.spardat.xma.datasource.test,_loc=de_AT");
// ProviderResultServer result = rp.provideTable(spec, TabularDataSourceServer.UNKNOWN_TIMESTAMP);
// TabularData data = (TabularData) result.table_;
// //data.save(new File("c:/tmp/xx.txt"));
// {
// ITabularDomData domData = (ITabularDomData) data;
// for (int i=0; i