/*******************************************************************************
 * Copyright (c) 2006 Oracle 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:
 *     Oracle Corporation - initial API and implementation
 *******************************************************************************/
package org.eclipse.bpel.validator.rules;


/**
 * JDK dependency here 
 */

import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Dependency on the BPEL validation model only in here ...
 */

import org.eclipse.bpel.validator.model.Filters;
import org.eclipse.bpel.validator.model.IFilter;
import org.eclipse.bpel.validator.model.IModelQueryLookups;
import org.eclipse.bpel.validator.model.INode;
import org.eclipse.bpel.validator.model.IProblem;
import org.eclipse.bpel.validator.model.ARule;
import org.eclipse.bpel.validator.model.NodeNameFilter;
import org.eclipse.bpel.validator.model.Selector;

/**
 * PartnerLink validation rules. 
 * <p>
 * Note, there is only dependency on the validator API. Actual BPEL model
 * is provided behind the INode facade.
 * <p>
 * @author Michal Chmielewski (michal.chmielewski@oracle.com)
 * @date Sep 19, 2006
 */

@SuppressWarnings("nls")

public class PartnerLinkValidator extends CValidator {

	/**
	 * What are my parent nodes ?
	 */
	static public IFilter<INode> PARENTS = new NodeNameFilter( ND_PARTNER_LINKS );
	
	INode fPartnerLinkType;
	INode fPartnerLink;
	
	String ncName ;
	String fMyRole;
	String fPartnerRole;

	protected String fInitializePartnerRole;

	
	List<INode> fRoles = Collections.emptyList();
	
	
	/**
	 * Start validation of the partner link node ...
	 */
	
	@Override
	protected void start () {
		
		super.start();	
		
		fPartnerLink = mNode;
		fPartnerLinkType = mModelQuery.lookup (mNode,
							IModelQueryLookups.LOOKUP_NODE_PARTNER_LINK_TYPE,
							mNode.getAttributeAsQName( AT_PARTNER_LINK_TYPE ) ) ;
		
		if (fPartnerLinkType != null) {
			fRoles = fPartnerLinkType.children();
		}
		
		ncName = mNode.getAttribute( AT_NAME );
	}

	
	/**
	 * Check the name attribute. 
	 * 
	 */
	
	public void rule_CheckName_1 () {
				
		checkNCName(mNode, ncName, AT_NAME );		
	}
	
	
	/**
	 * Check if partner link type is defined.
	 */
	
	public void rule_CheckPartnerLinkType_2 () {
		
		if (checkAttributeNode(mNode, fPartnerLinkType, AT_PARTNER_LINK_TYPE, KIND_NODE) == false) {
			fPartnerLinkType = null;
		}		
	}
	
		
	/**
	 * 
	 * Checks to see if myRole or partnerRole is set.
	 * One of them must be set. This is per SA00016 of the spec.
	 * 
	 */
	@ARule(
		author = "michal.chmielewski@oracle.com",
		desc="myRole or partnerRole (or both) must be set",
		date = "09/19/2006",
		sa=16		
	)	
	public void rule_CheckForUndefinedPartnerLinkRoles_4 () {
		
		fMyRole = fPartnerLink.getAttribute(AT_MY_ROLE);
		fPartnerRole = fPartnerLink.getAttribute(AT_PARTNER_ROLE);
		
		IProblem problem ;
		
		if (fMyRole == null && fPartnerRole == null) {
			
			problem = createError ( );
			problem.fill( "BPELC_PARTNER_LINK__NO_ROLES_SET", //$NON-NLS-1$
					fPartnerLink.nodeName(),
					ncName,
					AT_MY_ROLE,
					AT_PARTNER_ROLE
			);
		}		
	}
	
	/**
	 * Check if initializePartnerRole is set on the partnerLink
	 * if partnerRole is set (or not).
	 * 
	 * 
	 */
	@ARule(
		author = "michal.chmielewski@oracle.com",
		desc = "Check if initializePartnerRole is set on the partnerLink" +
	           " if partnerRole is set (or not).",
		date = "09/19/2006",
		sa = 17		
	)
	
	public void rule_CheckInitializePartnerRole_8 () {
		
		fInitializePartnerRole = getAttribute(fPartnerLink, 
				AT_INITIALIZE_PARTNER_ROLE, 
				KIND_ACTIVITY, 
				Filters.BOOLEAN_FILTER, 
				false);
		
		
		if (fPartnerRole != null) {
			
			if (isEmpty(fPartnerRole)) {
				fInitializePartnerRole = NO;
			}
			
			return ;
		}
		
		
		IProblem problem;
		
		if (fInitializePartnerRole != null) {
		
			problem = createError();
			problem.fill("BPELC_PARTNER_LINK__INIT_PARTNER_ROLE", //$NON-NLS-1$
					toString(mNode.nodeName()),
					AT_INITIALIZE_PARTNER_ROLE,
					AT_PARTNER_ROLE);
		}
		
		
		
	}
	
	
	/**
	 * Check the partner link roles. Once a partner link type is 
	 * deemed defined, then the roles are checked to see if they are 
	 * defined.
	 */
	
	
	public void rule_CheckPartnerLinkRoles_10 () {
				
		if (fPartnerLinkType == null) {
			return ;
		}
		
		if (fMyRole != null) {
			checkRole ( fMyRole );
		}
		
		if (fPartnerRole != null) {
			checkRole (fPartnerRole);
		}
		
	}
	

	/**
	 * 
	 * Checks to make sure there are at most 2 or at least 1
	 * partner links defined on this partner link type. 
	 * 
	 */
	
	public void rule_CheckRolesOnPartnerLinkType_17 () {
			
		if (fPartnerLinkType == null) {
			return ;
		}
	
		IProblem problem;
		
		if (fRoles.size() == 0) {
			
			problem = createWarning(mNode);
			problem.fill("BPELC_PARTNER_LINK_TYPE__NO_ROLES_DEFINED",  //$NON-NLS-1$
					AT_PARTNER_LINK_TYPE,AT_PARTNER_LINK );					
		}
				
		if (fRoles.size() > 2) {
			problem = createWarning(mNode);
			problem.fill("BPELC_PARTNER_LINK_TYPE__TWO_MANY_ROLES_DEFINED", //$NON-NLS-1$
					AT_PARTNER_LINK_TYPE,AT_PARTNER_LINK);
		}			
	}
	
	
	/**
	 * Check the port types in partner link types
	 * 
	 */
		
	public void rule_CheckPortTypes_18 () {
		
		if (fPartnerLinkType == null) {
			return ;
		}
		
		IProblem problem;						
		
		for(INode role : fRoles) {
			
			INode portType = mModelQuery.lookup (role, 
								IModelQueryLookups.LOOKUP_NODE_PORT_TYPE,
								role.getAttribute( AT_PORT_TYPE )) ;
			
			if ( isUndefined(portType) ) {

				// problem = createError( mNode );
				problem = createError( );
				problem.fill("BPELC_PLT__NO_PORT_TYPE",
						role.getAttribute(AT_NAME),
						role.getAttribute(AT_PORT_TYPE),
						AT_PARTNER_LINK_TYPE,
						AT_PORT_TYPE);
				
			} else {
				
				runRules("direct",portType);
				
			}
			
		}					 
	}
	
	
	
	/**
	 * Check the port type as per SA 1 of the BPEL spec.
	 * @param portType
	 */
	
	@ARule(
		sa = 1,
		desc = "Check solicit-response or notification port types",
		date = "02/16/2007",
		author = "michal.chmielewski@oracle.com",
		tag = "direct"
	)
		
	public void checkSolicitResponseAndNotification ( INode portType ) {
		
		IProblem problem;
		
		for(INode op: portType.getNodeList( WSDL_ND_OPERATION )) {
		
			List<INode> children = op.children();
			
			if (children.size() == 1) {
				
				if (WSDL_ND_OUTPUT.equals(children.get(0).nodeName())) {
					// notification
					problem = createError();
					problem.fill("BPELC_PL__NOTIFICATION",
							toString(mNode.nodeName()),
							mNode.getAttribute(AT_NAME),
							mNode.getAttributeAsQName(AT_PARTNER_LINK_TYPE),
							
							portType.getAttribute(AT_NAME),
							op.getAttribute(AT_NAME) );
				}
				
			} else if (children.size() > 1) {
				
				if (WSDL_ND_OUTPUT.equals(children.get(0).nodeName()) && 
					WSDL_ND_INPUT.equals(children.get(1).nodeName())) {
					
					// solicit-response
					problem = createError();
					problem.fill("BPELC_PL__SOLICIT_RESPONSE",
							toString(mNode.nodeName()),
							mNode.getAttribute(AT_NAME),
							mNode.getAttributeAsQName(AT_PARTNER_LINK_TYPE),
							
							portType.getAttribute(AT_NAME),
							op.getAttribute(AT_NAME) );
				}
			}
			
			
		}
	}
	
	
	/**
	 * Check the port type as per SA 1 of the BPEL spec.
	 * @param portType
	 */
	
	
	@ARule(
		sa = 2,
		desc = "Check for overloaded operation names in port types",
		date = "02/16/2007",
		author = "michal.chmielewski@oracle.com",
		tag = "direct"
	)
		
	public void checkOverloadedOperationNames ( INode portType ) {
		
		Set<String> names = new HashSet<String>();
		IProblem problem;
		
		for(INode op : portType.getNodeList( WSDL_ND_OPERATION ) ) {
			String name = op.getAttribute(AT_NAME);
			if (name == null) {
				continue;
			}
			
			if (names.contains(name)) {
				problem = createError();
				problem.fill("BPELC_PL__OVERLOADED",
						toString(mNode.nodeName()),
						mNode.getAttribute(AT_NAME),
						mNode.getAttributeAsQName(AT_PARTNER_LINK_TYPE),
						portType.getAttribute(AT_NAME),
						name );
			}
			names.add(name);
		}
	}
	
	
	
	
	/** ------------------------- private methods --------------------------- */
	
	void checkRole (final String roleName) {

		// see if such a role exists.
		INode roleNode = mSelector.selectNode(
					fPartnerLinkType, 
				    Selector.ALL,  
				    new IFilter<INode> () {
						public boolean select(INode node) {
							return roleName.equals( node.getAttribute(AT_NAME) );
						}
					}
		);
		
		
		setValue("role." + roleName , roleNode);
				
		IProblem problem;
		
		if (roleNode == null) {
			//problem = createError ( fPartnerLinkType );			
			problem = createError ();			
			problem.fill("BPELC_PARTNER_LINK_TYPE__NO_SUCH_ROLE",  //$NON-NLS-1$
					fPartnerLinkType.nodeName(),roleName);
			
			return ;
		}
		
		
		INode portTypeFromRole = mModelQuery.lookup( roleNode ,
				IModelQueryLookups.LOOKUP_NODE_PORT_TYPE,
				roleNode.getAttribute( AT_PORT_TYPE )) ;
		
		setValue("role.portType." + roleName , portTypeFromRole);		
		
	}
		
}
