/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.tuscany.sca.builder.impl; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import javax.xml.namespace.QName; import org.apache.tuscany.sca.assembly.Binding; import org.apache.tuscany.sca.assembly.Component; import org.apache.tuscany.sca.assembly.ComponentReference; import org.apache.tuscany.sca.assembly.ComponentService; import org.apache.tuscany.sca.assembly.CompositeReference; import org.apache.tuscany.sca.assembly.CompositeService; import org.apache.tuscany.sca.assembly.Contract; import org.apache.tuscany.sca.assembly.EndpointReference; import org.apache.tuscany.sca.assembly.Reference; import org.apache.tuscany.sca.assembly.Service; import org.apache.tuscany.sca.assembly.builder.BuilderContext; import org.apache.tuscany.sca.assembly.builder.BuilderExtensionPoint; import org.apache.tuscany.sca.assembly.builder.Messages; import org.apache.tuscany.sca.core.ExtensionPointRegistry; import org.apache.tuscany.sca.definitions.Definitions; import org.apache.tuscany.sca.monitor.Monitor; import org.apache.tuscany.sca.policy.ExtensionType; import org.apache.tuscany.sca.policy.Intent; import org.apache.tuscany.sca.policy.IntentMap; import org.apache.tuscany.sca.policy.PolicyExpression; import org.apache.tuscany.sca.policy.PolicySet; import org.apache.tuscany.sca.policy.PolicySubject; import org.apache.tuscany.sca.policy.Qualifier; /** * A composite builder that computes policy sets based on attached intents and policy sets. * Useful if you want to build the model without making any runtime decisions such as * reference/services matching * * @version $Rev$ $Date$ */ public class ComponentPolicyBuilderImpl { protected BuilderExtensionPoint builders; public ComponentPolicyBuilderImpl(ExtensionPointRegistry registry) { this.builders = registry.getExtensionPoint(BuilderExtensionPoint.class); } /** * Report a warning. * * @param monitor * @param problems * @param message * @param model */ protected void warning(Monitor monitor, String message, Object model, Object... messageParameters) { Monitor.warning(monitor, this, Messages.BUILDER_VALIDATION_BUNDLE, message, messageParameters); } /** * Report a error. * * @param monitor * @param problems * @param message * @param model */ protected void error(Monitor monitor, String message, Object model, Object... messageParameters) { Monitor.error(monitor, this, Messages.BUILDER_VALIDATION_BUNDLE, message, messageParameters); } /** * Inherit the intents and policySets from the list of models * * @param policySubject - the subject to which intents will be added * @param intentType - choose to copy interaction or implementation intents. Null = both * @param ignoreExclusiveIntents - when set true mutually exclusive intents won't be copied * @param models - the subjects from which intents will be copied */ protected void inherit(PolicySubject policySubject, Intent.Type intentType, boolean ignoreExclusiveIntents, Object... models) { for (Object model : models) { if (model instanceof PolicySubject) { PolicySubject subject = (PolicySubject)model; if (!ignoreExclusiveIntents) { // The intents are merged and the exclusion check will be done after for (Intent intent : subject.getRequiredIntents()) { if (!policySubject.getRequiredIntents().contains(intent)){ if ((intent.getType() != null) && (intentType != null) ) { if (intent.getType().equals(intentType)){ policySubject.getRequiredIntents().add(intent); } } else { policySubject.getRequiredIntents().add(intent); } } } } else { Set<Intent> intents = new HashSet<Intent>(); for (Intent i1 : subject.getRequiredIntents()) { boolean exclusive = false; for (Intent i2 : policySubject.getRequiredIntents()) { if (i1.getExcludedIntents().contains(i2) || i2.getExcludedIntents().contains(i1)) { exclusive = true; break; } } if (!exclusive) { if (!intents.contains(i1)){ if (intentType != null) { if (i1.getType().equals(intentType)){ intents.add(i1); } } else { intents.add(i1); } } } } policySubject.getRequiredIntents().addAll(intents); } //FIXME this duplicates the intents for a implementation //e.g <implementation.java requires="managedTransaction.local managedTransaction.local" //becomes twice //[{http://docs.oasis-open.org/ns/opencsa/sca/200912}managedTransaction.local, //{http://docs.oasis-open.org/ns/opencsa/sca/200912}managedTransaction.local] for (PolicySet policySet : subject.getPolicySets()){ if (!policySubject.getPolicySets().contains(policySet)){ policySubject.getPolicySets().add(policySet); } } } } } protected void configure(PolicySubject subject1, PolicySubject subject2, Intent.Type intentType, BuilderContext context) { if (subject1 != null) { resolveAndCheck(subject1, context); } if (subject2 != null) { resolveAndCheck(subject2, context); } inherit(subject1, intentType, false, subject2); checkMutualExclusion(subject1, context); } protected void configure(ComponentService componentService, BuilderContext context) { Service service = componentService.getService(); if (service != null) { configure(componentService, service, null, context); configureBindings(componentService, service, context); } } private void configureBindings(Contract componentContract, Contract componentTypeContract, BuilderContext context) { if (componentTypeContract == null) { return; } Map<String, Binding> componentTypeContractBindings = new HashMap<String, Binding>(); for (Binding binding : componentTypeContract.getBindings()) { componentTypeContractBindings.put(binding.getName(), binding); } for (Binding binding : componentContract.getBindings()) { Binding componentTypeBinding = componentTypeContractBindings.get(binding.getName()); if (binding instanceof PolicySubject && componentTypeBinding instanceof PolicySubject) { configure((PolicySubject)binding, (PolicySubject)componentTypeBinding, Intent.Type.interaction, context); } } } protected void configure(ComponentReference componentReference, BuilderContext context) { Reference reference = componentReference.getReference(); if (reference != null) { configure(componentReference, reference, null, context); configureBindings(componentReference, reference, context); } } protected void configure(CompositeService compositeService, BuilderContext context) { configure(compositeService, compositeService.getPromotedService(), null, context); } protected void configure(CompositeReference compositeReference, BuilderContext context) { for (ComponentReference reference : compositeReference.getPromotedReferences()) { configure(compositeReference, reference, null, context); } } public void configure(Component component, BuilderContext context) { Monitor monitor = context.getMonitor(); // fix up the component type by copying all implementation level // interaction intents to *all* the component type services for (ComponentService componentService : component.getServices()) { monitor.pushContext("Service: " + componentService.getName()); try { configure(componentService, component.getImplementation(), Intent.Type.interaction, context); removeConstrainedIntents(componentService, context); } finally { monitor.popContext(); } } // Inherit the intents and policySets from the componentType for (ComponentReference componentReference : component.getReferences()) { monitor.pushContext("Reference: " + componentReference.getName()); try { configure(componentReference, context); removeConstrainedIntents(componentReference, context); } finally { monitor.popContext(); } } for (ComponentService componentService : component.getServices()) { monitor.pushContext("Service: " + componentService.getName()); try { configure(componentService, context); removeConstrainedIntents(componentService, context); } finally { monitor.popContext(); } } } /** * Checks if any qualifiable intents of intents in an excluded intent list match * with a second intent. looking for the case where * * <intent name="intentA" excludes="intentB"/> * <intent name="intentB" > * <sca:qualifier name="q1" default="true"/> * <sca:qualifier name="q2" default="true"/> * </intent> * * And were * * requires="intentA intentB.q1" appears on an element * * @param excludedIntentList * @param intent * @return */ protected boolean checkQualifiedMutualExclusion(List<Intent> excludedIntentList, Intent intent){ for (Intent excludedIntent : excludedIntentList){ if (intent.getQualifiableIntent() != null && excludedIntent != null && intent.getQualifiableIntent().equals(excludedIntent)){ return true; } } return false; } /** * Check if two intents are mutually exclusive * * @param i1 * @param i2 * @param context * @return */ protected boolean checkMutualExclusion(Intent i1, Intent i2, BuilderContext context){ if ((i1 != i2) && (i1.getExcludedIntents().contains(i2) || i2.getExcludedIntents().contains(i1) || checkQualifiedMutualExclusion(i1.getExcludedIntents(), i2) || checkQualifiedMutualExclusion(i2.getExcludedIntents(), i1))) { error(context.getMonitor(), "MutuallyExclusiveIntentsAtBuild", this, i1, i2); return true; } return false; } /** * Check if a single policy subject requires mutually exclusive intents * @param subject1 - the policy subject to check * @param context - context containing useful things like the monitor instance * @return true if the policy subject contains mutually exclusive intents */ protected boolean checkMutualExclusion(PolicySubject subject1, BuilderContext context) { if (subject1 == null) { return false; } for (Intent i1 : subject1.getRequiredIntents()) { for (Intent i2 : subject1.getRequiredIntents()) { if (checkMutualExclusion(i1, i2, context)){ return true; } } } return false; } /** * Check if two policy subjects requires mutually exclusive intents * @param subject1 * @param subject2 * @param monitor * @return */ protected boolean checkMutualExclusion(PolicySubject subject1, PolicySubject subject2, BuilderContext context) { if (subject1 == subject2 || subject1 == null || subject2 == null) { return false; } for (Intent i1 : subject1.getRequiredIntents()) { for (Intent i2 : subject2.getRequiredIntents()) { if (checkMutualExclusion(i1, i2, context)){ return true; } } } return false; } protected boolean resolveAndCheck(PolicySubject subject, BuilderContext context) { if (subject == null) { return false; } // FIXME: [rfeng] Should we resolve the intents during the "build" phase? resolveAndNormalize(subject, context); checkMutualExclusion(subject, context); return false; } /** * Check if two names are equal * @param name1 * @param name2 * @return */ protected boolean isEqual(String name1, String name2) { if (name1 == name2) { return true; } if (name1 != null) { return name1.equals(name2); } else { return name2.equals(name1); } } protected static Intent resolve(Definitions definitions, Intent proxy) { for (Intent i : definitions.getIntents()) { if (i.equals(proxy)) { return i; } for (Intent qi : i.getQualifiedIntents()) { if (qi.equals(proxy)) { return qi; } } } return null; } // Replace qualifiable intents with their default qualifier. This can't be done until // after inheritance. protected void expandDefaultIntents(PolicySubject subject, BuilderContext context) { Set<Intent> copy = new HashSet<Intent>(subject.getRequiredIntents()); for (Intent i : copy) { if (i.getDefaultQualifiedIntent() != null) { subject.getRequiredIntents().remove(i); subject.getRequiredIntents().add(i.getDefaultQualifiedIntent()); } } } protected void resolveAndNormalize(PolicySubject subject, BuilderContext context) { Definitions definitions = context.getDefinitions(); Set<Intent> intents = new HashSet<Intent>(); if (definitions != null) { for (Intent i : subject.getRequiredIntents()) { Intent resolved = resolve(definitions, i); if (resolved != null) { intents.add(resolved); } else { error(context.getMonitor(), "IntentNotFoundAtBuild", subject, i); // Intent cannot be resolved } } } // Replace profile intents with their required intents while (!intents.isEmpty()) { boolean profileIntentsFound = false; Set<Intent> copy = new HashSet<Intent>(intents); for (Intent i : copy) { if (!i.getRequiredIntents().isEmpty()) { intents.remove(i); intents.addAll(i.getRequiredIntents()); profileIntentsFound = true; } } if (!profileIntentsFound) { // No more profileIntents break; } } // Replace unqualified intents if there is a qualified intent in the list Set<Intent> copy = new HashSet<Intent>(intents); for (Intent i : copy) { if (i.getQualifiableIntent() != null) { intents.remove(i.getQualifiableIntent()); } } subject.getRequiredIntents().clear(); subject.getRequiredIntents().addAll(intents); // TUSCANY-3503 - policy sets now only applied through direct // or external attachement // resolve policy set names that have been specified for the // policy subject against the real policy sets from the // definitions files Set<PolicySet> policySets = new HashSet<PolicySet>(); if (definitions != null) { for (PolicySet policySet : subject.getPolicySets()) { int index = definitions.getPolicySets().indexOf(policySet); if (index != -1) { policySets.add(definitions.getPolicySets().get(index)); } else { // PolicySet cannot be resolved warning(context.getMonitor(), "PolicySetNotFoundAtBuild", subject, policySet); } } } subject.getPolicySets().clear(); subject.getPolicySets().addAll(policySets); } protected void removeConstrainedIntents(PolicySubject subject, BuilderContext context) { List<Intent> intents = subject.getRequiredIntents(); // Remove the intents whose @contrains do not include the current element ExtensionType extensionType = subject.getExtensionType(); if(extensionType != null){ List<Intent> copy = new ArrayList<Intent>(intents); for (Intent i : copy) { List<ExtensionType> constrainedTypes = i.getConstrainedTypes(); if (( constrainedTypes.size() == 0 ) && ( i.getQualifiableIntent() != null ) ) constrainedTypes = i.getQualifiableIntent().getConstrainedTypes(); if (constrainedTypes.size() > 0){ boolean constraintFound = false; for (ExtensionType constrainedType : constrainedTypes){ if (constrainedType.getType().equals(extensionType.getType()) || constrainedType.getType().equals(extensionType.getBaseType())){ constraintFound = true; break; } } if(!constraintFound){ intents.remove(i); } } } } } protected void checkIntentsResolved(PolicySubject subject, BuilderContext context) { // find the policy sets that satisfy the intents that are now // attached to the policy subject. From the OASIS policy // spec CD02 rev7: // 1272 A policySet provides an intent if any of the statements are true: // 1273 1. The intent is contained in the policySet @provides list. // 1274 2. The intent is a qualified intent and the unqualified form of the intent is contained in the policySet // 1275 @provides list. // 1276 3. The policySet @provides list contains a qualified form of the intent (where the intent is qualifiable). for (Intent intent : subject.getRequiredIntents()) { boolean intentMatched = false; loop: for (PolicySet ps : subject.getPolicySets()) { // FIXME: We will have to check the policy references and intentMap too // as well as the appliesTo if (ps.getProvidedIntents().contains(intent)) { intentMatched = true; break; } for (Intent psProvidedIntent : ps.getProvidedIntents()){ if (isQualifiedBy(psProvidedIntent, intent)){ intentMatched = true; break loop; } } for (IntentMap map : ps.getIntentMaps()) { for (Qualifier q : map.getQualifiers()) { if (intent.equals(q.getIntent())) { intentMatched = true; break loop; } } } } if (!intentMatched){ // Reference side intents can still be resolved by the service binding, so we can only issue a // warning here. if ( subject instanceof EndpointReference ) { warning(context.getMonitor(), "IntentNotSatisfiedAtBuild", subject, intent.getName(), subject.toString()); } else { // Need to check the ExtensionType to see if the intent is provided there. If not, throw an error ExtensionType type = subject.getExtensionType(); if ( type == null ) { error(context.getMonitor(), "IntentNotSatisfiedAtBuild", subject, intent.getName(), subject.toString()); } else { // The ExtensionType on the subject only has the binding name. The one in the system // definitions will have the mayProvide/alwaysProvides values for ( ExtensionType et : context.getDefinitions().getBindingTypes() ) { if ( type.getType().equals(et.getType()) ) { type = et; } } if ( !type.getAlwaysProvidedIntents().contains(intent) && !type.getMayProvidedIntents().contains(intent)) { error(context.getMonitor(), "IntentNotSatisfiedAtBuild", subject, intent.getName(), subject.toString()); } } } } } } protected Set<QName> getPolicyNames(PolicySubject subject) { if (subject == null) { return Collections.emptySet(); } Set<QName> names = new HashSet<QName>(); for (PolicySet ps : subject.getPolicySets()) { for (PolicyExpression exp : ps.getPolicies()) { names.add(exp.getName()); } } return names; } protected boolean isQualifiedBy(Intent qualifiableIntent, Intent qualifiedIntent){ if (qualifiedIntent.getQualifiableIntent() == qualifiableIntent){ return true; } else { return false; } } }