001: ///////////////////////////////
002: // Makumba, Makumba tag library
003: // Copyright (C) 2000-2003 http://www.makumba.org
004: //
005: // This library is free software; you can redistribute it and/or
006: // modify it under the terms of the GNU Lesser General Public
007: // License as published by the Free Software Foundation; either
008: // version 2.1 of the License, or (at your option) any later version.
009: //
010: // This library is distributed in the hope that it will be useful,
011: // but WITHOUT ANY WARRANTY; without even the implied warranty of
012: // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
013: // Lesser General Public License for more details.
014: //
015: // You should have received a copy of the GNU Lesser General Public
016: // License along with this library; if not, write to the Free Software
017: // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
018: //
019: // -------------
020: // $Id: RecordInfo.java 2077 2007-11-20 16:58:18Z manuel_gay $
021: // $Name$
022: /////////////////////////////////////
023:
024: //TODO extra comments about changes from refactoring
025:
026: package org.makumba.providers.datadefinition.makumba;
027:
028: import java.util.ArrayList;
029: import java.util.Collection;
030: import java.util.Dictionary;
031: import java.util.Enumeration;
032: import java.util.Hashtable;
033: import java.util.Iterator;
034: import java.util.Properties;
035: import java.util.Vector;
036:
037: import org.makumba.DataDefinition;
038: import org.makumba.DataDefinitionNotFoundError;
039: import org.makumba.DataDefinitionParseError;
040: import org.makumba.FieldDefinition;
041: import org.makumba.MakumbaError;
042: import org.makumba.ValidationDefinition;
043: import org.makumba.ValidationRule;
044: import org.makumba.DataDefinition.QueryFragmentFunction;
045: import org.makumba.commons.NamedResourceFactory;
046: import org.makumba.commons.NamedResources;
047: import org.makumba.commons.RuntimeWrappedException;
048: import org.makumba.providers.datadefinition.makumba.validation.ComparisonValidationRule;
049: import org.makumba.providers.datadefinition.makumba.validation.NumberRangeValidationRule;
050: import org.makumba.providers.datadefinition.makumba.validation.RegExpValidationRule;
051: import org.makumba.providers.datadefinition.makumba.validation.StringLengthValidationRule;
052:
053: /**
054: * This is the internal representation of the org.makumba. One can make RecordHandlers based on an instance of this
055: * class and do useful things with it (generate sql tables, html code, etc)
056: */
057: public class RecordInfo implements java.io.Serializable,
058: DataDefinition, ValidationDefinition {
059: private static final long serialVersionUID = 1L;
060:
061: static ArrayList<String> operators = new ArrayList<String>();
062:
063: static {
064: operators.add(RegExpValidationRule.getOperator());
065: operators.add(NumberRangeValidationRule.getOperator());
066: operators.add(StringLengthValidationRule.getOperator());
067: operators.addAll(ComparisonValidationRule.getOperators());
068: }
069:
070: java.net.URL origin;
071:
072: String name;
073:
074: Properties templateValues;
075:
076: // Vector templateArgumentNames;
077:
078: Vector<String> fieldOrder = new Vector<String>();
079:
080: Hashtable<String, QueryFragmentFunction> functionNames = new Hashtable<String, QueryFragmentFunction>();
081:
082: String title;
083:
084: String indexName;
085:
086: static final String createName = "TS_create";
087:
088: static final String modifyName = "TS_modify";
089:
090: // for set and setComplex subtables
091: String mainPtr;
092:
093: // for set tables, also used for setintEnum and setcharEnum to store the
094: // name of the int or char field
095: String setField;
096:
097: // nr of relations, 0= none, 1= 1:n, 2= m:n
098: int relations = 0;
099:
100: Hashtable<String, FieldDefinition> fields = new Hashtable<String, FieldDefinition>();
101:
102: // Hashtable fieldIndexes=null;
103:
104: // for subtables
105: String subfield;
106:
107: String ptrSubfield = "";
108:
109: String subfieldPtr = "";
110:
111: RecordInfo papa;
112:
113: private Hashtable<String, ValidationRule> validationRuleNames = new Hashtable<String, ValidationRule>();
114:
115: private Hashtable<Object, MultipleUniqueKeyDefinition> multiFieldUniqueList = new Hashtable<Object, MultipleUniqueKeyDefinition>();
116:
117: void addStandardFields(String name) {
118: FieldInfo fi;
119:
120: indexName = name;
121:
122: fi = new FieldInfo(this , indexName);
123: fi.type = "ptrIndex";
124: fi.description = "Unique index";
125: fi.fixed = true;
126: fi.notNull = true;
127: fi.unique = true;
128: addField1(fi);
129:
130: fi = new FieldInfo(this , modifyName);
131: fi.type = "dateModify";
132: fi.notNull = true;
133: fi.description = "Last modification date";
134: addField1(fi);
135:
136: fi = new FieldInfo(this , createName);
137: fi.type = "dateCreate";
138: fi.description = "Creation date";
139: fi.fixed = true;
140: fi.notNull = true;
141: addField1(fi);
142: }
143:
144: public boolean isTemporary() {
145: return origin == null;
146: }
147:
148: RecordInfo() {
149: name = "temp" + hashCode();
150: }
151:
152: /** make a temporary recordInfo that is only used for query results */
153: public RecordInfo(String name) {
154: this .name = name;
155: origin = null;
156: }
157:
158: protected void addField1(FieldDefinition fi) {
159: fieldOrder.addElement(fi.getName());
160: fields.put(fi.getName(), fi);
161: ((FieldInfo) fi).dd = this ;
162: }
163:
164: /** only meant for building of temporary types */
165: public void addField(FieldDefinition fi) {
166: if (!isTemporary())
167: throw new RuntimeException(
168: "can't add field to non-temporary type");
169: addField1(fi);
170: // the field cannot be of set type...
171: // if(fieldIndexes==null)
172: // fieldIndexes=new Hashtable();
173: // fieldIndexes.put(fi.getName(), new Integer(fieldIndexes.size()));
174: }
175:
176: RecordInfo(java.net.URL origin, String path) {
177: name = path;
178: this .origin = origin;
179: // templateArgumentNames= new Vector(0);
180: }
181:
182: RecordInfo(RecordInfo ri, String subfield) {
183: // initStandardFields(subfield);
184: name = ri.name;
185: origin = ri.origin;
186: this .subfield = subfield;
187: this .papa = ri;
188: // this.templateArgumentNames= ri.templateArgumentNames;
189: ptrSubfield = papa.ptrSubfield + "->" + subfield;
190: subfieldPtr = papa.subfieldPtr + subfield + "->";
191: }
192:
193: static int infos = NamedResources.makeStaticCache(
194: "Data definitions parsed", new NamedResourceFactory() {
195: /**
196: *
197: */
198: private static final long serialVersionUID = 1L;
199:
200: protected Object getHashObject(Object name) {
201: java.net.URL u = RecordParser.findDataDefinition(
202: (String) name, "mdd");
203: if (u == null) {
204: throw new DataDefinitionNotFoundError(
205: (String) name);
206: }
207: return u;
208: }
209:
210: protected Object makeResource(Object name,
211: Object hashName) {
212: String nm = (String) name;
213: if (nm.indexOf('/') != -1)
214: nm = nm.replace('/', '.').substring(1);
215: return new RecordInfo((java.net.URL) hashName, nm);
216: }
217:
218: protected void configureResource(Object name,
219: Object hashName, Object resource) {
220: new RecordParser().parse((RecordInfo) resource);
221: }
222: });
223:
224: /**
225: * returns the record info with the given absolute name
226: *
227: * @throws org.makumba.DataDefinitionNotFoundError
228: * if the name is not a valid record info name
229: * @throws org.makumba.DataDefinitionParseError
230: * if the syntax is wrong or a referred resource can't be found
231: */
232: public static DataDefinition getRecordInfo(String name) {
233: int n = name.indexOf("->");
234: if (n == -1) {
235: try {
236: return getSimpleRecordInfo(name);
237: } catch (DataDefinitionNotFoundError e) {
238: n = name.lastIndexOf(".");
239: if (n == -1)
240: throw e;
241: try {
242: return getRecordInfo(name.substring(0, n) + "->"
243: + name.substring(n + 1));
244: } catch (DataDefinitionParseError f) {
245: throw e;
246: }
247: }
248: }
249:
250: DataDefinition ri = getRecordInfo(name.substring(0, n));
251: while (true) {
252: name = name.substring(n + 2);
253: n = name.indexOf("->");
254: if (n == -1)
255: break;
256: ri = ri.getFieldDefinition(name.substring(0, n))
257: .getSubtable();
258: }
259: ri = ri.getFieldDefinition(name).getSubtable();
260: return ri;
261: }
262:
263: public static synchronized DataDefinition getSimpleRecordInfo(
264: String path) {
265: // this is to avoid a stupid error if path is "..."
266: boolean dot = false;
267: for (int i = 0; i < path.length(); i++) {
268: if (path.charAt(i) == '.') {
269: if (dot)
270: throw new DataDefinitionParseError(
271: "two consecutive dots not allowed in type name");
272: dot = true;
273: } else
274: dot = false;
275:
276: // check if type name looks valid (no weird characters or
277: // spaces)
278: if (path.charAt(i) != '/' && path.charAt(i) != '.')
279: if (i == 0
280: && !Character.isJavaIdentifierStart(path
281: .charAt(i))
282: || i > 0
283: && !Character.isJavaIdentifierPart(path
284: .charAt(i)))
285: throw new DataDefinitionParseError(
286: "Invalid character \"" + path.charAt(i)
287: + "\" in type name \"" + path
288: + "\"");
289: }
290:
291: if (path.indexOf('/') != -1) {
292: path = path.replace('/', '.');
293: if (path.charAt(0) == '.')
294: path = path.substring(1);
295: }
296:
297: DataDefinition ri = null;
298: try {
299: ri = (DataDefinition) NamedResources.getStaticCache(infos)
300: .getResource(path);
301: } catch (RuntimeWrappedException e) {
302: if (e.getCause() instanceof DataDefinitionParseError)
303: throw (DataDefinitionParseError) e.getCause();
304: if (e.getCause() instanceof DataDefinitionNotFoundError)
305: throw (DataDefinitionNotFoundError) e.getCause();
306: if (e.getCause() instanceof MakumbaError)
307: throw (MakumbaError) e.getCause();
308: throw e;
309: }
310: if (path.indexOf("./") == -1)
311: ((RecordInfo) ri).name = path;
312: else
313: java.util.logging.Logger.getLogger(
314: "org.makumba." + "debug.abstr").severe(
315: "shit happens: " + path);
316: return ri;
317: }
318:
319: /** returns all the field names */
320: public Vector getFieldNames() {
321: return (Vector) fieldOrder.clone();
322: }
323:
324: public Vector<FieldDefinition> getReferenceFields() {
325: Vector<FieldDefinition> v = new Vector<FieldDefinition>();
326: for (Iterator iter = fields.values().iterator(); iter.hasNext();) {
327: FieldDefinition fd = (FieldDefinition) iter.next();
328: if (fd.isPointer() || fd.isExternalSet()
329: || fd.isComplexSet()) {
330: v.add(fd);
331: }
332: }
333: return v;
334: }
335:
336: /** returns the field info associated with a name */
337: public FieldDefinition getFieldDefinition(String nm) {
338: return (FieldDefinition) fields.get(nm);
339: }
340:
341: /**
342: * the field with the respective index, null if such a field doesn't exist
343: */
344: public FieldDefinition getFieldDefinition(int n) {
345: if (n < 0 || n >= fieldOrder.size())
346: return null;
347: return getFieldDefinition((String) fieldOrder.elementAt(n));
348: }
349:
350: public QueryFragmentFunction getFunction(String name) {
351: return functionNames.get(name);
352: }
353:
354: public void addFunction(String name, QueryFragmentFunction function) {
355: functionNames.put(name, function);
356: }
357:
358: public Collection<QueryFragmentFunction> getFunctions() {
359: return functionNames.values();
360: }
361:
362: /** returns the path-like abstract-level name of this record info */
363: public String getName() {
364: return name + ptrSubfield;
365: }
366:
367: // TODO: see if this method is still needed, can't it be expressed in
368: // terms of the DataDefinition interface?
369: String getBaseName() {
370: return name;
371: }
372:
373: /**
374: * if this is a subtable, the field prefix is maintable-> TODO: see if this method is still needed, can't it be
375: * expressed in terms of the DataDefinition interface?
376: */
377: public String fieldPrefix() {
378: return subfieldPtr;
379: }
380:
381: DataDefinition makeSubtable(String name) {
382: return new RecordInfo(this , name);
383: }
384:
385: /** which is the name of the index field, if any? */
386: public String getIndexPointerFieldName() {
387: return indexName;
388: }
389:
390: /**
391: * which is the name of the pointer to the main table, for set and internal set subtables
392: */
393: public String getSetOwnerFieldName() {
394: return mainPtr;
395: }
396:
397: // -----------
398: /** the title field indicated, or the default one */
399: public String getTitleFieldName() {
400: return title;
401: }
402:
403: /** which is the name of the creation timestamp field, if any? */
404: public String getCreationDateFieldName() {
405: return createName;
406: }
407:
408: /** which is the name of the modification timestamp field, if any? */
409: public String getLastModificationDateFieldName() {
410: return modifyName;
411: }
412:
413: /**
414: * If this type is the data pointed by a 1-1 pointer or subset, return the type of the main record, otherwise return
415: * null
416: */
417: public FieldDefinition getParentField() {
418: if (papa == null)
419: return null;
420: return papa.getFieldDefinition(subfield);
421: }
422:
423: /**
424: * which is the name of the set member (Pointer, Character or Integer), for set subtables
425: */
426: public String getSetMemberFieldName() {
427: return setField;
428: }
429:
430: // moved from RecordHandler
431: public void checkFieldNames(Dictionary d) {
432: for (Enumeration e = d.keys(); e.hasMoreElements();) {
433: Object o = e.nextElement();
434: if (!(o instanceof String))
435: throw new org.makumba.NoSuchFieldException(this ,
436: "Dictionaries passed to makumba DB operations should have String keys. Key <"
437: + o + "> is of type " + o.getClass()
438: + getName());
439: if (this .getFieldDefinition((String) o) == null)
440: throw new org.makumba.NoSuchFieldException(this ,
441: (String) o);
442: }
443: }
444:
445: public String toString() {
446: return getName();
447: }
448:
449: public java.net.URL getOrigin() {
450: return origin;
451: }
452:
453: public long lastModified() {
454: return new java.io.File(this .getOrigin().getFile())
455: .lastModified();
456: }
457:
458: // Validation definition specific methods //
459: public DataDefinition getDataDefinition() {
460: return this ;
461: }
462:
463: public void addValidationRule(ValidationRule rule) {
464: validationRuleNames.put(rule.getRuleName(), rule);
465: }
466:
467: public ValidationRule getValidationRule(String ruleName) {
468: return validationRuleNames.get(ruleName);
469: }
470:
471: public void addRule(String fieldName, Collection rules) {
472: getFieldDefinition(fieldName).addValidationRule(rules);
473: }
474:
475: public void addRule(String fieldName, ValidationRule rule) {
476: getFieldDefinition(fieldName).addValidationRule(rule);
477: }
478:
479: public Collection getValidationRules(String fieldName) {
480: return getFieldDefinition(fieldName).getValidationRules();
481: }
482:
483: public ValidationDefinition getValidationDefinition() {
484: return this ;
485: }
486:
487: public ArrayList getRulesSyntax() {
488: return operators;
489: }
490:
491: public boolean hasValidationRules() {
492: return validationRuleNames.size() > 0;
493: }
494:
495: // Mutliple unique keys methods
496: public MultipleUniqueKeyDefinition[] getMultiFieldUniqueKeys() {
497: return (MultipleUniqueKeyDefinition[]) multiFieldUniqueList
498: .values()
499: .toArray(
500: new MultipleUniqueKeyDefinition[multiFieldUniqueList
501: .values().size()]);
502: }
503:
504: public void addMultiUniqueKey(MultipleUniqueKeyDefinition definition) {
505: multiFieldUniqueList.put(definition.getFields(), definition);
506: }
507:
508: public boolean hasMultiUniqueKey(String[] fieldNames) {
509: return multiFieldUniqueList.get(fieldNames) != null;
510: }
511:
512: public void checkUpdate(String fieldName, Dictionary d) {
513: Object o = d.get(fieldName);
514: if (o != null)
515: switch (getFieldDefinition(fieldName).getIntegerType()) {
516: case FieldDefinition._dateCreate:
517: throw new org.makumba.InvalidValueException(
518: getFieldDefinition(fieldName),
519: "you cannot update a creation date");
520: case FieldDefinition._dateModify:
521: throw new org.makumba.InvalidValueException(
522: getFieldDefinition(fieldName),
523: "you cannot update a modification date");
524: case FieldDefinition._ptrIndex:
525: throw new org.makumba.InvalidValueException(
526: getFieldDefinition(fieldName),
527: "you cannot update an index pointer");
528: default:
529: base_checkUpdate(fieldName, d);
530: }
531: }
532:
533: private void base_checkUpdate(String fieldName, Dictionary d) {
534: getFieldDefinition(fieldName).checkUpdate(d);
535: }
536:
537: }
|