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