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: import java.io.IOException;
027: import java.util.ArrayList;
028: import java.util.Collection;
029: import java.util.Collections;
030: import java.util.List;
031:
032: import javax.ejb.EJBException;
033:
034: import org.jboss.ejb.EntityEnterpriseContext;
035: import org.jboss.ejb.GenericEntityObjectFactory;
036: import org.jboss.ha.framework.interfaces.DistributedState;
037: import org.jboss.metadata.BeanMetaData;
038: import org.jboss.metadata.ClusterConfigMetaData;
039: import org.jboss.metadata.EntityMetaData;
040:
041: /**
042: * EntityPersistenceStore implementation storing values in-memory
043: * and shared accross the cluster through the DistributedState service
044: * from the clustering framework. It always uses the DefaultPartition.
045: *
046: * @see org.jboss.ejb.EntityPersistenceStore
047: * @see org.jboss.ejb.plugins.CMPInMemoryPersistenceManager
048: * @see org.jboss.ha.framework.interfaces.DistributedState
049: *
050: * @author <a href="mailto:sacha.labourey@cogito-info.ch">Sacha Labourey</a>.
051: * @version $Revision: 57188 $
052: */
053: public class CMPClusteredInMemoryPersistenceManager implements
054: org.jboss.ejb.EntityPersistenceStore {
055: // Constants -----------------------------------------------------
056:
057: // Attributes ----------------------------------------------------
058:
059: protected org.jboss.ejb.EntityContainer con = null;
060: protected Field idField = null;
061:
062: protected DistributedState ds = null;
063:
064: protected String DS_CATEGORY = null;
065:
066: /**
067: * Optional isModified method used by storeEntity
068: */
069: protected Method isModified = null;
070:
071: // Static --------------------------------------------------------
072:
073: // Constructors --------------------------------------------------
074:
075: public CMPClusteredInMemoryPersistenceManager() {
076: }
077:
078: /**
079: * This callback is set by the container so that the plugin may access it
080: *
081: * @param con The container using this plugin.
082: */
083: public void setContainer(org.jboss.ejb.Container con) {
084: this .con = (org.jboss.ejb.EntityContainer) con;
085: }
086:
087: /**
088: * create the service, do expensive operations etc
089: */
090: public void create() throws Exception {
091: BeanMetaData bmd = con.getBeanMetaData();
092: ClusterConfigMetaData ccmd = bmd.getClusterConfigMetaData();
093: String partitionName = ccmd.getPartitionName();
094: String name = "jboss:service=DistributedState,partitionName="
095: + partitionName;
096: ds = (DistributedState) org.jboss.system.Registry.lookup(name);
097:
098: String ejbName = bmd.getEjbName();
099: this .DS_CATEGORY = "CMPClusteredInMemoryPersistenceManager-"
100: + ejbName;
101:
102: idField = con.getBeanClass().getField("id");
103:
104: try {
105: isModified = con.getBeanClass().getMethod("isModified",
106: new Class[0]);
107: if (!isModified.getReturnType().equals(Boolean.TYPE))
108: isModified = null; // Has to have "boolean" as return type!
109: } catch (NoSuchMethodException ignored) {
110: }
111: }
112:
113: /**
114: * start the service, create is already called
115: */
116: public void start() throws Exception {
117: }
118:
119: /**
120: * stop the service
121: */
122: public void stop() {
123: }
124:
125: /**
126: * destroy the service, tear down
127: */
128: public void destroy() {
129: }
130:
131: // Public --------------------------------------------------------
132:
133: // EntityPersistenceStore implementation ----------------------------------------------
134:
135: /**
136: * Returns a new instance of the bean class or a subclass of the bean class.
137: *
138: * @return the new instance
139: *
140: * @throws Exception
141: */
142: public Object createBeanClassInstance() throws Exception {
143: return con.getBeanClass().newInstance();
144: }
145:
146: /**
147: * Initializes the instance context.
148: *
149: * <p>This method is called before createEntity, and should
150: * reset the value of all cmpFields to 0 or null.
151: *
152: * @param ctx
153: */
154: public void initEntity(EntityEnterpriseContext ctx) {
155: // first get cmp metadata of this entity
156: Object instance = ctx.getInstance();
157: Class ejbClass = instance.getClass();
158: Field cmpField;
159: Class cmpFieldType;
160:
161: EntityMetaData metaData = (EntityMetaData) con
162: .getBeanMetaData();
163: java.util.Iterator i = metaData.getCMPFields();
164:
165: while (i.hasNext()) {
166: try {
167: // get the field declaration
168: try {
169: cmpField = ejbClass.getField((String) i.next());
170: cmpFieldType = cmpField.getType();
171: // find the type of the field and reset it
172: // to the default value
173: if (cmpFieldType.equals(boolean.class)) {
174: cmpField.setBoolean(instance, false);
175: } else if (cmpFieldType.equals(byte.class)) {
176: cmpField.setByte(instance, (byte) 0);
177: } else if (cmpFieldType.equals(int.class)) {
178: cmpField.setInt(instance, 0);
179: } else if (cmpFieldType.equals(long.class)) {
180: cmpField.setLong(instance, 0L);
181: } else if (cmpFieldType.equals(short.class)) {
182: cmpField.setShort(instance, (short) 0);
183: } else if (cmpFieldType.equals(char.class)) {
184: cmpField.setChar(instance, '\u0000');
185: } else if (cmpFieldType.equals(double.class)) {
186: cmpField.setDouble(instance, 0d);
187: } else if (cmpFieldType.equals(float.class)) {
188: cmpField.setFloat(instance, 0f);
189: } else {
190: cmpField.set(instance, null);
191: }
192: } catch (NoSuchFieldException e) {
193: // will be here with dependant value object's private attributes
194: // should not be a problem
195: }
196: } catch (Exception e) {
197: throw new EJBException(e);
198: }
199: }
200: }
201:
202: /**
203: * This method is called whenever an entity is to be created.
204: * The persistence manager is responsible for handling the results properly
205: * wrt the persistent store.
206: *
207: * @param m the create method in the home interface that was
208: * called
209: * @param args any create parameters
210: * @param ctx the instance ctx being used for this create call
211: * @return The primary key computed by CMP PM or null for BMP
212: *
213: * @throws Exception
214: */
215: public Object createEntity(Method m, Object[] args,
216: EntityEnterpriseContext ctx) throws Exception {
217: try {
218:
219: Object id = idField.get(ctx.getInstance());
220:
221: // Check exist
222: if (this .ds.get(DS_CATEGORY, id.toString()) != null)
223: throw new javax.ejb.DuplicateKeyException(
224: "Already exists:" + id);
225:
226: // Store to file
227: storeEntity(id, ctx.getInstance());
228:
229: return id;
230: } catch (IllegalAccessException e) {
231: throw new javax.ejb.CreateException(
232: "Could not create entity:" + e);
233: }
234: }
235:
236: /**
237: * This method is called after the ejbCreate.
238: * The persistence manager is responsible for handling the results properly
239: * wrt the persistent store.
240: *
241: * @param m the ejbPostCreate method in the bean class that was
242: * called
243: * @param args any create parameters
244: * @param ctx the instance being used for this create call
245: * @return The primary key computed by CMP PM or null for BMP
246: *
247: * @throws Exception
248: */
249: public Object postCreateEntity(Method m, Object[] args,
250: EntityEnterpriseContext ctx) throws Exception {
251: return null;
252: }
253:
254: /**
255: * This method is called when single entities are to be found. The
256: * persistence manager must find out whether the wanted instance is
257: * available in the persistence store, if so it returns the primary key of
258: * the object.
259: *
260: * @param finderMethod the find method in the home interface that was
261: * called
262: * @param args any finder parameters
263: * @param instance the instance to use for the finder call
264: * @return a primary key representing the found entity
265: *
266: * @throws java.rmi.RemoteException thrown if some system exception occurs
267: * @throws javax.ejb.FinderException thrown if some heuristic problem occurs
268: */
269: public Object findEntity(Method finderMethod, Object[] args,
270: EntityEnterpriseContext instance,
271: GenericEntityObjectFactory factory) throws Exception {
272: if (finderMethod.getName().equals("findByPrimaryKey")) {
273: if (this .ds.get(DS_CATEGORY, args[0].toString()) == null)
274: throw new javax.ejb.FinderException(args[0]
275: + " does not exist");
276:
277: return factory.getEntityEJBObject(args[0]);
278: } else
279: return null;
280: }
281:
282: /**
283: * This method is called when collections of entities are to be found. The
284: * persistence manager must find out whether the wanted instances are
285: * available in the persistence store, and if so it must return a
286: * collection of primaryKeys.
287: *
288: * @param finderMethod the find method in the home interface that was
289: * called
290: * @param args any finder parameters
291: * @param instance the instance to use for the finder call
292: * @return an primary key collection representing the found
293: * entities
294: *
295: * @throws java.rmi.RemoteException thrown if some system exception occurs
296: * @throws javax.ejb.FinderException thrown if some heuristic problem occurs
297: */
298: public Collection findEntities(Method finderMethod, Object[] args,
299: EntityEnterpriseContext instance,
300: GenericEntityObjectFactory factory) throws Exception {
301: Collection results = Collections.EMPTY_LIST;
302: if (finderMethod.getName().equals("findAll")) {
303: Collection tmpColl = this .ds.getAllKeys(DS_CATEGORY);
304: if (tmpColl != null)
305: results = GenericEntityObjectFactory.UTIL
306: .getEntityCollection(factory, tmpColl);
307: }
308: return results;
309: }
310:
311: /**
312: * This method is called when an entity shall be activated.
313: *
314: * <p>With the PersistenceManager factorization most EJB calls should not
315: * exists However this calls permits us to introduce optimizations in
316: * the persistence store. Particularly the context has a
317: * "PersistenceContext" that a PersistenceStore can use (JAWS does for
318: * smart updates) and this is as good a callback as any other to set it
319: * up.
320: *
321: * @param instance the instance to use for the activation
322: *
323: * @throws java.rmi.RemoteException thrown if some system exception occurs
324: */
325: public void activateEntity(EntityEnterpriseContext instance) {
326: }
327:
328: /**
329: * This method is called whenever an entity shall be load from the
330: * underlying storage. The persistence manager must load the state from
331: * the underlying storage and then call ejbLoad on the supplied instance.
332: *
333: * @param ctx the instance to synchronize
334: *
335: * @throws java.rmi.RemoteException thrown if some system exception occurs
336: */
337: public void loadEntity(EntityEnterpriseContext ctx) {
338: try {
339: // Read fields
340: byte[] content = (byte[]) this .ds.get(this .DS_CATEGORY, ctx
341: .getId().toString());
342:
343: if (content == null)
344: throw new javax.ejb.EJBException(
345: "No entry exists (any more?) with this id: "
346: + ctx.getId());
347:
348: java.io.ObjectInputStream in = new org.jboss.ejb.plugins.CMPClusteredInMemoryPersistenceManager.CMPObjectInputStream(
349: new java.io.ByteArrayInputStream(content));
350:
351: Object obj = ctx.getInstance();
352:
353: Field[] f = obj.getClass().getFields();
354: for (int i = 0; i < f.length; i++) {
355: f[i].set(obj, in.readObject());
356: }
357:
358: in.close();
359:
360: } catch (javax.ejb.EJBException e) {
361: throw e;
362: } catch (Exception e) {
363: throw new EJBException("Load failed", e);
364: }
365: }
366:
367: /**
368: * This method is used to determine if an entity should be stored.
369: *
370: * @param ctx the instance to check
371: * @return true, if the entity has been modified
372: * @throws Exception thrown if some system exception occurs
373: */
374: public boolean isStoreRequired(EntityEnterpriseContext ctx)
375: throws Exception {
376: if (isModified == null) {
377: return true;
378: }
379:
380: Object[] args = {};
381: Boolean modified = (Boolean) isModified.invoke(ctx
382: .getInstance(), args);
383: return modified.booleanValue();
384: }
385:
386: public boolean isModified(EntityEnterpriseContext ctx)
387: throws Exception {
388: return isStoreRequired(ctx);
389: }
390:
391: /**
392: * This method is called whenever an entity shall be stored to the
393: * underlying storage. The persistence manager must call ejbStore on the
394: * supplied instance and then store the state to the underlying storage.
395: *B
396: * @param ctx the instance to synchronize
397: *
398: * @throws java.rmi.RemoteException thrown if some system exception occurs
399: */
400: public void storeEntity(EntityEnterpriseContext ctx)
401: throws java.rmi.RemoteException {
402: try {
403: storeEntity(ctx.getId(), ctx.getInstance());
404: } catch (Exception e) {
405: throw new java.rmi.RemoteException(e.toString());
406: }
407: }
408:
409: /**
410: * This method is called when an entity shall be passivate. The persistence
411: * manager must call the ejbPassivate method on the instance.
412: *
413: * <p>See the activate discussion for the reason for exposing EJB callback
414: * calls to the store.
415: *
416: * @param instance the instance to passivate
417: *
418: * @throws java.rmi.RemoteException thrown if some system exception occurs
419: */
420: public void passivateEntity(EntityEnterpriseContext instance) {
421: // This plugin doesn't do anything specific
422: }
423:
424: /**
425: * This method is called when an entity shall be removed from the
426: * underlying storage. The persistence manager must call ejbRemove on the
427: * instance and then remove its state from the underlying storage.
428: *
429: * @param ctx the instance to remove
430: *
431: * @throws java.rmi.RemoteException thrown if some system exception occurs
432: * @throws javax.ejb.RemoveException thrown if the instance could not be removed
433: */
434: public void removeEntity(EntityEnterpriseContext ctx)
435: throws javax.ejb.RemoveException {
436: try {
437: if (this .ds.remove(this .DS_CATEGORY,
438: ctx.getId().toString(), false) == null)
439: throw new javax.ejb.RemoveException(
440: "Could not remove bean:" + ctx.getId());
441: } catch (Exception e) {
442: throw new javax.ejb.RemoveException(e.toString());
443: }
444: }
445:
446: // Y overrides ---------------------------------------------------
447:
448: // Package protected ---------------------------------------------
449:
450: // Protected -----------------------------------------------------
451:
452: protected void storeEntity(Object id, Object obj) throws Exception {
453: try {
454: // Store fields
455: java.io.ByteArrayOutputStream baos = new java.io.ByteArrayOutputStream();
456: java.io.ObjectOutputStream out = new org.jboss.ejb.plugins.CMPClusteredInMemoryPersistenceManager.CMPObjectOutputStream(
457: baos);
458:
459: Field[] f = obj.getClass().getFields();
460: for (int i = 0; i < f.length; i++) {
461: out.writeObject(f[i].get(obj));
462: }
463:
464: out.close();
465:
466: this .ds.set(this .DS_CATEGORY, id.toString(), baos
467: .toByteArray(), false);
468:
469: } catch (Exception e) {
470: throw new EJBException("Store failed", e);
471: }
472: }
473:
474: // Private -------------------------------------------------------
475:
476: // Inner classes -------------------------------------------------
477:
478: static class CMPObjectOutputStream extends
479: java.io.ObjectOutputStream {
480: public CMPObjectOutputStream(java.io.OutputStream out)
481: throws IOException {
482: super (out);
483: enableReplaceObject(true);
484: }
485:
486: protected Object replaceObject(Object obj) throws IOException {
487: if (obj instanceof javax.ejb.EJBObject)
488: return ((javax.ejb.EJBObject) obj).getHandle();
489:
490: return obj;
491: }
492: }
493:
494: static class CMPObjectInputStream extends java.io.ObjectInputStream {
495: public CMPObjectInputStream(java.io.InputStream in)
496: throws IOException {
497: super (in);
498: enableResolveObject(true);
499: }
500:
501: protected Object resolveObject(Object obj) throws IOException {
502: if (obj instanceof javax.ejb.Handle)
503: return ((javax.ejb.Handle) obj).getEJBObject();
504:
505: return obj;
506: }
507: }
508:
509: }
|