001/* 002 GRANITE DATA SERVICES 003 Copyright (C) 2011 GRANITE DATA SERVICES S.A.S. 004 005 This file is part of Granite Data Services. 006 007 Granite Data Services is free software; you can redistribute it and/or modify 008 it under the terms of the GNU Library General Public License as published by 009 the Free Software Foundation; either version 2 of the License, or (at your 010 option) any later version. 011 012 Granite Data Services is distributed in the hope that it will be useful, but 013 WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 014 FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License 015 for more details. 016 017 You should have received a copy of the GNU Library General Public License 018 along with this library; if not, see <http://www.gnu.org/licenses/>. 019*/ 020 021package org.granite.tide.ejb; 022 023import java.io.IOException; 024import java.io.ObjectInputStream; 025import java.io.ObjectOutputStream; 026import java.lang.reflect.Method; 027import java.util.ArrayList; 028import java.util.HashSet; 029import java.util.List; 030import java.util.Map; 031import java.util.Set; 032import java.util.concurrent.ConcurrentHashMap; 033 034import javax.ejb.NoSuchEJBException; 035import javax.naming.InitialContext; 036import javax.naming.NamingException; 037import javax.persistence.EntityManager; 038import javax.persistence.EntityManagerFactory; 039 040import org.granite.logging.Logger; 041import org.granite.messaging.service.EjbServiceMetadata; 042import org.granite.messaging.service.ServiceException; 043import org.granite.messaging.service.ServiceInvocationContext; 044import org.granite.tide.IInvocationCall; 045import org.granite.tide.IInvocationResult; 046import org.granite.tide.TidePersistenceManager; 047import org.granite.tide.TideServiceContext; 048import org.granite.tide.annotations.BypassTideMerge; 049import org.granite.tide.async.AsyncPublisher; 050import org.granite.tide.data.DataContext; 051import org.granite.tide.data.JPAPersistenceManager; 052import org.granite.tide.invocation.ContextEvent; 053import org.granite.tide.invocation.ContextUpdate; 054import org.granite.tide.invocation.InvocationCall; 055import org.granite.tide.invocation.InvocationResult; 056import org.granite.tide.util.AbstractContext; 057 058 059/** 060 * @author William DRAI 061 */ 062public class EjbServiceContext extends TideServiceContext { 063 064 private static final long serialVersionUID = 1L; 065 066 private static final Logger log = Logger.getLogger(EjbServiceContext.class); 067 068 public static final String CAPITALIZED_DESTINATION_ID = "{capitalized.component.name}"; 069 public static final String DESTINATION_ID = "{component.name}"; 070 071 private transient ConcurrentHashMap<String, EjbComponent> ejbLookupCache = new ConcurrentHashMap<String, EjbComponent>(); 072 private final Set<String> remoteObservers = new HashSet<String>(); 073 074 private final InitialContext initialContext; 075 private final String lookup; 076 077 private final EjbIdentity identity; 078 079 private String entityManagerFactoryJndiName = null; 080 private String entityManagerJndiName = null; 081 082 083 public EjbServiceContext() throws ServiceException { 084 super(); 085 lookup = ""; 086 initialContext = null; 087 identity = new EjbIdentity(); 088 } 089 090 public EjbServiceContext(String lookup, InitialContext ic) throws ServiceException { 091 super(); 092 this.lookup = lookup; 093 this.initialContext = ic; 094 identity = new EjbIdentity(); 095 } 096 097 098 @Override 099 protected AsyncPublisher getAsyncPublisher() { 100 return null; 101 } 102 103 104 public void setEntityManagerFactoryJndiName(String entityManagerFactoryJndiName) { 105 this.entityManagerFactoryJndiName = entityManagerFactoryJndiName; 106 } 107 108 public void setEntityManagerJndiName(String entityManagerJndiName) { 109 this.entityManagerJndiName = entityManagerJndiName; 110 } 111 112 /** 113 * Create a TidePersistenceManager 114 * 115 * @param create create if not existent (can be false for use in entity merge) 116 * @return a TidePersistenceManager 117 */ 118 @Override 119 protected TidePersistenceManager getTidePersistenceManager(boolean create) { 120 if (!create) 121 return null; 122 123 EntityManager em = getEntityManager(); 124 if (em == null) 125 return null; 126 127 return new JPAPersistenceManager(em); 128 } 129 130 131 /** 132 * Find the entity manager using the jndi names stored in the bean. 133 * @return The found entity manager 134 */ 135 private EntityManager getEntityManager() { 136 try { 137 InitialContext jndiContext = initialContext != null ? initialContext : new InitialContext(); 138 139 if (entityManagerFactoryJndiName != null) { 140 EntityManagerFactory factory = (EntityManagerFactory) jndiContext.lookup(entityManagerFactoryJndiName); 141 return factory.createEntityManager(); 142 } 143 else if (entityManagerJndiName != null) { 144 return (EntityManager) jndiContext.lookup(entityManagerJndiName); 145 } 146 } 147 catch (NamingException e) { 148 if (entityManagerFactoryJndiName != null) 149 throw new RuntimeException("Unable to find a EntityManagerFactory for jndiName " + entityManagerFactoryJndiName); 150 else if (entityManagerJndiName != null) 151 throw new RuntimeException("Unable to find a EntityManager for jndiName " + entityManagerJndiName); 152 } 153 154 return null; 155 } 156 157 158 public Object callComponent(Method method, Object... args) throws Exception { 159 String name = method.getDeclaringClass().getSimpleName(); 160 name = name.substring(0, 1).toLowerCase() + name.substring(1); 161 if (name.endsWith("Bean")) 162 name = name.substring(0, name.length() - "Bean".length()); 163 Object invokee = findComponent(name, null); 164 method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes()); 165 return method.invoke(invokee, args); 166 } 167 168 public Set<String> getRemoteObservers() { 169 return remoteObservers; 170 } 171 172 /* (non-Javadoc) 173 * @see org.granite.tide.ejb.EJBServiceContextIntf#findComponent(java.lang.String) 174 */ 175 @Override 176 public Object findComponent(String componentName, Class<?> componentClass) { 177 if ("identity".equals(componentName)) 178 return identity; 179 180 EjbComponent component = ejbLookupCache.get(componentName); 181 if (component != null) 182 return component.ejbInstance; 183 184 // Compute EJB JNDI binding. 185 String name = componentName; 186 if (lookup != null) { 187 name = lookup; 188 if (lookup.contains(CAPITALIZED_DESTINATION_ID)) 189 name = lookup.replace(CAPITALIZED_DESTINATION_ID, capitalize(componentName)); 190 if (lookup.contains(DESTINATION_ID)) 191 name = lookup.replace(DESTINATION_ID, componentName); 192 } 193 194 InitialContext ic = this.initialContext; 195 if (ic == null) { 196 try { 197 ic = new InitialContext(); 198 } 199 catch (Exception e) { 200 throw new ServiceException("Could not get InitialContext", e); 201 } 202 } 203 204 log.debug(">> New EjbServiceInvoker looking up: %s", name); 205 206 try { 207 component = new EjbComponent(); 208 component.ejbInstance = ic.lookup(name); 209 component.ejbClasses = new HashSet<Class<?>>(); 210 Class<?> scannedClass = null; 211 EjbScannedItemHandler itemHandler = EjbScannedItemHandler.instance(); 212 for (Class<?> i : component.ejbInstance.getClass().getInterfaces()) { 213 if (itemHandler.getScannedClasses().containsKey(i)) { 214 scannedClass = itemHandler.getScannedClasses().get(i); 215 break; 216 } 217 } 218 if (scannedClass == null) 219 scannedClass = itemHandler.getScannedClasses().get(component.ejbInstance.getClass()); 220 // GDS-768: handling of proxied no-interface EJBs in GlassFish v3 221 if (scannedClass == null && component.ejbInstance.getClass().getSuperclass() != null) 222 scannedClass = itemHandler.getScannedClasses().get(component.ejbInstance.getClass().getSuperclass()); 223 224 if (scannedClass != null) { 225 component.ejbClasses.add(scannedClass); 226 for (Map.Entry<Class<?>, Class<?>> me : itemHandler.getScannedClasses().entrySet()) { 227 if (me.getValue().equals(scannedClass)) 228 component.ejbClasses.add(me.getKey()); 229 } 230 component.ejbMetadata = new EjbServiceMetadata(scannedClass, component.ejbInstance.getClass()); 231 } 232 else 233 log.warn("Ejb " + componentName + " was not scanned: remove method will not be called if it is a Stateful bean. Add META-INF/services-config.properties if needed."); 234 235 EjbComponent tmpComponent = ejbLookupCache.putIfAbsent(componentName, component); 236 if (tmpComponent != null) 237 component = tmpComponent; 238 return component.ejbInstance; 239 } 240 catch (NamingException e) { 241 log.error("EJB not found " + name + ": " + e.getMessage()); 242 throw new ServiceException("Could not lookup for: " + name, e); 243 } 244 } 245 246 /* (non-Javadoc) 247 * @see org.granite.tide.ejb.EJBServiceContextIntf#findComponentClass(java.lang.String) 248 */ 249 @Override 250 public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass) { 251 if ("identity".equals(componentName)) { 252 Set<Class<?>> classes = new HashSet<Class<?>>(1); 253 classes.add(EjbIdentity.class); 254 return classes; 255 } 256 257 EjbComponent component = ejbLookupCache.get(componentName); 258 if (component == null) 259 findComponent(componentName, componentClass); 260 return ejbLookupCache.get(componentName).ejbClasses; 261 } 262 263 264 private String capitalize(String s) { 265 if (s == null || s.length() == 0) 266 return s; 267 if (s.length() == 1) 268 return s.toUpperCase(); 269 return s.substring(0, 1).toUpperCase() + s.substring(1); 270 } 271 272 /* (non-Javadoc) 273 * @see org.granite.tide.ejb.EJBServiceContextIntf#prepareCall(org.granite.messaging.service.ServiceInvocationContext, org.granite.tide.IInvocationCall, java.lang.String) 274 */ 275 @Override 276 public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) { 277 if ((c instanceof InvocationCall) && ((InvocationCall)c).getListeners() != null) 278 remoteObservers.addAll(((InvocationCall)c).getListeners()); 279 Context.create(this); 280 281 // Initialize an empty data context 282 DataContext.init(); 283 } 284 285 286 private static class EjbComponent { 287 public Object ejbInstance; 288 public Set<Class<?>> ejbClasses; 289 public EjbServiceMetadata ejbMetadata; 290 } 291 292 /* (non-Javadoc) 293 * @see org.granite.tide.ejb.EJBServiceContextIntf#postCall(org.granite.messaging.service.ServiceInvocationContext, java.lang.Object, java.lang.String) 294 */ 295 @Override 296 public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) { 297 try { 298 AbstractContext threadContext = AbstractContext.instance(); 299 300 List<ContextUpdate> results = new ArrayList<ContextUpdate>(threadContext.size()); 301 DataContext dataContext = DataContext.get(); 302 Object[][] updates = dataContext != null ? dataContext.getUpdates() : null; 303 304 for (Map.Entry<String, Object> entry : threadContext.entrySet()) 305 results.add(new ContextUpdate(entry.getKey(), null, entry.getValue(), 3, false)); 306 307 InvocationResult ires = new InvocationResult(result, results); 308 if (componentName != null || componentClass != null) { 309 Set<Class<?>> componentClasses = findComponentClasses(componentName, componentClass); 310 if (isBeanAnnotationPresent(componentClasses, context.getMethod().getName(), context.getMethod().getParameterTypes(), BypassTideMerge.class)) 311 ires.setMerge(false); 312 } 313 314 ires.setUpdates(updates); 315 ires.setEvents(new ArrayList<ContextEvent>(threadContext.getRemoteEvents())); 316 317 if (componentName != null) { 318 EjbComponent component = ejbLookupCache.get(componentName); 319 if (component != null && component.ejbMetadata != null 320 && component.ejbMetadata.isStateful() && component.ejbMetadata.isRemoveMethod(context.getMethod())) 321 ejbLookupCache.remove(componentName); 322 } 323 324 return ires; 325 } 326 finally { 327 AbstractContext.remove(); 328 } 329 } 330 331 /* (non-Javadoc) 332 * @see org.granite.tide.ejb.EJBServiceContextIntf#postCallFault(org.granite.messaging.service.ServiceInvocationContext, java.lang.Throwable, java.lang.String) 333 */ 334 @Override 335 public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) { 336 try { 337 if (componentName != null) { 338 EjbComponent component = ejbLookupCache.get(componentName); 339 if (t instanceof NoSuchEJBException || (component != null && component.ejbMetadata != null && 340 (component.ejbMetadata.isStateful() && 341 component.ejbMetadata.isRemoveMethod(context.getMethod()) && 342 !component.ejbMetadata.getRetainIfException(context.getMethod())) 343 )) { 344 ejbLookupCache.remove(componentName); 345 } 346 } 347 } 348 finally { 349 AbstractContext.remove(); 350 } 351 } 352 353 private void writeObject(ObjectOutputStream out) throws IOException { 354 out.defaultWriteObject(); 355 } 356 357 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 358 in.defaultReadObject(); 359 ejbLookupCache = new ConcurrentHashMap<String, EjbComponent>(); 360 } 361}