package de.superx.common; import static de.superx.servlet.SxSQL_Server.DEFAULT_MANDANTEN_ID; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.sql.Clob; import java.sql.Connection; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.sql.Statement; import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; import de.memtext.baseobjects.coll.IdObjectSet; import de.memtext.util.NumberUtils; import de.memtext.util.XMLUtils; import de.superx.bin.fm.EtlStarter; import de.superx.util.SqlStringUtils; import freemarker.cache.StringTemplateLoader; import freemarker.template.Configuration; import freemarker.template.SimpleHash; import freemarker.template.SimpleScalar; import freemarker.template.Template; import freemarker.template.TemplateException; import freemarker.template.TemplateHashModel; import freemarker.template.TemplateMethodModelEx; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; import freemarker.template.TemplateSequenceModel; /** * Template Processor for FreeMarker */ public class TemplateProcessor { private Configuration cfg; enum SqlVarType { list, hashlist, hash, hashsequence, sicht, sichtsequence, string, none } // wird für Lieferung ans Applet gebraucht public SxResultSet rawFmTemplates; protected HashMap repositoryMap = new HashMap(); protected String mandantenID = DEFAULT_MANDANTEN_ID; protected Connection con = null; public static final String REPOSITORY_SELECT = "select trim(both from id) as id,caption,content,gueltig_seit,gueltig_bis,comment,sort1,sort2,sort3 from sx_repository where aktiv=1"; static { try { freemarker.log.Logger.selectLoggerLibrary(freemarker.log.Logger.LIBRARY_NONE); } catch (Exception e) { System.out.println("FreeMarker warning: " + e); } } public TemplateProcessor(String mandantenID, Connection con) { this(mandantenID); this.con = con; } public TemplateProcessor(String mandantenID) { this.mandantenID = mandantenID; Configuration myCfg = new Configuration(Configuration.VERSION_2_3_21); myCfg.setTemplateLoader(new StringTemplateLoader()); try { // die Formatierungseinstellung wird u.a. für sortnr gebraucht, // sonst erscheint z.B. 1000 als 1.000 myCfg.setSetting("number_format", "###########.####"); myCfg.setSharedVariable("etl", new EtlStarter(mandantenID)); } catch (TemplateException e) { System.out.println("FreeMarker warning: " + e); e.printStackTrace(); } this.cfg = myCfg; } public void setRepositoryMap(HashMap repository) { this.repositoryMap = repository; } public void add(String id, String ts) { ((StringTemplateLoader) cfg.getTemplateLoader()).putTemplate(id, ts); } /** * synchronized, damit bei Mandantenfähigkeit nicht zufällig praktisch * gleichzeitig, 1 Thread ein Template für 17020 hinterlegt, dann ein 2. Thread * ebenfalls für 17020 (und damit den Eintrag des ersten überschreibt) und der * erste Thread dann fälschlicherweise den Eintrag vom 2. bekommt * CFG.setTemplateLoader(new StringTemplateLoader(stringTemplates)); * * trotz stringTemplates - sind Probleme aufgetaucht, wenn schnell * hintereinander z.b. 3x mal xil_proplist als templateName kam (Makro * eigentlich nicht sekundengenau gleichzeitig) als workaround wird zufällige * Nummer angehängt * * @param templateName * @param template * @return * @throws IOException */ protected synchronized Template buildTemplateObject(String templateName, String template) throws IOException { Template t = null; templateName = cleanName(templateName); templateName += "-rnd:" + NumberUtils.getRandomInt(1000000000); // replace all unicode whitespace chars with regular whitespace // (those can get introduce for example when copy-pasting from html) // TODO: Right place? template = template.replaceAll("\\p{javaSpaceChar}", " "); ((StringTemplateLoader) cfg.getTemplateLoader()).putTemplate(templateName, template); try { t = cfg.getTemplate(templateName); // damit nicht fälschlicherweise nochmal ausgelesen wird cfg.removeTemplateFromCache(templateName); } catch (freemarker.core.ParseException e) { throw new RuntimeException("FreeMarker-Parsing fehlgeschlagen - " + e.getMessage() + "DETAILS: Die Zeilennummer bezieht sich auf folgende Vorlage\n(Masken select_stmt nach SuperX-Verarbeitung mit generateSql)\n" + template); } return t; } /** * Wird vom SuperXDBServlet aufgerufen und von der anderen process Methode * * @param fmSql * @param hmap * @return * @throws TemplateException * @throws IOException * @throws SQLException */ public String process(String templateName, String fmSql, HashMap hmap, String sqldialect) throws TemplateException, IOException, SQLException { StringBuffer b = new StringBuffer(); fmSql = b.toString() + fmSql; templateName = cleanName(templateName); hmap.put("SQLdialect", sqldialect); hmap.putAll(repositoryMap); if (fmSql.indexOf("") > -1) { addSqlVars(hmap, fmSql, sqldialect); fmSql = removeSqlVars(fmSql); } Writer out = new StringWriter(); Template t = buildTemplateObject(templateName, fmSql); t.process(hmap, out); out.close(); return out.toString(); } private synchronized void addSqlVars(HashMap hmap, String fmSql, String sqldialect) throws SQLException, TemplateException, IOException { int i = fmSql.indexOf(""); int i2 = fmSql.indexOf(""); String sqlvarsxml = fmSql.substring(i, i2 + 10); Document doc = null; try { doc = XMLUtils.buildDocumentFromStringWithException(sqlvarsxml, false); } catch (SAXException e) { String msg = "Sqlvars konnten nicht zu XML-Dokument verarbeitet werden, Syntax checken "; System.out.println(msg); System.out.println(sqlvarsxml); e.printStackTrace(); throw new SQLException(msg + "vergl. catalina.out "); } NodeList list = doc.getElementsByTagName("sqlvar"); for (i = 0; i < list.getLength(); i++) { Node n = list.item(i); String varname = XMLUtils.getAttribValue(n, "name"); String sql = n.getTextContent(); SqlVarType type = SqlVarType.none; if ( XMLUtils.hasAttrib(n, "type") ) { type = SqlVarType.valueOf(XMLUtils.getAttribValue(n, "type").toLowerCase()); } String name_intern = ""; if (type == SqlVarType.sichtsequence) { SxResultSet roh = readFromDb(sql); FMListe liste = new FMListe(); for (Iterator it = roh.iterator(); it.hasNext();) { SxResultRow zeile = it.next(); String gewuenschterStand = "today"; liste.add(addSqlVarSicht(hmap, "nur_ein_dummy", (String) zeile.get(0), gewuenschterStand)); } hmap.put(varname, liste); } else if (type == SqlVarType.hashlist) { List results = selectFromDb(sql); hmap.put(varname, results); } else if (type == SqlVarType.sicht) { name_intern = XMLUtils.getAttribValue(n, "name_intern"); String gewuenschterStand = "today"; if (XMLUtils.hasAttrib(n, "stand")) gewuenschterStand = XMLUtils.getAttribValue(n, "stand"); addSqlVarSicht(hmap, varname, name_intern, gewuenschterStand); } else if (type == SqlVarType.list) { SxResultSet roh = readFromDb(sql); List results = new ArrayList(); for (Iterator it = roh.iterator(); it.hasNext();) { SxResultRow zeile = (SxResultRow) it.next(); results.add( zeile.get(0).toString()); } hmap.put(varname, results); } else if (type==SqlVarType.string) { String resultstring = process("sqlvar " + varname + NumberUtils.getRandomInt(99999999), sql, hmap, sqldialect); hmap.put(varname, resultstring); } else { if (SqlStringUtils.tableExists(con, "sx_repository", mandantenID)) { sql = "<#include \"SuperX_general\"/><#include \"SQL_lingua_franca\"/>" + sql; } sql = process("sqlvar " + varname + NumberUtils.getRandomInt(99999999), sql, hmap, sqldialect); SxResultSet roh = readFromDb(sql); FMListe liste = new FMListe(); boolean isSelectableItem = false; Object entry = ""; int rowcount = roh.size(); for (Iterator it = roh.iterator(); it.hasNext();) { SxResultRow zeile = (SxResultRow) it.next(); if (type == SqlVarType.hash || type == SqlVarType.hashsequence) { SxHash h = new SxHash(); for (int j = 0; j < zeile.size(); j++) { String key = roh.getColumnName(j).toLowerCase(); Object value = zeile.get(j); h.put(key, value); } if (rowcount == 1) { entry = h; } liste.add(h); } else { entry = zeile.get(0); if (entry == null) entry = ""; String nam = ""; if (zeile.size() > 1) { nam = SqlStringUtils.getValueAsString(zeile.get(1)); isSelectableItem = true; } SelectableItem item = new SelectableItem(entry, nam); if (zeile.size() > 2) { item.setStrukturInfo(zeile.get(2)); } if (zeile.size() > 3) { item.setStrukturInfo(zeile.get(3)); } liste.add(new SelectableItemNode(item)); } } if (type == SqlVarType.hashsequence || (type == SqlVarType.hash && rowcount > 1) || isSelectableItem) { hmap.put(varname, liste); liste = null; } else { hmap.put(varname, entry); entry = null; } } } } /** * Gets the result for sql query and returns it as a list of maps. * Each map contains one row of the result with the column labels * as keys (String, lower case!) and the data as values (String). * @param sql to execute on this.dbHandler * @return List of Map result of the sql query * @throws SQLException */ protected List selectFromDb(String sql) throws SQLException { List result = new ArrayList<>(); try (Statement st = con.createStatement(); ResultSet res = st.executeQuery(sql)) { ResultSetMetaData meta = res.getMetaData(); int colCount = meta.getColumnCount(); List colLabels = new ArrayList<>(colCount); for (int i = 0; i < colCount; i++) { colLabels.add(meta.getColumnLabel(i + 1)); } while (res.next()) { Map row = new HashMap<>(); for (String colLabel : colLabels) { row.put(colLabel, res.getString(colLabel)); } result.add(new SimpleHash(row)); } } return result; } /** * Konkret umgegesetzt in bin/FMParser * * @param hmap * @param varname * @param name_intern * @param gewuenschterStand * @return * @throws SQLException */ protected StandaloneSicht addSqlVarSicht(HashMap hmap, String varname, String name_intern, String gewuenschterStand) throws SQLException { throw new IllegalStateException("sqlvar mit type Sicht wird hier nicht unterstützt"); } protected SxResultSet readFromDb(String sql) throws SQLException { return new SxResultSet(); } private String removeSqlVars(String fmSql) { int i = fmSql.indexOf(""); int i2 = fmSql.indexOf(""); return fmSql.substring(0, i) + fmSql.substring(i2 + 10, fmSql.length()); } protected String cleanName(String templateName) { // es dürfen keine nach Dir-Trennzeichen aussehenden Bezeichnungen // vorkommen if (templateName.indexOf("/") > -1) templateName = templateName.replace('/', ' '); if (templateName.indexOf("\\") > -1) templateName = templateName.replace('\\', ' '); return templateName; } public void setTemplates(SxResultSet rawFmTemplates) { this.rawFmTemplates = rawFmTemplates; if (rawFmTemplates == null) { throw new RuntimeException("Keine FreeMarker Templates geliefert"); } for (Iterator it = rawFmTemplates.iterator(); it.hasNext();) { SxResultRow row = (SxResultRow) it.next(); String name = (String) row.get(0); String content = SqlStringUtils.getValueAsString(row.get(1)); add(name, content); cfg.addAutoInclude(name); } } /** * Repository INfo in eine Map packen * * @param Daten nach REPOSITORY_SELECT * @param map */ public static void repositoryToMap(SxResultSet rs, HashMap map) { IdObjectSet riColls = new IdObjectSet(); for (Iterator it = rs.iterator(); it.hasNext();) { SxResultRow row = (SxResultRow) it.next(); Object id = row.get(0); String name = (String) row.get(1); RepositoryItem ri = new RepositoryItem(); ri.setId(id); ri.setName(name); ri.setContent(SqlStringUtils.getValueAsString(row.get(2))); ri.setValidSince((Date) row.get(3)); ri.setValidTill((Date) row.get(4)); ri.setComment(SqlStringUtils.getValueAsString(row.get(5))); // TODO sortnr RepositoryItemCollection riColl = null; if (riColls.containsItemWithId(id)) { // Gibt schon eine Coll - einfach hinzufügen riColl = (RepositoryItemCollection) riColls.getById(id); } else { // gibt noch keine Coll für id neu anlegen riColl = new RepositoryItemCollection(id); riColls.add(riColl); } riColl.add(ri); } for (Iterator it = riColls.iterator(); it.hasNext();) { RepositoryItemCollection rcoll = (RepositoryItemCollection) it.next(); map.put(rcoll.getId(), rcoll); } } class FMListe implements TemplateMethodModelEx, TemplateSequenceModel { private static final long serialVersionUID = 1L; private LinkedList liste = new LinkedList(); public void add(Object o) { liste.add(o); } public Object exec(List l) throws TemplateModelException { Object result = null; if (l == null) { throw new IllegalArgumentException("keine Argumente übergeben"); } String key = ""; if (l.get(0).toString().equalsIgnoreCase("get")) { if (l.size() < 3) { throw new TemplateModelException("Syntax: get,id-column Schlüssel - mindestens 3 Elemente nötig"); } String idcolumn = l.get(1).toString(); key = l.get(2).toString(); for (Iterator it = liste.iterator(); it.hasNext();) { Object o = it.next(); if (o instanceof SxHash) { SxHash h = (SxHash) o; if (!h.containsKey(idcolumn)) throw new IllegalArgumentException("idcolumn " + idcolumn + " nicht gefunden"); if (h.get(idcolumn).toString().equals(key)) result = h; } } } else if (l.get(0).toString().equalsIgnoreCase("getById")) { if (l.size() < 2) throw new TemplateModelException("Syntax: getbyId,Schlüssel - mindestens 2 Elemente nötig"); for (Iterator it = liste.iterator(); it.hasNext();) { Object o = it.next(); if (o instanceof SelectableItem) { SelectableItem s = (SelectableItem) o; if (s.getId().toString().equals(key)) result = s; } } } else throw new IllegalArgumentException("nur getItem,Schlüssel implementiert"); if (result == null) { throw new IllegalArgumentException("Element " + key + " nicht gefunden in Freemarker hash"); } return result; } public TemplateModel get(int i) throws TemplateModelException { Object o = liste.get(i); TemplateModel result = null; if (o instanceof SxHash) { result = (SxHash) o; } else { result = new freemarker.template.DefaultObjectWrapper().wrap(o); } return result; } public int size() throws TemplateModelException { return liste.size(); } } class SxHash implements TemplateHashModel { private HashMap map = new HashMap(); public void put(String key, Object value) { map.put(key, value); } public boolean containsKey(Object key) { return map.containsKey(key); } public TemplateModel get(String key) throws TemplateModelException { if (!map.containsKey(key)) { throw new IllegalArgumentException("Kein attribut " + key + " bekannt"); } Object val = map.get(key); if (val == null) val = ""; TemplateModel result = null; if (val instanceof String) { result = new SimpleScalar(val.toString()); } else if (val instanceof java.sql.Date) { result = new freemarker.template.SimpleDate((java.sql.Date) val); } else if (val instanceof java.lang.Number) { result = new freemarker.template.SimpleNumber((java.lang.Number) val); } else if (val instanceof Clob) { result = new SimpleScalar(SqlStringUtils.getValueAsString(val)); } return result; } public boolean isEmpty() throws TemplateModelException { return map.isEmpty(); } } } // Created on 12.02.2005 at 13:46:05