001: package org.drools.rule;
002:
003: /*
004: * Copyright 2005 JBoss Inc
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: */
018:
019: import java.io.ByteArrayInputStream;
020: import java.io.ByteArrayOutputStream;
021: import java.io.Externalizable;
022: import java.io.IOException;
023: import java.io.InputStream;
024: import java.io.ObjectInput;
025: import java.io.ObjectOutput;
026: import java.io.ObjectOutputStream;
027: import java.security.AccessController;
028: import java.security.PrivilegedAction;
029: import java.security.ProtectionDomain;
030: import java.util.ArrayList;
031: import java.util.Collections;
032: import java.util.HashMap;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Map;
036: import java.util.Map.Entry;
037:
038: import org.drools.CheckedDroolsException;
039: import org.drools.RuntimeDroolsException;
040: import org.drools.base.ClassFieldExtractorFactory;
041: import org.drools.base.accumulators.JavaAccumulatorFunctionExecutor;
042: import org.drools.common.DroolsObjectInputStream;
043: import org.drools.spi.Accumulator;
044: import org.drools.spi.Consequence;
045: import org.drools.spi.EvalExpression;
046: import org.drools.spi.PredicateExpression;
047: import org.drools.spi.ReturnValueExpression;
048:
049: public class PackageCompilationData implements Externalizable {
050:
051: /**
052: *
053: */
054: private static final long serialVersionUID = 400L;
055:
056: private static final ProtectionDomain PROTECTION_DOMAIN;
057:
058: private Map invokerLookups;
059:
060: private Object AST;
061:
062: private Map store;
063:
064: private Map lineMappings;
065:
066: private transient PackageClassLoader classLoader;
067:
068: private transient ClassLoader parentClassLoader;
069:
070: private boolean dirty;
071:
072: static {
073: PROTECTION_DOMAIN = (ProtectionDomain) AccessController
074: .doPrivileged(new PrivilegedAction() {
075: public Object run() {
076: return PackageCompilationData.class
077: .getProtectionDomain();
078: }
079: });
080: }
081:
082: /**
083: * Default constructor - for Externalizable. This should never be used by a user, as it
084: * will result in an invalid state for the instance.
085: */
086: public PackageCompilationData() {
087:
088: }
089:
090: public PackageCompilationData(final ClassLoader parentClassLoader) {
091: initClassLoader(parentClassLoader);
092: this .invokerLookups = new HashMap();
093: this .store = new HashMap();
094: this .lineMappings = new HashMap();
095: this .dirty = false;
096: }
097:
098: public boolean isDirty() {
099: return this .dirty;
100: }
101:
102: private void initClassLoader(ClassLoader parentClassLoader) {
103: if (parentClassLoader == null) {
104: throw new RuntimeDroolsException(
105: "PackageCompilationData cannot have a null parentClassLoader");
106: }
107: this .parentClassLoader = parentClassLoader;
108: this .classLoader = new PackageClassLoader(
109: this .parentClassLoader);
110: }
111:
112: /**
113: * Handles the write serialization of the PackageCompilationData. Patterns in Rules may reference generated data which cannot be serialized by
114: * default methods. The PackageCompilationData holds a reference to the generated bytecode. The generated bytecode must be restored before any Rules.
115: *
116: */
117: public void writeExternal(final ObjectOutput stream)
118: throws IOException {
119: stream.writeObject(this .store);
120: stream.writeObject(this .AST);
121:
122: // Rules must be restored by an ObjectInputStream that can resolve using a given ClassLoader to handle seaprately by storing as
123: // a byte[]
124: final ByteArrayOutputStream bos = new ByteArrayOutputStream();
125: final ObjectOutput out = new ObjectOutputStream(bos);
126: out.writeObject(this .invokerLookups);
127: stream.writeObject(bos.toByteArray());
128: }
129:
130: /**
131: * Handles the read serialization of the PackageCompilationData. Patterns in Rules may reference generated data which cannot be serialized by
132: * default methods. The PackageCompilationData holds a reference to the generated bytecode; which must be restored before any Rules.
133: * A custom ObjectInputStream, able to resolve classes against the bytecode, is used to restore the Rules.
134: *
135: */
136: public void readExternal(final ObjectInput stream)
137: throws IOException, ClassNotFoundException {
138: if (stream instanceof DroolsObjectInputStream) {
139: DroolsObjectInputStream droolsStream = (DroolsObjectInputStream) stream;
140: initClassLoader(droolsStream.getClassLoader());
141: } else {
142: initClassLoader(Thread.currentThread()
143: .getContextClassLoader());
144: }
145:
146: this .store = (Map) stream.readObject();
147: this .AST = stream.readObject();
148:
149: // Return the rules stored as a byte[]
150: final byte[] bytes = (byte[]) stream.readObject();
151:
152: // Use a custom ObjectInputStream that can resolve against a given classLoader
153: final DroolsObjectInputStream streamWithLoader = new DroolsObjectInputStream(
154: new ByteArrayInputStream(bytes), this .classLoader);
155: this .invokerLookups = (Map) streamWithLoader.readObject();
156: }
157:
158: public ClassLoader getClassLoader() {
159: return this .classLoader;
160: }
161:
162: public byte[] read(final String resourceName) {
163: byte[] bytes = null;
164:
165: if (this .store != null) {
166: bytes = (byte[]) this .store.get(resourceName);
167: }
168: return bytes;
169: }
170:
171: public void write(final String resourceName, final byte[] clazzData)
172: throws RuntimeDroolsException {
173: if (this .store.put(resourceName, clazzData) != null) {
174: // we are updating an existing class so reload();
175: //reload();
176: this .dirty = true;
177: } else {
178: try {
179: wire(convertResourceToClassName(resourceName));
180: } catch (final Exception e) {
181: e.printStackTrace();
182: throw new RuntimeDroolsException(e);
183: }
184: }
185:
186: }
187:
188: public boolean remove(final String resourceName)
189: throws RuntimeDroolsException {
190: this .invokerLookups.remove(resourceName);
191: if (this .store.remove(convertClassToResourcePath(resourceName)) != null) {
192: // we need to make sure the class is removed from the classLoader
193: // reload();
194: this .dirty = true;
195: return true;
196: }
197: return false;
198: }
199:
200: public String[] list() {
201: if (this .store == null) {
202: return new String[0];
203: }
204: final List names = new ArrayList();
205:
206: for (final Iterator it = this .store.keySet().iterator(); it
207: .hasNext();) {
208: final String name = (String) it.next();
209: names.add(name);
210: }
211:
212: return (String[]) names.toArray(new String[this .store.size()]);
213: }
214:
215: /**
216: * This class drops the classLoader and reloads it. During this process it must re-wire all the invokeables.
217: * @throws CheckedDroolsException
218: */
219: public void reload() throws RuntimeDroolsException {
220: // drops the classLoader and adds a new one
221: this .classLoader = new PackageClassLoader(
222: this .parentClassLoader);
223:
224: // Wire up invokers
225: try {
226: for (final Iterator it = this .invokerLookups.entrySet()
227: .iterator(); it.hasNext();) {
228: Entry entry = (Entry) it.next();
229: wire((String) entry.getKey(), entry.getValue());
230: }
231: } catch (final ClassNotFoundException e) {
232: throw new RuntimeDroolsException(e);
233: } catch (final InstantiationError e) {
234: throw new RuntimeDroolsException(e);
235: } catch (final IllegalAccessException e) {
236: throw new RuntimeDroolsException(e);
237: } catch (final InstantiationException e) {
238: throw new RuntimeDroolsException(e);
239: } finally {
240: this .dirty = false;
241: }
242: }
243:
244: public void clear() {
245: this .store.clear();
246: this .invokerLookups.clear();
247: this .AST = null;
248: reload();
249: }
250:
251: public void wire(final String className)
252: throws ClassNotFoundException, InstantiationException,
253: IllegalAccessException {
254: final Object invoker = this .invokerLookups.get(className);
255: wire(className, invoker);
256: }
257:
258: public void wire(final String className, final Object invoker)
259: throws ClassNotFoundException, InstantiationException,
260: IllegalAccessException {
261: final Class clazz = this .classLoader.findClass(className);
262: if (invoker instanceof ReturnValueRestriction) {
263: ((ReturnValueRestriction) invoker)
264: .setReturnValueExpression((ReturnValueExpression) clazz
265: .newInstance());
266: } else if (invoker instanceof PredicateConstraint) {
267: ((PredicateConstraint) invoker)
268: .setPredicateExpression((PredicateExpression) clazz
269: .newInstance());
270: } else if (invoker instanceof EvalCondition) {
271: ((EvalCondition) invoker)
272: .setEvalExpression((EvalExpression) clazz
273: .newInstance());
274: } else if (invoker instanceof Accumulate) {
275: ((Accumulate) invoker).setAccumulator((Accumulator) clazz
276: .newInstance());
277: } else if (invoker instanceof Rule) {
278: ((Rule) invoker).setConsequence((Consequence) clazz
279: .newInstance());
280: } else if (invoker instanceof JavaAccumulatorFunctionExecutor) {
281: ((JavaAccumulatorFunctionExecutor) invoker)
282: .setExpression((ReturnValueExpression) clazz
283: .newInstance());
284: }
285: }
286:
287: public String toString() {
288: return this .getClass().getName() + this .store.toString();
289: }
290:
291: public void putInvoker(final String className, final Object invoker) {
292: this .invokerLookups.put(className, invoker);
293: }
294:
295: public void putAllInvokers(final Map invokers) {
296: this .invokerLookups.putAll(invokers);
297:
298: }
299:
300: public Map getInvokers() {
301: return this .invokerLookups;
302: }
303:
304: public void removeInvoker(final String className) {
305: this .invokerLookups.remove(className);
306: }
307:
308: public Map getLineMappings() {
309: if (this .lineMappings == null) {
310: this .lineMappings = new HashMap();
311: }
312: return this .lineMappings;
313: }
314:
315: public LineMappings getLineMappings(final String className) {
316: return (LineMappings) getLineMappings().get(className);
317: }
318:
319: public Object getAST() {
320: return this .AST;
321: }
322:
323: public void setAST(final Object ast) {
324: this .AST = ast;
325: }
326:
327: /**
328: * Lifted and adapted from Jakarta commons-jci
329: *
330: * @author mproctor
331: *
332: */
333: public class PackageClassLoader extends ClassLoader implements
334: DroolsClassLoader {
335:
336: public PackageClassLoader(final ClassLoader parentClassLoader) {
337: super (parentClassLoader);
338: }
339:
340: public Class fastFindClass(final String name) {
341: final Class clazz = findLoadedClass(name);
342:
343: if (clazz == null) {
344: final byte[] clazzBytes = read(convertClassToResourcePath(name));
345: if (clazzBytes != null) {
346: return defineClass(name, clazzBytes, 0,
347: clazzBytes.length, PROTECTION_DOMAIN);
348: }
349: }
350:
351: return clazz;
352: }
353:
354: /**
355: * Javadocs recommend that this method not be overloaded. We overload this so that we can prioritise the fastFindClass
356: * over method calls to parent.loadClass(name, false); and c = findBootstrapClass0(name); which the default implementation
357: * would first - hence why we call it "fastFindClass" instead of standard findClass, this indicates that we give it a
358: * higher priority than normal.
359: *
360: */
361: protected synchronized Class loadClass(final String name,
362: final boolean resolve) throws ClassNotFoundException {
363: Class clazz = fastFindClass(name);
364:
365: if (clazz == null) {
366: final ClassLoader parent = getParent();
367: if (parent != null) {
368: clazz = parent.loadClass(name);
369: } else {
370: throw new ClassNotFoundException(name);
371: }
372: }
373:
374: if (resolve) {
375: resolveClass(clazz);
376: }
377:
378: return clazz;
379: }
380:
381: protected Class findClass(final String name)
382: throws ClassNotFoundException {
383: final Class clazz = fastFindClass(name);
384: if (clazz == null) {
385: throw new ClassNotFoundException(name);
386: }
387: return clazz;
388: }
389:
390: public InputStream getResourceAsStream(final String name) {
391: final byte[] bytes = (byte[]) PackageCompilationData.this .store
392: .get(name);
393: if (bytes != null) {
394: return new ByteArrayInputStream(bytes);
395: } else {
396: InputStream input = this .getParent()
397: .getResourceAsStream(name);
398: if (input == null) {
399: input = super .getResourceAsStream(name);
400: }
401: return input;
402: }
403: }
404: }
405:
406: /**
407: * Please do not use - internal
408: * org/my/Class.xxx -> org.my.Class
409: */
410: public static String convertResourceToClassName(
411: final String pResourceName) {
412: return stripExtension(pResourceName).replace('/', '.');
413: }
414:
415: /**
416: * Please do not use - internal
417: * org.my.Class -> org/my/Class.class
418: */
419: public static String convertClassToResourcePath(final String pName) {
420: return pName.replace('.', '/') + ".class";
421: }
422:
423: /**
424: * Please do not use - internal
425: * org/my/Class.xxx -> org/my/Class
426: */
427: public static String stripExtension(final String pResourceName) {
428: final int i = pResourceName.lastIndexOf('.');
429: final String withoutExtension = pResourceName.substring(0, i);
430: return withoutExtension;
431: }
432:
433: }
|