001: package org.drools.brms.server.rules;
002:
003: import java.io.ByteArrayOutputStream;
004: import java.io.IOException;
005: import java.util.ArrayList;
006: import java.util.Collection;
007: import java.util.Collections;
008: import java.util.HashSet;
009: import java.util.Iterator;
010: import java.util.List;
011: import java.util.jar.JarEntry;
012: import java.util.jar.JarInputStream;
013:
014: import org.drools.base.ClassTypeResolver;
015: import org.drools.brms.client.modeldriven.SuggestionCompletionEngine;
016: import org.drools.brms.server.util.DataEnumLoader;
017: import org.drools.brms.server.util.SuggestionCompletionEngineBuilder;
018: import org.drools.compiler.DrlParser;
019: import org.drools.compiler.DroolsParserException;
020: import org.drools.compiler.ParserError;
021: import org.drools.lang.descr.FactTemplateDescr;
022: import org.drools.lang.descr.FieldTemplateDescr;
023: import org.drools.lang.descr.GlobalDescr;
024: import org.drools.lang.descr.ImportDescr;
025: import org.drools.lang.descr.PackageDescr;
026: import org.drools.lang.dsl.DSLMapping;
027: import org.drools.lang.dsl.DSLMappingEntry;
028: import org.drools.lang.dsl.DSLMappingFile;
029: import org.drools.rule.MapBackedClassLoader;
030: import org.drools.util.asm.ClassFieldInspector;
031:
032: /**
033: * This utility class loads suggestion completion stuff for the package
034: * configuration, introspecting from models, templates etc.
035: *
036: * This also includes DSL stuff, basically, everything you need to get started
037: * with a package. It also validates the package configuration, and can provide
038: * errors.
039: *
040: * This does NOT validate assets in the package, other then to load up DSLs,
041: * models etc as needed.
042: *
043: * FYI: the tests for this are in the BRMS module, in context of a full BRMS.
044: *
045: * @author Michael Neale
046: *
047: */
048: public class SuggestionCompletionLoader {
049:
050: private final SuggestionCompletionEngineBuilder builder = new SuggestionCompletionEngineBuilder();
051:
052: private final DrlParser parser = new DrlParser();
053:
054: private final MapBackedClassLoader loader;
055:
056: protected List errors = new ArrayList();
057:
058: // iterating over the import list
059: final ClassTypeResolver resolver;
060:
061: /**
062: * This uses the current classes classloader as a base, and jars can be
063: * added.
064: */
065: public SuggestionCompletionLoader() {
066: this (null);
067: }
068:
069: /**
070: * This allows a pre existing classloader to be used (and preferred) for
071: * resolving types.
072: */
073: public SuggestionCompletionLoader(ClassLoader classLoader) {
074: if (classLoader == null) {
075: classLoader = Thread.currentThread()
076: .getContextClassLoader();
077: if (classLoader == null) {
078: classLoader = this .getClass().getClassLoader();
079: }
080: }
081: this .loader = new MapBackedClassLoader(classLoader);
082: this .resolver = new ClassTypeResolver(new HashSet(),
083: this .loader);
084: }
085:
086: /**
087: * This will validate, and generate a new engine, ready to go. If there are
088: * errors, you can get them by doing getErrors();
089: *
090: * @param header
091: * The package configuration file content.
092: * @param jars
093: * a list of jars to look inside (pass in empty array if not
094: * needed) this is a list of {@link JarInputStream}
095: * @param dsls
096: * any dsl files. This is a list of {@link DSLMappingFile}.
097: * @return A SuggestionCompletionEngine ready to be used in anger.
098: */
099: public SuggestionCompletionEngine getSuggestionEngine(
100: final String header, final List jars, final List dsls) {
101: return this .getSuggestionEngine(header, jars, dsls,
102: Collections.EMPTY_LIST);
103: }
104:
105: /**
106: * This will validate, and generate a new engine, ready to go. If there are
107: * errors, you can get them by doing getErrors();
108: *
109: * @param header
110: * The package configuration file content.
111: * @param jars
112: * a list of jars to look inside (pass in empty array if not
113: * needed) this is a list of {@link JarInputStream}
114: * @param dsls
115: * any dsl files. This is a list of {@link DSLMappingFile}.
116: * @param dataEnums
117: * this is a list of String's which hold data enum definitions.
118: * (normally will be just one, but for completeness can load multiple).
119: * @return A SuggestionCompletionEngine ready to be used in anger.
120: */
121: public SuggestionCompletionEngine getSuggestionEngine(
122: final String header, final List jars, final List dsls,
123: final List dataEnums) {
124: this .builder.newCompletionEngine();
125:
126: if (!header.trim().equals("")) {
127: processPackageHeader(header, jars);
128: }
129:
130: // populating DSL sentences
131: this .populateDSLSentences(dsls);
132:
133: SuggestionCompletionEngine sce = this .builder.getInstance();
134:
135: populateDateEnums(dataEnums, sce);
136:
137: return sce;
138: }
139:
140: private void populateDateEnums(List dataEnums,
141: SuggestionCompletionEngine sce) {
142: for (Iterator iter = dataEnums.iterator(); iter.hasNext();) {
143: String enumFile = (String) iter.next();
144: DataEnumLoader enumLoader = new DataEnumLoader(enumFile);
145: if (enumLoader.hasErrors()) {
146: this .errors.addAll(enumLoader.getErrors());
147: } else {
148: sce.dataEnumLists.putAll(enumLoader.getData());
149: }
150: }
151:
152: }
153:
154: private void processPackageHeader(final String header,
155: final List jars) {
156: // get fact types from imports
157: PackageDescr pkgDescr;
158: try {
159: pkgDescr = this .parser.parse(header);
160: } catch (final DroolsParserException e1) {
161: throw new IllegalStateException(
162: "Serious error, unable to validate package.");
163: }
164:
165: if (this .parser.hasErrors()) {
166: for (final Iterator iter = this .parser.getErrors()
167: .iterator(); iter.hasNext();) {
168: final ParserError element = (ParserError) iter.next();
169: this .errors.add(element.getMessage());
170: }
171: }
172: // populating information for the model itself
173: this .populateModelInfo(pkgDescr, jars);
174:
175: // populating globals
176: this .populateGlobalInfo(pkgDescr, jars);
177:
178: }
179:
180: /**
181: * @param pkg
182: * @param errors
183: */
184: private void populateDSLSentences(final List dsls) {
185: // AssetItemIterator it = pkg.listAssetsByFormat( new
186: // String[]{AssetFormats.DSL} );
187: // while ( it.hasNext() ) {
188: // AssetItem item = (AssetItem) it.next();
189: // String dslData = item.getContent();
190: // DSLMappingFile file = new DSLMappingFile();
191: // try {
192: // if ( file.parseAndLoad( new StringReader( dslData ) ) ) {
193: // DSLMapping mapping = file.getMapping();
194: // for ( Iterator entries = mapping.getEntries().iterator();
195: // entries.hasNext(); ) {
196: // DSLMappingEntry entry = (DSLMappingEntry) entries.next();
197: // if (entry.getSection() == DSLMappingEntry.CONDITION) {
198: // builder.addDSLConditionSentence( entry.getMappingKey() );
199: // } else if (entry.getSection() == DSLMappingEntry.CONSEQUENCE) {
200: // builder.addDSLActionSentence( entry.getMappingKey() );
201: // }
202: //
203: // }
204: // } else {
205: // errors.add( file.getErrors().toString() );
206: // }
207: // } catch ( IOException e ) {
208: // errors.add( "Error while loading DSL language configuration : " +
209: // item.getBinaryContentAttachmentFileName() + " error message: " +
210: // e.getMessage() );
211: // }
212: // }
213:
214: for (final Iterator it = dsls.iterator(); it.hasNext();) {
215: final DSLMappingFile file = (DSLMappingFile) it.next();
216: final DSLMapping mapping = file.getMapping();
217: for (final Iterator entries = mapping.getEntries()
218: .iterator(); entries.hasNext();) {
219: final DSLMappingEntry entry = (DSLMappingEntry) entries
220: .next();
221: if (entry.getSection() == DSLMappingEntry.CONDITION) {
222: this .builder.addDSLConditionSentence(entry
223: .getMappingKey());
224: } else if (entry.getSection() == DSLMappingEntry.CONSEQUENCE) {
225: this .builder.addDSLActionSentence(entry
226: .getMappingKey());
227: }
228: }
229: }
230:
231: }
232:
233: /**
234: * Populate the global stuff.
235: */
236: private void populateGlobalInfo(final PackageDescr pkgDescr,
237: final List jars) {
238:
239: // populating information for the globals
240: for (final Iterator it = pkgDescr.getGlobals().iterator(); it
241: .hasNext();) {
242: final GlobalDescr global = (GlobalDescr) it.next();
243: try {
244: final String shortTypeName = global.getType();
245: if (!this .builder.hasFieldsForType(shortTypeName)) {
246: final Class clazz = loadClass(global.getType(),
247: jars);
248: loadClassFields(clazz, shortTypeName);
249:
250: this .builder.addGlobalType(global.getIdentifier(),
251: shortTypeName);
252: }
253:
254: this .builder.addGlobalType(global.getIdentifier(),
255: shortTypeName);
256: } catch (final IOException e) {
257: this .errors
258: .add("Error while inspecting class for global: "
259: + global.getType()
260: + " error message: "
261: + e.getMessage());
262: }
263:
264: }
265: }
266:
267: /**
268: * Populate the fact type data.
269: */
270: private void populateModelInfo(final PackageDescr pkgDescr,
271: final List jars) {
272: for (final Iterator it = pkgDescr.getImports().iterator(); it
273: .hasNext();) {
274: final ImportDescr imp = (ImportDescr) it.next();
275: final String className = imp.getTarget();
276: resolver.addImport(className);
277:
278: final Class clazz = loadClass(className, jars);
279:
280: if (clazz != null) {
281: try {
282: final String shortTypeName = getShortNameOfClass(clazz
283: .getName());
284: loadClassFields(clazz, shortTypeName);
285: this .builder.addFactType(shortTypeName);
286: } catch (final IOException e) {
287: this .errors
288: .add("Error while inspecting the class: "
289: + className + ". The error was: "
290: + e.getMessage());
291: }
292: }
293: }
294:
295: // iterating over templates
296: populateFactTemplateTypes(pkgDescr, resolver);
297: }
298:
299: /**
300: * Iterates over fact templates and add them to the model definition
301: *
302: * @param pkgDescr
303: */
304: private void populateFactTemplateTypes(final PackageDescr pkgDescr,
305: final ClassTypeResolver resolver) {
306: for (final Iterator it = pkgDescr.getFactTemplates().iterator(); it
307: .hasNext();) {
308: final FactTemplateDescr templ = (FactTemplateDescr) it
309: .next();
310: final String factType = templ.getName();
311: this .builder.addFactType(factType);
312:
313: final String[] fields = new String[templ.getFields().size()];
314: this .builder.addFieldsForType(factType, fields);
315:
316: int index = 0;
317: for (final Iterator fieldsIt = templ.getFields().iterator(); fieldsIt
318: .hasNext();) {
319: final FieldTemplateDescr fieldDescr = (FieldTemplateDescr) fieldsIt
320: .next();
321: fields[index++] = fieldDescr.getName();
322: final String fieldType = fieldDescr.getClassType();
323:
324: Class fieldTypeClass = null;
325: try {
326: fieldTypeClass = resolver.resolveType(fieldType);
327: } catch (final ClassNotFoundException e) {
328: this .errors
329: .add("Fact template field type not found: "
330: + fieldType);
331: }
332: this .builder.addFieldType(factType + "."
333: + fieldDescr.getName(),
334: getFieldType(fieldTypeClass));
335: }
336: }
337: }
338:
339: private Class loadClass(String className, List jars) {
340: Class clazz = null;
341: try {
342: clazz = resolver.resolveType(className);
343: } catch (ClassNotFoundException e1) {
344: try {
345: addJars(jars);
346: clazz = resolver.resolveType(className);
347: } catch (Exception e) {
348: this .errors.add("Class not found: " + className);
349: }
350: }
351: return clazz;
352: }
353:
354: private void loadClassFields(final Class clazz,
355: final String shortTypeName) throws IOException {
356: if (clazz == null) {
357: return;
358: }
359: final ClassFieldInspector inspector = new ClassFieldInspector(
360: clazz);
361: String[] fields = (String[]) inspector.getFieldNames().keySet()
362: .toArray(new String[inspector.getFieldNames().size()]);
363:
364: fields = removeIrrelevantFields(fields);
365:
366: this .builder.addFieldsForType(shortTypeName, fields);
367: for (int i = 0; i < fields.length; i++) {
368: final Class type = (Class) inspector.getFieldTypes().get(
369: fields[i]);
370: final String fieldType = getFieldType(type);
371: this .builder.addFieldType(shortTypeName + "." + fields[i],
372: fieldType);
373: }
374: }
375:
376: String getShortNameOfClass(final String clazz) {
377: return clazz.substring(clazz.lastIndexOf('.') + 1);
378: }
379:
380: /**
381: * This will remove the unneeded "fields" that come from java.lang.Object
382: * these are really not needed for the modeller.
383: */
384: String[] removeIrrelevantFields(final String[] fields) {
385: final List result = new ArrayList();
386: for (int i = 0; i < fields.length; i++) {
387: final String field = fields[i];
388: if (field.equals("class") || field.equals("hashCode")
389: || field.equals("toString")) {
390: // ignore
391: } else {
392: result.add(field);
393: }
394: }
395: return (String[]) result.toArray(new String[result.size()]);
396: }
397:
398: /**
399: * This will add the given jars to the classloader.
400: */
401: private void addJars(final List jars) throws IOException {
402: for (final Iterator it = jars.iterator(); it.hasNext();) {
403: final JarInputStream jis = (JarInputStream) it.next();
404: JarEntry entry = null;
405: final byte[] buf = new byte[1024];
406: int len = 0;
407: while ((entry = jis.getNextJarEntry()) != null) {
408: if (!entry.isDirectory()) {
409: final ByteArrayOutputStream out = new ByteArrayOutputStream();
410: while ((len = jis.read(buf)) >= 0) {
411: out.write(buf, 0, len);
412: }
413: this .loader.addResource(entry.getName(), out
414: .toByteArray());
415: }
416: }
417:
418: }
419: }
420:
421: /**
422: * @param inspector
423: * @param fields
424: * @param i
425: * @return
426: */
427: private String getFieldType(final Class type) {
428: String fieldType = null; // if null, will use standard operators
429: if (type != null) {
430: if (type.isPrimitive() && (type != boolean.class)) {
431: fieldType = SuggestionCompletionEngine.TYPE_NUMERIC;
432: } else if (Number.class.isAssignableFrom(type)) {
433: fieldType = SuggestionCompletionEngine.TYPE_NUMERIC;
434: } else if (String.class.isAssignableFrom(type)) {
435: fieldType = SuggestionCompletionEngine.TYPE_STRING;
436: } else if (Collection.class.isAssignableFrom(type)) {
437: fieldType = SuggestionCompletionEngine.TYPE_COLLECTION;
438: } else if (Comparable.class.isAssignableFrom(type)) {
439: fieldType = SuggestionCompletionEngine.TYPE_COMPARABLE;
440: }
441: }
442: return fieldType;
443: }
444:
445: /**
446: * @return true if there were errors when processing the package.
447: */
448: public boolean hasErrors() {
449: return (this .errors.size() > 0);
450: }
451:
452: /**
453: * Returns a list of String errors.
454: */
455: public List getErrors() {
456: return this.errors;
457: }
458:
459: }
|