/*******************************************************************************
 * 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.CMRField;
import org.eclipse.jst.j2ee.ejb.ContainerManagedEntity;
import org.eclipse.jst.j2ee.ejb.EJBJar;
import org.eclipse.jst.j2ee.ejb.EJBRelation;
import org.eclipse.jst.j2ee.ejb.EJBRelationshipRole;
import org.eclipse.jst.j2ee.ejb.EjbPackage;
import org.eclipse.jst.j2ee.ejb.EnterpriseBean;
import org.eclipse.jst.j2ee.ejb.Entity;
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.ejb.MultiplicityKind;
import org.eclipse.jst.j2ee.ejb.Relationships;
import org.eclipse.jst.j2ee.ejb.RoleSource;
import org.eclipse.jst.j2ee.internal.J2EEConstants;
import org.eclipse.jst.j2ee.internal.J2EEVersionConstants;
import org.eclipse.wst.validation.internal.core.ValidationException;
import org.eclipse.wst.validation.internal.provisional.core.IMessage;

/**
 * @version 	1.0
 * @author
 */
public class EJBJar20VRule extends AValidationRule implements IMessagePrefixEjb20Constants {
	private static final Map MESSAGE_IDS;
	private DuplicatesTable _ejbName = null;
	private DuplicatesTable _asName = null;
	private DuplicatesTable _relationshipName = null;
	private DuplicatesTable _relationshipRoleName = null;
	private static final Object[] DEPENDS_ON = new Object[]{IValidationRuleList.EJB20_STATELESS_SESSION_BEANCLASS, IValidationRuleList.EJB20_STATELESS_SESSION_REMOTE, IValidationRuleList.EJB20_STATELESS_SESSION_HOME, IValidationRuleList.EJB20_STATELESS_SESSION_LOCAL, IValidationRuleList.EJB20_STATELESS_SESSION_LOCALHOME, IValidationRuleList.EJB20_STATEFUL_SESSION_BEANCLASS, IValidationRuleList.EJB20_STATEFUL_SESSION_REMOTE, IValidationRuleList.EJB20_STATEFUL_SESSION_HOME, IValidationRuleList.EJB20_STATEFUL_SESSION_LOCAL, IValidationRuleList.EJB20_STATEFUL_SESSION_LOCALHOME, IValidationRuleList.EJB20_CMP_BEANCLASS, IValidationRuleList.EJB20_CMP_REMOTE, IValidationRuleList.EJB20_CMP_HOME, IValidationRuleList.EJB20_CMP_LOCAL, IValidationRuleList.EJB20_CMP_LOCALHOME, IValidationRuleList.EJB20_CMP_KEYCLASS, IValidationRuleList.EJB20_BMP_BEANCLASS, IValidationRuleList.EJB20_BMP_REMOTE, IValidationRuleList.EJB20_BMP_HOME, IValidationRuleList.EJB20_BMP_LOCAL, IValidationRuleList.EJB20_BMP_LOCALHOME, IValidationRuleList.EJB20_BMP_KEYCLASS, IValidationRuleList.EJB20_MESSAGE_BEANCLASS, IValidationRuleList.EJB20_EJBEXT};
	
	static {
		MESSAGE_IDS = new HashMap();
		
		MESSAGE_IDS.put(CHKJ2800_NAMED, new String[]{CHKJ2800_NAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2800_UNNAMED, new String[]{CHKJ2800_UNNAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2802_NAMED, new String[]{CHKJ2802_NAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2802_UNNAMED, new String[]{CHKJ2802_UNNAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2803_NAMED, new String[]{CHKJ2803_NAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2803_UNNAMED, new String[]{CHKJ2803_UNNAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2804_NAMED, new String[]{CHKJ2804_NAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2804_UNNAMED, new String[]{CHKJ2804_UNNAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2805_NAMED, new String[]{CHKJ2805_NAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2805_UNNAMED, new String[]{CHKJ2805_UNNAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2806, new String[]{CHKJ2806+SPEC});
		MESSAGE_IDS.put(CHKJ2807, new String[]{CHKJ2807+SPEC});
		MESSAGE_IDS.put(CHKJ2808, new String[]{CHKJ2808+SPEC});
		MESSAGE_IDS.put(CHKJ2809, new String[]{CHKJ2809+SPEC});
		MESSAGE_IDS.put(CHKJ2810_NAMED, new String[]{CHKJ2810_NAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2810_UNNAMED, new String[]{CHKJ2810_UNNAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2811_NAMED, new String[]{CHKJ2811_NAMED+SPEC});
		MESSAGE_IDS.put(CHKJ2814, new String[]{CHKJ2814+SPEC});
		MESSAGE_IDS.put(CHKJ2816, new String[]{CHKJ2816+SPEC});
		MESSAGE_IDS.put(CHKJ2817, new String[]{CHKJ2817+SPEC});
		MESSAGE_IDS.put(CHKJ2818, new String[]{CHKJ2818+SPEC});
		MESSAGE_IDS.put(CHKJ2820, new String[]{CHKJ2820+SPEC});
		MESSAGE_IDS.put(CHKJ2821, new String[]{CHKJ2821+SPEC});
		MESSAGE_IDS.put(CHKJ2822, new String[]{CHKJ2822+SPEC});
		MESSAGE_IDS.put(CHKJ2823, new String[]{CHKJ2823+SPEC});
		MESSAGE_IDS.put(CHKJ2824, new String[]{CHKJ2824+SPEC});
		MESSAGE_IDS.put(CHKJ2825, new String[]{CHKJ2825+SPEC});
		MESSAGE_IDS.put(CHKJ2826, new String[]{CHKJ2826+SPEC});
		MESSAGE_IDS.put(CHKJ2827, new String[]{CHKJ2827+SPEC});
		MESSAGE_IDS.put(CHKJ2828, new String[]{CHKJ2828+SPEC});
		MESSAGE_IDS.put(CHKJ2829, new String[]{CHKJ2829+SPEC});
		MESSAGE_IDS.put(CHKJ2830, new String[]{CHKJ2830+SPEC});
		MESSAGE_IDS.put(CHKJ2831, new String[]{CHKJ2831+SPEC});
		MESSAGE_IDS.put(CHKJ2832, new String[]{CHKJ2832+SPEC});
		MESSAGE_IDS.put(CHKJ2833, new String[]{CHKJ2833+SPEC});
		MESSAGE_IDS.put(CHKJ2834, new String[]{CHKJ2834+SPEC});
		MESSAGE_IDS.put(CHKJ2835, new String[]{CHKJ2835+SPEC});
		MESSAGE_IDS.put(CHKJ2836, new String[]{CHKJ2836+SPEC});
		MESSAGE_IDS.put(CHKJ2837, new String[]{CHKJ2837+SPEC});
		MESSAGE_IDS.put(CHKJ2838, new String[]{CHKJ2838+SPEC});
		MESSAGE_IDS.put(CHKJ2839, new String[]{CHKJ2839+SPEC});
		MESSAGE_IDS.put(CHKJ2841, new String[]{CHKJ2841+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(CHKJ2848, new String[]{CHKJ2848+SPEC});
		MESSAGE_IDS.put(CHKJ2850, new String[]{CHKJ2850+SPEC});
		MESSAGE_IDS.put(CHKJ2854, new String[]{CHKJ2854+SPEC});
		MESSAGE_IDS.put(CHKJ2855, new String[]{CHKJ2855+SPEC});
		MESSAGE_IDS.put(CHKJ2856, new String[]{CHKJ2856+SPEC});
		MESSAGE_IDS.put(CHKJ2857, new String[]{CHKJ2857+SPEC});
		MESSAGE_IDS.put(CHKJ2858, new String[]{CHKJ2858+SPEC});
		MESSAGE_IDS.put(CHKJ2859, new String[]{CHKJ2859+SPEC});
		MESSAGE_IDS.put(CHKJ2860, new String[]{CHKJ2860+SPEC});
		MESSAGE_IDS.put(CHKJ2866, new String[]{CHKJ2866+SPEC});
		MESSAGE_IDS.put(CHKJ2867, new String[]{CHKJ2867+SPEC});
		MESSAGE_IDS.put(CHKJ2868, new String[]{CHKJ2868+SPEC});
		MESSAGE_IDS.put(CHKJ2869, new String[]{CHKJ2869+SPEC});
		MESSAGE_IDS.put(CHKJ2871, new String[]{CHKJ2871+SPEC});
		MESSAGE_IDS.put(CHKJ2872, new String[]{CHKJ2872+SPEC});
		MESSAGE_IDS.put(CHKJ2873, new String[]{CHKJ2873+SPEC});
		MESSAGE_IDS.put(CHKJ2874, new String[]{CHKJ2874+SPEC});
		MESSAGE_IDS.put(CHKJ2875, new String[]{CHKJ2875 + SPEC});
		MESSAGE_IDS.put(CHKJ2895, new String[]{CHKJ2895+SPEC});
		MESSAGE_IDS.put(CHKJ2852, new String[]{CHKJ2852});
		MESSAGE_IDS.put(CHKJ2907, new String[]{CHKJ2907});
	}
	
	public EJBJar20VRule() {
		_ejbName = new DuplicatesTable();
		_asName = new DuplicatesTable();
		_relationshipName = new DuplicatesTable();
		_relationshipRoleName = new DuplicatesTable();
	}
	
	public Object[] getDependsOn() {
		// EJBJar doesn't depend on anything else
		return DEPENDS_ON;
	}

	public Object getId() {
		return IValidationRuleList.EJB20_EJBJAR;
	}

	public Map getMessageIds() {
		return MESSAGE_IDS;
	}

	public Object getTarget(Object parent, Object clazz) {
		// Since this rule doesn't depend on another rule, this
		// method (which is called on a dependent rule) will never
		// be called.
		return null;
	}

	/*
	 * @see IValidationRule#reset()
	 */
	public void reset() {
		super.reset();
		_ejbName.clear();
		_asName.clear();
		_relationshipName.clear();
		_relationshipRoleName.clear();
	}

	protected 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);
			}
		}
	}
	
	protected void logMissingRule(IEJBValidationContext vc, Object ruleId) {
		log(vc, ruleId + " = null"); //$NON-NLS-1$
		addInternalErrorMessage(vc, null);
	}
	
	protected void log(IEJBValidationContext vc, String message) {
		Logger logger = vc.getMsgLogger();
		if (logger != null && logger.isLoggingLevel(Level.SEVERE)) {
			logger.write(Level.SEVERE, message);
		}
	}
	
	/*
	 * @see IValidationRule#validate(IEJBValidationContext, Object, Object)
		 */
	public void validate(IEJBValidationContext vc, Object targetParent, Object target) throws ValidationCancelledException, ValidationException {
		Logger logger = vc.getMsgLogger();
		if(logger != null && logger.isLoggingLevel(Level.FINEST)) {
			LogEntry entry = vc.getLogEntry();
			entry.setSourceID("EJBJar20VRule - validate"); //$NON-NLS-1$
			entry.setText(getClass().getName() + "::validate(" + targetParent + ", " + target); //$NON-NLS-1$ //$NON-NLS-2$
			logger.write(Level.FINEST, entry);
		}
		
		EJBJar ejbJar = (EJBJar) target;
		List beans = ejbJar.getEnterpriseBeans();
		Iterator iterator = beans.iterator();
		EnterpriseBean bean = null;
		while (iterator.hasNext()) {
			try {
				bean = (EnterpriseBean) iterator.next();

				register(vc, ejbJar, bean);
	
				Object id = null;			
				switch(bean.getVersionID()) {
					case J2EEVersionConstants.EJB_1_0_ID:
					case J2EEVersionConstants.EJB_1_1_ID:
						id = IValidationRuleList.EJB11_ENTERPRISEBEAN;
						break;
					case J2EEVersionConstants.EJB_2_0_ID:
					case J2EEVersionConstants.EJB_2_1_ID: 
						id = IValidationRuleList.EJB20_ENTERPRISEBEAN;
						break; 
					default:
						// What version is it then?
						if(bean instanceof ContainerManagedEntity) {
							validateCMPVersionElement(vc, ejbJar, (ContainerManagedEntity)bean);
						}
						else {
							// log
							log(vc, "!bean.isVersion1_X() && !bean.isVersion2_X()"); //$NON-NLS-1$
						}
						continue;
				}				 
				
				IValidationRule vRule = EJBValidationRuleFactory.getFactory().getRule(vc, id);
				if(vRule == null) {
					logMissingRule(vc, id);
					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?
					throw exc;
				}
				catch(ValidationException e) {
					throw e;
				}
				catch(Throwable exc) {
					addInternalErrorMessage(vc, exc);
				}
				finally {
					EJBValidationRuleFactory.getFactory().release(vRule);
				}
				
			}
			catch(ValidationCancelledException e) {
				throw e;
			}
			catch(ValidationException exc) {
				// something goes wrong, just proceed with the next bean
				IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2852, IEJBValidationContext.WARNING, bean, new String[]{J2EEConstants.EJBJAR_DD_SHORT_NAME, bean.getName()}, this);
				vc.addMessage(message);
				if (logger.isLoggingLevel(Level.SEVERE)) {
					logger.write(Level.SEVERE, exc);
				}
			}
			catch (Throwable exc) {
				IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2852, IEJBValidationContext.WARNING, bean, new String[]{J2EEConstants.EJBJAR_DD_SHORT_NAME, bean.getName()}, this);
				vc.addMessage(message);
				if (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);
		//TODO The Datasource validation will be done by the extensions
		validateUniqueEjbNames(vc, ejbJar);
		validateUniqueAbstractSchemaNames(vc, ejbJar);
		validateRelationships(vc, ejbJar);
		validateClientJAR(vc, ejbJar);
	}
	
	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));
		
		if((bean instanceof ContainerManagedEntity) && bean.getVersionID() >= J2EEVersionConstants.EJB_2_0_ID) {
			// To check if every bean name is unique, need to build a list
			_asName.add(new ASNameWrapper((ContainerManagedEntity)bean));
		}
	}

	protected void validateCMPVersionElement(IEJBValidationContext vc, EJBJar ejbJar, ContainerManagedEntity cmp) {
		if(cmp.eIsSet(EjbPackage.eINSTANCE.getContainerManagedEntity_Version())) {
			String version = cmp.getVersion();
			if(! (ContainerManagedEntity.VERSION_1_X.equals(version) || ContainerManagedEntity.VERSION_2_X.equals(version))) {
				IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2872, IEJBValidationContext.ERROR, cmp, this);
				vc.addMessage(message);
			}			
		}
	}
	
		

	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;
			}

			public boolean equals(Object o) {
				if (o instanceof RoleWrapper) {
					RoleWrapper other = (RoleWrapper) o;
					return _role.getRoleName().equals(other.getRole().getRoleName());
				}
				return false;
			}

			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.eIsSet(CommonPackage.eINSTANCE.getSecurityRole_RoleName())) || (role.getRoleName().equals(""))) { //$NON-NLS-1$
					// role-name not set
					if(role == null) {
						IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2825, IEJBValidationContext.WARNING, role, this);
						vc.addMessage(message);
					}
					else {
						IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2825, IEJBValidationContext.WARNING, assemblyDescriptor, 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("EJBJar20VRule.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)) && (!mp.isSetUnchecked())) {
				IMessage message = MessageUtility.getUtility().getMessage(vc, IEJBValidatorMessageConstants.CHKJ2845, IEJBValidationContext.WARNING, mp, this);
				vc.addMessage(message);
			}
		}
	}

	protected void validateUniqueAbstractSchemaNames(IEJBValidationContext vc, EJBJar ejbJar) {
		List names = _asName.getDuplicates();
		if(names.size() == 0) {
			return;
		}
		
		Iterator iterator = names.iterator();
		while(iterator.hasNext()) {
			ASNameWrapper wrapper = (ASNameWrapper)iterator.next();
			IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2854, IEJBValidationContext.ERROR, wrapper.getBean(), new String[]{wrapper.getBean().getAbstractSchemaName()}, this);
			vc.addMessage(message);
		}
	}
	
	protected 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 validateRelationships(IEJBValidationContext vc, EJBJar ejbJar) {
		Relationships relationships = ejbJar.getRelationshipList();
		if(relationships == null) {
			return;
		}
		
		List ejbRelationList = relationships.getEjbRelations();
		Iterator iterator = ejbRelationList.iterator();
		while(iterator.hasNext()) {
			EJBRelation relation = (EJBRelation)iterator.next();
			
			_relationshipName.add(new EJBRelationNameWrapper(relation));
			
			boolean atLeastOneCmrFieldExists = false;
			List roles = relation.getRelationshipRoles();
			Iterator roleIterator = roles.iterator();
			while(roleIterator.hasNext()) {
				EJBRelationshipRole role = (EJBRelationshipRole)roleIterator.next();
				
				_relationshipRoleName.add(new EJBRelationshipRoleNameWrapper(role));

				boolean validMultiplicity = role.isSetMultiplicity();
				if(!validMultiplicity) {
					IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2818, IEJBValidationContext.INFO, role, this);
					vc.addMessage(message);
				}
	
				// 10.3.4.2 
				// The cascade-delete element can only be specified for an ejb-relationship-role
				// element contained in an ejb-relation element if the *other* ejb-relationship-role
				// element in the same ejb-relation element specifies a multiplicity of One.
				EJBRelationshipRole oppositeRole = role.getOpposite();
				if(role.isSetCascadeDelete()) {
					boolean isOtherMultiplicityOne = ((oppositeRole.isSetMultiplicity()) && (oppositeRole.getMultiplicity().getValue() == MultiplicityKind.ONE));
					if(!isOtherMultiplicityOne) {
						IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2848, IEJBValidationContext.INFO, role, this);
						vc.addMessage(message);
					}
				}
				
				CMRField cmrField = role.getCmrField();
				if(cmrField != null) {
					atLeastOneCmrFieldExists = true;
					if(cmrField.eIsSet(EjbPackage.eINSTANCE.getCMRField_CollectionType())) {
						String cmrFieldType = cmrField.getCollectionTypeName();
						if((!ITypeConstants.CLASSNAME_JAVA_UTIL_COLLECTION.equals(cmrFieldType)) &&
							(!ITypeConstants.CLASSNAME_JAVA_UTIL_SET.equals(cmrFieldType))) {
							// CHKJ2868: The value of the <cmr-field-type> element must be either "java.util.Collection" or "java.util.Set" (EJB 2.0: 10.3.13, 22.5).
							IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2868, IEJBValidationContext.INFO, role, this);
							vc.addMessage(message);
						}
					}
				}
				
				RoleSource sourceRoleSource = role.getSource();
				if(sourceRoleSource == null) {
					IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2821, IEJBValidationContext.INFO, role, new String[]{role.getName()}, this);
					vc.addMessage(message);
					continue;
				}

				Entity sourceEntity = sourceRoleSource.getEntityBean();
				if(sourceEntity == null) {
					IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2821, IEJBValidationContext.INFO, role, new String[]{role.getName()}, this);
					vc.addMessage(message);
				}
				else if(sourceEntity.isContainerManagedEntity()) {
					// CHKJ2866: <cmr-field> {0} must be defined on {1} (EJB 2.0: 22.5).
					if(cmrField != null) {
						ContainerManagedEntity cmp = (ContainerManagedEntity)sourceEntity;
						List cmrFields = cmp.getCMRFields();
						boolean found_cmrField = false;
						if((cmrFields != null) && (cmrFields.size() > 0)) {
							found_cmrField = cmrFields.contains(cmrField);
						}
						
						if(!found_cmrField) {
							IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2866, IEJBValidationContext.INFO, role, new String[]{cmrField.getName(), sourceEntity.getName()}, this);
							vc.addMessage(message);
						}
					}
				}
				else {
					// CHKJ2871: <ejb-name> {0} must refer to a CMP bean (EJB 2.0: 10.3.2, 10.3.13).
					IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2871, IEJBValidationContext.INFO, role, new String[]{sourceEntity.getName()}, this);
					vc.addMessage(message);
				}
				
				if( oppositeRole == null )
					continue;
				RoleSource oppositeRoleSource = oppositeRole.getSource();
				if(oppositeRoleSource == null) {
					// Do not emit an error message - when we iterate to the role where
					// this is the source, this error will be detected.
					continue;
				}

				Entity targetEntity = oppositeRoleSource.getEntityBean();
				if(targetEntity == null) {
					// Do not emit an error message - when we iterate to the role where
					// this is the source, this error will be detected.
				}
				else if(targetEntity.getLocalInterface() == null) {
					// Does the target of this role have a local interface?
					// CHKJ2867: This bean can have only unidirectional relationships to other beans (EJB 2.0: 10.3.2).
					IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2867, IEJBValidationContext.INFO, oppositeRole, this);
					vc.addMessage(message);
				}
			}
			
			// Verify unique role names within the <ejb-relation>
			validateUniqueRelationshipRoleNames(vc, ejbJar);
			_relationshipRoleName.clear();
			
			if(!atLeastOneCmrFieldExists) {
				// CHKJ2869I: At least one <ejb-relationship-role> must define a <cmr-field> (EJB 2.0: 10.3.2, 10.3.13, 22.5).
				IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2869, IEJBValidationContext.INFO, relation, this);
				vc.addMessage(message);
			}
		}
		
		validateUniqueRelationNames(vc, ejbJar);
	}
	
	protected void validateUniqueRelationNames(IEJBValidationContext vc, EJBJar ejbJar) {
		List names = _relationshipName.getDuplicates();
		if(names.size() == 0) {
			return;
		}
		
		Iterator iterator = names.iterator();
		while(iterator.hasNext()) {
			EJBRelationNameWrapper wrapper = (EJBRelationNameWrapper)iterator.next();
			IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2816, IEJBValidationContext.INFO, wrapper.getRelation(), new String[]{wrapper.getRelation().getName()}, this);
			vc.addMessage(message);
		}
	}
	
	protected void validateUniqueRelationshipRoleNames(IEJBValidationContext vc, EJBJar ejbJar) {
		List names = _relationshipRoleName.getDuplicates();
		if(names.size() == 0) {
			return;
		}
		
		Iterator iterator = names.iterator();
		while(iterator.hasNext()) {
			EJBRelationshipRoleNameWrapper wrapper = (EJBRelationshipRoleNameWrapper)iterator.next();
			IMessage message = MessageUtility.getUtility().getMessage(vc, IMessagePrefixEjb20Constants.CHKJ2817, IEJBValidationContext.INFO, wrapper.getRelationshipRole(), new String[]{wrapper.getRelationshipRole().getName()}, 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) {
				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;
	}

	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);
		}
	}
	
	class ASNameWrapper {
		private ContainerManagedEntity _bean = null;

		public ASNameWrapper(ContainerManagedEntity bean) {
			_bean = bean;
		}

		public boolean equals(Object o) {
			if (o instanceof ASNameWrapper) {
				ASNameWrapper other = (ASNameWrapper)o;
				if((_bean.getAbstractSchemaName() == null) && (other.getBean().getAbstractSchemaName() == null)) {
					return true;
				}
				else if(_bean.getAbstractSchemaName() == null) {
					return false;
				}
				else if(other.getBean().getAbstractSchemaName() == null) {
					return false;
				}
				return _bean.getAbstractSchemaName().equals(other.getBean().getAbstractSchemaName());
			}
			return false;
		}

		public int hashCode() {
			if((getBean() != null) && (getBean().getAbstractSchemaName() != null)) {
				return getBean().getAbstractSchemaName().hashCode();
			}
			return super.hashCode();
		}

		public ContainerManagedEntity getBean() {
			return _bean;
		}
	}

	/**
	 * 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;
		}

		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;
		}
		
		public int hashCode() {
			if((getBean() != null) && (getBean().getName() != null)) {
				return getBean().getName().hashCode();
			}
			return super.hashCode();
		}

		public EnterpriseBean getBean() {
			return _bean;
		}
	}

	/**
	 * Need to build up a list of duplicate relation 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 EJBRelationNameWrapper {
		private EJBRelation _relation = null;

		public EJBRelationNameWrapper(EJBRelation rel) {
			_relation = rel;
		}

		public boolean equals(Object o) {
			if (o instanceof EJBRelationNameWrapper) {
				EJBRelationNameWrapper other = (EJBRelationNameWrapper)o;
				if((_relation.getName() == null) && (other.getRelation().getName() == null)) {
					return true;
				}
				else if(_relation.getName() == null) {
					return false;
				}
				else if(other.getRelation().getName() == null) {
					return false;
				}
				return _relation.getName().equals(other.getRelation().getName());
			}
			return false;
		}
		
		public int hashCode() {
			if((getRelation() != null) && (getRelation().getName() != null)) {
				return getRelation().getName().hashCode();
			}
			return super.hashCode();
		}

		public EJBRelation getRelation() {
			return _relation;
		}
	}

	/**
	 * Need to build up a list of duplicate relation 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 EJBRelationshipRoleNameWrapper {
		private EJBRelationshipRole _relationshipRole = null;

		public EJBRelationshipRoleNameWrapper(EJBRelationshipRole role) {
			_relationshipRole = role;
		}

		public boolean equals(Object o) {
			if (o instanceof EJBRelationshipRoleNameWrapper) {
				EJBRelationshipRoleNameWrapper other = (EJBRelationshipRoleNameWrapper)o;
				if((_relationshipRole.getRoleName() == null) && (other.getRelationshipRole().getRoleName() == null)) {
					return true;
				}
				else if(_relationshipRole.getRoleName() == null) {
					return false;
				}
				else if(other.getRelationshipRole().getRoleName() == null) {
					return false;
				}
				return _relationshipRole.getRoleName().equals(other.getRelationshipRole().getRoleName());
			}
			return false;
		}
		
		public int hashCode() {
			if((getRelationshipRole() != null) && (getRelationshipRole().getRoleName() != null)) {
				return getRelationshipRole().getRoleName().hashCode();
			}
			return super.hashCode();
		}

		public EJBRelationshipRole getRelationshipRole() {
			return _relationshipRole;
		}
	}
}
