001: /*
002: * Copyright (c) 1998 - 2005 Versant Corporation
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * Versant Corporation - initial API and implementation
010: */
011: package com.versant.core.storagemanager;
012:
013: import com.versant.core.common.config.ConfigInfo;
014: import com.versant.core.common.config.ConfigParser;
015: import com.versant.core.metadata.parser.MetaDataParser;
016: import com.versant.core.metadata.ModelMetaData;
017: import com.versant.core.common.BindingSupportImpl;
018: import com.versant.core.common.Utils;
019: import com.versant.core.util.BeanUtils;
020: import com.versant.core.util.PropertiesLoader;
021: import com.versant.core.util.classhelper.ClassHelper;
022: import com.versant.core.logging.LogEventStore;
023: import com.versant.core.storagemanager.logging.LoggingStorageManagerFactory;
024:
025: import com.versant.core.compiler.ClassCompiler;
026: import com.versant.core.compiler.PizzaClassCompiler;
027: import com.versant.core.compiler.ClassSpec;
028:
029: import com.versant.core.server.CompiledQueryCache;
030:
031: import java.util.*;
032: import java.util.zip.Deflater;
033: import java.lang.reflect.Method;
034: import java.lang.reflect.InvocationTargetException;
035: import java.lang.reflect.Constructor;
036: import java.io.*;
037:
038: /**
039: * Bean to create StorageManagerFactory's. This needs some more work to avoid
040: * referencing the SMF implementation classes directly as it does now.
041: */
042: public class StorageManagerFactoryBuilder {
043:
044: private ConfigInfo config;
045: private ClassLoader loader;
046: private LogEventStore pes;
047: private StorageCache cache;
048: private boolean onlyMetaData;
049: private boolean fullInit = true;
050: private boolean continueAfterMetaDataError;
051:
052: private ClassCompiler classCompiler;
053:
054: private HashMap classSpecs;
055: private CompiledQueryCache compiledQueryCache;
056: private boolean keepHyperdriveBytecode;
057: private Map hyperdriveBytecode;
058: private int hyperdriveBytecodeMaxSize;
059:
060: public StorageManagerFactoryBuilder() {
061: }
062:
063: /**
064: * Create a SMF based on our properties.
065: */
066: public StorageManagerFactory createStorageManagerFactory() {
067:
068: if (config == null) {
069: throw BindingSupportImpl.getInstance().internal(
070: "config property not set");
071: }
072:
073: if (loader == null) {
074: throw BindingSupportImpl.getInstance().internal(
075: "loader property not set");
076: }
077: if (Utils.isStringEmpty(config.url)) {
078: if (Utils
079: .isStringEmpty(config.props
080: .getProperty(ConfigParser.OPTION_CONNECTION_FACTORY_NAME))
081: && !Utils
082: .isDataSource(
083: config.props
084: .getProperty(ConfigParser.STD_CON_DRIVER_NAME),
085: loader)) {
086:
087: throw BindingSupportImpl.getInstance().internal(
088: "javax.jdo.option.ConnectionURL property is required "
089: + "if using a JDBC Driver");
090:
091: } else if (Utils.isStringEmpty(config.db)) {
092: throw BindingSupportImpl
093: .getInstance()
094: .internal(
095: ConfigParser.STORE_DB
096: + " property is required if using a JDBC DataSource");
097: }
098:
099: }
100:
101: if (cache == null) {
102: cache = new NOPStorageCache();
103: }
104: if (pes == null) {
105: pes = new LogEventStore();
106: BeanUtils.setProperties(pes, config.perfProps);
107: }
108: if (config.jdoMetaData == null) {
109: MetaDataParser p = new MetaDataParser();
110: config.jdoMetaData = p.parse(config.jdoResources, loader);
111: }
112: if (config.hyperdrive) {
113: classSpecs = new HashMap();
114: } else {
115: classSpecs = null;
116: }
117: compiledQueryCache = new CompiledQueryCache(
118: config.compiledQueryCacheSize);
119: StorageManagerFactory smf = createSmfForURL();
120: ModelMetaData jmd = smf.getModelMetaData();
121: jmd.forceClassRegistration();
122: cache.setJDOMetaData(jmd);
123: smf = new LoggingStorageManagerFactory(smf, pes);
124:
125: if (config.hyperdrive && !classSpecs.isEmpty()) {
126: if (config.hyperdriveSrcDir != null) {
127: writeGeneratedSourceCode(classSpecs,
128: config.hyperdriveSrcDir);
129: }
130: if (!onlyMetaData || config.hyperdriveClassDir != null) {
131: compileAndInitGeneratedClasses(
132: config.hyperdriveClassDir, jmd);
133: }
134: }
135:
136: if (!onlyMetaData) {
137: smf.init(fullInit, loader);
138: }
139: return smf;
140: }
141:
142: private StorageManagerFactory createSmfForURL() {
143: StorageManagerFactory smf;
144: Properties p;
145:
146: try {
147: if (!Utils.isStringEmpty(config.db)) {
148: p = PropertiesLoader.loadPropertiesForDB(loader,
149: "openaccess", config.db);
150: } else {
151: p = PropertiesLoader.loadPropertiesForURL(loader,
152: "openaccess", config.url);
153: }
154: } catch (IOException e) {
155: throw BindingSupportImpl.getInstance().invalidOperation(
156: e.toString(), e);
157: }
158:
159: String resName = p.getProperty(PropertiesLoader.RES_NAME_PROP);
160: String smfClassName = p.getProperty("smf");
161: if (smfClassName == null) {
162: throw BindingSupportImpl.getInstance().internal(
163: "No 'smf' property in " + resName);
164: }
165: try {
166: Class cls = ClassHelper.get().classForName(smfClassName,
167: true, loader);
168: Constructor cons = cls
169: .getConstructor(new Class[] { StorageManagerFactoryBuilder.class });
170: smf = (StorageManagerFactory) cons
171: .newInstance(new Object[] { this });
172: } catch (Throwable e) {
173: if (BindingSupportImpl.getInstance().isError(e)
174: && !BindingSupportImpl.getInstance()
175: .isOutOfMemoryError(e)) {
176: throw (Error) e;
177: }
178: if (BindingSupportImpl.getInstance().isOwnException(e)) {
179: throw (RuntimeException) e;
180: }
181: if (e instanceof InvocationTargetException
182: && ((InvocationTargetException) e)
183: .getTargetException() != null) {
184: Throwable inner = ((InvocationTargetException) e)
185: .getTargetException();
186: if (BindingSupportImpl.getInstance().isOwnException(
187: inner))
188: throw (RuntimeException) inner;
189: else
190: throw BindingSupportImpl.getInstance().internal(
191: inner.toString(), inner);
192: } else {
193: throw BindingSupportImpl.getInstance().internal(
194: e.toString(), e);
195: }
196: }
197: return smf;
198: }
199:
200: /**
201: * Compile, load and init all of the dynamically generated classes. This
202: * will only compile classes that cannot be loaded from our classloader.
203: */
204:
205: private void compileAndInitGeneratedClasses(
206: String hyperdriveClassDir, ModelMetaData jmd) {
207: // attempt to load each class and create a single String of source
208: // code for those which cannot be loaded
209: ArrayList toInit = new ArrayList();
210: ArrayList notGenerated = new ArrayList();
211: Map toCompile = new HashMap();
212: for (Iterator i = classSpecs.entrySet().iterator(); i.hasNext();) {
213: Map.Entry e = (Map.Entry) i.next();
214: String name = (String) e.getKey();
215: ClassSpec spec = (ClassSpec) e.getValue();
216: try {
217: toInit.add(Class.forName(name, false, loader));
218: notGenerated.add(name);
219: } catch (ClassNotFoundException x) {
220: toCompile.put(name, spec.toSrcCode());
221: }
222: i.remove(); // remove one at a time to reduce max mem usage
223: }
224: classSpecs = null;
225:
226: // compile classes we could not load
227: if (!toCompile.isEmpty()) {
228: if (classCompiler == null) {
229: classCompiler = new PizzaClassCompiler();
230: }
231: Map compiled = classCompiler.compile(toCompile, loader);
232: classCompiler = null;
233: if (hyperdriveClassDir != null) {
234: writeClassFiles(compiled, hyperdriveClassDir);
235: }
236: loadCompiledClasses(compiled, toInit);
237: if (keepHyperdriveBytecode) {
238: hyperdriveBytecode = compiled;
239: }
240: }
241:
242: initHyperdriveClasses(toInit, jmd);
243:
244: if (keepHyperdriveBytecode) {
245: // put in null bytecode for all classes loaded and not generated
246: if (hyperdriveBytecode == null) {
247: hyperdriveBytecode = new HashMap();
248: }
249: for (int i = notGenerated.size() - 1; i >= 0; i--) {
250: hyperdriveBytecode.put(notGenerated.get(i), null);
251: }
252: }
253: }
254:
255: /**
256: * Invoke the initStatics method on each Class in toInit that has one and
257: * check the return value see if the class has been previously initialized.
258: * If it has then throw an exception.
259: */
260:
261: public static void initHyperdriveClasses(List toInit,
262: ModelMetaData jmd) {
263: for (Iterator i = toInit.iterator(); i.hasNext();) {
264: Class cls = (Class) i.next();
265: Object res = null;
266: try {
267: Method m = cls.getMethod("initStatics",
268: new Class[] { ModelMetaData.class });
269: res = m.invoke(null, new Object[] { jmd });
270: } catch (NoSuchMethodException e) {
271: // ignore
272: } catch (InvocationTargetException e) {
273: Throwable t = e.getTargetException();
274: throw BindingSupportImpl.getInstance().internal(
275: t.toString(), t);
276: } catch (Exception x) {
277: throw BindingSupportImpl.getInstance().internal(
278: x.toString(), x);
279: }
280: if (res instanceof Boolean) {
281: if (!((Boolean) res).booleanValue()) {
282: throw BindingSupportImpl
283: .getInstance()
284: .internal(
285: cls.getName()
286: + " is in use (try versant.useClassloader=true)");
287: }
288: }
289: }
290: }
291:
292: /**
293: * Load and init the dynamically compiled classes. This is done in
294: * alphabetical order to make sure that any class load problems are
295: * detirministic. If keepHyperdriveBytecode is true then the bytecode
296: * for each class is compressed and kept in the map, otherwise it is
297: * discarded.
298: */
299:
300: private void loadCompiledClasses(Map map, Collection toInit) {
301: ArrayList names = new ArrayList(map.keySet());
302: Collections.sort(names);
303: Method defineClass = null;
304: try {
305: defineClass = ClassLoader.class.getDeclaredMethod(
306: "defineClass", new Class[] { String.class,
307: byte[].class, Integer.TYPE, Integer.TYPE });
308: } catch (NoSuchMethodException e) {
309: // not possible really
310: throw BindingSupportImpl.getInstance().internal(
311: e.toString(), e);
312: }
313: defineClass.setAccessible(true);
314: Deflater def = null;
315: byte[] outbuf = null;
316: if (keepHyperdriveBytecode) {
317: def = new Deflater(9);
318: hyperdriveBytecodeMaxSize = 0;
319: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
320: byte[] bytecode = (byte[]) ((Map.Entry) i.next())
321: .getValue();
322: if (bytecode.length > hyperdriveBytecodeMaxSize) {
323: hyperdriveBytecodeMaxSize = bytecode.length;
324: }
325: }
326: outbuf = new byte[hyperdriveBytecodeMaxSize + 128];
327: }
328: for (Iterator i = names.iterator(); i.hasNext();) {
329: Object className = i.next();
330: byte[] bytecode = (byte[]) map.get(className);
331: try {
332: Class cls = (Class) defineClass.invoke(loader,
333: new Object[] { null, bytecode, new Integer(0),
334: new Integer(bytecode.length) });
335: toInit.add(cls);
336: } catch (InvocationTargetException e) {
337: Throwable t = e.getTargetException();
338: throw BindingSupportImpl.getInstance().internal(
339: t.toString(), t);
340: } catch (Exception x) {
341: throw BindingSupportImpl.getInstance().internal(
342: x.toString(), x);
343: }
344: if (keepHyperdriveBytecode) {
345: def.reset();
346: def.setInput(bytecode);
347: def.finish();
348: int sz = def.deflate(outbuf);
349: bytecode = new byte[sz];
350: System.arraycopy(outbuf, 0, bytecode, 0, sz);
351: map.put(className, bytecode);
352: } else {
353: map.remove(className);
354: }
355: }
356: }
357:
358: /**
359: * Write .class files for all classes in c to dir.
360: */
361:
362: private void writeClassFiles(Map map, String dir) {
363: for (Iterator i = map.entrySet().iterator(); i.hasNext();) {
364: Map.Entry e = (Map.Entry) i.next();
365: String className = (String) e.getKey();
366: byte[] bytecode = (byte[]) e.getValue();
367: File f = new File(dir, className + ".class");
368: try {
369: FileOutputStream o = new FileOutputStream(f);
370: o.write(bytecode, 0, bytecode.length);
371: o.close();
372: } catch (IOException x) {
373: throw BindingSupportImpl.getInstance().runtime(
374: "Error writing to " + f + ": " + x, x);
375: }
376: }
377: }
378:
379: /**
380: * Write out src for all generated classes to dir.
381: */
382:
383: private void writeGeneratedSourceCode(HashMap classSpecs, String dir) {
384: for (Iterator i = classSpecs.entrySet().iterator(); i.hasNext();) {
385: Map.Entry e = (Map.Entry) i.next();
386: ClassSpec spec = (ClassSpec) e.getValue();
387: File f = new File(dir, spec.getName() + ".java");
388: try {
389: FileWriter o = new FileWriter(f);
390: o.write(spec.toSrcCode());
391: o.close();
392: } catch (IOException x) {
393: throw BindingSupportImpl.getInstance().runtime(
394: "Error writing to " + f + ": " + x, x);
395: }
396: }
397: }
398:
399: public ConfigInfo getConfig() {
400: return config;
401: }
402:
403: public void setConfig(ConfigInfo config) {
404: this .config = config;
405: }
406:
407: public ClassLoader getLoader() {
408: return loader;
409: }
410:
411: public void setLoader(ClassLoader loader) {
412: this .loader = loader;
413: }
414:
415: public LogEventStore getLogEventStore() {
416: return pes;
417: }
418:
419: public void setLogEventStore(LogEventStore logEventStore) {
420: this .pes = logEventStore;
421: }
422:
423: public StorageCache getCache() {
424: return cache;
425: }
426:
427: public void setCache(StorageCache cache) {
428: this .cache = cache;
429: }
430:
431: public boolean isOnlyMetaData() {
432: return onlyMetaData;
433: }
434:
435: /**
436: * If this flag is true then the SMF is created just to access the
437: * complete JDO meta data. It will not attempt to connect to a datastore.
438: */
439: public void setOnlyMetaData(boolean onlyMetaData) {
440: this .onlyMetaData = onlyMetaData;
441: }
442:
443: public boolean isFullInit() {
444: return fullInit;
445: }
446:
447: /**
448: * If this flag is true then the SMF is completely initialized ready for
449: * use e.g. the JDBC store will populate the keygen tables. This is ignored
450: * if the onlyMetaData flag is set.
451: */
452: public void setFullInit(boolean fullInit) {
453: this .fullInit = fullInit;
454: }
455:
456: public ClassCompiler getClassCompiler() {
457: return classCompiler;
458: }
459:
460: public void setClassCompiler(ClassCompiler classCompiler) {
461: this .classCompiler = classCompiler;
462: }
463:
464: public boolean isContinueAfterMetaDataError() {
465: return continueAfterMetaDataError;
466: }
467:
468: /**
469: * If this flag is set then the store should attempt to recover from
470: * meta data errors and continue instead of throwing an exception. The
471: * errors must be added to the meta data. This is to support the
472: * Workbench and other tools.
473: */
474: public void setContinueAfterMetaDataError(
475: boolean continueAfterMetaDataError) {
476: this .continueAfterMetaDataError = continueAfterMetaDataError;
477: }
478:
479: /**
480: * StorageManagerFactory's supporting hyperdrive code generation must add
481: * dynamically generated classes to this map during construction. This
482: * maps class name -> ClassSpec.
483: */
484: public HashMap getClassSpecs() {
485: return classSpecs;
486: }
487:
488: /**
489: * StorageManager's must store CompiledQueries in this cache.
490: */
491: public CompiledQueryCache getCompiledQueryCache() {
492: return compiledQueryCache;
493: }
494:
495: /**
496: * If this property is true then the bytecode for generated hyperdrive
497: * classes is avilable via {@link #getHyperdriveBytecode()} after the
498: * call to {@link #createStorageManagerFactory()} .
499: */
500: public void setKeepHyperdriveBytecode(boolean keepHyperdriveBytecode) {
501: this .keepHyperdriveBytecode = keepHyperdriveBytecode;
502: }
503:
504: public boolean isKeepHyperdriveBytecode() {
505: return keepHyperdriveBytecode;
506: }
507:
508: /**
509: * If keepHyperdriveBytecode is true and hyperdrive classes were
510: * generated then this maps each class name to its compressed byte[]
511: * bytecode or null if the class was loaded from our classloader and not
512: * compiled at runtime. Otherwise it is null.
513: *
514: * @see #setKeepHyperdriveBytecode(boolean)
515: * @see #getHyperdriveBytecodeMaxSize
516: */
517: public Map getHyperdriveBytecode() {
518: return hyperdriveBytecode;
519: }
520:
521: /**
522: * If keepHyperdriveBytecode is true and hyperdrive classes were
523: * generated then this is the length in bytes of the uncompressed
524: * bytecode for the biggest class. This is useful for sizing
525: * decompression buffers.
526: *
527: * @see #setKeepHyperdriveBytecode(boolean)
528: */
529: public int getHyperdriveBytecodeMaxSize() {
530: return hyperdriveBytecodeMaxSize;
531: }
532:
533: }
|