/******************************************************************************* * Copyright (c) 2001, 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.jst.j2ee.model.internal.validation; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.logging.Level; import org.eclipse.jem.java.Method; import org.eclipse.jem.util.logger.LogEntry; import org.eclipse.jem.util.logger.proxy.Logger; import org.eclipse.jst.j2ee.common.CommonPackage; import org.eclipse.jst.j2ee.common.SecurityRole; import org.eclipse.jst.j2ee.ejb.AssemblyDescriptor; import org.eclipse.jst.j2ee.ejb.EJBJar; import org.eclipse.jst.j2ee.ejb.EnterpriseBean; import org.eclipse.jst.j2ee.ejb.MethodElement; import org.eclipse.jst.j2ee.ejb.MethodPermission; import org.eclipse.jst.j2ee.ejb.MethodTransaction; import org.eclipse.jst.j2ee.internal.J2EEConstants; import org.eclipse.wst.validation.internal.core.ValidationException; import org.eclipse.wst.validation.internal.provisional.core.IMessage; /** * This class checks ejb-jar.xml for errors or potential errors. * If any problems are found, an error, warning, or info marker is added to the task list. * * 15.2.5.3 Declaration of security roles referenced from the bean's code * The Bean Provider is responsible for declaring in the security-role-ref elements of the deploy-ment * descriptor all the security role names used in the enterprise bean code. Declaring the security roles * references in the code allows the Application Assembler or Deployer to link the names of the security * roles used in the code to the security roles defined for an assembled application through the secu-rity- * role elements. * The Bean Provider must declare each security role referenced in the code using the secu-rity- * role-ref element as follows: * Declare the name of the security role using the role-name element. The name must be the * security role name that is used as a parameter to the isCallerInRole(String role-Name) * method. * Optional: Provide a description of the security role in the description element. * A security role reference, including the name defined by the role-name element, is scoped to the ses-sion * or entity bean element whose declaration contains the security-role-ref element. * The following example illustrates how an enterprise bean's references to security roles are declared in * the deployment descriptor. * ... * <enterprise-beans> * ... * <entity> * <ejb-name>AardvarkPayroll</ejb-name> * <ejb-class>com.aardvark.payroll.PayrollBean</ejb-class> * ... * <security-role-ref> * <description> * This security role should be assigned to the * employees of the payroll department who are * allowed to update employees' salaries. * </description> * <role-name>payroll</role-name> * </security-role-ref> * ... * </entity> * ... * </enterprise-beans> * ... * * The deployment descriptor above indicates that the enterprise bean AardvarkPayroll makes the * security check using isCallerInRole("payroll") in its business method. * * * 15.3.3 Linking security role references to security roles * If the Application Assembler defines the security-role elements in the deployment descriptor, he * or she is also responsible for linking all the security role references declared in the secu-rity- * role-ref elements to the security roles defined in the security-role elements. * The Application Assembler links each security role reference to a security role using the role-link * element. The value of the role-link element must be the name of one of the security roles defined in * a security-role element. * A role-link element must be used even if the value of role-name is the same as the value of the * role-link reference. * The following deployment descriptor example shows how to link the security role reference named * payroll to the security role named payroll-department. * ... * <enterprise-beans> * ... * <entity> * <ejb-name>AardvarkPayroll</ejb-name> * <ejb-class>com.aardvark.payroll.PayrollBean</ejb-class> * ... * <security-role-ref> * <description> * This role should be assigned to the * employees of the payroll department. * Members of this role have access to * anyone's payroll record. * * The role has been linked to the * payroll-department role. * </description> * <role-name>payroll</role-name> * <role-link>payroll-department</role-link> * </security-role-ref> * ... * </entity> * ... * </enterprise-beans> * ... */ public class EJBJar11VRule extends AValidationRule implements IMessagePrefixEjb11Constants { private DuplicatesTable _ejbName = null; private static final Object ID = IValidationRuleList.EJB11_EJBJAR; private static final Object[] DEPENDS_ON = new Object[]{IValidationRuleList.EJB11_SESSION_BEANCLASS, IValidationRuleList.EJB11_SESSION_REMOTE, IValidationRuleList.EJB11_SESSION_HOME, IValidationRuleList.EJB11_CMP_BEANCLASS, IValidationRuleList.EJB11_CMP_REMOTE, IValidationRuleList.EJB11_CMP_HOME, IValidationRuleList.EJB11_CMP_KEYCLASS, IValidationRuleList.EJB11_BMP_BEANCLASS, IValidationRuleList.EJB11_BMP_REMOTE, IValidationRuleList.EJB11_BMP_HOME, IValidationRuleList.EJB11_BMP_KEYCLASS, IValidationRuleList.EJB11_EJBEXT}; private static final Map MESSAGE_IDS; static { MESSAGE_IDS = new HashMap(); MESSAGE_IDS.put(CHKJ2814, new String[]{CHKJ2814 + SPEC}); MESSAGE_IDS.put(CHKJ2825, new String[]{CHKJ2825 + SPEC}); MESSAGE_IDS.put(CHKJ2826, new String[]{CHKJ2826 + SPEC}); MESSAGE_IDS.put(CHKJ2842, new String[]{CHKJ2842 + SPEC}); MESSAGE_IDS.put(CHKJ2843, new String[]{CHKJ2843 + SPEC}); MESSAGE_IDS.put(CHKJ2844, new String[]{CHKJ2844 + SPEC}); MESSAGE_IDS.put(CHKJ2845, new String[]{CHKJ2845 + SPEC}); MESSAGE_IDS.put(CHKJ2846, new String[]{CHKJ2846 + SPEC}); MESSAGE_IDS.put(CHKJ2847, new String[]{CHKJ2847 + SPEC}); MESSAGE_IDS.put(CHKJ2850, new String[]{CHKJ2850 + SPEC}); MESSAGE_IDS.put(CHKJ2852, new String[]{CHKJ2852}); MESSAGE_IDS.put(CHKJ2875, new String[]{CHKJ2875 + SPEC}); MESSAGE_IDS.put(CHKJ2895, new String[]{CHKJ2895 + SPEC}); } public EJBJar11VRule() { _ejbName = new DuplicatesTable(); } public final Map getMessageIds() { return MESSAGE_IDS; } public final Object[] getDependsOn() { return DEPENDS_ON; } public final Object getId() { return ID; } public Object getTarget(Object parent, Object target) { return null; } /** * 15.3.1 Security roles * The Application Assembler can define one or more security roles in * the deployment descriptor. The Application Assembler then assigns * groups of methods of the enterprise beans' home and remote interfaces * to the security roles to define the security view of the application. * Because the Application Assembler does not, in general, know the * security environment of the operational environment, the security * roles are meant to be logical roles (or actors), each representing * a type of user that should have the same access rights to the * application. The Deployer then assigns user groups and/or user * accounts defined in the operational environment to the security roles * defined by the Application Assembler. * Defining the security roles in the deployment descriptor is optional [17] * for the Application Assembler. Their omission in the deployment * descriptor means that the Application Assembler chose not to pass any * security deployment related instructions to the Deployer in the * deployment descriptor. The Application Assembler is responsible for * the following: * - Define each security role using a security-role element. * - Use the role-name element to define the name of the security role. * - Optionally, use the description element to provide a description of * a security role. * The security roles defined by the security-role elements are scoped to * the ejb-jar file level, and apply to all the enterprise beans in the * ejb-jar file. * [17] If the Application Assembler does not define security roles in the * deployment descriptor, the Deployer will have to define security * roles at deployment time. *... */ protected void validateAssemblyDescriptorElement(IEJBValidationContext vc, EJBJar ejbJar) { vc.terminateIfCancelled(); // Validate the security roles, if they're defined in the assembly-descriptor. if (ejbJar == null) { // nothing to validate return; } /** * Need to build up a list of duplicate role names, but the validation message * needs to be registered against the duplicate SecurityRole instance. * (Without the instance, we cannot get line numbers.) * * This class wrappers the SecurityRol instance so that the wrapper's * implemention of equals compares the names, but the validation message will * still be able to get the ref from the duplicate name. */ class RoleWrapper { private SecurityRole _role = null; public RoleWrapper(SecurityRole role) { _role = role; } @Override public boolean equals(Object o) { if (o instanceof RoleWrapper) { RoleWrapper other = (RoleWrapper) o; return _role.getRoleName().equals(other.getRole().getRoleName()); } return false; } @Override public int hashCode() { return super.hashCode() + _role.getRoleName().hashCode(); } public SecurityRole getRole() { return _role; } } AssemblyDescriptor assemblyDescriptor = ejbJar.getAssemblyDescriptor(); if (assemblyDescriptor == null) { // nothing to validate return; } List roles = assemblyDescriptor.getSecurityRoles(); if (roles != null) { DuplicatesTable roleNames = new DuplicatesTable(); SecurityRole role = null; Iterator roleIt = roles.iterator(); while (roleIt.hasNext()) { vc.terminateIfCancelled(); // Check that the role-name element has been set role = (SecurityRole) roleIt.next(); if (role == null) { // role-name not set IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2825, IEJBValidationContext.WARNING, ejbJar, this); vc.addMessage(message); } else if ((!role.eIsSet(CommonPackage.eINSTANCE.getSecurityRole_RoleName())) || (role.getRoleName().equals(""))) { //$NON-NLS-1$ // role-name not set IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2825, IEJBValidationContext.WARNING, role, this); vc.addMessage(message); } else { // Build up hashtable to check for duplicate role-names. roleNames.add(new RoleWrapper(role)); } } // Check that there are no duplicate role-names. (15.3.1) if (roleNames.containsDuplicates()) { List duplicates = roleNames.getDuplicates(); Iterator iterator = duplicates.iterator(); while (iterator.hasNext()) { IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2826, IEJBValidationContext.WARNING, ((RoleWrapper) iterator.next()).getRole(), this); vc.addMessage(message); } } roleNames.clear(); } List methTrans = assemblyDescriptor.getMethodTransactions(); MethodTransaction mt = null; Iterator iterator = methTrans.iterator(); while (iterator.hasNext()) { vc.terminateIfCancelled(); try { mt = (MethodTransaction) iterator.next(); } catch (Throwable exc) { Logger logger = vc.getMsgLogger(); if (logger != null && logger.isLoggingLevel(Level.FINER)) { logger.write(Level.FINER, exc); } mt = null; } if (mt == null) { Logger logger = vc.getMsgLogger(); if (logger != null && logger.isLoggingLevel(Level.FINEST)) { LogEntry entry = vc.getLogEntry(); entry.setSourceID("DDValidator.validateAssemblyDescriptorElement"); //$NON-NLS-1$ entry.setText("mt is null"); //$NON-NLS-1$ logger.write(Level.FINEST, entry); } continue; } boolean hasValidMethod = validateMethodElements(vc, ejbJar, mt.getMethodElements()); if (!hasValidMethod) { IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2847, IEJBValidationContext.WARNING, mt, this); vc.addMessage(message); } } List methodPermissions = assemblyDescriptor.getMethodPermissions(); iterator = methodPermissions.iterator(); while (iterator.hasNext()) { MethodPermission mp = (MethodPermission) iterator.next(); boolean hasValidMethod = validateMethodElements(vc, ejbJar, mp.getMethodElements()); if (!hasValidMethod) { // 15.3.2, p. 229, a <method-permission> must have at least one method listed (and that method must be found) IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2846, IEJBValidationContext.WARNING, mp, this); vc.addMessage(message); } // at least one security-role must be defined List mproles = mp.getRoles(); if ((mproles == null) || (mproles.size() == 0)) { IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2845, IEJBValidationContext.WARNING, mp, this); vc.addMessage(message); } } } /** * This validateDeploymentDescriptor is called if the EJBJar could load, which means * that the syntax of the JAR is (mostly) correct. * * EJB spec 1.1, section C.4, "Added the requirement for the Bean Provider to specify whether the * enterprise bean uses a bean-managed or container-managed transaction." */ public void validate(IEJBValidationContext vc, Object targetParent, Object target) throws ValidationException { EJBJar ejbJar = (EJBJar) target; List enterpriseBeans = ejbJar.getEnterpriseBeans(); Iterator iterator = enterpriseBeans.iterator(); EnterpriseBean bean = null; String beanName = null; while (iterator.hasNext()) { try { bean = (EnterpriseBean) iterator.next(); register(vc, ejbJar, bean); Object id = IValidationRuleList.EJB11_ENTERPRISEBEAN; IValidationRule vRule = EJBValidationRuleFactory.getFactory().getRule(vc, id); if (vRule == null) { // This has already been logged by the AbstractEJBValidationRuleFactory, so just // need to add "Cannot validate" to the task list. continue; } try { vRule.preValidate(vc, ejbJar, bean); vRule.validate(vc, ejbJar, bean); vRule.postValidate(vc, ejbJar, bean); } catch (ValidationCancelledException exc) { // Clean up the messages which are on the task list? Or is it nicer to leave them behind? } catch(ValidationException e) { throw e; } catch (Throwable exc) { addInternalErrorMessage(vc, exc); } finally { EJBValidationRuleFactory.getFactory().release(vRule); } } catch(ValidationCancelledException e) { throw e; } catch (ValidationException e) { throw e; } catch (Throwable exc) { // If there's a problem, proceed with the next bean. IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2852, IEJBValidationContext.WARNING, bean, new String[] { J2EEConstants.EJBJAR_DD_SHORT_NAME, beanName }, this); vc.addMessage(message); Logger logger = vc.getMsgLogger(); if (logger != null && logger.isLoggingLevel(Level.SEVERE)) { logger.write(Level.SEVERE, exc); } } } // Since the assembly descriptor is not specific to a bean, validate it once, after all bean processing is complete. validateAssemblyDescriptorElement(vc, ejbJar); validateUniqueEjbNames(vc, ejbJar); validateClientJAR(vc, ejbJar); } @Override public void reset() { super.reset(); _ejbName.clear(); } protected void register(IEJBValidationContext vc, EJBJar ejbJar, EnterpriseBean bean) { // To check if every bean name is unique, need to build a list _ejbName.add(new EjbNameWrapper(bean)); } private void addInternalErrorMessage(IEJBValidationContext vc, Throwable exc) { IMessage mssg = vc.getMessage(); mssg.setId(IEJBValidatorMessageConstants.CHKJ2900); vc.addMessage(mssg); if(exc != null) { Logger logger = vc.getMsgLogger(); if (logger != null && logger.isLoggingLevel(Level.SEVERE)) { logger.write(Level.SEVERE, exc); } } } public void validateUniqueEjbNames(IEJBValidationContext vc, EJBJar ejbJar) { List names = _ejbName.getDuplicates(); if(names.size() == 0) { return; } Iterator iterator = names.iterator(); while(iterator.hasNext()) { EjbNameWrapper wrapper = (EjbNameWrapper)iterator.next(); IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2895, IEJBValidationContext.WARNING, wrapper.getBean(), new String[]{wrapper.getBean().getName()}, this); vc.addMessage(message); } } protected void validateClientJAR(IEJBValidationContext vc, EJBJar ejbJar) { String clientJARName = ejbJar.getEjbClientJar(); if(clientJARName == null) { // No client JAR specified; everything's okay. return; } Boolean exists = (Boolean)vc.loadModel(EJBValidatorModelEnum.EJB_CLIENTJAR, new Object[]{clientJARName}); if(exists == null) { // Helper doesn't support load model. WAS? // Can't perform this check, so just return. return; } if(!exists.booleanValue()) { IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2875, IEJBValidationContext.ERROR, ejbJar, new String[]{clientJARName}, this); vc.addMessage(message); } } /** * Both section 11.4.1 and 15.3.2 need the <method> element. Also refer * to 16.5 for syntax. * * Return true if at least one of the methods referenced by this list of * MethodElement can be found. */ protected boolean validateMethodElements(IEJBValidationContext vc, EJBJar ejbJar, List elements) { if ((elements == null) || (elements.size() == 0)) { return false; } boolean hasValidMethod = false; Iterator iterator = elements.iterator(); while (iterator.hasNext()) { vc.terminateIfCancelled(); MethodElement element = (MethodElement) iterator.next(); EnterpriseBean bean = element.getEnterpriseBean(); if (bean == null) { IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2814, IEJBValidationContext.WARNING, element, this); vc.addMessage(message); continue; } if (element.getName() != null) { // Do not attempt to access the methods on the home or remote interface if there' // been a problem locating or reflecting those types boolean reflected = true; try { ValidationRuleUtility.isValidType(bean.getHomeInterface()); } catch (InvalidInputException e) { reflected = false; String className = (e.getJavaClass() == null) ? IEJBValidatorConstants.NULL_HOME : e.getJavaClass().getQualifiedName(); String[] msgParm = { className }; IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2850, IEJBValidationContext.WARNING, bean, msgParm, this); vc.addMessage(message); } try { ValidationRuleUtility.isValidType(bean.getRemoteInterface()); } catch (InvalidInputException e) { reflected = false; String className = (e.getJavaClass() == null) ? IEJBValidatorConstants.NULL_REMOTE : e.getJavaClass().getQualifiedName(); String[] msgParm = { className }; IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2850, IEJBValidationContext.WARNING, bean, msgParm, this); vc.addMessage(message); } if(reflected) { // The "element.getMethods()" has a null pointer exception when it attempts to retrieve the methods from the home/remote interface, // if either of the interfaces don't exist. String name = element.getName(); Method[] methods = element.getMethods(); // get all methods which will be retrieved for the given method-permission boolean hasMethods = ((methods != null) && (methods.length > 0)); if (!hasMethods) { // warning IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2843, IEJBValidationContext.WARNING, element, new String[] { bean.getName()}, this); vc.addMessage(message); } else { hasValidMethod = true; // a <method-permission> must have at least one method (15.3.2, p.229) if (name.equals("*")) { //$NON-NLS-1$ List params = element.getMethodParams(); if ((params != null) && (params.size() > 0)) { // warning IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2842, IEJBValidationContext.WARNING, element, this); vc.addMessage(message); } } } } } else { // error IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2844, IEJBValidationContext.WARNING, element, this); vc.addMessage(message); } } return hasValidMethod; } /** * Need to build up a list of duplicate EJB names, but the validation message * needs to be registered against the duplicate EnterpriseBean instance. * (Without the instance, we cannot get line numbers.) * * This class wrappers the EnterpriseBean instance so that the wrapper's * implemention of equals compares the names, but the validation message will * still be able to get the ref from the duplicate name. */ class EjbNameWrapper { private EnterpriseBean _bean = null; public EjbNameWrapper(EnterpriseBean bean) { _bean = bean; } @Override public boolean equals(Object o) { if (o instanceof EjbNameWrapper) { EjbNameWrapper other = (EjbNameWrapper)o; if((_bean.getName() == null) && (other.getBean().getName() == null)) { return true; } else if(_bean.getName() == null) { return false; } else if(other.getBean().getName() == null) { return false; } return _bean.getName().equals(other.getBean().getName()); } return false; } @Override public int hashCode() { if((getBean() != null) && (getBean().getName() != null)) { return getBean().getName().hashCode(); } return super.hashCode(); } public EnterpriseBean getBean() { return _bean; } } }