/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.tide.ejb;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import javax.ejb.NoSuchEJBException;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import org.granite.logging.Logger;
import org.granite.messaging.service.EjbServiceMetadata;
import org.granite.messaging.service.ServiceException;
import org.granite.messaging.service.ServiceInvocationContext;
import org.granite.tide.IInvocationCall;
import org.granite.tide.IInvocationResult;
import org.granite.tide.TidePersistenceManager;
import org.granite.tide.TideServiceContext;
import org.granite.tide.annotations.BypassTideMerge;
import org.granite.tide.async.AsyncPublisher;
import org.granite.tide.data.DataContext;
import org.granite.tide.data.DisableRemoteUpdates;
import org.granite.tide.data.JPAPersistenceManager;
import org.granite.tide.invocation.ContextEvent;
import org.granite.tide.invocation.ContextUpdate;
import org.granite.tide.invocation.InvocationCall;
import org.granite.tide.invocation.InvocationResult;
import org.granite.tide.util.AbstractContext;
/**
* @author William DRAI
*/
public class EjbServiceContext extends TideServiceContext {
private static final long serialVersionUID = 1L;
private static final Logger log = Logger.getLogger(EjbServiceContext.class);
public static final String CAPITALIZED_DESTINATION_ID = "{capitalized.component.name}";
public static final String DESTINATION_ID = "{component.name}";
private transient ConcurrentHashMap<String, EjbComponent> ejbLookupCache = new ConcurrentHashMap<String, EjbComponent>();
private final Set<String> remoteObservers = new HashSet<String>();
private final InitialContext initialContext;
private final String lookup;
private final EjbIdentity identity;
private String entityManagerFactoryJndiName = null;
private String entityManagerJndiName = null;
public EjbServiceContext() throws ServiceException {
super();
lookup = "";
initialContext = null;
identity = new EjbIdentity();
}
public EjbServiceContext(String lookup, InitialContext ic) throws ServiceException {
super();
this.lookup = lookup;
this.initialContext = ic;
identity = new EjbIdentity();
}
@Override
protected AsyncPublisher getAsyncPublisher() {
return null;
}
public void setEntityManagerFactoryJndiName(String entityManagerFactoryJndiName) {
this.entityManagerFactoryJndiName = entityManagerFactoryJndiName;
}
public void setEntityManagerJndiName(String entityManagerJndiName) {
this.entityManagerJndiName = entityManagerJndiName;
}
/**
* Create a TidePersistenceManager
*
* @param create create if not existent (can be false for use in entity merge)
* @return a TidePersistenceManager
*/
@Override
protected TidePersistenceManager getTidePersistenceManager(boolean create) {
if (!create)
return null;
EntityManager em = getEntityManager();
if (em == null)
return null;
return new JPAPersistenceManager(em);
}
/**
* Find the entity manager using the jndi names stored in the bean.
* @return The found entity manager
*/
private EntityManager getEntityManager() {
try {
InitialContext jndiContext = initialContext != null ? initialContext : new InitialContext();
if (entityManagerFactoryJndiName != null) {
EntityManagerFactory factory = (EntityManagerFactory) jndiContext.lookup(entityManagerFactoryJndiName);
return factory.createEntityManager();
}
else if (entityManagerJndiName != null) {
return (EntityManager) jndiContext.lookup(entityManagerJndiName);
}
}
catch (NamingException e) {
if (entityManagerFactoryJndiName != null)
throw new RuntimeException("Unable to find a EntityManagerFactory for jndiName " + entityManagerFactoryJndiName);
else if (entityManagerJndiName != null)
throw new RuntimeException("Unable to find a EntityManager for jndiName " + entityManagerJndiName);
}
return null;
}
public Object callComponent(Method method, Object... args) throws Exception {
String name = method.getDeclaringClass().getSimpleName();
name = name.substring(0, 1).toLowerCase() + name.substring(1);
if (name.endsWith("Bean"))
name = name.substring(0, name.length() - "Bean".length());
Object invokee = findComponent(name, null, null);
method = invokee.getClass().getMethod(method.getName(), method.getParameterTypes());
return method.invoke(invokee, args);
}
public Set<String> getRemoteObservers() {
return remoteObservers;
}
@Override
public Object findComponent(String componentName, Class<?> componentClass, String methodName) {
if ("identity".equals(componentName))
return identity;
if (componentName == null && componentClass != null) {
log.error("Typed component lookup not supported for EJBs (requested class: " + componentClass + ")");
throw new ServiceException("Typed component lookup not supported for EJBs (requested class: " + componentClass + ")");
}
EjbComponent component = ejbLookupCache.get(componentName);
if (component != null)
return component.ejbInstance;
// Compute EJB JNDI binding.
String name = componentName;
if (lookup != null) {
name = lookup;
if (lookup.contains(CAPITALIZED_DESTINATION_ID))
name = lookup.replace(CAPITALIZED_DESTINATION_ID, capitalize(componentName));
if (lookup.contains(DESTINATION_ID))
name = lookup.replace(DESTINATION_ID, componentName);
}
InitialContext ic = this.initialContext;
if (ic == null) {
try {
ic = new InitialContext();
}
catch (Exception e) {
throw new ServiceException("Could not get InitialContext", e);
}
}
log.debug(">> New EjbServiceInvoker looking up: %s", name);
try {
component = new EjbComponent();
component.ejbInstance = ic.lookup(name);
component.ejbClasses = new HashSet<Class<?>>();
Class<?> scannedClass = null;
EjbScannedItemHandler itemHandler = EjbScannedItemHandler.instance();
for (Class<?> i : component.ejbInstance.getClass().getInterfaces()) {
if (itemHandler.getScannedClasses().containsKey(i)) {
scannedClass = itemHandler.getScannedClasses().get(i);
break;
}
}
if (scannedClass == null)
scannedClass = itemHandler.getScannedClasses().get(component.ejbInstance.getClass());
// GDS-768: handling of proxied no-interface EJBs in GlassFish v3
if (scannedClass == null && component.ejbInstance.getClass().getSuperclass() != null)
scannedClass = itemHandler.getScannedClasses().get(component.ejbInstance.getClass().getSuperclass());
if (scannedClass != null) {
component.ejbClasses.add(scannedClass);
for (Map.Entry<Class<?>, Class<?>> me : itemHandler.getScannedClasses().entrySet()) {
if (me.getValue().equals(scannedClass))
component.ejbClasses.add(me.getKey());
}
component.ejbMetadata = new EjbServiceMetadata(scannedClass, component.ejbInstance.getClass());
}
else
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.");
EjbComponent tmpComponent = ejbLookupCache.putIfAbsent(componentName, component);
if (tmpComponent != null)
component = tmpComponent;
return component.ejbInstance;
}
catch (NamingException e) {
log.error("EJB not found " + name + ": " + e.getMessage());
throw new ServiceException("Could not lookup for: " + name, e);
}
}
@Override
public Set<Class<?>> findComponentClasses(String componentName, Class<?> componentClass, String methodName) {
if ("identity".equals(componentName)) {
Set<Class<?>> classes = new HashSet<Class<?>>(1);
classes.add(EjbIdentity.class);
return classes;
}
EjbComponent component = ejbLookupCache.get(componentName);
if (component == null)
findComponent(componentName, componentClass, methodName);
return ejbLookupCache.get(componentName).ejbClasses;
}
private String capitalize(String s) {
if (s == null || s.length() == 0)
return s;
if (s.length() == 1)
return s.toUpperCase();
return s.substring(0, 1).toUpperCase() + s.substring(1);
}
@Override
public void prepareCall(ServiceInvocationContext context, IInvocationCall c, String componentName, Class<?> componentClass) {
if ((c instanceof InvocationCall) && ((InvocationCall)c).getListeners() != null)
remoteObservers.addAll(((InvocationCall)c).getListeners());
Context.create(this);
// Initialize an empty data context
DataContext.init();
}
private static class EjbComponent {
public Object ejbInstance;
public Set<Class<?>> ejbClasses;
public EjbServiceMetadata ejbMetadata;
}
@Override
public IInvocationResult postCall(ServiceInvocationContext context, Object result, String componentName, Class<?> componentClass) {
try {
AbstractContext threadContext = AbstractContext.instance();
List<ContextUpdate> results = new ArrayList<ContextUpdate>(threadContext.size());
for (Map.Entry<String, Object> entry : threadContext.entrySet())
results.add(new ContextUpdate(entry.getKey(), null, entry.getValue(), 3, false));
InvocationResult ires = new InvocationResult(result, results);
if (componentName != null || componentClass != null) {
Set<Class<?>> componentClasses = findComponentClasses(componentName, componentClass, null);
if (isBeanAnnotationPresent(componentClasses, context.getMethod().getName(), context.getMethod().getParameterTypes(), BypassTideMerge.class))
ires.setMerge(false);
}
boolean enableUpdates = true;
if (componentName != null || componentClass != null) {
Set<Class<?>> componentClasses = findComponentClasses(componentName, componentClass, null);
if (isBeanAnnotationPresent(componentClasses, context.getMethod().getName(), context.getMethod().getParameterTypes(), DisableRemoteUpdates.class))
enableUpdates = false;
}
if (enableUpdates) {
DataContext dataContext = DataContext.get();
Object[][] updates = dataContext != null ? dataContext.getUpdates() : null;
ires.setUpdates(updates);
}
ires.setEvents(new ArrayList<ContextEvent>(threadContext.getRemoteEvents()));
if (componentName != null) {
EjbComponent component = ejbLookupCache.get(componentName);
if (component != null && component.ejbMetadata != null
&& component.ejbMetadata.isStateful() && component.ejbMetadata.isRemoveMethod(context.getMethod()))
ejbLookupCache.remove(componentName);
}
return ires;
}
finally {
AbstractContext.remove();
}
}
@Override
public void postCallFault(ServiceInvocationContext context, Throwable t, String componentName, Class<?> componentClass) {
try {
if (componentName != null) {
EjbComponent component = ejbLookupCache.get(componentName);
if (t instanceof NoSuchEJBException || (component != null && component.ejbMetadata != null &&
(component.ejbMetadata.isStateful() &&
component.ejbMetadata.isRemoveMethod(context.getMethod()) &&
!component.ejbMetadata.getRetainIfException(context.getMethod()))
)) {
ejbLookupCache.remove(componentName);
}
}
}
finally {
AbstractContext.remove();
}
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.defaultWriteObject();
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
in.defaultReadObject();
ejbLookupCache = new ConcurrentHashMap<String, EjbComponent>();
}
}