001: /*
002: * JBoss, Home of Professional Open Source.
003: * Copyright 2006, Red Hat Middleware LLC, and individual contributors
004: * as indicated by the @author tags. See the copyright.txt file in the
005: * distribution for a full listing of individual contributors.
006: *
007: * This is free software; you can redistribute it and/or modify it
008: * under the terms of the GNU Lesser General Public License as
009: * published by the Free Software Foundation; either version 2.1 of
010: * the License, or (at your option) any later version.
011: *
012: * This software is distributed in the hope that it will be useful,
013: * but WITHOUT ANY WARRANTY; without even the implied warranty of
014: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015: * Lesser General Public License for more details.
016: *
017: * You should have received a copy of the GNU Lesser General Public
018: * License along with this software; if not, write to the Free
019: * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
021: */
022: package org.jboss.ejb.plugins;
023:
024: import java.lang.reflect.Field;
025: import java.lang.reflect.Method;
026:
027: import java.io.IOException;
028:
029: import java.util.HashMap;
030: import java.util.Iterator;
031: import java.util.Collection;
032: import java.util.Collections;
033:
034: import javax.ejb.EJBException;
035:
036: import org.jboss.ejb.Container;
037: import org.jboss.ejb.EntityEnterpriseContext;
038: import org.jboss.ejb.EntityPersistenceStore;
039: import org.jboss.ejb.EntityContainer;
040: import org.jboss.ejb.GenericEntityObjectFactory;
041:
042: import org.jboss.metadata.EntityMetaData;
043:
044: import org.jboss.system.ServiceMBeanSupport;
045:
046: /**
047: * EntityPersistenceStore implementation storing values in-memory
048: * for very efficient access.
049: *
050: * @see org.jboss.ejb.EntityPersistenceStore
051: * @see org.jboss.ejb.plugins.CMPFilePersistenceManager
052: *
053: * @version <tt>$Revision: 57209 $</tt>
054: * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
055: * @author <a href="mailto:jason@planet57.com">Jason Dillon</a>
056: *
057: * <p><b>Revisions:</b>
058: *
059: * <p><b>28.12.2001 - Sacha Labourey:</b>
060: * <ul>
061: * <li> First implementation highly based on CMPFilePersistenceManager</li>
062: * </ul>
063: * <p><b>25.05.2002 - Dain Sundstrom:</b>
064: * <ul>
065: * <li> Replaced FinderResults with Collection</li>
066: * </ul>
067: */
068: public class CMPInMemoryPersistenceManager extends ServiceMBeanSupport
069: implements EntityPersistenceStore {
070: // Attributes ----------------------------------------------------
071:
072: protected EntityContainer con;
073: protected HashMap beans;
074: protected Field idField;
075:
076: /**
077: * Optional isModified method used by storeEntity.
078: */
079: protected Method isModified;
080:
081: // Constructors --------------------------------------------------
082:
083: public CMPInMemoryPersistenceManager() {
084: }
085:
086: /**
087: * This callback is set by the container so that the plugin may access it
088: *
089: * @param con The container using this plugin.
090: *
091: * @throws ClassCastException Container is not a EntityContainer.
092: */
093: public void setContainer(final Container con) {
094: this .con = (EntityContainer) con;
095: }
096:
097: /**
098: * create the service, do expensive operations etc
099: */
100: protected void createService() throws Exception {
101: boolean debug = log.isDebugEnabled();
102:
103: this .beans = new HashMap(1000);
104:
105: String ejbName = con.getBeanMetaData().getEjbName();
106:
107: idField = con.getBeanClass().getField("id");
108: if (debug)
109: log.debug("Using id field: " + idField);
110:
111: // Lookup the isModified method if it exists
112: try {
113: isModified = con.getBeanClass().getMethod("isModified",
114: new Class[0]);
115: if (!isModified.getReturnType().equals(Boolean.TYPE)) {
116: isModified = null; // Has to have "boolean" as return type!
117: log
118: .warn("Found isModified method, but return type is not boolean; ignoring");
119: } else {
120: if (debug)
121: log.debug("Using isModified method: " + isModified);
122: }
123: } catch (NoSuchMethodException ignored) {
124: }
125: }
126:
127: protected void stopService() throws Exception {
128: this .beans.clear();
129: }
130:
131: // EntityPersistenceStore implementation ----------------------------------------------
132:
133: /**
134: * Returns a new instance of the bean class or a subclass of the bean class.
135: *
136: * @return the new instance
137: *
138: * @throws Exception
139: */
140: public Object createBeanClassInstance() throws Exception {
141: return con.getBeanClass().newInstance();
142: }
143:
144: /**
145: * Initializes the instance context.
146: *
147: * <p>This method is called before createEntity, and should
148: * reset the value of all cmpFields to 0 or null.
149: *
150: * @param ctx
151: */
152: public void initEntity(EntityEnterpriseContext ctx) {
153: // first get cmp metadata of this entity
154: Object instance = ctx.getInstance();
155: Class ejbClass = instance.getClass();
156: Field cmpField;
157: Class cmpFieldType;
158:
159: EntityMetaData metaData = (EntityMetaData) con
160: .getBeanMetaData();
161: Iterator i = metaData.getCMPFields();
162:
163: while (i.hasNext()) {
164: // get the field declaration
165: try {
166: cmpField = ejbClass.getField((String) i.next());
167: cmpFieldType = cmpField.getType();
168:
169: // find the type of the field and resets it
170: // to the default value
171: if (cmpFieldType.equals(boolean.class)) {
172: cmpField.setBoolean(instance, false);
173: } else if (cmpFieldType.equals(byte.class)) {
174: cmpField.setByte(instance, (byte) 0);
175: } else if (cmpFieldType.equals(int.class)) {
176: cmpField.setInt(instance, 0);
177: } else if (cmpFieldType.equals(long.class)) {
178: cmpField.setLong(instance, 0L);
179: } else if (cmpFieldType.equals(short.class)) {
180: cmpField.setShort(instance, (short) 0);
181: } else if (cmpFieldType.equals(char.class)) {
182: cmpField.setChar(instance, '\u0000');
183: } else if (cmpFieldType.equals(double.class)) {
184: cmpField.setDouble(instance, 0d);
185: } else if (cmpFieldType.equals(float.class)) {
186: cmpField.setFloat(instance, 0f);
187: } else {
188: cmpField.set(instance, null);
189: }
190: } catch (NoSuchFieldException e) {
191: // will be here with dependant value object's private attributes
192: // should not be a problem
193: } catch (Exception e) {
194: throw new EJBException(e);
195: }
196: }
197: }
198:
199: /**
200: * This method is called whenever an entity is to be created.
201: * The persistence manager is responsible for handling the results properly
202: * wrt the persistent store.
203: *
204: * @param m the create method in the home interface that was
205: * called
206: * @param args any create parameters
207: * @param ctx the instance being used for this create call
208: * @return The primary key computed by CMP PM or null for BMP
209: *
210: * @throws Exception
211: */
212: public Object createEntity(Method m, Object[] args,
213: EntityEnterpriseContext ctx) throws Exception {
214: try {
215: Object id = idField.get(ctx.getInstance());
216:
217: // Check exist
218: if (this .beans.containsKey(id))
219: throw new javax.ejb.DuplicateKeyException(
220: "Already exists: " + id);
221:
222: // Store to file
223: storeEntity(id, ctx.getInstance());
224:
225: return id;
226: } catch (IllegalAccessException e) {
227: throw new javax.ejb.CreateException(
228: "Could not create entity: " + e);
229: }
230: }
231:
232: /**
233: * This method is called after the createEntity.
234: * The persistence manager is responsible for handling the results properly
235: * wrt the persistent store.
236: *
237: * @param m the ejbPostCreate method in the bean class that was
238: * called
239: * @param args any create parameters
240: * @param ctx the instance being used for this create call
241: * @return null
242: *
243: * @throws Exception
244: */
245: public Object postCreateEntity(final Method m, final Object[] args,
246: final EntityEnterpriseContext ctx) throws Exception {
247: return null;
248: }
249:
250: /**
251: * This method is called when single entities are to be found. The
252: * persistence manager must find out whether the wanted instance is
253: * available in the persistence store, if so it returns the primary key of
254: * the object.
255: *
256: * @param finderMethod the find method in the home interface that was
257: * called
258: * @param args any finder parameters
259: * @param instance the instance to use for the finder call
260: * @return a primary key representing the found entity
261: *
262: * @throws Exception thrown if some heuristic problem occurs
263: */
264: public Object findEntity(Method finderMethod, Object[] args,
265: EntityEnterpriseContext instance,
266: GenericEntityObjectFactory factory) throws Exception {
267: if (finderMethod.getName().equals("findByPrimaryKey")) {
268: if (!this .beans.containsKey(args[0]))
269: throw new javax.ejb.FinderException(args[0]
270: + " does not exist");
271:
272: return factory.getEntityEJBObject(args[0]);
273: }
274:
275: return null;
276: }
277:
278: /**
279: * This method is called when collections of entities are to be found. The
280: * persistence manager must find out whether the wanted instances are
281: * available in the persistence store, and if so it must return a
282: * collection of primaryKeys.
283: *
284: * @param finderMethod the find method in the home interface that was
285: * called
286: * @param args any finder parameters
287: * @param instance the instance to use for the finder call
288: * @return an primary key collection representing the found
289: * entities
290: *
291: * @throws Exception thrown if some heuristic problem occurs
292: */
293: public Collection findEntities(final Method finderMethod,
294: final Object[] args,
295: final EntityEnterpriseContext instance,
296: GenericEntityObjectFactory factory) throws Exception {
297: if (finderMethod.getName().equals("findAll")) {
298: return GenericEntityObjectFactory.UTIL.getEntityCollection(
299: factory, this .beans.keySet());
300: } else {
301: // we only support find all
302: return Collections.EMPTY_LIST;
303: }
304: }
305:
306: /**
307: * Non-operation.
308: */
309: public void activateEntity(EntityEnterpriseContext instance) {
310: // nothing to do
311: }
312:
313: /**
314: * This method is called whenever an entity shall be load from the
315: * underlying storage. The persistence manager must load the state from
316: * the underlying storage and then call ejbLoad on the supplied instance.
317: *
318: * @param ctx the instance to synchronize
319: */
320: public void loadEntity(EntityEnterpriseContext ctx) {
321: try {
322: // Read fields
323:
324: java.io.ObjectInputStream in = new CMPObjectInputStream(
325: new java.io.ByteArrayInputStream(
326: (byte[]) this .beans.get(ctx.getId())));
327:
328: Object obj = ctx.getInstance();
329:
330: Field[] f = obj.getClass().getFields();
331: for (int i = 0; i < f.length; i++) {
332: f[i].set(obj, in.readObject());
333: }
334:
335: in.close();
336: } catch (Exception e) {
337: throw new EJBException("Load failed", e);
338: }
339: }
340:
341: /**
342: * This method is used to determine if an entity should be stored.
343: *
344: * @param ctx the instance to check
345: * @return true, if the entity has been modified
346: * @throws Exception thrown if some system exception occurs
347: */
348: public boolean isStoreRequired(EntityEnterpriseContext ctx)
349: throws Exception {
350: if (isModified == null) {
351: return true;
352: }
353:
354: Boolean modified = (Boolean) isModified.invoke(ctx
355: .getInstance(), new Object[0]);
356: return modified.booleanValue();
357: }
358:
359: public boolean isModified(EntityEnterpriseContext ctx)
360: throws Exception {
361: return isStoreRequired(ctx);
362: }
363:
364: /**
365: * This method is called whenever an entity shall be stored to the
366: * underlying storage. The persistence manager must call ejbStore on the
367: * supplied instance and then store the state to the underlying storage.
368: *
369: * @param ctx the instance to synchronize
370: */
371: public void storeEntity(EntityEnterpriseContext ctx) {
372: storeEntity(ctx.getId(), ctx.getInstance());
373: }
374:
375: /**
376: * Non-operation.
377: */
378: public void passivateEntity(EntityEnterpriseContext instance) {
379: // This plugin doesn't do anything specific
380: }
381:
382: /**
383: * This method is called when an entity shall be removed from the
384: * underlying storage. The persistence manager must call ejbRemove on the
385: * instance and then remove its state from the underlying storage.
386: *
387: * @param ctx the instance to remove
388: *
389: * @throws javax.ejb.RemoveException thrown if the instance could not be removed
390: */
391: public void removeEntity(EntityEnterpriseContext ctx)
392: throws javax.ejb.RemoveException {
393: if (this .beans.remove(ctx.getId()) == null)
394: throw new javax.ejb.RemoveException(
395: "Could not remove bean:" + ctx.getId());
396: }
397:
398: // Protected -----------------------------------------------------
399:
400: protected void storeEntity(Object id, Object obj) {
401: try {
402: // Store fields
403: java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
404: java.io.ObjectOutputStream out = new CMPObjectOutputStream(
405: baos);
406:
407: try {
408: Field[] f = obj.getClass().getFields();
409: for (int i = 0; i < f.length; i++) {
410: out.writeObject(f[i].get(obj));
411: }
412: } finally {
413: out.close();
414: }
415:
416: this .beans.put(id, baos.toByteArray());
417: } catch (Exception e) {
418: throw new EJBException("Store failed", e);
419: }
420: }
421:
422: // Inner classes -------------------------------------------------
423:
424: static class CMPObjectOutputStream extends
425: java.io.ObjectOutputStream {
426: public CMPObjectOutputStream(java.io.OutputStream out)
427: throws IOException {
428: super (out);
429: enableReplaceObject(true);
430: }
431:
432: protected Object replaceObject(Object obj) throws IOException {
433: if (obj instanceof javax.ejb.EJBObject)
434: return ((javax.ejb.EJBObject) obj).getHandle();
435:
436: return obj;
437: }
438: }
439:
440: static class CMPObjectInputStream extends java.io.ObjectInputStream {
441: public CMPObjectInputStream(java.io.InputStream in)
442: throws IOException {
443: super (in);
444: enableResolveObject(true);
445: }
446:
447: protected Object resolveObject(Object obj) throws IOException {
448: if (obj instanceof javax.ejb.Handle)
449: return ((javax.ejb.Handle) obj).getEJBObject();
450:
451: return obj;
452: }
453: }
454:
455: }
|