blob: b634aaec083876f5c161c7cd7d58916d1ffd4bae [file] [log] [blame]
/*
* Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package com.sun.enterprise.connectors.work.context;
import com.sun.appserv.connectors.internal.api.WorkContextHandler;
import com.sun.enterprise.connectors.work.LogFacade;
import com.sun.enterprise.connectors.work.WorkCoordinator;
import com.sun.enterprise.connectors.work.OneWork;
import com.sun.appserv.connectors.internal.api.ConnectorRuntime;
import org.glassfish.connectors.config.GroupMap;
import org.glassfish.connectors.config.PrincipalMap;
import org.glassfish.connectors.config.WorkSecurityMap;
import org.glassfish.security.common.PrincipalImpl;
import org.glassfish.security.common.Group;
import org.jvnet.hk2.annotations.Service;
import org.glassfish.hk2.api.PerLookup;
import org.glassfish.logging.annotation.LogMessageInfo;
import com.sun.enterprise.transaction.api.JavaEETransactionManager;
import jakarta.inject.Inject;
import jakarta.resource.spi.work.*;
import javax.security.auth.Subject;
import javax.security.auth.callback.CallbackHandler;
import java.util.*;
import java.util.logging.Logger;
import java.util.logging.Level;
import java.io.Serializable;
/**
* Handles work contexts submitted as part of the work instance
*
* @author Jagadish Ramu
* @since GlassFish v3
*/
@Service
@PerLookup
public class WorkContextHandlerImpl implements WorkContextHandler {
private static final List<Class<? extends WorkContext>> containerSupportedContexts =
new ArrayList<Class<? extends WorkContext>>();
private static final Logger logger = LogFacade.getLogger();
private final static Locale locale = Locale.getDefault();
@Inject
private ConnectorRuntime runtime ;
private ClassLoader rarCL;
static {
containerSupportedContexts.add(TransactionContext.class);
containerSupportedContexts.add(SecurityContext.class);
containerSupportedContexts.add(HintsContext.class);
containerSupportedContexts.add(CustomWorkContext_A.class);
containerSupportedContexts.add(CustomWorkContext_B.class);
containerSupportedContexts.add(CustomWorkContext_D.class);
}
public WorkContextHandlerImpl(){
}
public WorkContextHandlerImpl(ConnectorRuntime runtime, ClassLoader cl) {
this.runtime = runtime;
this.rarCL = cl;
}
/**
* {@inheritDoc}
*/
public void init(String raName, ClassLoader cl){
this.rarCL = cl;
}
//TODO V3 setting scope as per-lookup and this variable seems to cache over multiple invocations ?
private Set<WorkContext> validContexts = new HashSet<WorkContext>();
/**
* indicates whether the provided workContextClass is supported by the container
*
* @param strict indicates whether the type-check need to be strict or not i.e.,
* exact type or its super-class type
* @param workContextClassName work context class name
* @return boolean indicating whether the workContextClass is supported or not
*/
public boolean isContextSupported(boolean strict, String workContextClassName) {
boolean result = false;
if (strict) {
result = canContainerHandleSameContextType(workContextClassName);
} else {
result = canContainerHandleContext(workContextClassName);
}
return result;
}
@LogMessageInfo(
message = "Unable to load Work Context class {0}.",
comment = "Can not find Work Context class.",
level = "WARNING",
cause = "Work Context class is not available to application server.",
action = "Make sure that the Work Context class is available to server.",
publish = true)
private static final String RAR_LOAD_WORK_CONTEXT_ERROR = "AS-RAR-05006";
/**
* checks whether the container can handle the exact context type provided
*
* @param workContextClassName work context class name
* @return boolean indicating whether the workContextClass is supported or not
*/
private boolean canContainerHandleSameContextType(String workContextClassName) {
boolean result = false;
for (Class workContextClass : containerSupportedContexts) {
//TODO JSR-322-WORK-CONTEXT : Still need to do class.equals () ??
Class clz = null;
try {
clz = loadClass(workContextClassName);
} catch (ClassNotFoundException cnfe) {
logger.log(Level.WARNING, RAR_LOAD_WORK_CONTEXT_ERROR, new Object[]{workContextClassName, cnfe});
break;
}
if (workContextClass.equals(clz)) {
result = true;
debug("Container can handle the context [Strict] : " + workContextClassName);
break;
}
}
return result;
}
/**
* checks whether the container can handle the contextClass in question.
* If the exact class type is not supported, container will look for any of its super-class
* If any of the super-class is available, it is indicated as 'can handle'
*
* @param contextClassName work context class name
* @return boolean indicating whether the contextClass is supported or not
*/
public boolean canContainerHandleContext(String contextClassName) {
boolean result = false;
//JSR-322-WORK-CONTEXT First check whether exact 'context-type' is supported.
if (!canContainerHandleSameContextType(contextClassName)) {
Class context = null;
try {
context = loadClass(contextClassName);
} catch (ClassNotFoundException e) {
debug("Container cannot load the context class [isAssignable] : " + contextClassName + " ");
}
//TODO JSR-322-WORK-CONTEXT : can we use workContext.getName() ??
if(context != null){
for (Class workContextClass : containerSupportedContexts) {
if (workContextClass.isAssignableFrom(context)) {
result = true;
debug("Container can handle the context [isAssignable] : " + contextClassName);
break;
}
}
}else{
logger.log(Level.WARNING, RAR_LOAD_WORK_CONTEXT_ERROR, contextClassName);
}
} else {
result = true;
}
return result;
}
private Class loadClass(String contextClassName) throws ClassNotFoundException {
return rarCL.loadClass(contextClassName);
}
@LogMessageInfo(
message = "Cannot specify both Execution Context [{0}] as well Transaction Context [{1}] for Work [{2}] execution. Only one can be specified.",
comment = "ExecutionContext conflict.",
level = "WARNING",
cause = "Submitted Work has Transaction Context as well it is a Work Context Provider which is specification violation.",
action = "Make sure that either Execution Context or Work Context Provider with Transaction Context is passed, but not both.",
publish = true)
private static final String RAR_EXECUTION_CONTEXT_CONFLICT = "AS-RAR-05007";
@LogMessageInfo(
message = "Duplicate Work Context for type [ {0} ].",
comment = "Duplicate Work Context.",
level = "WARNING",
cause = "Multiple Work Contexts of same type submitted.",
action = "Make sure that same context type is not submitted multiple times in the Work Context.",
publish = true)
private static final String RAR_EXECUTION_CONTEXT_DUPLICATE = "AS-RAR-05008";
@LogMessageInfo(
message = "Application server cannot handle the following Work Context : {0}.",
comment = "Unsupported Work Context.",
level = "WARNING",
cause = "Work Context in question is not supported by application server.",
action = "Check the application server documentation for supported Work Contexts.",
publish = true)
private static final String RAR_EXECUTION_CONTEXT_NOT_SUPPORT = "AS-RAR-05009";
/**
* validate the submitted work
*
* @param work work instance to be validated
* @param ec ExecutionContext
* @throws WorkCompletedException when a submitted context is not supported
* @throws WorkRejectedException when validation fails
*/
public void validateWork(Work work, ExecutionContext ec) throws WorkCompletedException, WorkRejectedException {
//JSR-322-WORK-CONTEXT-REQ If work instance is a Work Context provider, handle them
if (work instanceof WorkContextProvider) {
WorkContextProvider icp = (WorkContextProvider) work;
//JSR-322-WORK-CONTEXT-REQ
//TODO V3 hack - ec & getEC() test
ExecutionContext transactionContext = getExecutionContext(work);
if (ec != null && transactionContext != ec) {
WorkRejectedException wre =
new WorkRejectedException();
wre.setErrorCode(WorkContextErrorCodes.CONTEXT_SETUP_FAILED);
logger.log(Level.WARNING, RAR_EXECUTION_CONTEXT_CONFLICT, new Object[]{ec, transactionContext, work, wre});
if(transactionContext instanceof WorkContext){
WorkContextLifecycleListener listener = getListener((WorkContext)transactionContext);
notifyContextSetupFailure(listener, WorkContextErrorCodes.CONTEXT_SETUP_FAILED);
}
throw wre;
}
List<WorkContext> contexts = icp.getWorkContexts();
if (contexts != null) {
for (WorkContext ic : contexts) {
WorkContextLifecycleListener listener = getListener(ic);
//JSR-322-WORK-CONTEXT-REQ strict=false in the method below as the check has to be lenient.
if (isContextSupported(false, ic.getClass().getName())) {
if (isUniqueSubmission(ic, validContexts)) {
validContexts.add(ic);
} else {
//JSR-322-WORK-CONTEXT-REQ If a particular IC type is submitted twice,
// container does not support it, fail work submission.
WorkCompletedException wce = new WorkCompletedException();
wce.setErrorCode(WorkContextErrorCodes.DUPLICATE_CONTEXTS);
logger.log(Level.WARNING, RAR_EXECUTION_CONTEXT_DUPLICATE, new Object[]{ic.getClass().getName(), wce});
notifyContextSetupFailure(listener, WorkContextErrorCodes.DUPLICATE_CONTEXTS);
throw wce;
}
} else {
//JSR-322-WORK-CONTEXT-REQ unable to handle the work context or its generic type
// (any of its super types) container does not support it, fail work submission.
WorkCompletedException wce = new WorkCompletedException();
wce.setErrorCode(WorkContextErrorCodes.UNSUPPORTED_CONTEXT_TYPE);
logger.log(Level.WARNING, RAR_EXECUTION_CONTEXT_NOT_SUPPORT, new Object[]{ic.getClass().getName(), wce});
notifyContextSetupFailure(listener, WorkContextErrorCodes.UNSUPPORTED_CONTEXT_TYPE);
throw wce;
}
}
}
}
}
/**
* check whether the work-context is a unique submission
*
* @param ic work-context
* @param supportedContexts supported contexts
* @return boolean indicating whether the work-context submitted is unique
*/
//TODO V3 - rework - check whether multiple implementations of same IC supported by the container is submitted.
private boolean isUniqueSubmission(WorkContext ic, Collection<WorkContext> supportedContexts) {
//TODO JSR-322-WORK-CONTEXT : can we use workContext.getName() ??
for (WorkContext workContext : supportedContexts) {
String workContextName = workContext.getClass().getName().toLowerCase(locale);
String icName = ic.getClass().getName().toLowerCase(locale);
if (workContextName.equalsIgnoreCase(icName)) {
debug("Not a unique workContext submission : " + workContext.getClass().getName());
return false;
}
}
return true;
}
/**
* setup the work context or execution context for the work
*
* @param ec ExecutionContext
* @param wc Work coordinator
*/
public void setupContext(ExecutionContext ec, WorkCoordinator wc, OneWork work) throws WorkCompletedException {
boolean useExecutionContext = true;
for (WorkContext ic : validContexts) {
WorkContextLifecycleListener listener = getListener(ic);
if (ic instanceof TransactionContext) {
useExecutionContext = false;
setupTransactionWorkContext((TransactionContext) ic, listener);
} else if (ic instanceof SecurityContext) {
setupSecurityWorkContext((SecurityContext) ic, listener, wc.getRAName());
} else if (ic instanceof HintsContext) {
setupHintsContext((HintsContext)ic, listener, work);
} else {
Class<? extends WorkContext> claz = null;
String className = ic.getClass().getName();
if (!isContextSupported(true, className)) {
claz = getMostSpecificWorkContextSupported(ic);
if (claz == null) {
debug("Cannot handle work context [ " + className + " ]");
continue; //TODO V3 continue ?
}
}
setupCustomWorkContext(ic, listener, claz);
//Handle custom work contexts, if any, supported by GlassFish
}
}
//Transaction Context is not provided, so proceed with connector 1.5 way
if (useExecutionContext) {
try {
setupExecutionContext(ec);
} catch (WorkException we) {
wc.setException(we);
} catch (Exception e) {
wc.setException(e);
}
}
}
private void setupHintsContext(HintsContext ic, WorkContextLifecycleListener listener, OneWork work) {
Map<String, Serializable> hints = ic.getHints();
Object value = hints.get(HintsContext.NAME_HINT);
if(value != null){
work.setName(value.toString());
notifyContextSetupComplete(listener);
}
}
/**
* check whether the work-context is a work-context-lifecycle-listener and return the listener
*
* @param wc Work-Context
* @return Work-Context-Lifecycle-Listener from the Work-Context
*/
private WorkContextLifecycleListener getListener(WorkContext wc) {
WorkContextLifecycleListener listener = null;
//check whether the WorkContext has a listener.
if (wc instanceof WorkContextLifecycleListener) {
listener = (WorkContextLifecycleListener) wc;
}
return listener;
}
@LogMessageInfo(
message = "Setting custom Work Context class [ {0} ] using most specific supportted Work Context class [ {1} ].",
comment = "Handle custom Work Context.",
level = "INFO",
cause = "Requested Work Context is not supported, but a super type of the context is supported.",
action = "",
publish = true)
private static final String RAR_USE_SUPER_WORK_CONTEXT = "AS-RAR-05010";
/**
* handles custom work contexts
*
* @param ic work-context
* @param listener listener
* @param claz work context class
*/
private void setupCustomWorkContext(WorkContext ic, WorkContextLifecycleListener listener,
Class<? extends WorkContext> claz) {
if (claz != null) {
Object params[] = {ic.getClass().getName(), claz.getName()};
logger.log(Level.INFO, RAR_USE_SUPER_WORK_CONTEXT, params);
} else {
debug("setting exact customWorkContext for WorkContext [ " + ic.getClass().getName() + " ] ");
}
notifyContextSetupComplete(listener);
}
/**
* provide the most specific work context support for the work context in question
*
* @param ic work-context
* @return supported work-context
*/
private Class<? extends WorkContext> getMostSpecificWorkContextSupported(WorkContext ic) {
List<Class> assignableClasses = new ArrayList<Class>();
for (Class<? extends WorkContext> icClass : containerSupportedContexts) {
if (icClass.isAssignableFrom(ic.getClass())) {
assignableClasses.add(icClass);
}
}
assignableClasses = sortBasedOnInheritence(assignableClasses);
Object params[]= {ic.getClass().getName(), assignableClasses.get(0).getName()};
logger.log(Level.INFO, RAR_USE_SUPER_WORK_CONTEXT, params);
return assignableClasses.get(0);
}
/**
* sort the classes based on inhertience
*
* @param assignableClasses list of classes
* @return sorted classes list
*/
private List<Class> sortBasedOnInheritence(List<Class> assignableClasses) {
int size = assignableClasses.size();
Class[] sortedClassesArray = new Class[size];
for (Class claz : assignableClasses) {
int count = getNumberOfAssignableClasses(claz, assignableClasses);
sortedClassesArray[count - 1] = claz;
}
return Arrays.asList(sortedClassesArray);
}
/**
* given a list of classes, provides the number of assignable (type) classes for the provided class
*
* @param claz class
* @param assignableClasses list of assiginable classes
* @return number of assignable classes
*/
private int getNumberOfAssignableClasses(Class claz, List<Class> assignableClasses) {
int count = 0;
for (Class assignableClass : assignableClasses) {
if (claz.isAssignableFrom(assignableClass)) {
++count;
}
}
return count;
}
@LogMessageInfo(
message = "Unable to set Security Context.",
comment = "Unable to set Security Context.",
level = "WARNING",
cause = "Unable to set Security Context.",
action = "Check the server.log for exceptions",
publish = true)
private static final String RAR_SETUP_SECURITY_CONTEXT_ERROR = "AS-RAR-05011";
/**
* setup security work context for the work
*
* @param securityWorkContext security work context
* @param listener listener to be notified
* @param raName resource-adapter name
*/
private void setupSecurityWorkContext(SecurityContext securityWorkContext,
WorkContextLifecycleListener listener, String raName)
throws WorkCompletedException{
try {
Subject executionSubject = new Subject();
Subject serviceSubject = new Subject(); //TODO need to populate with server's credentials ?
//Map securityMap = getSecurityWorkContextMap(raName);
Map securityMap = getWorkContextMap(raName);
CallbackHandler handler = new ConnectorCallbackHandler(executionSubject, runtime.getCallbackHandler(), securityMap);
securityWorkContext.setupSecurityContext(handler, executionSubject, serviceSubject);
notifyContextSetupComplete(listener);
} catch (Exception e) {
logger.log(Level.WARNING, RAR_SETUP_SECURITY_CONTEXT_ERROR, e);
notifyContextSetupFailure(listener, WorkContextErrorCodes.CONTEXT_SETUP_FAILED);
WorkCompletedException wce = new WorkCompletedException(e.getMessage());
wce.initCause(e);
throw wce;
}
}
/**
* get the security work context map (if any) for the resource-adapter
* look for <[raname]-principals-map> & <[raname]-groups-map> jvm-options
* to generate the map
*
* @param raName resource-adapter name
* @return security-map
*/
/*
private Map getSecurityWorkContextMap(String raName) {
HashMap eisASMap = new HashMap();
String principalsMap = System.getProperty(raName + "-principals-map");
if (principalsMap != null) {
StringTokenizer tokenizer = new StringTokenizer(principalsMap, ",");
while (tokenizer.hasMoreElements()) {
String nameValue = (String) tokenizer.nextElement();
if (nameValue != null && nameValue.contains("=")) {
int delimiterLocation = nameValue.indexOf("=");
String eisPrincipal = nameValue.substring(0, delimiterLocation);
String appserverPrincipal = nameValue.substring(delimiterLocation + 1);
eisASMap.put(new PrincipalImpl(eisPrincipal), new PrincipalImpl(appserverPrincipal));
}
}
}
//TODO V3 refactor (common code for principals & groups)
String groupsMap = System.getProperty(raName + "-groups-map");
if (groupsMap != null) {
StringTokenizer tokenizer = new StringTokenizer(groupsMap, ",");
while (tokenizer.hasMoreElements()) {
String nameValue = (String) tokenizer.nextElement();
if (nameValue != null && nameValue.contains("=")) {
int delimiterLocation = nameValue.indexOf("=");
String eisGroup = nameValue.substring(0, delimiterLocation);
String appserverGroup = nameValue.substring(delimiterLocation + 1);
eisASMap.put(new Group(eisGroup), new Group(appserverGroup));
}
}
return eisASMap;
}
return null;
}
*/
/**
* Given a resource-adapter name, get all its work-context-map
* @param raName resource-adapter-name
* @return work-context-map
*/
private Map getWorkContextMap(String raName){
List<WorkSecurityMap> maps = runtime.getWorkSecurityMap(raName);
List<PrincipalMap> principalsMap = getPrincipalsMap(maps);
List<GroupMap> groupsMap = getGroupsMap(maps);
HashMap eisASMap = new HashMap();
for(PrincipalMap map : principalsMap){
eisASMap.put(new PrincipalImpl(map.getEisPrincipal()), new PrincipalImpl(map.getMappedPrincipal()));
}
for(GroupMap map : groupsMap){
eisASMap.put(new Group(map.getEisGroup()), new Group(map.getMappedGroup()));
}
return eisASMap;
}
/**
* get the complete list of principal map from all the work-context-maps
* @param maps work security maps
* @return all principal-map
*/
private List<PrincipalMap> getPrincipalsMap(List<WorkSecurityMap> maps) {
List<PrincipalMap> principalsMap = new ArrayList<PrincipalMap>();
for(WorkSecurityMap map : maps){
List<PrincipalMap> principalMap = map.getPrincipalMap();
if(principalMap != null && principalMap.size() > 0){
principalsMap.addAll(principalMap);
}
}
return principalsMap;
}
/**
* get the complete list of group map from all the work-context-maps
* @param maps work security maps
* @return all group-map
*/
private List<GroupMap> getGroupsMap(List<WorkSecurityMap> maps) {
List<GroupMap> groupsMap = new ArrayList<GroupMap>();
for(WorkSecurityMap map : maps){
List<GroupMap> groupMap = map.getGroupMap();
if(groupMap != null && groupMap.size() > 0){
groupsMap.addAll(groupMap);
}
}
return groupsMap;
}
/**
* notify the work-context-listener that the context setup has failed
* Error code provides specific information
*
* @param listener listener to be notified
* @param errorCode error-code
*/
private void notifyContextSetupFailure(WorkContextLifecycleListener listener, String errorCode) {
if (listener != null) {
debug("notifying context setup failure");
listener.contextSetupFailed(errorCode);
}
}
/**
* notify the work-context-listener that the context setup is complete
*
* @param listener listener to be notified
*/
private void notifyContextSetupComplete(WorkContextLifecycleListener listener) {
if (listener != null) {
debug("notifying context setup complete");
listener.contextSetupComplete();
}
}
/**
* setup transaction-work-context for the work
*
* @param tic transaction-context
* @param listener listener that has to be notified
*/
private void setupTransactionWorkContext(TransactionContext tic,
WorkContextLifecycleListener listener) throws WorkCompletedException {
try {
setupExecutionContext(tic);
notifyContextSetupComplete(listener);
} catch (Exception e) {
notifyContextSetupFailure(listener, WorkContextErrorCodes.CONTEXT_SETUP_FAILED); //TODO
WorkCompletedException wce = new WorkCompletedException(e.getMessage());
wce.initCause(e);
throw wce;
}
}
/**
* sets the execution context for the work (traditional, 1.5 way)
*
* @param ec ExecutionContext
* @throws WorkException when unable to setup the execution context
*/
private void setupExecutionContext(ExecutionContext ec) throws WorkException {
JavaEETransactionManager tm = runtime.getTransactionManager();
if (ec != null && ec.getXid() != null) {
tm.recreate(ec.getXid(), ec.getTransactionTimeout());
}
}
public static void debug(String message) {
if(logger.isLoggable(Level.FINEST)){
logger.log(Level.FINEST, message);
}
}
/**
* {@inheritDoc}
*/
public static ExecutionContext getExecutionContext(Work work) {
ExecutionContext ec = null;
if (work instanceof WorkContextProvider) {
WorkContextProvider icp = (WorkContextProvider) work;
List<WorkContext> icList = icp.getWorkContexts();
if(icList != null){
for (WorkContext ic : icList) {
if (ic instanceof TransactionContext) {
ec = (TransactionContext) ic;
break;
}
}
}
}
return ec;
}
/**
* {@inheritDoc}
*/
public boolean isContextSupported(Class contextClass) {
return canContainerHandleSameContextType(contextClass.getClass().getName());
}
}