package com.oberon.ooql.rmi;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Locale;
import java.util.Vector;
import java.util.List;
import java.sql.SQLException;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.SimpleDateFormat;
import java.util.StringTokenizer;

import com.oberon.client.Application;
import com.oberon.client.ApplicationRequest;
import com.oberon.client.charts.DrawChart;
import com.oberon.ooql.connection.*;
import com.oberon.ooql.parser.OOQLExecute;
import com.oberon.ooql.sdk.*;
import com.oberon.ooql.sdk.Class;
import com.oberon.ooql.sdk.common.Params;
import com.oberon.ooql.sdk.common.Utils;
import com.oberon.ooql.sdk.common.WebServiceClient;
import com.oberon.ooql.sdk.db.DB_Base;
import com.oberon.ooql.sdk.db.DB_Graph;
import com.oberon.util.FileUtils;
import com.oberon.util.StringUtils;
import com.oberon.util.XMLUtils;

import javax.servlet.ServletContext;
import javax.servlet.http.*;
import javax.xml.ws.WebServiceContext;

import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.rmi.ConnectException;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.PaletteData;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.widgets.Display;
import org.jdom.Element;
import org.jdom.filter.ElementFilter;

/**
 * Utility functions for JSP Web Pages, {@link WebService}s and generic RMI usage
 *
 * @author   Mirko Solazzi
 * @version  4.0
 */

@Deprecated
public class JRClient {

	/** Default date format */
	public final static SimpleDateFormat dateFormat = Params.sformat;
	/** The value separator for multivalue {@link Field}s */
	public final static String ATTRSEP = Params.ATTRSEP;
	/** Administrator user name */
	public final static String ADMUSR =Params.ADMUSR;

	private static ImageData imgDataOn;
	private static ImageData imgDataOff;		

	//---------------------------------------------------------------------------------
	//	Utilities
	//---------------------------------------------------------------------------------


	/** Check if DB supports tablespaces */
	public static boolean isTableSpace() {
		return DB_Base.isTableSpace;
	}

	/** Get the Temporary directory */
	public static String getTempDir() {
		return Params.tempDir;	
	}

	/** Get the Mail server name or ID */
	public static String getMailServer() {
		return Params.mailServer;	
	}

	/** Get the Web server url */
	public static String getWebRoot() {
		return Params.webRoot;	
	}

	/** Get the SVN repository url */
	public static String getSVNUrl() {
		return Params.svnURL;	
	}

	/** Set the SVN repository url */
	public static void setSVNUrl(String url) {
		Params.svnURL = url;	
	}

	/** Get the SVN repository account 
	 *  @return  array of String: [0] url / [1] user / [2] password 
	 */
	public static String[] getSVNAccount() {
		return Utils.getSVNAccount();		
	}

	/** Set the Administrator User [ADMIN] password */
	public static void setAdminPWD(String password) {
		Params.ADMPWD = password;	
	}

	//------------------------------------------------------------------------------------

	/**
	 * Check if the input string is blank (null , empty or formed exclusively by spaces)
	 */
	public static boolean isBlank(String string) {
		return StringUtils.isBlank(string);
	}

	/**
	 * Escape quotes inside a string
	 */
	public static String escape(String value) {
		return StringUtils.escape(value);
	}

	/**
	 * Convert integer value (decimal) to hexadecimal value
	 */
	public static String getHexValue( int decValue ) {
		return getHexValue(decValue);
	}

	/**
	 * Convert hexadecimal value to integer value (decimal)
	 */
	public static int getIntValue( String hexValue ) {
		return Utils.getIntValue(hexValue)	;
	}

	/**
	 * Replace all occurrences of a sub-string into a given string with another substitute sub-string
	 *
	 * @param source       the source string
	 * @param replace      the sub-string to replace
	 * @param substitute   the substitute sub-string
	 *
	 * Example:  replaceString("My source string"," s"," S")  return "My Source String"
	 */
	public static String replaceString (String source, String replace, String substitute) {
		return StringUtils.replaceString(source,replace,substitute);
	}

	/**
	 * Get the first occurrence of sub-string included between two bound sub-strings (left and right)
	 *
	 * @param sourceText   the source string
	 * @param leftBound    the left side sub-string
	 * @param rightBound   the right side sub-string
	 *
	 * Example:  getStringPart("user[MyName]","user[","]")  return "MyName"
	 */
	public static String getStringPart( String sourceText, String leftBound, String rightBound ) {
		return StringUtils.getStringPart( sourceText, leftBound, rightBound );
	}

	/**
	 * Check if a text match a given pattern
	 *
	 * @param pattern       the pattern (use wildcards "*" match 0/n chars and "?" match a single char)
	 * @param text    		the text to verify
	 *
	 * Example:
	 *   matchPattern("*Nam?","MyName")   return true
	 *   matchPattern("*Nam?","MyNames")  return false
	 */
	static boolean matchPattern(String pattern, String text) {
		return StringUtils.matchPattern(pattern,text);
	}

	/**
	 * Check if a text match one of given patterns
	 *
	 * @param patterns    Vector of the pattern (use wildcards "*" match 0/n chars and "?" match a single char)
	 * @param text    		the text to verify
	 *
	 */
	public static boolean checkPattern(Vector patterns, String text) {
		return StringUtils.checkPattern(patterns, text);
	}

	/**
	 * Return vector elements as a single string separated with a fixed separator chars
	 *
	 * @param vector   the input vector
	 * @param delim   the element separator
	 *
	 */
	public static String vectorToString(Vector vector, String delim) {
		return StringUtils.vectorToString(vector, delim);
	}

	/**
	 * Return array elements as a single string separated with a fixed separator chars
	 *
	 * @param array   the input array
	 * @param delim   the element separator
	 *
	 */
	public static String arrayToString(String[] array, String delim) {
		return StringUtils.arrayToString(array, delim);
	}

	/**
	 * Split a string into a token vector
	 *
	 * @param stringToTokenize   the input string
	 * @param delim   		     the token separator
	 *
	 */
	public static Vector StringTokensToVector( String stringToTokenize , String delim )  {
		return StringUtils.StringTokensToVector( stringToTokenize , delim );
	}

	/**
	 * Collect hashtable keys into a vector
	 *
	 */
	public static Vector hashTableKeysToVector(Hashtable hTable) {
		return Utils.hashTableKeysToVector(hTable);
	}

	/**
	 * Sort a vector
	 *
	 * @param vector  	 the vector to sort
	 * @param increase   if true order from less values to high values
	 * @param asString   if true treat the vector elements as strings
	 *
	 */
	public static void orderVector( Vector vector, boolean increase , boolean asString ) {
		StringUtils.orderStringVector( vector , increase );
	}


	/**
	 * Sort a vector of strings
	 *
	 * @param vector  	 the vector to sort
	 * @param increase   if true order from less values to high values
	 *
	 */
	public static void orderStringVector( Vector vector, boolean increase ) {
		StringUtils.orderStringVector( vector , increase );
	}

	/**
	 * Return a XML dom structure as XML text
	 *
	 * @param element   the xml element root
	 * @param pretty    enable/disable the xml indented style
	 * @param encoding  set the encoding charset ( UTF-8 , ISO-8859-1 , ...)
	 *
	 */
	public static String elementToString( Element element ,boolean pretty,String encoding ) throws OberonException {
		return XMLUtils.elementToString(element,pretty,encoding);
	}

	/**
	 * Write a XML dom structure as XML text to a stream 
	 *
	 * @param element   the xml element root
	 * @param writer    the output stream writer
	 * @param pretty    enable/disable the xml indented style
	 * @param encoding  set the encoding charset ( UTF-8 , ISO-8859-1 , ...)
	 *
	 */
	public static void elementToStream(Element element, OutputStreamWriter writer,boolean pretty,String encoding ) throws IOException,OberonException {
		XMLUtils.elementToStream(element, writer, pretty, encoding );
	}

	/**
	 * Return the administration java element from XML element 
	 * @param element   the xml element root
	 */
	public static AdminBase elementToAdminObject(Element element) {
		return Utils.elementToAdminObject(element);
	}

	/**
	 * Parse XML text and return the relative XML dom structure
	 *
	 * @param xmlText   the xml text
	 *
	 */
	public static Element parseXML(String xmlText) throws Exception {
		return XMLUtils.parseXML(xmlText);
	}

	/**
	 * Returns current date/time (GMT) in {@link #dateFormat} format
	 */
	public static String getCurrentDate() {
		return Utils.getCurrentDate();
	}

	/**
	 * Returns the related java class for an administration type 
	 */
	public static java.lang.Class getClassFromType(String adminType) {
		return Utils.getClassFromType(adminType);
	}

	/**
	 * Generates hashed password ("MD5" algorithm)
	 * 
	 * @param password  the password to hash
	 * @param length    length of the result string
	 */
	public static String hashPassword(String password,int length) {
		return StringUtils.hashPassword(password, length);
	}

	/**
	 * Perform login and generate a session ID for {@link WebService} usage
	 * 
	 * @param userName     the account {@link User} name 
	 * @param password     the account password
	 *
	 * @throws Exception  if userName or password is wrong
	 */
	public static String generateSessionId(String userName,String password) throws Exception {
		return StringUtils.generateSessionId(userName,password);
	}

	/**
	 * Compute the userName and password from session ID (for {@link WebService} usage)
	 * 
	 * @param sessionId     the session ID 
	 *
	 * @throws Exception  if session ID is wrong
	 * 
	 * @return a String array with 3 elements ( session ID creation timestamp , userName and password ) 
	 */
	public static String[] parseSessionId(String sessionId) throws Exception {
		return StringUtils.parseSessionId(sessionId);
	}   	

	/**
	 * Get the CXF WebService Http Session
	 * 
	 * @since 1.3
	 * 
	 * @param context     the CXF WebService context
	 */
	public static HttpSession getCXFHttpSession(WebServiceContext context) throws OberonException {
		try { 
			return WebServiceClient.getCXFHttpSession(context);
		} catch (Exception e) {	
			throw OberonException.generateOberonException(e,"getCXFHttpSession("+context+")"); 
		} catch (Throwable tr) {
			throw new OberonException("CXFNotInstalled",(String[])null);
		}
	}   

	//---------------------------------------------------------------------------------
	//	Files
	//---------------------------------------------------------------------------------
	/**
	 * Read data from a local file system file
	 * 
	 * @param filepath    the file absolute path 
	 */
	public static byte[] getBytesFromFile(String filepath) throws IOException,OberonException {
		return FileUtils.getBytesFromFile(filepath);
	}

	/**
	 * Save data to a local file system file
	 * 
	 * @param path      the file path
	 * @param filename  the file name 
	 * @param filedata  the file content data  
	 */
	public static File saveBytesToFile( String path , String filename , byte[] filedata ) throws IOException,OberonException {
		return FileUtils.saveToFile(path, filename, filedata);
	}

	/**
	 * Create a directory tree for a given path on the local file system
	 * 
	 * @param path    the directory path 
	 */
	public static void createLocalDir( String path ) throws OberonException {
		FileUtils.createLocalDir(path);
	}

	/**
	 * Copy or rename a file on the local file system
	 * 
	 * @param fromFile    the original file
	 * @param toFile      the destination file
	 * @param move        if true move (rename) the file
	 */   
	public static void copyFile( File fromFile,File toFile, boolean move ) throws IOException,OberonException {
		FileUtils.copyFile(fromFile, toFile.getParent(), toFile.getName(), move);
	}

	/**
	 * Return the list of file inside a directory or recursively on its sub-directories
	 * 
	 * @param path           the directory path
	 * @param subdirs        if true recurse on sub-directories (return the full paths)
	 */  
	public static Vector listFiles(String path, boolean subdirs) {
		return FileUtils.listFiles(path,subdirs);
	}

	/**
	 * Remove all empty directories recursively starting from the given path
	 * 
	 * @param path           the directory start path 
	 */ 
	public static void cleanDirs(String path) {
		FileUtils.cleanDirs(path);
	}




	/**
	 * Perform {@link User} login.
	 * 
	 * @param sessionId   the session ID 
	 * @param session     http session where store the User framework (can be null)
	 * 
	 * @return  the user Framework
	 * 
	 * @throws Exception   when session ID is wrong
	 * @see #generateSessionId(String, String)
	 */
	public static Framework doLogin(String sessionId,HttpSession session) throws Exception {
		String[] sAccount= parseSessionId(sessionId);
		return doLogin(sAccount[1],sAccount[2],session);
	}

	/**
	 * Perform {@link User} login.
	 * 
	 * @param userName    the User name 
	 * @param password    the password
	 * @param session     http session where store the User framework (can be null)
	 * 
	 * @return  the user Framework
	 * 
	 * @throws Exception   when userName and/or password are wrong
	 */
	public static Framework doLogin(String userName,String password,HttpSession session) throws Exception {

		ConnectionManager.openConnections(null);
		String sCommand="framework open '"+userName+"' password '"+StringUtils.escape(password)+"';";
		Framework framework=new Framework(userName,password);
		framework.setVerbose(false);
		framework.setSQLTrace(false);
		// link the framework
		OBConnection conn = ConnectionManager.getConnection(framework);	   	  
		try {
			if (ConnectionManager.isRMIConnection()) {
				RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
				servlet.performOOQL( sCommand , framework );
				framework=servlet.getFramework();
			} else {
				OOQLExecute execOOQL = new OOQLExecute();
				framework=execOOQL.executeOOQL(sCommand,framework);
			}
			if (session != null) { Application.setFramework(session, framework); }
		} catch (ConnectException ex) { 
			conn.setValid(false);
			throw new OberonException(ex.getMessage());
		} finally {
			ConnectionManager.releaseConnection(conn,framework);
		}
		return framework;
	}

	/**
	 * Generate the menu HTML code to include into JSP pages
	 * 
	 * @param menuName   the name of {@link Menu}
	 * @param session    http session
	 */
	public static void createMenu(String menuName, HttpSession session) {
		session.removeAttribute("Menu");
		session.setAttribute("Menu",createMenu(menuName, session, 0));
	}

	// recurse the createMenu for sub-Menus
	private static String createMenu(String menuName, HttpSession session,int level)  {
		StringBuffer sResult= new StringBuffer();
		String sTab="";
		String sRootPath=(String)session.getAttribute("RootPath");
		if (sRootPath==null) { sRootPath=""; }
		String sDictSection=(String)session.getAttribute("MenuSection");
		if (sDictSection==null) { sDictSection="Menu"; }
		for (int i=0;i<=level;i++) {   sTab+="\t";  }
		try {
			String sCommand="menu show '"+menuName+"' get { label ownchild.{ name label image href access } }  token xml ;";		   
			Element eMenu = parseXML(performOOQL(sCommand,session));				   
			List lst = eMenu.getChildren();
			Element eChilds = (Element)lst.get(1);
			Iterator itr = eChilds.getChildren().iterator();
			while (itr.hasNext()) {
				Element eChild = (Element)itr.next();			   
				if (eChild.getName().equals("command") && eChild.getChild("access").getText().equals("true")) {
					String sLabel = eChild.getChild("label").getText().trim();
					if (sLabel.length()>0) {
						sResult.append(sTab).append("<li");					
						if (level==0) { sResult.append(" class='topl'"); }					
						sResult.append("><a href='").append(sRootPath).append('/').append(eChild.getChild("href").getText()).append('\'');
						if (level==0) { sResult.append(" class='topl_link'"); }
						sResult.append("><span>");
						if (eChild.getChild("image").getText().equals("true")) {
							sResult.append("<img src=\"").append(sRootPath);
							sResult.append("/servlet/JRImageServlet?type=Command&name=").append(eChild.getAttributeValue("name")).append("\">");
						}   
						sResult.append(getDictKey(sDictSection,sLabel,session)).append("</span></a></li>\n");
					} else {
						sResult.append(sTab).append("<li");					
						if (level==0) { sResult.append(" class='spc'"); } else { sResult.append(" class='spc_sub'");  }					
						sResult.append("><span></span></li>\n");
					}
				} else if (eChild.getName().equals("menu")) {
					String sMenu = eChild.getAttributeValue("name");
					String sMenuResult=createMenu(sMenu,session,level+1);
					if (sMenuResult.length()>0) {
						sResult.append(sTab).append("<li");
						if (level==0) { sResult.append(" class='topl'"); }
						sResult.append("><a href='#nogo' ");
						if (level==0) { sResult.append(" id='").append(sMenu).append("' class='topl_link'><span class='down'>"); }
						else { sResult.append(" class='fly'><span>"); }
						if (eChild.getChild("image").getText().equals("true")) {
							sResult.append("<img src=\"").append(sRootPath);
							sResult.append("/servlet/JRImageServlet?type=Menu&name=").append(sMenu).append("\">");
						} 
						sResult.append(getDictKey(sDictSection,eChild.getChild("label").getText().trim(),session)).append("</span></a>\n");
						if (level==0) { sResult.append(sTab).append(" <ul class='subl'>"); 
						} else { sResult.append(" <ul>"); }
						sResult.append('\n').append(sMenuResult).append('\n').append(sTab).append("</ul>\n").append(sTab).append("</li>\n");
					}
				}
			}
		} catch (Exception e) { 
			if (e instanceof OberonException) {
				System.out.println("JRClient.createMenu("+menuName+"): "+((OberonException)e).getLocalizedMessage(session));
			} else {
				System.out.println("JRClient.createMenu("+menuName+"): "+e.getMessage());
			}
		}
		return sResult.toString();
	}

	//---------------------------------------------------------------------------------
	//	OOQL Interface
	//---------------------------------------------------------------------------------

	/**
	 * Execute OOQL commands
	 *
	 * @param commands    multiple OOQL commands (each command must ends with ";")
	 * @param session     http session to retrieve and store the User framework
	 *
	 * @return a vector where each element represents the result of a single command
	 */
	public static Vector performOOQLCommands(String commands,HttpSession session ) throws Exception {
		Vector vRes=null;
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Framework framework = Application.getFramework(session);	
		if (framework!=null) {
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {	   
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
					vRes=servlet.performOOQL( commands , framework );
					Application.setFramework(session, servlet.getFramework());
				} else {
					OOQLExecute execOOQL = new OOQLExecute();
					framework=execOOQL.executeOOQL(commands,framework);
					vRes=execOOQL.getResults();
					Application.setFramework(session, framework);
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		return vRes;
	}

	/**
	 * Execute a single OOQL command
	 *
	 * @param command     he OOQL command (must ends with ";")
	 * @param session     http session to retrieve and store the User framework
	 */
	public static String performOOQL(String command,HttpSession session ) throws Exception {
		return (String)performOOQLCommands(command,session).elementAt(0);
	}

	/**
	 * Open an OBEROn transaction
	 *
	 * @param session   http session to retrieve and store the User framework
	 */
	public static void openTrans(HttpSession session) throws Exception {
		performOOQLCommands("framework trans open;",session);		
	}

	/**
	 * Commit a opened OBEROn transaction
	 *
	 * @param session   http session to retrieve and store the User framework
	 */
	public static void commitTrans(HttpSession session) throws Exception {
		performOOQLCommands("framework trans commit;",session);			
	}

	/**
	 *  Abort a opened OBEROn transaction
	 *
	 * @param session   http session to retrieve and store the User framework
	 */
	public static void abortTrans(HttpSession session) throws Exception {
		performOOQLCommands("framework trans abort;",session);			
	}


	/**
	 * Open an administrative object and read properties from the database or retrieve them from memory
	 *
	 * @param adminType   the administrative object type (ex.: class , field , lifecycle ... )
	 * @param name        the administrative object name
	 * @param session     http session to retrieve the User framework
	 * 
	 * @return a instance of the administrative object type (ex.: {@link Class} , {@link Field} , {@link Lifecycle} ...)
	 * 
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static AdminBase loadAdminObject(String adminType,String name,HttpSession session) throws Exception {
		adminType=adminType.toLowerCase();
		Element eObject = null;
		ServletContext context = session.getServletContext();
		if ( context!= null ) { 
			eObject = (Element)context.getAttribute(adminType+"|"+name);
		} else {
			eObject = (Element)session.getAttribute(adminType+"|"+name); 
		}
		if (eObject==null) {
			String sCommand = adminType+" show '"+name+"' token xml;";
			eObject = parseXML(performOOQL(sCommand,session));
			if ( context!= null ) { 
				context.setAttribute(adminType+"|"+name,eObject);
			} else {
				session.setAttribute(adminType+"|"+name,eObject);
			}
		}
		return Utils.elementToAdminObject(eObject);
	}    

	/**
	 * Reset the client cache removing all administrative objects
	 *
	 * @param session     http session   
	 */
	public static void resetCache(HttpSession session) throws Exception {
		String[] AdminType={ "objectspace","filespace","localarea","field",
				"class","linktype","user","team","assignment",
				"filetype","program","webservice","menu",
				"command","lifecycle","workflow","form","autonumber","graph",
				"metricsystem","unitmeasure","objectgroup","query","view" };
		ServletContext context =  session.getServletContext();	
		if ( context!= null ) { 
			Enumeration en  = context.getAttributeNames();
			while (en.hasMoreElements()) {
				String sAttribute = (String) en.nextElement();
				for (int j=0;j<AdminType.length;j++) {
					if (sAttribute.startsWith(AdminType[j]+"|")) {
						context.removeAttribute(sAttribute);
					}
				}
			}
		} else {
			Enumeration en  = session.getAttributeNames();
			while (en.hasMoreElements()) {
				String sAttribute = (String) en.nextElement();
				for (int j=0;j<AdminType.length;j++) {
					if (sAttribute.startsWith(AdminType[j]+"|")) {
						session.removeAttribute(sAttribute);
					}
				}
			} 
		}
	}  

	/**
	 * Get the administrative object features
	 *
	 * @param adminType   the administrative object type (ex.: class , field , lifecycle ... )
	 * @param adminName   the administrative object name
	 * @param featureName the {@link Feature} name ("" to get all features)
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static Vector getFeatures(String adminType,String adminName, String featureName, HttpSession session) throws Exception {
		String sCommand = adminType+" show '"+adminName+"' get feature["+featureName+"].{to.type to.name value} token xml;";
		Element eFeatures = parseXML(performOOQL(sCommand,session));
		Iterator elementList = eFeatures.getDescendants();
		Feature feature=null;
		Vector vFeatures = new Vector();
		while (elementList.hasNext()) {
			try{
				Element nextElement = (Element) elementList.next();
				String sAttrName = nextElement.getName();
				if (sAttrName.equals("feature") ) {
					feature = new Feature();
					feature.setName(nextElement.getAttributeValue("name"));	
					String sType = nextElement.getChildText("to.type").trim();
					if (sType.length()>0) {
						feature.setBetween(adminType, adminName, nextElement.getChildText("to.type"), nextElement.getChildText("to.name"));
					} else {
						feature.setFor(adminType, adminName);
					}
					feature.setValue(nextElement.getChildText("value"));
					vFeatures.add(feature);
				} 
			} catch (Exception e){}
		}
		return vFeatures;	  
	}

	/**
	 * Open a ObjectObj OBEROn object and read properties from the database
	 *
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param getCommand  list of OOQL property identifiers/names (example:  stage,holder,description ...)
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static ObjectObj loadObject(String objectID,String getCommand,HttpSession session) throws Exception {
		ObjectObj object = new ObjectObj();
		String sCommand = "object show "+objectID+" get { counter "+replaceString(getCommand,","," ")+" } token xml;";
		Element eObject = parseXML(performOOQL(sCommand,session));
		object.fromXML(eObject);
		object.setLinked(true);
		return object;
	}

	/**
	 * Open a Link and read properties from the database
	 *
	 * @param linkID      the {@link Link}'s ID
	 * @param getCommand  list of OOQL property identifiers/names (example:  to.id,from.class ...)
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static Link loadLink(String linkID,String getCommand,HttpSession session) throws Exception {
		Link link = new Link();
		String sCommand = "link show "+linkID+" get { from.object to.object counter "+replaceString(getCommand,","," ")+" } token xml;";
		Element eObject = parseXML(performOOQL(sCommand,session));
		link.fromXML(eObject);
		return link;
	}

	/**
	 * Get ObjectObj properties from the database in XML format
	 *
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param getCommand  list of OOQL property identifiers/names (example:  stage,holder,defaultfiletype.file ...)
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static Element getObjectData(String objectID,String getCommand,HttpSession session) throws Exception {
		String sCommand = "object show "+objectID+" get { "+replaceString(getCommand,","," ")+" } token xml;";
		Element eObject = parseXML(performOOQL(sCommand,session));
		return eObject;
	}

	/**
	 * Get Link properties from the database in XML format
	 *
	 * @param linkID      the {@link Link}'s ID
	 * @param getCommand  list of OOQL property identifiers/names (example:  to.id,from.class ...)
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static Element getLinkData(String linkID,String getCommand,HttpSession session) throws Exception {
		String sCommand = "link show "+linkID+" get { "+replaceString(getCommand,","," ")+" } token xml;";
		Element eLink = parseXML(performOOQL(sCommand,session));
		return eLink;
	}  

	/**
	 * Navigate a ObjectObj OBEROn object 
	 *
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param getCommand  list of OOQL property identifiers/names (example:  stage,holder,defaultfiletype.file ...)
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static Vector navigateObject(String objectID,String linkType,String versus,String getCommand,String getLinkCommand,HttpSession session) throws Exception {
		StringBuffer sCommand= new StringBuffer("object navigate ");
		sCommand.append(objectID).append(' ').append(versus).append(" linktype '").append(linkType).append("' ");
		Vector getFields = new Vector();
		getFields.add("level");
		getFields.add("linktype");
		getFields.add("versus");
		getFields.add("class");
		getFields.add("name");
		getFields.add("revision"); 	 
		if (getCommand.length()>0)     { 
			sCommand.append("get {").append(replaceString(getCommand,","," ")).append("} ");
			Vector vGet = StringTokensToVector(getCommand, ",");
			int iSize = vGet.size();
			for (int i=0;i<iSize;i++) {		
				getFields.add("object."+vGet.elementAt(i));
			}
		}
		if (getLinkCommand.length()>0) {
			sCommand.append("getlink {").append(replaceString(getLinkCommand,","," ")).append("} "); 
			Vector vGet = StringTokensToVector(getLinkCommand, ",");
			int iSize = vGet.size();
			for (int i=0;i<iSize;i++) {		
				getFields.add("link."+vGet.elementAt(i));
			}
		}
		sCommand.append("token '<|>';");
		Vector vLinks = StringTokensToVector(performOOQL(sCommand.toString(),session), "\n");
		orderVector(vLinks, true, true);
		int iSize = vLinks.size();
		int iGSize = getFields.size();
		for (int i=0;i<iSize;i++) {		
			Hashtable hLink = new Hashtable();
			Vector vData = StringTokensToVector((String) vLinks.elementAt(i),"<|>");
			for (int j=0;j<iGSize;j++) {
				hLink.put(getFields.elementAt(j), vData.elementAt(j));
			}
			vLinks.setElementAt(hLink, i);
		}
		return vLinks;
	}


	/**
	 * Get ObjectObj/Link properties related to View-Columns
	 *
	 * @param ids         array of couples of {@link ObjectObj}[0] and {@link Link}[1] IDs
	 * @param view        the {@link View}
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 * 
	 * @return a Vector of String array containing data values for each object related to View-Columns
	 */
	public static Vector getObjectViewData(String[][] ids,View view,HttpSession session) {
		Vector vResult=new Vector();

		// Split object and link fields into two arrays
		Vector vColumns=view.getColumns();
		int iValidCol=0;
		int iObjCol=0;
		int iLinkCol=0;
		int iSize = vColumns.size();
		for (int i=0;i<iSize;i++) {
			Column col = (Column)vColumns.elementAt(i);
			if (col.getGetObject().length()>0) { iValidCol++; iObjCol++; } else
				if (col.getGetLink().length()>0) { iValidCol++; iLinkCol++; }
		}	    
		int[] vIndexes =new int[iValidCol];
		String[] vObjFields=new String[iObjCol];
		String[] vLinkFields=new String[iLinkCol];
		iValidCol=0;
		iObjCol=0;
		iLinkCol=0;
		int iNdxCol = 0;
		iSize = vColumns.size();
		for (int i=0;i<iSize;i++) {
			Column col = (Column)vColumns.elementAt(i);
			if (col.getGetObject().length()>0) {
				vIndexes[iValidCol]=iNdxCol;
				iValidCol++;
				vObjFields[iObjCol]=col.getGetObject();
				iObjCol++;
				iNdxCol++;
			} else if (col.getGetLink().length()>0) { 
				iNdxCol++; 
			}
		}
		iNdxCol = 0;
		iSize = vColumns.size();
		for (int i=0;i<iSize;i++) {
			Column col = (Column)vColumns.elementAt(i);
			if (col.getGetLink().length()>0) {
				vIndexes[iValidCol]=iNdxCol;
				iValidCol++;
				vLinkFields[iLinkCol]=col.getGetLink();
				iLinkCol++;
				iNdxCol++;
			} else if (col.getGetObject().length()>0) { iNdxCol++; }
		}   

		// Get the values from DB
		if (vObjFields.length>0 || vLinkFields.length>0) {
			Hashtable hData=new Hashtable();
			try {
				if (vObjFields.length>0) {
					StringBuffer sObjCommand = new StringBuffer("object show <ID> get { ");
					for (int i=0;i<vObjFields.length;i++) {
						sObjCommand.append(vObjFields[i]).append(' ');
					}
					sObjCommand.append(" } token {'").append(Application.sToken+"' ';' ','} ;\n");

					StringBuffer sIDs=new StringBuffer();
					Vector vIDs=new Vector();
					for (int i=0;i<ids.length;i++) {					
						if (ids[i][0].length()>0 && sIDs.indexOf(ids[i][0])<0) {
							if (sIDs.length()>0) {sIDs.append(','); }
							sIDs.append(ids[i][0]);
							vIDs.add(ids[i][0]);
						}
					}
					if (sIDs.length()>0) {
						String sObjectCommand=StringUtils.replaceString(sObjCommand.toString(), "<ID>",sIDs.toString());
						Vector vRes = performOOQLCommands( sObjectCommand ,session );
						if (sIDs.indexOf(",")>0) {
							vRes=StringUtils.StringTokensToVector((String)vRes.elementAt(0),"\n");
						} else {
							vRes.setElementAt(StringUtils.escapeNL((String)vRes.elementAt(0)),0);
						}
						iSize = vRes.size();
						for (int i=0;i<iSize;i++) {
							hData.put((String)vIDs.elementAt(i), vRes.elementAt(i));
						}
					}
				}

				if (vLinkFields.length>0) {
					StringBuffer sLinkCommand = new StringBuffer("link show <ID> get { ");
					for (int i=0;i<vLinkFields.length;i++) {
						sLinkCommand.append(vLinkFields[i]).append(' ');
					}
					sLinkCommand.append(" } token {'").append(Application.sToken).append("' ';' ','} ;\n");

					StringBuffer sIDs=new StringBuffer();
					Vector vIDs=new Vector();
					for (int i=0;i<ids.length;i++) {					
						if (ids[i][1].length()>0 && sIDs.indexOf(ids[i][1])<0) {
							if (sIDs.length()>0) {sIDs.append(','); }
							sIDs.append(ids[i][1]);
							vIDs.add(ids[i][1]);
						}
					}
					if (sIDs.length()>0) {
						String sLnkCommand=StringUtils.replaceString(sLinkCommand.toString(), "<ID>",sIDs.toString());
						Vector vRes = performOOQLCommands( sLnkCommand ,session );
						if (sIDs.indexOf(",")>0) {
							vRes=StringUtils.StringTokensToVector((String)vRes.elementAt(0),"\n");
						} else {
							vRes.setElementAt(StringUtils.escapeNL((String)vRes.elementAt(0)),0);
						}
						iSize = vRes.size();
						for (int i=0;i<iSize;i++) {
							hData.put((String)vIDs.elementAt(i), vRes.elementAt(i));
						}
					}
				}

				// Compose the output vector (re-mix object and link values)
				for (int i=0;i<ids.length;i++) {		
					String sObjData=(String)hData.get(ids[i][0]);
					String sLinkData=(String)hData.get(ids[i][1]);
					String[] data = new String[vObjFields.length+vLinkFields.length];
					if (sObjData!=null) {
						Vector vRes=StringUtils.StringTokensToVector(" "+sObjData+" ",Application.sToken);
						for (int j=0;j<vObjFields.length;j++) {
							data[ vIndexes[j] ] = ((String)vRes.elementAt(j)).trim();
						}
						if (sLinkData!=null) {
							vRes=StringUtils.StringTokensToVector(" "+sLinkData+" ",Application.sToken);
							for (int j=0;j<vLinkFields.length;j++) {
								data[ vIndexes[j+vObjFields.length] ] = ((String)vRes.elementAt(j)).trim();
							}
						} else {
							for (int j=0;j<vLinkFields.length;j++) {
								data[ vIndexes[j+vObjFields.length] ] = "";
							}
						}
						vResult.add(data);
					}	
				}

			} catch (Exception e){ }
		} else {
			for (int i=0;i<ids.length;i++) {
				vResult.add(new String[0]);
			}
		}
		return vResult;
	}

	/**
	 * Extract ObjectValidations from the ObjectObj XML data
	 *
	 * @param objectData    the ObjectObj XML data
	 * 
	 * The ObjectObj XML data must be extracted with "stage.validation.{ status signed signer signcomment }" in the getCommand
	 * 
	 * @see #getObjectData(String, String, HttpSession)
	 * 
	 * @return hashtable of {@link ObjectValidation}s addressed with their internal id
	 */
	public static Hashtable objectValidationFromXML(Element objectData) {
		Hashtable hValidate=new Hashtable();
		String sAttrName="";
		Iterator elementList = objectData.getDescendants();
		while (elementList.hasNext()) {
			try{
				Element nextElement = (Element) elementList.next();
				sAttrName = nextElement.getName();
				if (sAttrName.equals("validation") ) {
					ObjectValidation vValidation = new ObjectValidation();
					vValidation.setName(nextElement.getAttributeValue("name"));
					vValidation.setId(Integer.parseInt(nextElement.getAttributeValue("id")));
					vValidation.setStatus(ObjectValidation.getStatusFromString(nextElement.getChildText("status")));
					vValidation.setSigner(nextElement.getChildText("signer"));
					vValidation.setComment(nextElement.getChildText("signcomment"));
					hValidate.put(""+vValidation.getId(),vValidation);
				}
			} catch (Exception e){}
		}
		return hValidate;
	}

	/**
	 * Get ObjectValidations for a given ObjectObj from the database
	 *
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param session     http session to retrieve the User framework
	 *  
	 * @return a hashtable of {@link ObjectValidation}s addressed with their internal id
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static Hashtable getObjectValidations(String objectID,HttpSession session) throws Exception {  
		Element eObject = getObjectData(objectID," stage.validation.{ status signed signer signcomment } ",session); 
		return objectValidationFromXML(eObject);
	} 


	/**
	 * Extract ObjectFiles from the ObjectObj XML data
	 *
	 * @param objectData    the ObjectObj XML data
	 * 
	 * The ObjectObj XML data must be extracted with "filetype.file.{ filespace synchronized locker size putdate synclocalarea }" in the getCommand
	 * 
	 * @see #getObjectData(String, String, HttpSession)
	 * 
	 * @return vector of {@link ObjectFile}s 
	 */
	private static Vector objectFilesFromXML(Element eFiles) {
		Vector vFiles =new Vector();
		String sAttrName="";
		Iterator elementList = eFiles.getDescendants();
		ObjectFile fFile=null;
		while (elementList.hasNext()) {
			try{
				Element nextElement = (Element) elementList.next();
				sAttrName = nextElement.getName();
				if (sAttrName.equals("file") ) {
					fFile = new ObjectFile();
					fFile.setName(nextElement.getAttributeValue("name"));
					fFile.setFileType(nextElement.getAttributeValue("filetype"));
					if ( nextElement.getChildText("synchronized").equals("true") ) {
						fFile.setFileSpace("["+nextElement.getChildText("filespace")+"]");
					} else {
						fFile.setFileSpace(nextElement.getChildText("filespace"));
					}
					fFile.setFileSize(Integer.parseInt(nextElement.getChildText("size")));
					fFile.setLocker(nextElement.getChildText("locker"));
					fFile.setPutDate(nextElement.getChildText("putdate"));
					fFile.setIndexed(nextElement.getChildText("indexed").equals("true"));
					vFiles.add(fFile);
				} else if (sAttrName.equals("synclocalarea") ) {
					fFile.addLocalArea(nextElement.getText(),0,"");
				}
			} catch (Exception e){}
		}
		return vFiles;
	}

	/**
	 * Get ObjectFiles for a given ObjectObj from the database
	 *
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param session     http session to retrieve the User framework
	 *  
	 * @return a Vector of {@link ObjectFile}s
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static Vector getObjectFiles(String objectID,HttpSession session) throws Exception {  
		Element eObject = getObjectData(objectID," filetype.file.{ filespace synchronized locker size putdate indexed synclocalarea } ",session); 
		return objectFilesFromXML(eObject);
	}  


	/**
	 * Returns the list of available filetypes for the object, according to the current lifecycles
	 * 
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param session     http session to retrieve the User framework
	 * 
	 * @return a Vector of {@link FileType} names (String); the default filetype is the first element
	 */
	public static Vector getObjectFileTypes(String objectID,HttpSession session) throws Exception {  
		String sCommand = "object show "+objectID+" get {  lifecycle.{ defaultfiletype filetype.name }  }  token { '|' '|' '|' '|' '|' };";
		Vector vFileTypes=StringTokensToVector(performOOQL(sCommand,session),"|");
		try { 
			String sDefaultFileType=((String)vFileTypes.elementAt(0)).trim();
			int iPos = vFileTypes.indexOf(sDefaultFileType, 1);
			if (iPos>0) {	vFileTypes.remove(iPos); }
		} catch (Exception ex){}
		return vFileTypes;
	}

	/**
	 * Get ObjectObj contextual Commands.
	 * The contextual command list depend on ObjectObj's current Lifecycle-Stage and on User access rights.
	 * 
	 * @param object      the ObjectObj instance
	 * @param session     http session to retrieve the User framework
	 * 
	 * @see #getObjectActions(ObjectObj , HttpSession)
	 * 
	 * @return vector of {@link Command}s 
	 */
	@Deprecated
	public static Vector getObjectMenuCommands(ObjectObj object,HttpSession session) throws Exception {
		return getObjectActions(object,session);
	}

	/**
	 * Get ObjectObj contextual action Commands.
	 * The contextual command list depend on ObjectObj's current Lifecycle-Stage and on User access rights.
	 * 
	 * @param object      the ObjectObj instance
	 * @param session     http session to retrieve the User framework
	 * @since 2.5
	 * 
	 * @return vector of {@link Command}s 
	 */
	public static Vector getObjectActions(ObjectObj object,HttpSession session) throws Exception {
		String sCommand = "lifecycle show '"+object.getLifecycle()+"' get { stage['"+object.getStage()+"'].command.{ href label access executable alt} } token xml;";
		Framework framework = Application.getFramework(session);
		if (framework!=null) { 
			framework.emptyEnv();
			framework.setEnv("id", object.getID());
		}
		Element eCommands = parseXML(performOOQL(sCommand,session));
		Iterator elementList = eCommands.getDescendants();
		Vector vCommands=new Vector();
		while (elementList.hasNext()) {
			try{
				Element nextElement = (Element) elementList.next();
				String sAttrName = nextElement.getName();
				if (sAttrName.equals("command") ) {
					if ( nextElement.getChildText("access").equals("true")) {
						Command command = new Command();
						command.setName(nextElement.getAttributeValue("name"));
						command.setId(Integer.parseInt(nextElement.getAttributeValue("id")));
						command.setLabel(nextElement.getChildText("label"));
						command.setHRef(nextElement.getChildText("href"));
						command.setAlt(nextElement.getChildText("alt"));
						command.setExecutable(nextElement.getChildText("executable").equals("true"));
						vCommands.add(command);
					}
				}
			} catch (Exception e){}
		}
		return vCommands;
	}

	/**
	 * Get ObjectObj contextual action Commands.
	 * The contextual command list depend on ObjectObj's current Lifecycle-Stage and on User access rights.
	 * 
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param session     http session to retrieve the User framework
	 * @since 2.5 
	 * 
	 * @return vector of {@link Command}s 
	 */
	public static Vector getObjectActions(String objectID,HttpSession session) throws Exception {
		String sCommand = "object show "+objectID+" get { actions.{ href label alt executable } } token xml;";
		Framework framework = Application.getFramework(session);
		if (framework!=null) { 
			framework.emptyEnv();
			framework.setEnv("id", objectID);
		}
		Element eCommands = parseXML(performOOQL(sCommand,session));
		Iterator elementList = eCommands.getDescendants();
		Vector vCommands=new Vector();
		while (elementList.hasNext()) {
			try{
				Element nextElement = (Element) elementList.next();
				String sAttrName = nextElement.getName();
				if (sAttrName.equals("command") ) {
					Command command = new Command();
					command.setName(nextElement.getAttributeValue("name"));
					command.setId(Integer.parseInt(nextElement.getAttributeValue("id")));
					command.setLabel(nextElement.getChildText("label"));
					command.setHRef(nextElement.getChildText("href"));
					command.setAlt(nextElement.getChildText("alt"));
					command.setExecutable(nextElement.getChildText("executable").equals("true"));
					vCommands.add(command);
				}
			} catch (Exception e){}
		}
		return vCommands;
	}


	/**
	 * Execute a command action
	 * 
	 * @param commandName     the {@link Command}'s name
	 * @param parameters      additional framework environment parameters (can be null)
	 * @param session         http session to retrieve the User framework
	 * @since 2.5 
	 * 
	 * @return execution result 
	 */
	public static String executeCommand(String commandName,Hashtable parameters,HttpSession session) throws Exception {
		String sCommand = "command execute '"+commandName+"' ;";
		if (parameters!=null) {
			Framework framework = Application.getFramework(session);
			for (Enumeration en=parameters.keys(); en.hasMoreElements();) {
				String nameP = (String)en.nextElement();
				try {
					String valueP = (String)parameters.get(nameP);		      
					framework.setEnv(nameP,valueP);	  
				} catch (Exception e) {}
			}
		}
		return performOOQL(sCommand,session);
	}


	/**
	 * Get the list of LinkTypes allowed for a Class type.
	 *  
	 * @param className    the {@link Class} name
	 * @param session      http session to retrieve the User framework
	 *
	 * @return  vector of {@link LinkType} names (String)
	 */
	public static Vector getClassLinkTypes(String className,HttpSession session) throws Exception {
		String sCommand = "class show '"+className+"' get { linktype } token { '' '|' };";
		return StringTokensToVector(performOOQL(sCommand,session),"|");  
	}

	/**
	 * Get the default Form for a Class type.
	 *  
	 * @param className    the {@link Class} name
	 * @param session      http session to retrieve the User framework
	 *
	 * @return  the {@link Form} name
	 */
	public static String getDefaultForm(String className,boolean top,HttpSession session) throws Exception {
		if (StringUtils.isBlank(className)) { return""; }	 
		try {
			String sType = "form"; 
			if (top) { sType = "baseform";  }
			return performOOQL("class show '"+className+"' get "+sType+";",session); 
		} catch (Exception e) { 
			throw new OberonException("InvalidForm",new String[]{"Class",className,e.getMessage()});
		}
	}

	/**
	 * Get the default Form for a LinkType.
	 *  
	 * @param linkType     the {@link Link} type
	 * @param session      http session to retrieve the User framework
	 *
	 * @return  the {@link Form} name
	 */
	public static String getLinkDefaultForm(String linkType,HttpSession session) throws Exception {
		if (StringUtils.isBlank(linkType)) { return""; }	 
		try {
			return performOOQL("linktype show '"+linkType+"' get form;",session); 
		} catch (Exception e) { 
			throw new OberonException("InvalidForm",new String[]{"Linktype",linkType,e.getMessage()});
		}
	} 

	/**
	 * Get the User views.
	 *  
	 * @param session    http session to retrieve the User framework
	 *
	 * @return  vector of the {@link View} names or null
	 * 
	 * @see Form#setFilterParameters(String)
	 */
	public static Vector getUserViews(HttpSession session) throws Exception {	 
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			String sCommand = "view list * user '"+framework.getUserName()+"';"; 
			return StringTokensToVector(performOOQL(sCommand,session),"\n");
		} else {
			return new Vector();	 
		}
	}

	/**
	 * Get visible Form-Items list for a object instance or class.
	 *  
	 * @param objectIDClass     the {@link ObjectObj}'s ID or Classname 
	 * @param form              the {@link Form} applied to show/edit the ObjectObj's data
	 * @param parameters        list of parameters used by Item extraction {@link Program}s
	 * @param session           http session to retrieve the User framework
	 *
	 * @return  array of two vector elements:
	 *            [0] = vector of visible  Form - {@link Item}s
	 *            [1] = vector of editable Form - {@link Item} names
	 * 
	 * @see Form#setFilterParameters(String)
	 */
	public static Vector[] getFormUserItems(String objectIDClass,Form form,String parameters,HttpSession session) throws Exception {	 	     
		//Get filter parameters values
		String sFilterPar=form.getFilterParameters();
		String sArgs="";
		String sObjectClass=objectIDClass;
		if ( objectIDClass.startsWith("#") && !StringUtils.isBlank(sFilterPar) ) {
			String sCommand = "object show "+objectIDClass+" get { class "+sFilterPar+" } token '<<|>>';";
			Vector vRes = StringTokensToVector(performOOQL(sCommand,session),"<<|>>");
			sObjectClass=((String)vRes.elementAt(0));
			int iSize = vRes.size();
			for (int i=1;i<iSize;i++) {
				sArgs += "'"+StringUtils.escape(((String)vRes.elementAt(i)))+"' ";
			}
		}
		sArgs+=parameters;
		Vector vForms = new Vector();
		vForms.add(form.getName());
		return getFormUserItems(true,sObjectClass,form,sArgs,vForms,session);
	}

	/**
	 * Get visible Form-Items list for a link instance or linktype.
	 *  
	 * @param linkIDType        the {@link Link}'s ID or Linktype 
	 * @param form              the {@link Form} applied to show/edit the ObjectObj's data
	 * @param parameters        list of parameters used by Item extraction {@link Program}s
	 * @param session           http session to retrieve the User framework
	 *
	 * @return  array of two vector elements:
	 *            [0] = vector of visible  Form - {@link Item}s
	 *            [1] = vector of editable Form - {@link Item} names
	 *            
	 * @see Form#setFilterParameters(String)
	 */
	public static Vector[] getLinkFormUserItems(String linkIDType,Form form,String parameters,HttpSession session) throws Exception {	 	     
		//Get filter parameters values
		String sFilterPar=form.getFilterParameters();
		String sArgs="";
		String sLinkOrType=linkIDType;
		if ( linkIDType!=null && linkIDType.startsWith("#") && !StringUtils.isBlank(sFilterPar) ) {
			String sCommand = "link show "+linkIDType+" get { linktype "+sFilterPar+" } token '<<|>>';";
			Vector vRes = StringTokensToVector(performOOQL(sCommand,session),"<<|>>");
			sLinkOrType=((String)vRes.elementAt(0));
			int iSize = vRes.size();
			for (int i=1;i<iSize;i++) {
				sArgs += "'"+StringUtils.escape(((String)vRes.elementAt(i)))+"' ";
			}
		}
		sArgs+=parameters;
		Vector vForms = new Vector();
		vForms.add(form.getName());
		return getFormUserItems(false,sLinkOrType,form,sArgs,vForms,session);
	}

	private static Vector[] getFormUserItems (boolean isObject,String sClassType,Form form,String sArgs,Vector vForms,HttpSession session) throws Exception {
		String sCommand = "form show '"+form.getName()+"' get { item.visible item.editable item.required item.disabled } "; 
		if (sArgs.length()>0) {
			sCommand+="input "+sArgs;
		}
		sCommand+=" token xml;";
		Element eUserItems = parseXML(performOOQL(sCommand,session));
		Iterator elementList = eUserItems.getDescendants();
		Vector vItems=new Vector();
		Vector vEditableItems=new Vector();
		Vector vRequiredItems=new Vector();
		Vector vDisabledItems=new Vector();
		while (elementList.hasNext()) {
			try{
				Element nextElement = (Element) elementList.next();
				String sAttrName = nextElement.getName();
				if (isBlank(nextElement.getText())) { continue; }

				if (sAttrName.equals("item.visible") ) {					
					Item item = form.getItem(nextElement.getText());					
					if (item.getType()!= Item.ITEMTYPE_FORM) {
						vItems.add(item);
					} else {
						// Form
						String sFormName=item.getItemValue();
						if (sClassType!=null && (sFormName.length()==0 || sFormName.equals("?")) ) {
							if (isObject) {
								sFormName = getDefaultForm(sClassType,false,session);
							} else {
								sFormName = getLinkDefaultForm(sClassType,session);	        			   
							}
						}
						if (sFormName.length()>0 && vForms.indexOf(sFormName)<0) {
							Form subform = (Form)loadAdminObject("form",sFormName,session);
							vForms.add(sFormName);
							Vector[] vFormItems = getFormUserItems(isObject,sClassType,subform,sArgs,vForms,session);
							vItems.addAll(vFormItems[0]);
							vEditableItems.addAll(vFormItems[1]);
							vRequiredItems.addAll(vFormItems[2]);
							vDisabledItems.addAll(vFormItems[3]);
						}
					}
				} else if (sAttrName.equals("item.editable") ) {
					vEditableItems.add(nextElement.getText());
				} else if (sAttrName.equals("item.required") ) {
					vRequiredItems.add(nextElement.getText());
				} else if (sAttrName.equals("item.disabled") ) {
					vDisabledItems.add(nextElement.getText());
				}
			} catch (Exception e){}
		}
		return new Vector[]{vItems,vEditableItems,vRequiredItems,vDisabledItems};
	}

	/**
	 * Get all fields included in the Form
	 *
	 * @param objclass          the {@link ObjectObj}'s Classname
	 * @param form              the {@link Form} applied to show/edit the ObjectObj's data
	 * @param session           http session to retrieve the User framework
	 *
	 * @return  vector of the Form - {@link Item}s
	 * 
	 * @see Form#setFilterParameters(String)
	 */
	public static Vector getFormItemFields(String objclass,Form form,HttpSession session) throws Exception {	
		Vector vForms=new Vector();
		return getFormItemFields(true,objclass,form,vForms,session);
	}

	/**
	 * Get all fields included in the Form
	 *
	 * @param linktype          the {@link Link}'s LinkType
	 * @param form              the {@link Form} applied to show/edit the ObjectObj's data
	 * @param session           http session to retrieve the User framework
	 *
	 * @return  vector of the Form - {@link Item}s
	 * 
	 * @see Form#setFilterParameters(String)
	 */
	public static Vector getLinkFormItemFields(String linktype,Form form,HttpSession session) throws Exception {	
		Vector vForms=new Vector();
		return getFormItemFields(false,linktype,form,vForms,session);
	}

	private static Vector getFormItemFields(boolean isObject,String classtype,Form form,Vector vForms,HttpSession session) throws Exception {	
		Vector vItems = form.getItems();
		Vector vFields = new Vector();
		int iSize = vItems.size();
		for (int i=0;i<iSize;i++) {
			Item item = (Item)vItems.elementAt(i);
			if ( item.getType() == Item.ITEMTYPE_FIELD ) {
				vFields.add(item);		   
			} else if (item.getType()== Item.ITEMTYPE_FORM) {
				// Form
				String sFormName=item.getItemValue();
				if (sFormName.length()==0 || sFormName.equals("?") ) {
					if (isObject) {
						sFormName = getDefaultForm(classtype,false,session);
					} else {
						sFormName = getLinkDefaultForm(classtype,session); 
					}
				}
				if (sFormName.length()>0 && vForms.indexOf(sFormName)<0) {
					Form subform = (Form)loadAdminObject("form",sFormName,session);
					vForms.add(sFormName);
					vFields.addAll(getFormItemFields(isObject,classtype,subform,vForms,session));
				}
			}
		}
		return vFields;
	}

	/**
	 * Get ObjectObj properties related to Form-Item fields
	 *
	 * @param objectID    the {@link ObjectObj}'s ID
	 * @param items       vector of {@link Item}s
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 * 
	 * @return a hashtable with data values associated to keys like "field[&lt;fieldName&gt;]"
	 */
	public static Hashtable getObjectFormData(String objectID,Vector items,HttpSession session) throws Exception {
		return getObjectLinkFormData(true,objectID,items,session);
	}

	/**
	 * Get Link properties related to Form-Item fields
	 *
	 * @param linkID      the {@link Link}'s ID
	 * @param items       vector of {@link Item}s
	 * @param session     http session to retrieve the User framework
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 * 
	 * @return a hashtable with data values associated to keys like "field[&lt;fieldName&gt;]"
	 */
	public static Hashtable getLinkFormData(String linkID,Vector items,HttpSession session) throws Exception {
		return getObjectLinkFormData(false,linkID,items,session);
	}

	private static Hashtable getObjectLinkFormData(boolean isObject,String sID,Vector items,HttpSession session) throws Exception {
		Hashtable hObjectData=new Hashtable();
		Vector vGetData = new Vector();
		String sLoadFields = null;
		Form form = null;
		if ( !StringUtils.isBlank(sID) ) {  
			// Load object/link values	
			StringBuffer sObjectGet= new StringBuffer();
			String sGetData="";
			int iSize = items.size();
			for (int i=0;i<iSize;i++) {
				Item item = (Item)  items.elementAt(i);
				int iItemType = item.getType();
				String sFieldName= item.getItemValue();
				if (iItemType==Item.ITEMTYPE_FIELD ) {
					sGetData="field['"+sFieldName+"']";	 
				} else if (iItemType==Item.ITEMTYPE_GETOBJ || iItemType==Item.ITEMTYPE_GETLINK || iItemType==Item.ITEMTYPE_BASIC) {
					sGetData=sFieldName;	   
				} else if (iItemType==Item.ITEMTYPE_ACTION || iItemType==Item.ITEMTYPE_XFIELD) {
					if (sLoadFields==null) { 
						sLoadFields = sFieldName; 
					} else {
						sLoadFields+=","+sFieldName;
					}
					form = item.getForm();
				} else { sGetData ="";}
				if (sGetData.length()>0) {
					vGetData.addElement(sGetData);
					sObjectGet.append(sGetData).append(' ');
				}
			}
			if (sObjectGet.length()>0) {
				StringBuffer sCommand = new StringBuffer();
				if (isObject) {
					sCommand.append("object show ").append(sID).append(" get { ").append(sObjectGet).append(" } token '<<|>>';");
				} else {
					sCommand.append("link show ").append(sID).append(" get { ").append(sObjectGet).append(" } token '<<|>>';");
				}
				Vector vRes = StringTokensToVector(performOOQL(sCommand.toString(),session),"<<|>>");
				iSize = vGetData.size();
				for (int i=0;i<iSize;i++) {
					try { 
						hObjectData.put((String)vGetData.elementAt(i),vRes.elementAt(i));
					} catch (Exception e) {}
				}
			}

			// Load Action parameter values
			if (sLoadFields!=null && sLoadFields.length()>0) {
				Framework framework = Application.getFramework(session);
				framework.emptyEnv();
				framework.setEnv("id",sID);	
				StringBuffer sResult=new StringBuffer("<fieldranges>");
				sLoadFields="range['"+replaceString(sLoadFields, ",", "'] range['")+"']";
				String sRes = performOOQL("form show '"+form.getName()+"' get { "+sLoadFields+" } token xml;",session);		    
				sResult.append(replaceString(sRes,"<?xml version=\"1.0\" encoding=\"UTF-8\"?>",""));
				sResult.append("</fieldranges>");	

				Element eRanges = parseXML(sResult.toString());
				Iterator fieldList = eRanges.getDescendants(new ElementFilter("field"));
				while (fieldList.hasNext()) {
					try{
						Element fieldElement = (Element) fieldList.next();
						String sFieldName = fieldElement.getAttributeValue("name");
						Iterator rangeList = fieldElement.getDescendants(new ElementFilter("range"));
						// Current value is the first range element
						if (rangeList.hasNext()) {						 
							String value = ((Element)rangeList.next()).getText();
							StringTokenizer rangev = new StringTokenizer(" "+value+" ","|");
							hObjectData.put(sFieldName,rangev.nextToken().trim()); 
						}
					} catch (Exception ex) {}
				}
			}

		} else {
			// Load field default values
			StringBuffer sFieldList= new StringBuffer();
			int iSize = items.size();
			for (int i=0;i<iSize;i++) {
				Item item = (Item)  items.elementAt(i);
				String sFieldName= item.getItemValue();
				if ((item.getType()==Item.ITEMTYPE_FIELD || item.getType()==Item.ITEMTYPE_XFIELD) &&
						!sFieldName.equals("description")) {
					sFieldList.append(sFieldName).append(',');
				}
			}
			if (sFieldList.length()>0) {
				sFieldList.append("xx");
				String sCommand = "field select "+sFieldList.toString()+" get { name default } token '\t' ;";
				Vector vRes = StringTokensToVector(performOOQL(sCommand,session),"\n");
				iSize = vRes.size();
				for (int i=0;i<iSize;i++) {
					try {	
						StringTokenizer sT=new StringTokenizer((String)vRes.elementAt(i),"\t");	
						hObjectData.put("field['"+sT.nextToken()+"']",sT.nextToken());
					} catch (Exception e) {}
				}
			}
		}
		return hObjectData;
	}

	/**
	 * Get visible View-Column list
	 *  
	 * @param id                a {@link ObjectObj} or {@link Link} id (can be empty) 
	 * @param view              the {@link View} applied to show/edit the data
	 * @param parameters        list of parameters used by Column extraction {@link Program}s
	 * @param session           http session to retrieve the User framework
	 *
	 * @return  array of two vector elements: 
	 *            [0] = vector of visible  View - {@link Column}s
	 *            [1] = vector of editable View - {@link Column} names
	 * 
	 * @see View#setFilterParameters(String)
	 */ 
	public static Vector[] getViewUserColumns(String id,View view,String parameters,HttpSession session) throws Exception {
		//Get filter parameters values
		String sFilterPar=view.getFilterParameters();
		String sArgs="";
		String sCommand ;
		if ( id.startsWith("#") && !StringUtils.isBlank(sFilterPar) ) {
			sCommand = "object show "+id+" get { class "+sFilterPar+" } token '<<|>>';";
			Vector vRes = StringTokensToVector(performOOQL(sCommand,session),"<<|>>");
			int iSize = vRes.size();
			for (int i=1;i<iSize;i++) {
				sArgs += "'"+StringUtils.escape(((String)vRes.elementAt(i)))+"' ";
			}
		}
		sArgs+=parameters;	 
		sCommand = "view show '"+view.getName()+"' get { column.seeable column.modifiable } "; 
		if (sArgs.length()>0) {
			sCommand+="input "+sArgs;
		}

		sCommand+=" token xml;";
		Element eUserColumns = parseXML(performOOQL(sCommand,session));
		Iterator elementList = eUserColumns.getDescendants();
		Vector vColumns=new Vector();
		Vector vEditableColumns=new Vector();
		while (elementList.hasNext()) {
			try{
				Element nextElement = (Element) elementList.next();
				String sAttrName = nextElement.getName();
				if (isBlank(nextElement.getText())) { continue; }
				if (sAttrName.equals("column.seeable") ) {
					Column column = view.getColumn(nextElement.getText());
					vColumns.add(column); 
				} else if (sAttrName.equals("column.modifiable") ) {
					vEditableColumns.add(nextElement.getText());					
				}
			} catch (Exception e){}
		}
		return new Vector[]{ vColumns ,vEditableColumns };
	}

	/**
	 * Split View-Columns in getObject and getLink part
	 *  
	 * @param columns :  View-Column vector
	 *
	 * @return  hashtable with the following elements: 
	 *            	 "columns":     valid columns      (Column[]) 
	 * 	         		 "getObject":   object get command (String)
	 * 	             "getObject[]": object get list    (String[])               
	 * 	             "getLink":     link get command   (String)
	 * 	             "getLink[]":   link get list      (String[]) 
	 *	 	           "indexes":     column indexes     (int[])   
	 * 
	 * @see View#setFilterParameters(String)
	 */ 
	public static Hashtable splitViewColumns(Vector columns) throws Exception {
		int iValidCol=0;
		int iObjCol=0;
		int iLinkCol=0;
		int iSize = columns.size();
		for (int i=0;i<iSize;i++) {
			Column col = (Column)columns.elementAt(i);
			if (col.getGetObject().length()>0) { iValidCol++; iObjCol++; } else
				if (col.getGetLink().length()>0) { iValidCol++; iLinkCol++; }
		}
		int[] vIndexes =new int[iValidCol];
		Column[] vCols =new Column[iValidCol];
		String[] vObjFields=new String[iObjCol];
		String[] vLinkFields=new String[iLinkCol];
		iValidCol=0;
		iObjCol=0;
		iLinkCol=0;
		int iNdxCol = 0;
		for (int i=0;i<iSize;i++) {
			Column col = (Column)columns.elementAt(i);
			if (col.getGetObject().length()>0) {
				vCols[iNdxCol]=col;
				vIndexes[iValidCol]=iNdxCol;
				iValidCol++;
				vObjFields[iObjCol]=col.getGetObject();
				iObjCol++;
				iNdxCol++;
			} else if (col.getGetLink().length()>0) { 
				vCols[iNdxCol]=col;
				iNdxCol++; 
			}
		}
		iNdxCol = 0;
		for (int i=0;i<iSize;i++) {
			Column col = (Column)columns.elementAt(i);
			if (col.getGetLink().length()>0) {
				vIndexes[iValidCol]=iNdxCol;
				iValidCol++;
				vLinkFields[iLinkCol]=col.getGetLink();
				iLinkCol++;
				iNdxCol++;
			} else if (col.getGetObject().length()>0) { iNdxCol++; }
		}
		String sObjCommand = "";
		for (int i=0;i<vObjFields.length;i++) {
			sObjCommand+=vObjFields[i]+" ";
		}
		String sLnkCommand = "";
		for (int i=0;i<vLinkFields.length;i++) {
			sLnkCommand+=vLinkFields[i]+" ";
		}

		Hashtable hReturn = new Hashtable();
		hReturn.put("columns", vCols);
		hReturn.put("getObject", sObjCommand);
		hReturn.put("getObject[]", vObjFields);
		hReturn.put("getLink", sLnkCommand);
		hReturn.put("getLink[]", vLinkFields);
		hReturn.put("indexes", vIndexes);  
		return hReturn;
	}


	/**
	 * Replace substitution keywords with ObjectObj or Link data values. 
	 * 
	 * @param inputString   the input string containing the substitution keywords
	 * @param objlink       the {@link ObjectObj} or {@link Link} instance
	 * @param session       http session to retrieve the User framework
	 * 
	 * @return a string where the substitution keywords are replace with related values.
	 * 
	 * Example:  <br>
	 * inputString="objname=&lt;name&gt;&objid=&lt;id&gt;"<br>
	 * resultString="objname=2140938&objid=#00034-65F5A443"<br>
	 * 
	 * Note:  the resultString is http url encoded (UTF-8)
	 */
	public static String replaceObjectKeyWords(String inputString,ObjectBase objlink,HttpSession session) throws Exception {
		return replaceObjectKeyWords(inputString,objlink,true,session);
	}

	/**
	 * Replace substitution keywords with ObjectObj or Link data values or dictionary translations 
	 * 
	 * @param inputString   the input string containing the substitution keywords
	 * @param objlink       the {@link ObjectObj} or {@link Link} instance
	 * @param encode		 if true apply the url encoding to keyword values
	 * @param session       http session to retrieve the User framework
	 * 
	 * @return a string where the substitution keywords are replace with related values.
	 * 
	 * Example:  <br>
	 * inputString="objname=&lt;name&gt;&objid=&lt;id&gt;&text=&lt;[Section,DictKey]&gt;"<br>
	 * resultString="objname=2140938&objid=#00034-65F5A443&text=DictKey_Translation"<br>
	 */  
	public static String replaceObjectKeyWords(String inputString,ObjectBase objlink,boolean encode,HttpSession session) throws Exception {
		if (objlink instanceof ObjectObj) {
			ObjectObj object = (ObjectObj)objlink;		
			int i=0;
			String sObjectGet="";
			String sTranslation;
			Vector vGetData =null;
			while ( inputString.indexOf('<')>=0 && inputString.indexOf('>')>=0  && i<100 ) {				
				if (inputString.indexOf("<id>")>=0 && object.getID().length()>0 ) {
					inputString=replaceString(inputString, "<id>", encode?java.net.URLEncoder.encode(object.getID(),StringUtils.UTF8):object.getID());
				} else if (inputString.indexOf("<class>")>=0  && object.getClassName().length()>0) {
					inputString=replaceString(inputString, "<class>", encode?java.net.URLEncoder.encode(object.getClassName(),StringUtils.UTF8):object.getClassName());
				} else if (inputString.indexOf("<name>")>=0  && object.getName().length()>0) {
					inputString=replaceString(inputString, "<name>", encode?java.net.URLEncoder.encode(object.getName(),StringUtils.UTF8):object.getName());
				} else if (inputString.indexOf("<revision>")>=0  && object.getRevision().length()>0) {
					inputString=replaceString(inputString, "<revision>", encode?java.net.URLEncoder.encode(object.getRevision(),StringUtils.UTF8):object.getRevision()); 
				} else if (inputString.indexOf("<description>")>=0  && object.getDescription().length()>0) {
					inputString=replaceString(inputString, "<description>", encode?java.net.URLEncoder.encode(object.getDescription(),StringUtils.UTF8):object.getDescription()); 
				} else if (inputString.indexOf("<lifecycle>")>=0  && object.getLifecycle().length()>0) {
					inputString=replaceString(inputString, "<lilecycle>", encode?java.net.URLEncoder.encode(object.getLifecycle(),StringUtils.UTF8):object.getLifecycle()); 
				} else if (inputString.indexOf("<currentstage>")>=0  && object.getStage().length()>0) {
					inputString=replaceString(inputString, "<currentstage>", encode?java.net.URLEncoder.encode(object.getStage(),StringUtils.UTF8):object.getStage()); 
				} else if (inputString.indexOf("<holder>")>=0  && object.getHolder().length()>0) {
					inputString=replaceString(inputString, "<holder>", encode?java.net.URLEncoder.encode(object.getHolder(),StringUtils.UTF8):object.getHolder()); 
				} else if (inputString.indexOf("<origdate>")>=0  && object.getCrtDate().length()>0) {
					inputString=replaceString(inputString, "<origdate>", encode?java.net.URLEncoder.encode(object.getCrtDate(),StringUtils.UTF8):object.getCrtDate()); 
				} else if (inputString.indexOf("<moddate>")>=0  && object.getModDate().length()>0) {
					inputString=replaceString(inputString, "<moddate>", encode?java.net.URLEncoder.encode(object.getModDate(),StringUtils.UTF8):object.getModDate()); 
				} else if (inputString.indexOf("<locker>")>=0  && object.getLocker().length()>0) {
					inputString=replaceString(inputString, "<locker>", encode?java.net.URLEncoder.encode(object.getLocker(),StringUtils.UTF8):object.getLocker()); 
				} else if (inputString.indexOf("<objectspace>")>=0  && object.getObjectSpace().length()>0) {
					inputString=replaceString(inputString, "<objectspace>", encode?java.net.URLEncoder.encode(object.getObjectSpace(),StringUtils.UTF8):object.getObjectSpace()); 
				} else if (inputString.indexOf("<[")>=0 && inputString.indexOf("]>")>0) {
					String sKeyWord = getStringPart(inputString,"<[","]>");					
					if (sKeyWord.indexOf(",")<0) {
						sTranslation = getDictKey("*",sKeyWord,session);
					} else {
						StringTokenizer sT = new StringTokenizer(sKeyWord,",");
						sTranslation = getDictKey(sT.nextToken(),sT.nextToken(),session);
					}        	  
					inputString=replaceString(inputString, "<["+sKeyWord+"]>", encode?java.net.URLEncoder.encode(sTranslation,StringUtils.UTF8):sTranslation);
				} else {
					String sKeyWord = getStringPart(inputString,"<",">");
					sObjectGet+=sKeyWord+" ";
					if ( vGetData==null ) { vGetData=new Vector(); }
					vGetData.addElement(sKeyWord);
					inputString=replaceString(inputString, "<"+sKeyWord+">", "$$"+sKeyWord+">");
				}
				i++;
			}
			if ( object.getID().length()>0 && sObjectGet.length()>0 ) {
				String sCommand = "object show "+object.getID()+" get { "+sObjectGet+" } token '<<|>>';";	
				Vector vRes = StringTokensToVector(performOOQL(sCommand,session),"<<|>>");
				int iSize = vGetData.size();
				for (int j=0;j<iSize;j++) {
					if (j<vRes.size()) {
						inputString=replaceString(inputString, "$$"+(String)vGetData.elementAt(j)+">",  encode?java.net.URLEncoder.encode((String)vRes.elementAt(j),StringUtils.UTF8):(String)vRes.elementAt(j));
					} else {
						inputString=replaceString(inputString, "$$"+(String)vGetData.elementAt(j)+">", "");
					}
				}	
			}
			inputString=replaceString(inputString, "$$","<");
		} else if (objlink instanceof Link) {
			Link link = (Link)objlink;
			int i=0;
			String sLinkGet="";
			String sTranslation;
			Vector vGetData =null;
			while ( inputString.indexOf('<')>=0 && inputString.indexOf('>')>0  && i<100 ) {
				if (inputString.indexOf("<id>")>=0  && link.getID().length()>0 ) {
					inputString=replaceString(inputString, "<id>",  encode?java.net.URLEncoder.encode(link.getID(),StringUtils.UTF8):link.getID());
				} else if (inputString.indexOf("<linktype>")>=0   && link.getLinkType().length()>0 ) {
					inputString=replaceString(inputString, "<linktype>",  encode?java.net.URLEncoder.encode(link.getLinkType(),StringUtils.UTF8):link.getLinkType());
				} else if (inputString.indexOf("<active>")>=0 ) {
					inputString=replaceString(inputString, "<active>", ""+link.isActive());
				} else if (inputString.indexOf("<hidden>")>=0 ) {
					inputString=replaceString(inputString, "<hidden>", ""+link.isHidden());	  
				} else if (inputString.indexOf("<origdate>")>=0  && link.getCrtDate().length()>0) {
					inputString=replaceString(inputString, "<origdate>", encode?java.net.URLEncoder.encode(link.getCrtDate(),StringUtils.UTF8):link.getCrtDate()); 	
				} else if (inputString.indexOf("<[")>=0 && inputString.indexOf("]>")>0) {
					String sKeyWord = getStringPart(inputString,"<[","]>");					
					if (sKeyWord.indexOf(",")<0) {
						sTranslation = getDictKey("*",sKeyWord,session);
					} else {
						StringTokenizer sT = new StringTokenizer(sKeyWord,",");
						sTranslation = getDictKey(sT.nextToken(),sT.nextToken(),session);
					}        	  
					inputString=replaceString(inputString, "<["+sKeyWord+"]>", encode?java.net.URLEncoder.encode(sTranslation,StringUtils.UTF8):sTranslation);
				} else {
					String sKeyWord = getStringPart(inputString,"<",">");
					sLinkGet+=sKeyWord+" ";
					if ( vGetData==null ) { vGetData=new Vector(); }
					vGetData.addElement(sKeyWord);
					inputString=replaceString(inputString, "<"+sKeyWord+">", "$$"+sKeyWord+">");
				}
				i++;
			}
			if (  link.getID().length()>0 && sLinkGet.length()>0 ) {
				String sCommand = "link show "+link.getID()+" get { "+sLinkGet+" } token '<<|>>';";
				Vector vRes = StringTokensToVector(performOOQL(sCommand,session),"<<|>>");
				int iSize = vGetData.size();
				for (int j=0;j<iSize;j++) {
					if (j<vRes.size()) {
						inputString=replaceString(inputString, "$$"+(String)vGetData.elementAt(j)+">", encode?java.net.URLEncoder.encode((String)vRes.elementAt(j),StringUtils.UTF8):(String)vRes.elementAt(j));
					} else {
						inputString=replaceString(inputString, "$$"+(String)vGetData.elementAt(j)+">", "");
					}
				}					  
			}		  
			inputString=replaceString(inputString, "$$","<");
		}
		return inputString;
	}


	/**
	 * Replace dictionary keywords with translations 
	 * 
	 * @param inputString   the input string containing the substitution keywords
	 * @param encode		    if true apply the url encoding to keyword values
	 * @param session       http session to retrieve the User framework
	 * 
	 * @return a string where the substitution keywords are replace with related values.
	 * 
	 * @since 2.2.02
	 * 
	 * Example:  <br>
	 * inputString="text=&lt;[Section,DictKey]&gt;"<br>
	 * resultString="text=DictKey_Translation"<br>
	 */  
	public static String replaceDictionaryKeyWords(String inputString,boolean encode,HttpSession session) throws Exception {
		int i=0;
		String sTranslation;
		while ( inputString.indexOf("<[")>=0 && inputString.indexOf("]>")>0  && i<50 ) {								
			String sKeyWord = getStringPart(inputString,"<[","]>");					
			if (sKeyWord.indexOf(",")<0) {
				sTranslation = getDictKey("*",sKeyWord,session);
			} else {
				StringTokenizer sT = new StringTokenizer(sKeyWord,",");
				sTranslation = getDictKey(sT.nextToken(),sT.nextToken(),session);
			}        	  
			inputString=replaceString(inputString, "<["+sKeyWord+"]>", encode?java.net.URLEncoder.encode(sTranslation,StringUtils.UTF8):sTranslation);
			i++;
		}
		return inputString;
	}

	/**
	 * Save OBEROn object changes to the database
	 *
	 * @param object      the OBEROn object
	 * @param session     http session to retrieve the User framework
	 * 
	 * Note: reset the Changes before apply modification to the object
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static void saveObject(ObjectObj object,HttpSession session) throws Exception {
		String sCommand=object.getSaveCommand();
		performOOQLCommands(sCommand,session);
	}

	/**
	 * Save Link object changes to the database
	 *
	 * @param link        the Link object
	 * @param session     http session to retrieve the User framework
	 * 
	 * Note: reset the Changes before apply modification to the object
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static void saveLink(Link link,HttpSession session) throws Exception {
		String sCommand=link.getSaveCommand();
		performOOQLCommands(sCommand,session);
	}  

	/**
	 * Clone a OBEROn object
	 *
	 * @param object      the OBEROn object
	 * @param copyFiles   true to replicate the attached files
	 * @param session     http session to retrieve the User framework
	 *  
	 * @return the new {@link ObjectObj}'s ID
	 *  
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static String cloneObject(ObjectObj object,boolean copyFiles,HttpSession session) throws Exception {
		String sCommand=object.getCloneCommand(copyFiles);
		return performOOQL(sCommand,session);
	}

	/**
	 * Revise a OBEROn object
	 *
	 * @param object      the OBEROn object
	 * @param copyFiles   true to replicate the attached files
	 * @param session     http session to retrieve the User framework
	 *  
	 * @return the new {@link ObjectObj}'s ID
	 * 
	 * @throws OberonException  usually due to DB SQL exceptions
	 */
	public static String reviseObject(ObjectObj object,boolean copyFiles,HttpSession session) throws Exception {
		String sCommand=object.getReviseCommand(copyFiles);
		return performOOQL(sCommand,session);
	}


	/**
	 * Return the first revision for a Lifecycle revision rule
	 *
	 * @param lifecycle      the {@link Lifecycle}
	 */
	public static String getFirstRevision(Lifecycle lifecycle) throws Exception {	  
		return AutoNumber.resetValue(lifecycle.getRevisionRule());
	}

	/**
	 * Return the next value of a AutoNumber
	 *
	 * @param autonumberName      the {@link AutoNumber} name
	 */
	public static String getNextValue(String autonumberName,HttpSession session) throws Exception {
		String sCommand="autonumber increase '"+autonumberName+"';";
		return performOOQL(sCommand,session);
	}


	//---------------------------------------------------------------------------------
	//	I/O Servlet Interface
	//---------------------------------------------------------------------------------

	/**
	 * Return a administrative object icon
	 *
	 * @param adminType   the administrative object type (ex.: class , field , lifecycle ... )
	 * @param name        the object name
	 * 
	 * @return the image data
	 *
	 */
	public static byte[] getImage(String adminType,String name) throws Exception {
		byte[] baImage=null;
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		OBConnection conn = ConnectionManager.getConnection(null);
		try {
			if (ConnectionManager.isRMIConnection()) {
				RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
				baImage=servlet.getImage(adminType,name);
			} else {
				baImage=DB_Base.loadImage(((DBConnection)conn).getSqlConn(),adminType, name,false );
			}
		} catch (ConnectException ex) { 
			conn.setValid(false);
			throw new OberonException(ex.getMessage());
		} catch (Exception ex) { 
			throw OberonException.generateOberonException(ex,"getImage("+adminType+","+name+")");
		} finally {
			ConnectionManager.releaseConnection(conn,null);
		}
		return baImage;
	}

	/**
	 * Return a administrative object icon
	 *
	 * @param adminId   the administrative object internal id
	 *
	 * @return the image data
	 *
	 */
	static  public byte[] getImage( int adminId ) throws Exception {
		return getImage("",adminId);
	}

	/**
	 * Return a ObjectObj's icon
	 *
	 * @param objectId   the ObjectObj's Id
	 *
	 * @return the image data
	 *
	 */
	static  public byte[] getImage( String objectId ) throws Exception {
		return getImage(DB_Base.getOSCodeFromID(objectId),DB_Base.getIdFromID(objectId));
	}

	// Return object icon
	private static byte[] getImage(String sOS, int adminId ) throws Exception {
		byte[] baImage=null;
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		OBConnection conn = ConnectionManager.getConnection(null);
		try {	   
			if (ConnectionManager.isRMIConnection()) {
				RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
				baImage=servlet.getImage(sOS,adminId);
			} else {
				baImage=DB_Base.loadImage(((DBConnection)conn).getSqlConn(),sOS, adminId,false );
			}
		} catch (ConnectException ex) { 
			conn.setValid(false);
			throw new OberonException(ex.getMessage());
		} catch (Exception ex) { 
			throw OberonException.generateOberonException(ex,"getImage("+sOS+","+adminId+")");			
		} finally {
			ConnectionManager.releaseConnection(conn,null);
		}
		return baImage;
	}


	/**
	 * Set a administrative object icon
	 * 
	 * @param adminType    administrative object type
	 * @param name         administrative object name
	 * @param image        byte array image
	 */
	public static void setImage(String adminType,String name,byte[] image) throws OberonException,SQLException,IOException{
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		OBConnection conn = ConnectionManager.getConnection(null);
		try {
			if ( ConnectionManager.isRMIConnection() ) {			
				RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
				servlet.setImage("",adminType,name,image);
			} else {
				java.sql.Connection sqlconn = ((DBConnection)conn).getSqlConn();
				int iId = DB_Base.checkExists(sqlconn,adminType,name,false);
				if (iId>Integer.MIN_VALUE) {
					boolean bUpdate = DB_Base.lockImage(sqlconn,"", iId ,false);
					DB_Base.setImage(sqlconn, "",iId , "" , image , bUpdate,false);
				}
			}
		} catch (ConnectException ex) { 
			conn.setValid(false);
			throw OberonException.generateOberonException(ex);
		} finally {
			ConnectionManager.releaseConnection(conn,null);
		}		
	}

	/**
	 * Extract a file from an ObjectObj instance
	 * 
	 * @param objectID     the {@link ObjectObj}'s ID
	 * @param fileTypeName the {@link FileType} name
	 * @param fileName     the file name to set
	 * @param lock         true to lock the {@link ObjectObj}'s file after the operation
	 * @param session      http session to retrieve the User framework
	 *
	 * @return the file data
	 * 
	 * @throws Exception usually due to DB SQL exceptions or {@link Trigger} exceptions or IO exceptions
	 */
	static  public byte[] getFile(String objectID,String fileTypeName,String fileName,boolean lock,HttpSession session) throws Exception {
		byte[] baFile=null;
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {
				String sLock=(lock?"lock":"");
				String sCommand="object fileget "+objectID+" "+sLock+" server filetype '"+fileTypeName+"' name '"+StringUtils.escape(fileName)+"' path '';";
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
					servlet.performOOQL( sCommand , framework );
					Application.setFramework(session, servlet.getFramework());
					baFile=servlet.downloadFile(objectID,fileName);
				} else {
					OOQLExecute execOOQL = new OOQLExecute();
					framework=execOOQL.executeOOQL(sCommand,framework);
					Application.setFramework(session, framework);
					StringTokenizer sT=new StringTokenizer(objectID,"-");
					sT.nextToken();
					String sFilePath=Params.sUploadDir+sT.nextToken()+"/"+fileName;
					baFile =  FileUtils.getBytesFromFile(sFilePath) ;
					try {
						File fGet=new File(sFilePath);
						if ( fGet.exists() ) { fGet.delete(); }
					} catch (Exception ex) {}
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} catch (Exception ex) { 
				throw OberonException.generateOberonException(ex,"getFile("+objectID+","+fileTypeName+","+fileName+","+lock+")");				
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		return baFile;
	}


	/**
	 * Attach one a file to an ObjectObj instance
	 * 
	 * @param fileContent  contains the file data
	 * @param objectID     the {@link ObjectObj}'s ID
	 * @param fileTypeName the {@link FileType} name
	 * @param fileName     the file name to set
	 * @param lock         true to lock the {@link ObjectObj}'s file after the operation
	 * @param overwrite    if true all files associated to the {@link ObjectObj} for the same FileType<br>
	 * 					   will be remove before the new file upload is performed
	 * @param indexed      if true the file content and metadata will be indexed for searching
	 * @param session      http session to retrieve the User framework
	 *
	 * @throws Exception usually due to DB SQL exceptions or {@link Trigger} exceptions or IO exceptions
	 */
	public static void putFile(byte[] fileContent,String objectID,String fileTypeName,String fileName,
			boolean lock,boolean overwrite,boolean indexed, HttpSession session) throws Exception {
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {	   
				String sLock=(lock?"":"unlock");
				String sOverwrite=(overwrite?"overwrite":"");
				String sIndexed=(indexed?"indexed":"");
				String sCommand="object fileput "+objectID+" "+sLock+" server "+sIndexed
				+" "+sOverwrite+" filetype '"+fileTypeName+"' name '"+StringUtils.escape(fileName)+"' ;";
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
					servlet.uploadFile(fileContent,objectID,fileName);
					servlet.performOOQL( sCommand , framework );
					Application.setFramework(session, servlet.getFramework());
				} else {
					StringTokenizer sT=new StringTokenizer(objectID,"-");
					sT.nextToken();
					String sDestPath=Params.sUploadDir+sT.nextToken();
					FileUtils.createLocalDir(sDestPath);
					File fPut = new File(sDestPath+"/"+fileName) ;
					if ( fPut.exists() ) { fPut.delete(); }
					BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(fPut), 4096);
					for (int i=0;i<fileContent.length;i++) {
						bos.write(fileContent[i]);
					}
					bos.close();    
					OOQLExecute execOOQL = new OOQLExecute();
					framework=execOOQL.executeOOQL(sCommand,framework);
					Application.setFramework(session, framework);   
				}		  
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} catch (Exception ex) { 
				throw OberonException.generateOberonException(ex,"putFile("+(fileContent==null?"null":fileContent.length+"bytes")+
						","+objectID+","+fileTypeName+","+fileName+","+lock+","+overwrite+","+indexed+")");								
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}		
	}


	/**
	 * Create an image representing a progress bar
	 * [need the SWT jar installed]
	 * 
	 * @param value         the percentage of completion
	 * @param width         the image width
	 * @param height        the image height
	 *
	 * @since 2.2.02
	 * @return the progress bar image
	 *
	 */
	static  public byte[] createProgressBarImage(float value,int width, int height ,HttpSession session) throws Exception {
		if (imgDataOn==null || imgDataOff==null) {
			initImages(width, height,session);
		}
		try {
			ImageData imgData = imgDataOn.scaledTo(Math.min(width,1+(int)Math.floor( width*value / 100)),height);
			Image img = new Image(Application.display,imgDataOff.scaledTo(width, height));
			GC gc = new GC(img);
			gc.drawImage(new Image(Application.display,imgData), 0, 0);
			gc.dispose();  
			return DrawChart.encodePNGImage(img);
		} catch (Exception ex) { 
			throw OberonException.generateOberonException(ex,"createProgressBarImage("+value+","+width+","+height+")" );							
		}
	}

	private static void initImages(int width, int height,HttpSession session) {
		if (Application.display==null) { Application.display = new Display(); }
		String sImagesPath=session.getServletContext().getRealPath("/")+"resources";	
		try {			
			FileInputStream fs = new FileInputStream(new File(sImagesPath+"/progressOn.png"));
			imgDataOn = (new Image(Application.display,fs)).getImageData();
			fs = new FileInputStream(new File(sImagesPath+"/progressOff.png"));
			imgDataOff = (new Image(Application.display,fs)).getImageData();
			imgDataOff = imgDataOff.scaledTo(width,height);
		} catch (Exception ex) {
			PaletteData palette = new PaletteData(new RGB[] { Application.display.getSystemColor(SWT.COLOR_GRAY).getRGB() , Application.display.getSystemColor(SWT.COLOR_GREEN).getRGB() });
			imgDataOn  = new ImageData(width,height, 1, palette);
			for (int i = 0; i < width; i++) {
				for (int j = 0; j < height; j++) {
					imgDataOn.setPixel(i, j, 1);
				}
			}
			imgDataOff = new ImageData(width,height, 1 , palette);
		}
	}


	/**
	 * Generates a chart image based on the Graph style parameters and input data values
	 * 
	 * @param graphName    the {@link Graph} name
	 * @param XMLvalues    input data values in XML format (see {@link GraphValues})
	 * @param session      http session to retrieve the User framework
	 *
	 * @return the chart image
	 *
	 */
	static  public byte[] drawGraph(String graphName,String XMLvalues,HttpSession session) throws Exception {
		if (Application.display==null) { Application.display = new Display(); }
		byte[] baGraph=null;
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {	   
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
					baGraph=servlet.drawGraph(graphName, XMLvalues, framework);
				} else {
					Graph graph = DB_Graph.link(graphName, framework, null);
					if (XMLvalues==null || XMLvalues.length()==0) {
						baGraph=graph.preview(framework);
					} else {
						baGraph=graph.draw(new GraphValues(XMLvalues),framework);
					}
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} catch (Exception e ) {
				throw OberonException.generateOberonException(e,"drawGraph("+graphName+","+XMLvalues+")");
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		return baGraph;
	}


	//---------------------------------------------------------------------------------
	//	Dictionary
	//---------------------------------------------------------------------------------

	/**
	 * Get a keyword translation for the default framework language from the specified section
	 *
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param session     http session to retrieve the User framework
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static String getDictKey(String section,String key,HttpSession session) throws Exception {
		return getDictKey(null,section,key,session);
	}

	/**
	 * Get a keyword translation for the specified language and section
	 *
	 * @param language    the translation language
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param session     http session to retrieve the User framework
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static String getDictKey(String language,String section,String key,HttpSession session) throws Exception {
		String sReturn = "";
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			if (language==null) language=(String)session.getAttribute(Arg.Language); 
			if (language==null) language=framework.getLanguage();
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();		     
					sReturn=servlet.getDictKey(language, section, key,"-",framework);
				} else {
					sReturn=Dictionary.getKey(language, section, key, framework);
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} catch (Exception e ) {
				throw OberonException.generateOberonException(e,"getDictKey("+language+","+section+","+key+")");
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		if (sReturn.length()==0) { return key; } else { return sReturn; }
	}

	/**
	 * Get a sub-keyword translation for the default framework language from the specified section
	 *
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param subKey      the sub-keyword name
	 * @param session     http session to retrieve the User framework
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static String getDictSubKey(String section,String key,String subKey,HttpSession session) throws Exception {
		return getDictSubKey(null,section,key,subKey,session);
	}

	/**
	 * Get a sub-keyword translation for the specified language and section
	 *
	 * @param language    the translation language
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param subKey      the sub-keyword name
	 * @param session     http session to retrieve the User framework
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static String getDictSubKey(String language,String section,String key,String subKey,HttpSession session) throws Exception {
		String sReturn = "";
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			if (language==null) language=(String)session.getAttribute(Arg.Language);
			if (language==null) language=framework.getLanguage();
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {						  
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();	
					sReturn=servlet.getDictKey(language, section, key,subKey, framework);
				} else {
					sReturn=Dictionary.getSubKey(language, section, key, subKey, framework);
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} catch (Exception e ) {
				throw OberonException.generateOberonException(e,"getDictSubKey("+language+","+section+","+key+","+subKey+")");
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		if (sReturn.length()==0) { return subKey; } else { return sReturn; }
	}

	/**
	 * List the sub-keyword names for a given keyword and section
	 *
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param session     http session to retrieve the User framework
	 *
	 * @return  a vector of sub-keyword names
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static Vector listDictSubKeys(String section ,String key,HttpSession session) throws Exception {
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Vector vReturn ;
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
					vReturn= servlet.listDictSubKeys(section, key, framework);
				} else {
					vReturn= Dictionary.listSubKeys(section, key, framework);		
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} catch (Exception e ) {
				throw OberonException.generateOberonException(e,"listDictSubKeys("+section+","+key+")");
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		return vReturn;
	}

	/**
	 * Get the sub-keyword translations for the default framework language from the specified section
	 *
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param session     http session to retrieve the User framework
	 *
	 * @return  hashtable with mapped sub-keyword translations
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static Hashtable getDictSubKeys(String section,String key,HttpSession session) throws Exception {
		Vector vSubKeys = listDictSubKeys(section ,key,session);
		return getDictSubKeys(null,section,key,vSubKeys,session);
	}

	/**
	 * Get the sub-keyword translations for the specified language and section
	 *
	 * @param language    the translation language
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param session     http session to retrieve the User framework
	 *
	 * @return  hashtable with mapped sub-keyword translations
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static Hashtable getDictSubKeys(String language,String section,String key,HttpSession session) throws Exception {
		Vector vSubKeys = listDictSubKeys(section ,key,session);
		return getDictSubKeys(language,section,key,vSubKeys,session);
	}

	/**
	 * Get the sub-keyword translations for the specified language and section
	 *
	 * @param language    the translation language
	 * @param section     the dictionary section
	 * @param key         the keyword name
	 * @param subKeys     a vector of sub-keyword names
	 * @param session     http session to retrieve the User framework
	 *
	 * @return  hashtable with mapped sub-keyword translations
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static Hashtable getDictSubKeys(String language,String section,String key,Vector subKeys,HttpSession session) throws Exception {
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		Hashtable hReturn ;
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			if (language==null) language=(String)session.getAttribute(Arg.Language);
			if (language==null) language=framework.getLanguage();
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
					hReturn = servlet.getDictSubKeys(language,section, key, subKeys,framework);
				} else {
					hReturn = Dictionary.getSubKeys(language, section, key, subKeys, framework);
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			} catch (Exception e ) {
				throw OberonException.generateOberonException(e,"getDictSubKeys("+language+","+section+","+key+","+subKeys+")");
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		return hReturn;
	}

	/**
	 * Extract the sub-keyword translations using language,section,key and subkey patterns
	 *
	 * @param language    the dictionary language pattern
	 * @param section     the dictionary section pattern
	 * @param key         the keyword pattern
	 * @param subKey      the sub-key pattern
	 * @param value       the translation pattern
	 * @param xml 	    if true the result will be in xml format       
	 * @param session     http session to retrieve the User framework
	 *
	 * @return  a structured text or xml data list (String)
	 *
	 * @throws Exception  usually due to DB SQL exceptions
	 * @see Dictionary
	 */
	public static String selectDictKeys(String language,String section,String key,String subKey,String value,boolean xml,HttpSession session) throws Exception {
		if (!ConnectionManager.isConnected()) { throw new OberonException("NotConnected",(String[])null); }
		String sReturn ;
		Framework framework = Application.getFramework(session);
		if (framework!=null) {
			if (language==null) language=(String)session.getAttribute(Arg.Language);
			if (language==null) language=framework.getLanguage();
			OBConnection conn = ConnectionManager.getConnection(framework);
			try {
				if (ConnectionManager.isRMIConnection()) {
					RMIClient servlet=(RMIClient)((RMIConnection)conn).getRemoteInterface();
					sReturn = servlet.selectDictKeys(language,section, key, subKey,value,xml,framework);
				} else {
					sReturn = Dictionary.selectKeys(language, section, key, subKey,value,xml, framework);
				}
			} catch (ConnectException ex) { 
				conn.setValid(false);
				throw new OberonException(ex.getMessage());
			}	catch (Exception e ) {
				throw OberonException.generateOberonException(e,"selectDictKeys("+language+","+section+","+key+","+subKey+","+value+","+xml+")");
			} finally {
				ConnectionManager.releaseConnection(conn,framework);
			}
		} else {
			throw new OberonException("FrameworkNull",(String[])null); 
		}
		return sReturn;
	}

	/**
	 * Clean the dictionary cache
	 * @see Dictionary
	 */
	public static void resetDictionaryCache(HttpSession session) throws Exception {
		performOOQL("dictionary remove cache ;", session);
	}


	// Text Form
	//--------------------------------------------------------------------------------------------------------------------------------
	public static String createTextForm( ObjectBase object , Form form , ApplicationRequest request) {
		HttpSession session = request.getSession();
		if (form==null) return "";
		String section = StringUtils.getTagValue(form.getFormat(), "tS");
		if (section.length()==0) { section=Application.dictionary_section; } 
		String sCommonSection=Application.dictionary_common_section;
		if (sCommonSection==null) { sCommonSection=section; }		
		StringBuffer sReturn=new StringBuffer();

		String formName=form.getName();
		String sID=null;
		boolean isObject=true;
		if (object!=null) {
			if ( object instanceof Link ) {	isObject=false;	}
			sID=((ObjectObj)object).getID();
		}
		try {
			Locale locale = 
				Application.app!=null?new Locale(Application.app.translateOberonLanguageToISO639Language((String)session.getAttribute(Arg.Language))):
					new Locale(Application.dictionary_default_language);
				Framework framework = Application.getFramework(session); 
				Hashtable hInputData = setFrameworkEnv(framework,request,true);

				Vector[]vItems = (( isObject )?getFormUserItems(sID,form,"",session):getLinkFormUserItems(sID,form,"",session));   

				Hashtable hObjectData;
				if (object!=null) {
					hObjectData=(( isObject )?getObjectFormData(sID,vItems[0],session):getLinkFormData(sID,vItems[0],session));
				} else { hObjectData=new Hashtable(); }

				Enumeration en = hInputData.keys();
				while (en.hasMoreElements()) {
					String key = (String) en.nextElement();
					hObjectData.put(key,hInputData.get(key));
				}

				String sItemName="";
				for (int i=0;i<vItems[0].size();i++) {
					try {
						Item item = (Item) vItems[0].elementAt(i);
						String sExpression = item.getItemValue();
						sItemName   = item.getName();		  
						int itemType = item.getType();
						String sLabel = StringUtils.getTagValue(item.getFormat(),"L");
						if ( sLabel.length()>0 ) { 
							sReturn.append(Application.translateLabel(sLabel,section)).append(": ");
						} 

						switch (itemType) {
						case Item.ITEMTYPE_BASIC: 
						case Item.ITEMTYPE_FIELD: 
						case Item.ITEMTYPE_XFIELD: 
						case Item.ITEMTYPE_ACTION:   {
							if (itemType==Item.ITEMTYPE_BASIC) { sExpression="_"+sExpression; }
							String sDataValue = (String)hObjectData.get("field['"+sExpression+"']");
							if (sDataValue==null) { 
								if (sExpression.startsWith("_")) {
									sDataValue = (String)hObjectData.get(sExpression.substring(1));
								} else {
									sDataValue = (String)hObjectData.get(sExpression);
								}
								if (sDataValue==null) { sDataValue=""; }
							}
							sReturn.append(getLabel(item,false,section,session));
							int iFieldType = item.getFieldType();
							if (iFieldType==Item.FIELDTYPE_SELECT || iFieldType==Item.FIELDTYPE_CHECKBOX || iFieldType==Item.FIELDTYPE_RADIO) {
								sReturn.append(getDictSubKey(section,sExpression, sDataValue ,session));
							} else if (iFieldType!=Item.FIELDTYPE_HIDDEN) {
								sReturn.append(formatValue(sDataValue,item.getFormat(),locale));
							}						
						} break;

						case Item.ITEMTYPE_GETOBJ:   {
							sReturn.append(getLabel(item,false,section,session));
							String sDataValue=getTranslatedValue(section,item.getFormat(),(String)hObjectData.get(sExpression),session); 
							sReturn.append(formatValue(sDataValue,item.getFormat(),locale));
						}  break;

						case Item.ITEMTYPE_GETLINK:  {
							sReturn.append(getLabel(item,false,section,session));
							String sDataValue=getTranslatedValue(section,item.getFormat(),(String)hObjectData.get(sExpression),session); 
							sReturn.append(formatValue(sDataValue,item.getFormat(),locale));
						} break;

						case Item.ITEMTYPE_SECTION:  {
							sReturn.append("\n").append(Application.getTranslation(section,sExpression));
						} break;

						case Item.ITEMTYPE_SPACER: {
							if ( item.getHeight()>1 ) { 
								sReturn.append("\n");
							} 
						}  break;

						case Item.ITEMTYPE_HREF:  {
							sLabel = getLabel(item,false,section,session);
							String sHref = sExpression;
							if (object!=null) {
								sHref = replaceObjectKeyWords(sExpression,object,false,session);
							} else {
								sHref = replaceDictionaryKeyWords(sExpression,false,session);
							}
							sReturn.append(sLabel.length()>0?Application.getTranslation(section,sLabel):getHrefLabel(sHref));
						} break;

						case Item.ITEMTYPE_LABEL:        {
							sReturn.append(Application.getTranslation(section,sExpression));
						}  break;

						}
						if (item.getFormat().indexOf("</td>")>=0) {
							sReturn.append("\t\t");
						}    
						if (item.getFormat().indexOf("</tr>")>=0) {	
							sReturn.append("\n"); 
						}
					} catch (Exception e) { return "Item "+sItemName+" Exception: "+e.getMessage(); 
					} catch (Error e)     { return "Item "+sItemName+" Error: "+e.getMessage(); }
				}  

		} catch (Exception exx) { return "Form '"+formName+"' Exception: "+exx.getMessage(); 
		} catch (Error exx)     { return "Form '"+formName+"' Error: "+exx.getMessage(); }
		return sReturn.toString();
	}

	private static String getLabel(Item item,boolean isRequired,String section,HttpSession session) throws Exception {
		String sReturn="";
		String sLabel = StringUtils.getTagValue(item.getFormat(),"l");
		if ( sLabel.length()>0 ) { 
			sReturn+=Application.translateLabel(sLabel,section);
			if ( isRequired ) { sReturn+="*"; } 
		}   	 
		return sReturn;
	}

	public static Hashtable setFrameworkEnv(Framework framework,ApplicationRequest request,boolean bTable) {		
		String valueP;	
		String nameP;
		Hashtable hValues = null;
		if (bTable) { hValues=new Hashtable(); }
		Enumeration en=request.getParameterNames();
		if ( en.hasMoreElements() ) framework.emptyEnv();
		while (en.hasMoreElements()) {
			nameP = (String)en.nextElement();
			try {
				valueP = (String)request.getParameter(nameP);		      
				if (nameP.endsWith("[]")) { nameP=nameP.substring(0,nameP.length()-2);}
				if (bTable) { hValues.put("field['"+nameP+"']",valueP); }
				framework.setEnv(nameP,java.net.URLDecoder.decode(valueP,StringUtils.UTF8));	  
			} catch (Exception e) {}
		}
		return hValues;
	}

	private static String getTranslatedValue(String section,String keyFormat,String subKey,HttpSession session )throws Exception {
		String sReturn=subKey;
		String sLabel = StringUtils.getTagValue(keyFormat,"t");
		if ( sLabel.length()>0 ) { 
			if (sLabel.indexOf(",")<0) {
				sReturn=getDictSubKey(section,sLabel,subKey,session);
			} else {
				StringTokenizer sT = new StringTokenizer(sLabel,",");
				sReturn=getDictSubKey(sT.nextToken(),sT.nextToken(),subKey,session);
			}
		}   	 
		return sReturn;
	}  

	private static String getHrefLabel(String sHref) {
		String sLabel;
		int iPos = sHref.indexOf('?');
		if (iPos>0) {
			sLabel= sHref.substring(0,iPos);
		} else {
			sLabel= sHref;
		}
		sLabel = replaceString(sLabel,"%40","@");
		if (sLabel.startsWith("http://")) {  sLabel= sLabel.substring(7); }
		else if (sLabel.startsWith("https://")) {  sLabel= sLabel.substring(8); }
		else if (sLabel.startsWith("mailto:")) {  sLabel= sLabel.substring(7); }
		else if (sLabel.startsWith("mail.jsp")) { sLabel=getStringPart(sHref,"email=","&"); }
		return sLabel;
	}

	private static String formatValue(String value, String formatTags ,Locale locale) {	     
		String format;
		if ( (format = StringUtils.getTagValue(formatTags,"d")).length()>0 ) {
			try {									
				java.util.Date data;
				SimpleDateFormat toformat;
				if (value.length()<=11) {
					SimpleDateFormat fromformat = new SimpleDateFormat("yyyy-MM-dd");
					data = fromformat.parse(value);
					toformat = new SimpleDateFormat(format,locale);
				} else {
					SimpleDateFormat fromformat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss zzz");
					data = fromformat.parse(value); 
					toformat = new SimpleDateFormat(format+" HH:mm:ss zzz",locale);
				}					 
				value=toformat.format(data);
			} catch (Exception e) {  }	 
		} else if ( (format = StringUtils.getTagValue(formatTags,"f")).length()>0 ) {
			try {
				StringTokenizer sT= new StringTokenizer(format,".");
				java.text.DecimalFormat myformat = new DecimalFormat();
				sT.nextToken();
				int iDecimals = Integer.parseInt(sT.nextToken());
				myformat.setMinimumFractionDigits(iDecimals);
				myformat.setMaximumFractionDigits(iDecimals);
				DecimalFormatSymbols newSymbols = new DecimalFormatSymbols(locale);
				String symbols = Application.number_separator_symbols;
				if (symbols!=null && symbols.length()==2) {
					newSymbols.setGroupingSeparator(symbols.charAt(0));
					newSymbols.setDecimalSeparator(symbols.charAt(1));
				} 
				myformat.setDecimalFormatSymbols(newSymbols);
				value=myformat.format(Float.parseFloat(value));
			} catch (Exception e) {}
		} else if (  (format = StringUtils.getTagValue(formatTags,"F")).length()>0   ) {
			value = replaceString(format,"{VALUE}",value);
		}
		return value;
	}

}
