0001: /*
0002: * Copyright 2003 The Apache Software Foundation.
0003: *
0004: * Licensed under the Apache License, Version 2.0 (the "License");
0005: * you may not use this file except in compliance with the License.
0006: * You may obtain a copy of the License at
0007: *
0008: * http://www.apache.org/licenses/LICENSE-2.0
0009: *
0010: * Unless required by applicable law or agreed to in writing, software
0011: * distributed under the License is distributed on an "AS IS" BASIS,
0012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0013: * See the License for the specific language governing permissions and
0014: * limitations under the License.
0015: */
0016:
0017: package velosurf.sql;
0018:
0019: import java.util.Iterator;
0020: import java.util.List;
0021: import java.util.ArrayList;
0022: import java.util.StringTokenizer;
0023: import java.util.Arrays;
0024: import java.util.Date;
0025: import java.util.regex.Pattern;
0026: import java.io.InputStream;
0027: import java.sql.SQLException;
0028: import java.text.DateFormat;
0029: import java.text.SimpleDateFormat;
0030:
0031: import org.jdom.Document;
0032: import org.jdom.Element;
0033: import org.jdom.Text;
0034: import org.jdom.input.SAXBuilder;
0035:
0036: import velosurf.util.Logger;
0037: import velosurf.util.StringLists;
0038: import velosurf.util.Strings;
0039: import velosurf.util.XIncludeResolver;
0040: import velosurf.cache.Cache;
0041: import velosurf.model.Entity;
0042: import velosurf.model.Action;
0043: import velosurf.model.Attribute;
0044: import velosurf.model.Transaction;
0045: import velosurf.model.ImportedKey;
0046: import velosurf.model.ExportedKey;
0047: import velosurf.validation.Email;
0048: import velosurf.validation.Length;
0049: import velosurf.validation.Range;
0050: import velosurf.validation.NotNull;
0051: import velosurf.validation.OneOf;
0052: import velosurf.validation.Reference;
0053: import velosurf.validation.Regex;
0054: import velosurf.validation.FieldConstraint;
0055: import velosurf.validation.DateRange;
0056: import velosurf.validation.NotEmpty;
0057:
0058: /** A configuration loader for the Database object.
0059: *
0060: * @author <a href="mailto:claude.brisson@gmail.com">Claude Brisson</a>
0061: */
0062:
0063: public class ConfigLoader {
0064:
0065: /** Database. */
0066: private Database database = null;
0067: /** <<code>xi:include</code>> tag resolver. */
0068: private XIncludeResolver xincludeResolver = null;
0069: /** Syntax checker pattern for the <code>result</code> attribute of <<code>attribute</code>> tags. */
0070: private static final Pattern attributeResultSyntax = Pattern
0071: .compile("^scalar|(?:(?:row|rowset)(?:/.+)?)$");
0072:
0073: /**
0074: * Constructor.
0075: * @param db database
0076: */
0077: public ConfigLoader(Database db) {
0078: this (db, null);
0079: }
0080:
0081: /**
0082: * Constructor.
0083: * @param db database
0084: * @param xincludeResolver <<code>xi:include</code>> tag resolver
0085: */
0086: public ConfigLoader(Database db, XIncludeResolver xincludeResolver) {
0087: database = db;
0088: this .xincludeResolver = xincludeResolver;
0089: }
0090:
0091: /**
0092: * Main method of the ConfigLoader.
0093: * @param config the configuration input stream
0094: * @throws Exception
0095: */
0096: public void loadConfig(InputStream config) throws Exception {
0097:
0098: Logger.info("reading properties...");
0099:
0100: /* build JDOM tree */
0101: Document document = new SAXBuilder().build(config);
0102: if (xincludeResolver != null) {
0103: document = xincludeResolver.resolve(document);
0104: }
0105: Element database = document.getRootElement();
0106:
0107: setDatabaseAttributes(database);
0108:
0109: /* define root attributes */
0110: defineAttributes(database, this .database.getRootEntity());
0111:
0112: /* define root actions */
0113: defineActions(database, this .database.getRootEntity());
0114:
0115: /* define entities */
0116: defineEntities(database);
0117:
0118: Logger.info("Config file successfully read.");
0119: }
0120:
0121: /**
0122: * Adapt the case to match chosen database case policy.
0123: * @param str string to adapt
0124: * @return adapted string
0125: */
0126: private String adaptCase(String str) {
0127: return database.adaptCase(str);
0128: }
0129:
0130: /**
0131: * Parses database XML attributes.
0132: * @param database parent element
0133: */
0134: private void setDatabaseAttributes(Element database) {
0135:
0136: /* log level */
0137: String loglevel = database.getAttributeValue("loglevel");
0138: if (checkSyntax("loglevel", loglevel, new String[] { "trace",
0139: "debug", "info", "warn", "error", "fatal" })) {
0140: if ("trace".equalsIgnoreCase(loglevel))
0141: Logger.setLogLevel(Logger.TRACE_ID);
0142: else if ("debug".equalsIgnoreCase(loglevel))
0143: Logger.setLogLevel(Logger.DEBUG_ID);
0144: else if ("info".equalsIgnoreCase(loglevel))
0145: Logger.setLogLevel(Logger.INFO_ID);
0146: else if ("warn".equalsIgnoreCase(loglevel))
0147: Logger.setLogLevel(Logger.WARN_ID);
0148: else if ("error".equalsIgnoreCase(loglevel))
0149: Logger.setLogLevel(Logger.ERROR_ID);
0150: else if ("fatal".equalsIgnoreCase(loglevel))
0151: Logger.setLogLevel(Logger.FATAL_ID);
0152: else
0153: Logger
0154: .error("Invalid loglevel. Should be one of: trace, debug, info, warn, error, fatal.");
0155: }
0156:
0157: /* default-access - deprecated - for compatibility only, replaced with read-only=true|false */
0158: String access = database.getAttributeValue("default-access");
0159: if (access != null) {
0160: Logger
0161: .warn("The syntax <database default-access=\"rw|ro\"> is deprecated.");
0162: Logger
0163: .warn("Please use <database read-only=\"true|false\"> instead.");
0164: if (checkSyntax("access", access,
0165: new String[] { "ro", "rw" })) {
0166: this .database.setReadOnly(!access
0167: .equalsIgnoreCase("rw"));
0168: }
0169: }
0170:
0171: /* read-only */
0172: String ro = database.getAttributeValue("read-only");
0173: if (ro != null) {
0174: /* check syntax but continue anyway with read-only database if the syntax is bad */
0175: checkSyntax("read-only", ro, new String[] { "true",
0176: "false", "yes", "no" });
0177: /* default to true - using Boolean.parseBoolean is not possible */
0178: this .database.setReadOnly(!ro.equalsIgnoreCase("false")
0179: && !ro.equalsIgnoreCase("no"));
0180: }
0181:
0182: String caching = database.getAttributeValue("default-caching");
0183: if (caching != null) {
0184: Logger
0185: .warn("attribute 'default-caching' is deprecatd, please use 'caching' instead.");
0186: } else {
0187: caching = database.getAttributeValue("caching");
0188: }
0189: if (checkSyntax("caching", caching, new String[] { "none",
0190: "no", "yes", "soft", "full" })) {
0191: int val = parseCaching(caching);
0192: this .database.setCaching(val);
0193: if (val == Cache.FULL_CACHE)
0194: Logger
0195: .warn("The 'full' caching method is deprecated and will be removed in future versions.");
0196: }
0197:
0198: String reverseMode = database.getAttributeValue("reverse");
0199: if (checkSyntax("reverse", reverseMode, new String[] { "none",
0200: "partial", "tables", "full" })) {
0201: int mode = -1;
0202: if ("full".equalsIgnoreCase(reverseMode)
0203: || reverseMode == null) {
0204: mode = ReverseEngineer.REVERSE_FULL;
0205: } else if ("partial".equalsIgnoreCase(reverseMode)) {
0206: mode = ReverseEngineer.REVERSE_PARTIAL;
0207: } else if ("tables".equalsIgnoreCase(reverseMode)) {
0208: mode = ReverseEngineer.REVERSE_TABLES;
0209: } else if ("none".equalsIgnoreCase(reverseMode)) {
0210: mode = ReverseEngineer.REVERSE_NONE;
0211: }
0212: this .database.getReverseEngineer().setReverseMode(mode);
0213: }
0214:
0215: Element credentials = database.getChild("credentials");
0216: if (credentials == null) {
0217: credentials = database;
0218: }
0219:
0220: this .database.setUser(credentials.getAttributeValue("user"));
0221: this .database.setPassword(credentials
0222: .getAttributeValue("password"));
0223:
0224: String url = credentials.getAttributeValue("url");
0225: if (url == null) {
0226: url = database.getAttributeValue("url");
0227: }
0228: this .database.setURL(url);
0229:
0230: String driver = credentials.getAttributeValue("driver");
0231: if (driver == null) {
0232: driver = database.getAttributeValue("driver");
0233: }
0234: this .database.setDriver(driver);
0235:
0236: String schema = adaptCase(credentials
0237: .getAttributeValue("schema"));
0238: if (schema == null) {
0239: schema = adaptCase(database.getAttributeValue("schema"));
0240: }
0241: if (schema != null) {
0242: this .database.setSchema(schema);
0243: }
0244:
0245: /* load driver now so as to know default behaviours */
0246: this .database.loadDriver();
0247:
0248: int min = 0;
0249: String minstr = database.getAttributeValue("min-connections");
0250: if (minstr != null) {
0251: try {
0252: min = Integer.parseInt(minstr);
0253: if (min > 0)
0254: this .database.setMinConnections(min);
0255: else
0256: Logger
0257: .error("the parameter 'min-connections' wants an integer > 0 !");
0258: } catch (NumberFormatException nfe) {
0259: Logger
0260: .error("the parameter 'min-connections' wants an integer!");
0261: }
0262: }
0263:
0264: String maxstr = database.getAttributeValue("max-connections");
0265: if (maxstr != null) {
0266: try {
0267: int max = Integer.parseInt(maxstr);
0268: if (max >= min)
0269: this .database.setMaxConnections(max);
0270: else
0271: Logger
0272: .error("the parameter 'max-connections' must be >= min-connection!");
0273: } catch (NumberFormatException nfe) {
0274: Logger
0275: .error("the parameter 'max-connections' wants an integer!");
0276: }
0277: }
0278:
0279: this .database.setSeed(database.getAttributeValue("seed"));
0280:
0281: String caseSensivity = database.getAttributeValue("case");
0282: /* if case-sensivity has not been set explicitely, deduce it from the driver */
0283: if (caseSensivity == null) {
0284: caseSensivity = this .database.getDriverInfo()
0285: .getCaseSensivity();
0286: }
0287: if (checkSyntax("case", caseSensivity, new String[] {
0288: "sensitive", "uppercase", "lowercase" })) {
0289: Logger.info("Case sensivity: " + caseSensivity);
0290: if ("sensitive".equalsIgnoreCase(caseSensivity)) {
0291: this .database.setCase(Database.CASE_SENSITIVE);
0292: } else if ("uppercase".equalsIgnoreCase(caseSensivity)) {
0293: this .database.setCase(Database.UPPERCASE);
0294: } else if ("lowercase".equalsIgnoreCase(caseSensivity)) {
0295: this .database.setCase(Database.LOWERCASE);
0296: }
0297: }
0298:
0299: /* root database entity (never read-only koz not a real entity and we must allow external parameters */
0300: Entity root = new Entity(this .database, "velosurf.root", false,
0301: Cache.NO_CACHE);
0302: this .database.addEntity(root);
0303: }
0304:
0305: /**
0306: * Define Velosurf attributes.
0307: * @param parent parent XML element
0308: * @param entity parent entity
0309: * @throws SQLException
0310: */
0311: @SuppressWarnings("deprecation")
0312: private void defineAttributes(Element parent, Entity entity)
0313: throws SQLException {
0314: for (Iterator attributes = parent.getChildren("attribute")
0315: .iterator(); attributes.hasNext();) {
0316: Element element = (Element) attributes.next();
0317: String name = adaptCase(element.getAttributeValue("name"));
0318: Attribute attribute = new Attribute(name, entity);
0319: String result = element.getAttributeValue("result");
0320: if (result == null) {
0321: Logger
0322: .warn("Attribute '"
0323: + name
0324: + "' doesn't have a 'result' attribute... using rowset as default.");
0325: result = "rowset";
0326: }
0327: if (attributeResultSyntax.matcher(result).matches()) {
0328: int type = 0;
0329: if (result.equals("scalar")) {
0330: type = Attribute.SCALAR;
0331: } else if (result.startsWith("rowset")) {
0332: type = Attribute.ROWSET;
0333: } else if (result.startsWith("row")) {
0334: type = Attribute.ROW;
0335: } else {
0336: throw new SQLException(
0337: "bad syntax for the 'result' attribute: "
0338: + result);
0339: }
0340: attribute.setResultType(type);
0341: int slash = result.indexOf("/");
0342: if (slash > -1 && slash + 1 < result.length()) {
0343: attribute.setResultEntity(adaptCase(result
0344: .substring(slash + 1)));
0345: }
0346: }
0347:
0348: String foreignKey = element
0349: .getAttributeValue("foreign-key");
0350: if (foreignKey != null) {
0351: Logger
0352: .warn("use of the foreign-key attribute for the <attribute> tag is deprecated. Please use <imported-key>");
0353: if (attribute.getResultEntity() == null) {
0354: throw new SQLException(
0355: "Attribute '"
0356: + name
0357: + "' is a foreign key, Velosurf needs to know its result entity!");
0358: }
0359: attribute.setForeignKeyColumn(foreignKey);
0360: }
0361:
0362: /* attribute parameters and query */
0363: if (foreignKey != null) {
0364: attribute.addParamName(adaptCase(foreignKey));
0365: } else {
0366: String query = "";
0367: Iterator queryElements = element.getContent()
0368: .iterator();
0369: while (queryElements.hasNext()) {
0370: Object content = queryElements.next();
0371: if (content instanceof Text)
0372: query += Strings
0373: .trimSpacesAndEOL(((Text) content)
0374: .getText());
0375: else if (content instanceof Element) {
0376: query += " ? ";
0377: Element elem = (Element) content;
0378: attribute
0379: .addParamName(adaptCase(elem.getName()));
0380: } else {
0381: Logger
0382: .error("Try upgrading your jdom library!");
0383: throw new SQLException(
0384: "Was expecting an org.jdom.Element, found a "
0385: + content.getClass().getName()
0386: + ": '" + content + "'");
0387: }
0388: }
0389: /* trim */
0390: query = Pattern.compile(";\\s*\\Z").matcher(query)
0391: .replaceFirst("");
0392: attribute.setQuery(query);
0393: }
0394:
0395: /* caching */
0396: String caching = element.getAttributeValue("caching");
0397: if (checkSyntax("caching", caching, new String[] { "no",
0398: "yes" })) {
0399: attribute.setCaching(caching.equals("yes"));
0400: }
0401:
0402: entity.addAttribute(attribute);
0403: }
0404: }
0405:
0406: /**
0407: * Define foreign keys.
0408: * @param parent parent XML element
0409: * @param entity parent entity
0410: */
0411: private void defineForeignKeys(Element parent, Entity entity) {
0412: for (Iterator imported = parent.getChildren("imported-key")
0413: .iterator(); imported.hasNext();) {
0414: Element keyelem = (Element) imported.next();
0415: String name = keyelem.getAttributeValue("name");
0416: if (name == null) {
0417: Logger
0418: .error("tag <imported-key> needs a 'name' attribute!");
0419: continue;
0420: }
0421: String pkEntity = keyelem.getAttributeValue("entity");
0422: if (pkEntity == null) {
0423: Logger
0424: .error("tag <imported-key> needs an 'entity' attribute (name='"
0425: + name + "')!");
0426: continue;
0427: }
0428: List<String> fkCols = null;
0429: String foreignCols = keyelem
0430: .getAttributeValue("foreign-cols");
0431: if (foreignCols != null) {
0432: fkCols = new ArrayList<String>();
0433: List<String> aliases = Arrays.asList(foreignCols
0434: .split(","));
0435:
0436: /* resolve names */
0437: for (String col : aliases) {
0438: fkCols.add(entity.resolveName(col));
0439: }
0440: }
0441:
0442: ImportedKey importedKey = new ImportedKey(name, entity,
0443: pkEntity, fkCols);
0444:
0445: /* caching */
0446: String caching = keyelem.getAttributeValue("caching");
0447: if (checkSyntax("caching", caching, new String[] { "no",
0448: "yes" })) {
0449: importedKey.setCaching(caching.equals("yes"));
0450: }
0451:
0452: entity.addAttribute(importedKey);
0453: }
0454: for (Iterator exported = parent.getChildren("exported-key")
0455: .iterator(); exported.hasNext();) {
0456: Element keyelem = (Element) exported.next();
0457: String name = keyelem.getAttributeValue("name");
0458: if (name == null) {
0459: Logger
0460: .error("tag <exported-key> needs a 'name' attribute!");
0461: continue;
0462: }
0463: String pkEntity = keyelem.getAttributeValue("entity");
0464: if (pkEntity == null) {
0465: Logger
0466: .error("tag <exported-key> needs an 'entity' attribute (name='"
0467: + name + "')!");
0468: continue;
0469: }
0470: List<String> fkCols = null;
0471: String foreignCols = keyelem
0472: .getAttributeValue("foreign-cols");
0473: if (foreignCols != null) {
0474: fkCols = new ArrayList<String>();
0475: List<String> aliases = Arrays.asList(foreignCols
0476: .split(","));
0477:
0478: /* resolve names */
0479: for (String col : aliases) {
0480: fkCols.add(entity.resolveName(col));
0481: }
0482: }
0483: ExportedKey exportedKey = new ExportedKey(name, entity,
0484: pkEntity, fkCols);
0485:
0486: /* caching */
0487: String caching = keyelem.getAttributeValue("caching");
0488: if (checkSyntax("caching", caching, new String[] { "no",
0489: "yes" })) {
0490: exportedKey.setCaching(caching.equals("yes"));
0491: }
0492: String order = keyelem.getAttributeValue("order");
0493: if (order != null) {
0494: exportedKey.setOrder(order);
0495: }
0496:
0497: entity.addAttribute(exportedKey);
0498: }
0499: }
0500:
0501: /**
0502: * Define actions.
0503: * @param parent parent XML element
0504: * @param entity parent entity
0505: */
0506: private void defineActions(Element parent, Entity entity) {
0507: for (Iterator actions = parent.getChildren("action").iterator(); actions
0508: .hasNext();) {
0509: Element element = (Element) actions.next();
0510: String name = adaptCase(element.getAttributeValue("name"));
0511: Action action = null;
0512: if (isTransaction(element)) {
0513: Transaction transaction = new Transaction(name, entity);
0514: action = transaction;
0515: List<String> queries = new ArrayList<String>();
0516: List<List<String>> parameters = new ArrayList<List<String>>();
0517: StringBuilder query = new StringBuilder();
0518: List<String> paramNames = new ArrayList<String>();
0519: Iterator queryElements = element.getContent()
0520: .iterator();
0521: while (queryElements.hasNext()) {
0522: Object content = queryElements.next();
0523: if (content instanceof Text) {
0524: String text = Strings
0525: .trimSpacesAndEOL(((Text) content)
0526: .getText());
0527: int i = text.indexOf(';');
0528: if (i != -1) {
0529: query.append(text.substring(0, i));
0530: queries.add(query.toString());
0531: parameters.add(paramNames);
0532: query = new StringBuilder();
0533: paramNames = new ArrayList<String>();
0534: }
0535: query.append(text.substring(i + 1));
0536: } else {
0537: query.append(" ? ");
0538: Element elem = (Element) content;
0539: paramNames.add(elem.getName());
0540: }
0541: }
0542: if (query.length() > 0) {
0543: queries.add(query.toString());
0544: parameters.add(paramNames);
0545: }
0546: transaction.setQueries(queries);
0547: transaction.setParamNamesLists(parameters);
0548: } else { /* simple action */
0549: action = new Action(name, entity);
0550: String query = "";
0551: Iterator queryElements = element.getContent()
0552: .iterator();
0553: while (queryElements.hasNext()) {
0554: Object content = queryElements.next();
0555: if (content instanceof Text) {
0556: query += Strings
0557: .trimSpacesAndEOL(((Text) content)
0558: .getText());
0559: } else {
0560: query += " ? ";
0561: Element elem = (Element) content;
0562: action.addParamName(adaptCase(elem.getName()));
0563: }
0564: }
0565: action.setQuery(query);
0566: }
0567: entity.addAction(action);
0568: }
0569: }
0570:
0571: /**
0572: * Define entities.
0573: * @param database database XML element
0574: * @throws Exception
0575: */
0576: private void defineEntities(Element database) throws Exception {
0577: for (Iterator entities = database.getChildren("entity")
0578: .iterator(); entities.hasNext();) {
0579: Element element = (Element) entities.next();
0580: String origName = element.getAttributeValue("name");
0581: element.removeAttribute("name");
0582: String name = adaptCase(origName);
0583: String table = adaptCase(element.getAttributeValue("table"));
0584: element.removeAttribute("table");
0585:
0586: Entity entity = this .database
0587: .getEntityCreate(adaptCase(name));
0588: if (table != null) {
0589: entity.setTableName(table);
0590: }
0591: this .database.getReverseEngineer().addTableMatching(
0592: entity.getTableName(), entity);
0593:
0594: /* custom class */
0595: String cls = element.getAttributeValue("class");
0596: element.removeAttribute("class");
0597: if (cls != null) {
0598: try {
0599: Class clazz = Class.forName(cls);
0600: clazz.newInstance();
0601: /* looks ok */
0602: entity.setInstanceClass(cls);
0603: } catch (Throwable t) {
0604: Logger.error("Cannot instantiate class " + cls);
0605: Logger.log(t);
0606: }
0607: }
0608:
0609: /* access (deprecated) */
0610: String access = element.getAttributeValue("access");
0611: if (access != null) {
0612: Logger
0613: .warn("The syntax <entity access=\"rw|ro\"> is deprecated.");
0614: Logger
0615: .warn("Please use <entity read-only=\"true|false\"> instead.");
0616: if (checkSyntax(name + ".access", access, new String[] {
0617: "ro", "rw" })) {
0618: access = access.toLowerCase();
0619: if (access.equalsIgnoreCase("ro"))
0620: entity.setReadOnly(true);
0621: else if (access.equalsIgnoreCase("rw"))
0622: entity.setReadOnly(false);
0623: }
0624: }
0625:
0626: /* read-only */
0627: String ro = element.getAttributeValue("read-only");
0628: element.removeAttribute("read-only");
0629: if (ro != null) {
0630: /* check syntax but continue anyway with read-only database if the syntax is bad */
0631: checkSyntax("read-only", ro, new String[] { "true",
0632: "false" });
0633: /* default to true - using Boolean.parseBoolean is not possible */
0634: this .database
0635: .setReadOnly(!ro.equalsIgnoreCase("false"));
0636: }
0637:
0638: /* caching */
0639: String caching = element.getAttributeValue("caching");
0640: element.removeAttribute("caching");
0641: if (checkSyntax("caching", caching, new String[] { "none",
0642: "no", "yes", "soft", "full" }))
0643: entity.setCachingMethod(parseCaching(caching));
0644:
0645: /* obfuscation */
0646: String obfuscate = element.getAttributeValue("obfuscate");
0647: element.removeAttribute("obfuscate");
0648: if (obfuscate != null) {
0649: List<String> obfuscatedCols = new ArrayList<String>();
0650: StringTokenizer tokenizer = new StringTokenizer(
0651: obfuscate, ", ");
0652: while (tokenizer.hasMoreTokens()) {
0653: obfuscatedCols
0654: .add(adaptCase(tokenizer.nextToken()));
0655: }
0656: entity.setObfuscated(obfuscatedCols);
0657: }
0658:
0659: /* localization */
0660: String localize = element.getAttributeValue("localize");
0661: element.removeAttribute("localize");
0662: if (localize != null) {
0663: List<String> localizedCols = new ArrayList<String>();
0664: StringTokenizer tokenizer = new StringTokenizer(
0665: localize, ", ");
0666: while (tokenizer.hasMoreTokens()) {
0667: localizedCols.add(adaptCase(tokenizer.nextToken()));
0668: }
0669: entity.setLocalized(localizedCols);
0670: }
0671:
0672: /* aliases */
0673: Element aliases = element.getChild("aliases");
0674: if (aliases != null) {
0675: for (org.jdom.Attribute att : (List<org.jdom.Attribute>) aliases
0676: .getAttributes()) {
0677: String column = att.getValue();
0678: String alias = att.getName();
0679: entity.addAlias(alias, column);
0680: }
0681: }
0682:
0683: /* define entity attributes */
0684: defineAttributes(element, entity);
0685:
0686: /* define entity actions */
0687: defineActions(element, entity);
0688:
0689: /* define entity imported and exported keys */
0690: defineForeignKeys(element, entity);
0691:
0692: /* define entity constraints */
0693: defineConstraints(element, entity);
0694: }
0695: }
0696:
0697: /**
0698: * Define constraints.
0699: * @param element parent XML element
0700: * @param entity parent entity
0701: * @throws Exception
0702: */
0703: private void defineConstraints(Element element, Entity entity)
0704: throws Exception {
0705: DateFormat format = new SimpleDateFormat("yyyyMMdd");
0706: String str;
0707: for (Iterator columns = element.getChildren("constraint")
0708: .iterator(); columns.hasNext();) {
0709: Element colElement = (Element) columns.next();
0710: String column = colElement.getAttributeValue("column");
0711: if (column == null) {
0712: Logger
0713: .error("constraint tag needs a 'column' attribute");
0714: continue;
0715: }
0716: colElement.removeAttribute("column");
0717:
0718: String type;
0719: boolean hasType = ((type = colElement
0720: .getAttributeValue("type")) != null);
0721:
0722: /* short-syntax length */
0723: int minLen = 0, maxLen = Integer.MAX_VALUE;
0724: String minstr = colElement.getAttributeValue("min-len");
0725: String maxstr = colElement.getAttributeValue("max-len");
0726: if (minstr != null || maxstr != null) {
0727: colElement.removeAttribute("min-len");
0728: colElement.removeAttribute("max-len");
0729: if (minstr != null) {
0730: minLen = Integer.parseInt(minstr);
0731: }
0732: if (maxstr != null) {
0733: maxLen = Integer.parseInt(maxstr);
0734: }
0735: entity
0736: .addConstraint(column, new Length(minLen,
0737: maxLen));
0738: }
0739: /* short-syntax range */
0740: minstr = colElement.getAttributeValue("min");
0741: maxstr = colElement.getAttributeValue("max");
0742: if (minstr != null || maxstr != null || hasType
0743: && ("integer".equals(type) | "number".equals(type))) {
0744: colElement.removeAttribute("min");
0745: colElement.removeAttribute("max");
0746: Range numberConstraint = null;
0747: numberConstraint = new Range();
0748: if (minstr != null) {
0749: Number min = Double.parseDouble(minstr);
0750: numberConstraint.setMin(min);
0751: }
0752: if (maxstr != null) {
0753: Number max = Double.parseDouble(maxstr);
0754: numberConstraint.setMax(max);
0755: }
0756: if (hasType && "integer".equals(type)) {
0757: numberConstraint.setInteger(true);
0758: colElement.removeAttribute("type");
0759: }
0760: entity.addConstraint(column, numberConstraint);
0761: }
0762: /* short-syntax date range - use yyyyMMdd date format */
0763: String afterstr = colElement.getAttributeValue("after");
0764: String beforestr = colElement.getAttributeValue("before");
0765: if (afterstr != null || beforestr != null || hasType
0766: && "date".equals(type)) {
0767: colElement.removeAttribute("after");
0768: colElement.removeAttribute("before");
0769: DateRange dateConstraint = new DateRange();
0770: if (afterstr != null) {
0771: Date after = format.parse(afterstr);
0772: dateConstraint.setAfterDate(after);
0773: }
0774: if (beforestr != null) {
0775: Date before = format.parse(beforestr);
0776: dateConstraint.setBeforeDate(before);
0777: }
0778: if (hasType && "date".equals(type)) {
0779: colElement.removeAttribute("type");
0780: }
0781: entity.addConstraint(column, dateConstraint);
0782: }
0783: /* short-syntax, email */
0784: if (hasType && "email".equals(type)) {
0785: entity.addConstraint(column, new Email());
0786: colElement.removeAttribute("type");
0787: }
0788: /* short-syntax, others */
0789: for (Iterator atts = colElement.getAttributes().iterator(); atts
0790: .hasNext();) {
0791: org.jdom.Attribute attribute = (org.jdom.Attribute) atts
0792: .next();
0793: String name = attribute.getName();
0794: String value = attribute.getValue();
0795: if (name.equals("not-null")) {
0796: if (attribute.getBooleanValue()) {
0797: entity.addConstraint(column, new NotNull());
0798: }
0799: }
0800: if (name.equals("not-empty")) {
0801: if (attribute.getBooleanValue()) {
0802: entity.addConstraint(column, new NotEmpty());
0803: }
0804: } else if (name.equals("one-of")) {
0805: entity.addConstraint(column, new OneOf(Arrays
0806: .asList(value.split(","))));
0807: } else if (name.equals("reference")) {
0808: int dot = value.indexOf(".");
0809: if (dot == -1 || dot == value.length() - 1) {
0810: Logger
0811: .error("bad syntax for reference constraint (entity "
0812: + entity.getName()
0813: + ", column "
0814: + column
0815: + "). Should be 'table.column'.");
0816: } else {
0817: String table = value.substring(0, dot);
0818: String col = value.substring(dot + 1);
0819: entity.addConstraint(column, new Reference(
0820: database, table, col));
0821: }
0822: } else if (name.equals("regex")) {
0823: entity.addConstraint(column, new Regex(Pattern
0824: .compile(value)));
0825: } else {
0826: if (!name.equals("name")) {
0827: Logger.error("ignoring unknown constraint '"
0828: + name + "=" + attribute.getValue()
0829: + "' (entity " + entity.getName()
0830: + ", column " + column + ").");
0831: }
0832: }
0833: }
0834: /* long syntax */
0835: Length length = null;
0836: for (Iterator constraints = colElement.getChildren()
0837: .iterator(); constraints.hasNext();) {
0838: Element constraintElement = (Element) constraints
0839: .next();
0840: String name = constraintElement.getName();
0841: FieldConstraint constraint = null;
0842: if (name.equals("email")) {
0843: boolean dnsCheck = false;
0844: boolean smtpCheck = false;
0845: str = constraintElement
0846: .getAttributeValue("dns-check");
0847: if (checkSyntax("dns-check", str, new String[] {
0848: "yes", "no" })) {
0849: dnsCheck = (str.equalsIgnoreCase("yes"));
0850: }
0851: str = constraintElement
0852: .getAttributeValue("smtp-check");
0853: if (checkSyntax("smtp-check", str, new String[] {
0854: "yes", "no" })) {
0855: smtpCheck = (str.equalsIgnoreCase("yes"));
0856: }
0857: constraint = new Email(dnsCheck, smtpCheck);
0858: } else if (name.equals("min-len")) {
0859: if (length != null) {
0860: length.setMinLength(Integer
0861: .parseInt(constraintElement
0862: .getAttributeValue("value")));
0863: } else {
0864: constraint = length = new Length(Integer
0865: .parseInt(constraintElement
0866: .getAttributeValue("value")),
0867: Integer.MAX_VALUE);
0868: }
0869: } else if (name.equals("max-len")) {
0870: if (length != null) {
0871: length.setMaxLength(Integer
0872: .parseInt(constraintElement
0873: .getAttributeValue("value")));
0874: } else {
0875: constraint = length = new Length(0, Integer
0876: .parseInt(constraintElement
0877: .getAttributeValue("value")));
0878: }
0879: } else if (name.equals("integer")
0880: || name.equals("number")) {
0881: Range range = new Range();
0882: range.setInteger(name.equals("integer"));
0883: minstr = constraintElement.getAttributeValue("min");
0884: if (minstr != null) {
0885: range.setMin(Double.parseDouble(minstr));
0886: }
0887: maxstr = constraintElement.getAttributeValue("max");
0888: if (maxstr != null) {
0889: range.setMax(Double.parseDouble(maxstr));
0890: }
0891: constraint = range;
0892: } else if (name.equals("date")) {
0893: DateRange daterange = new DateRange();
0894: minstr = constraintElement
0895: .getAttributeValue("after");
0896: if (minstr != null) {
0897: daterange.setAfterDate(format.parse(minstr));
0898: }
0899: maxstr = constraintElement
0900: .getAttributeValue("before");
0901: if (maxstr != null) {
0902: daterange.setBeforeDate(format.parse(maxstr));
0903: }
0904: String dateformat = constraintElement
0905: .getAttributeValue("format");
0906: if (dateformat != null) {
0907: daterange.setDateFormat(new SimpleDateFormat(
0908: dateformat));
0909: }
0910: constraint = daterange;
0911: } else if (name.equals("not-null")) {
0912: constraint = new NotNull();
0913: } else if (name.equals("not-empty")) {
0914: constraint = new NotEmpty();
0915: } else if (name.equals("one-of")) {
0916: List<String> values = new ArrayList<String>();
0917: for (Iterator it = constraintElement.getChildren(
0918: "value").iterator(); it.hasNext();) {
0919: values.add((String) ((Element) it.next())
0920: .getText());
0921: }
0922: constraint = new OneOf(values);
0923: } else if (name.equals("reference")) {
0924: String fk = constraintElement
0925: .getAttributeValue("foreign-key");
0926: if (fk == null) {
0927: Logger
0928: .error("reference constraint needs a 'foreign-key' attribute");
0929: }
0930: int dot = fk.indexOf(".");
0931: if (dot == -1 || dot == fk.length() - 1) {
0932: Logger
0933: .error("bad syntax for reference constraint (entity "
0934: + entity.getName()
0935: + ", column "
0936: + column
0937: + "). Should be 'table.column'.");
0938: } else {
0939: String table = fk.substring(0, dot);
0940: String col = fk.substring(dot + 1);
0941: constraint = new Reference(database, table, col);
0942: }
0943: } else if (name.equals("regex")) {
0944: constraint = new Regex(Pattern
0945: .compile(constraintElement
0946: .getAttributeValue("pattern")));
0947: } else {
0948: Logger
0949: .error("ignoring unknown constraint '"
0950: + name
0951: + "' (entity \"+entity.getName()+\", column \"+column+\").");
0952: }
0953: if (constraint != null) {
0954: String msg = constraintElement
0955: .getAttributeValue("message");
0956: if (msg != null) {
0957: constraint.setMessage(msg);
0958: }
0959: entity.addConstraint(column, constraint);
0960: }
0961: }
0962: }
0963: }
0964:
0965: /** Check the syntax of a parameter in the config file.
0966: *
0967: * @param paramName name of the parameter
0968: * @param paramValue value of the parameter
0969: * @param possibleValues possible values for the parameter
0970: * @return whether the syntax is correct
0971: */
0972: private boolean checkSyntax(String paramName, String paramValue,
0973: String[] possibleValues) {
0974: if (paramValue == null)
0975: return false;
0976: List possible = Arrays.asList(possibleValues);
0977: if (paramValue != null
0978: && Arrays.asList(possibleValues).contains(
0979: paramValue.toLowerCase()))
0980: return true;
0981: else {
0982: Logger.error("Parameter '" + paramName + "' wants one of: "
0983: + StringLists.join(possible, ","));
0984: return false;
0985: }
0986: }
0987:
0988: /** Parse a caching value.
0989: *
0990: * @param caching string describing the type of caching
0991: * @return type of caching
0992: */
0993: private static int parseCaching(String caching) {
0994: return caching == null || caching.equalsIgnoreCase("none")
0995: || caching.equalsIgnoreCase("no") ? Cache.NO_CACHE
0996: : caching.equalsIgnoreCase("soft")
0997: || caching.equalsIgnoreCase("yes") ? Cache.SOFT_CACHE
0998: : caching.equalsIgnoreCase("full") ? Cache.FULL_CACHE
0999: : Cache.NO_CACHE;
1000: }
1001:
1002: /** Check whether the action defined by this XML tree is a simple action or a transaction.
1003: *
1004: * @param element XML tree defining an action
1005: * @return true if the action is a transaction
1006: */
1007: public static boolean isTransaction(Element element) {
1008: Iterator queryElements = element.getContent().iterator();
1009: while (queryElements.hasNext()) {
1010: Object content = queryElements.next();
1011: if (content instanceof Text) {
1012: String text = Strings.trimSpacesAndEOL(((Text) content)
1013: .getText());
1014: char[] chars = text.toCharArray();
1015: boolean insideLitteral = false;
1016: for (int i = 0; i < chars.length; i++) {
1017: if (chars[i] == '\'')
1018: insideLitteral = !insideLitteral;
1019: else if (!insideLitteral && chars[i] == ';'
1020: && i < chars.length - 1)
1021: return true;
1022: }
1023: }
1024: }
1025: return false;
1026: }
1027: }
|