1 /*
   2  * Copyright (c) 1997, 2012, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package com.sun.xml.internal.ws.policy.sourcemodel;
  27 
  28 import com.sun.xml.internal.ws.policy.sourcemodel.wspolicy.NamespaceVersion;
  29 import com.sun.xml.internal.ws.policy.PolicyConstants;
  30 import com.sun.xml.internal.ws.policy.PolicyException;
  31 import com.sun.xml.internal.ws.policy.privateutil.LocalizationMessages;
  32 import com.sun.xml.internal.ws.policy.privateutil.PolicyLogger;
  33 import com.sun.xml.internal.ws.policy.privateutil.PolicyUtils;
  34 import com.sun.xml.internal.ws.policy.spi.PrefixMapper;
  35 
  36 import java.util.Collection;
  37 import java.util.HashMap;
  38 import java.util.HashSet;
  39 import java.util.LinkedList;
  40 import java.util.List;
  41 import java.util.Map;
  42 import java.util.Map.Entry;
  43 import java.util.Queue;
  44 import java.util.Set;
  45 import javax.xml.namespace.QName;
  46 
  47 /**
  48  * This class is a root of unmarshaled policy source structure. Each instance of the class contains factory method
  49  * to create new {@link com.sun.xml.internal.ws.policy.sourcemodel.ModelNode} instances associated with the actual model instance.
  50  *
  51  * @author Marek Potociar
  52  * @author Fabian Ritzmann
  53  */
  54 public class PolicySourceModel implements Cloneable {
  55 
  56     private static final PolicyLogger LOGGER = PolicyLogger.getLogger(PolicySourceModel.class);
  57 
  58     private static final Map<String, String> DEFAULT_NAMESPACE_TO_PREFIX = new HashMap<String, String>();
  59     static {
  60         PrefixMapper[] prefixMappers = PolicyUtils.ServiceProvider.load(PrefixMapper.class);
  61         if (prefixMappers != null) {
  62             for (PrefixMapper mapper: prefixMappers) {
  63                 DEFAULT_NAMESPACE_TO_PREFIX.putAll(mapper.getPrefixMap());
  64             }
  65         }
  66 
  67         for (NamespaceVersion version : NamespaceVersion.values()) {
  68             DEFAULT_NAMESPACE_TO_PREFIX.put(version.toString(), version.getDefaultNamespacePrefix());
  69         }
  70         DEFAULT_NAMESPACE_TO_PREFIX.put(PolicyConstants.WSU_NAMESPACE_URI,
  71                 PolicyConstants.WSU_NAMESPACE_PREFIX);
  72         DEFAULT_NAMESPACE_TO_PREFIX.put(PolicyConstants.SUN_POLICY_NAMESPACE_URI,
  73                 PolicyConstants.SUN_POLICY_NAMESPACE_PREFIX);
  74     }
  75 
  76     // Map namespaces to prefixes
  77     private final Map<String, String> namespaceToPrefix =
  78             new HashMap<String, String>(DEFAULT_NAMESPACE_TO_PREFIX);
  79 
  80     private ModelNode rootNode;
  81     private final String policyId;
  82     private final String policyName;
  83     private final NamespaceVersion nsVersion;
  84     private final List<ModelNode> references = new LinkedList<ModelNode>(); // links to policy reference nodes
  85     private boolean expanded = false;
  86 
  87 
  88     /**
  89      * Factory method that creates new policy source model instance.
  90      *
  91      * This method is only intended to be used by code that has no dependencies on
  92      * JAX-WS. Otherwise use com.sun.xml.internal.ws.policy.api.SourceModel.
  93      *
  94      * @param nsVersion The policy version
  95      * @return Newly created policy source model instance.
  96      */
  97     public static PolicySourceModel createPolicySourceModel(final NamespaceVersion nsVersion) {
  98         return new PolicySourceModel(nsVersion);
  99     }
 100 
 101     /**
 102      * Factory method that creates new policy source model instance and initializes it according to parameters provided.
 103      *
 104      * This method is only intended to be used by code that has no dependencies on
 105      * JAX-WS. Otherwise use com.sun.xml.internal.ws.policy.api.SourceModel.
 106      *
 107      * @param nsVersion The policy version
 108      * @param policyId local policy identifier - relative URI. May be {@code null}.
 109      * @param policyName global policy identifier - absolute policy expression URI. May be {@code null}.
 110      * @return Newly created policy source model instance with its name and id properly set.
 111      */
 112     public static PolicySourceModel createPolicySourceModel(final NamespaceVersion nsVersion, final String policyId, final String policyName) {
 113         return new PolicySourceModel(nsVersion, policyId, policyName);
 114     }
 115 
 116     /**
 117      * Constructor that creates a new policy source model instance without any
 118      * id or name identifier. The namespace-to-prefix map is initialized with mapping
 119      * of policy namespace to the default value set by
 120      * {@link PolicyConstants#POLICY_NAMESPACE_PREFIX POLICY_NAMESPACE_PREFIX constant}.
 121      *
 122      * @param nsVersion The WS-Policy version.
 123      */
 124     private PolicySourceModel(NamespaceVersion nsVersion) {
 125         this(nsVersion, null, null);
 126     }
 127 
 128     /**
 129      * Constructor that creates a new policy source model instance with given
 130      * id or name identifier.
 131      *
 132      * @param nsVersion The WS-Policy version.
 133      * @param policyId Relative policy reference within an XML document. May be {@code null}.
 134      * @param policyName Absolute IRI of policy expression. May be {@code null}.
 135      */
 136     private PolicySourceModel(NamespaceVersion nsVersion, String policyId, String policyName) {
 137         this(nsVersion, policyId, policyName, null);
 138     }
 139 
 140     /**
 141      * Constructor that creates a new policy source model instance with given
 142      * id or name identifier and a set of PrefixMappers.
 143      *
 144      * This constructor is intended to be used by the JAX-WS com.sun.xml.internal.ws.policy.api.SourceModel.
 145      *
 146      * @param nsVersion The WS-Policy version.
 147      * @param policyId Relative policy reference within an XML document. May be {@code null}.
 148      * @param policyName Absolute IRI of policy expression. May be {@code null}.
 149      * @param prefixMappers A collection of PrefixMappers to be used with this instance. May be {@code null}.
 150      */
 151     protected PolicySourceModel(NamespaceVersion nsVersion, String policyId,
 152             String policyName, Collection<PrefixMapper> prefixMappers) {
 153         this.rootNode = ModelNode.createRootPolicyNode(this);
 154         this.nsVersion = nsVersion;
 155         this.policyId = policyId;
 156         this.policyName = policyName;
 157         if (prefixMappers != null) {
 158             for (PrefixMapper prefixMapper : prefixMappers) {
 159                 this.namespaceToPrefix.putAll(prefixMapper.getPrefixMap());
 160             }
 161         }
 162     }
 163 
 164     /**
 165      * Returns a root node of this policy source model. It is allways of POLICY type.
 166      *
 167      * @return root policy source model node - allways of POLICY type.
 168      */
 169     public ModelNode getRootNode() {
 170         return rootNode;
 171     }
 172 
 173     /**
 174      * Returns a policy name of this policy source model.
 175      *
 176      * @return policy name.
 177      */
 178     public String getPolicyName() {
 179         return policyName;
 180     }
 181 
 182     /**
 183      * Returns a policy ID of this policy source model.
 184      *
 185      * @return policy ID.
 186      */
 187     public String getPolicyId() {
 188         return policyId;
 189     }
 190 
 191     /**
 192      * Returns an original namespace version of this policy source model.
 193      *
 194      * @return namespace version.
 195      */
 196     public NamespaceVersion getNamespaceVersion() {
 197         return nsVersion;
 198     }
 199 
 200     /**
 201      * Provides information about how namespaces used in this {@link PolicySourceModel}
 202      * instance should be mapped to their default prefixes when marshalled.
 203      *
 204      * @return immutable map that holds information about namespaces used in the
 205      *         model and their mapping to prefixes that should be used when marshalling
 206      *         this model.
 207      * @throws PolicyException Thrown if one of the prefix mappers threw an exception.
 208      */
 209     Map<String, String> getNamespaceToPrefixMapping() throws PolicyException {
 210         final Map<String, String> nsToPrefixMap = new HashMap<String, String>();
 211 
 212         final Collection<String> namespaces = getUsedNamespaces();
 213         for (String namespace : namespaces) {
 214             final String prefix = getDefaultPrefix(namespace);
 215             if (prefix != null) {
 216                 nsToPrefixMap.put(namespace, prefix);
 217             }
 218         }
 219 
 220         return nsToPrefixMap;
 221     }
 222 
 223     /**
 224      * An {@code Object.equals(Object obj)} method override.
 225      * <p/>
 226      * When child nodes are tested for equality, the parent policy source model is not considered. Thus two different
 227      * policy source models instances may be equal based on their node content.
 228      */
 229     @Override
 230     public boolean equals(final Object obj) {
 231         if (this == obj) {
 232             return true;
 233         }
 234 
 235         if (!(obj instanceof PolicySourceModel)) {
 236             return false;
 237         }
 238 
 239         boolean result = true;
 240         final PolicySourceModel that = (PolicySourceModel) obj;
 241 
 242         result = result && ((this.policyId == null) ? that.policyId == null : this.policyId.equals(that.policyId));
 243         result = result && ((this.policyName == null) ? that.policyName == null : this.policyName.equals(that.policyName));
 244         result = result && this.rootNode.equals(that.rootNode);
 245 
 246         return result;
 247     }
 248 
 249     /**
 250      * An {@code Object.hashCode()} method override.
 251      */
 252     @Override
 253     public int hashCode() {
 254         int result = 17;
 255 
 256         result = 37 * result + ((this.policyId == null) ? 0 : this.policyId.hashCode());
 257         result = 37 * result + ((this.policyName == null) ? 0 : this.policyName.hashCode());
 258         result = 37 * result + this.rootNode.hashCode();
 259 
 260         return result;
 261     }
 262 
 263     /**
 264      * Returns a string representation of the object. In general, the <code>toString</code> method
 265      * returns a string that "textually represents" this object.
 266      *
 267      * @return  a string representation of the object.
 268      */
 269     @Override
 270     public String toString() {
 271         final String innerIndent = PolicyUtils.Text.createIndent(1);
 272         final StringBuffer buffer = new StringBuffer(60);
 273 
 274         buffer.append("Policy source model {").append(PolicyUtils.Text.NEW_LINE);
 275         buffer.append(innerIndent).append("policy id = '").append(policyId).append('\'').append(PolicyUtils.Text.NEW_LINE);
 276         buffer.append(innerIndent).append("policy name = '").append(policyName).append('\'').append(PolicyUtils.Text.NEW_LINE);
 277         rootNode.toString(1, buffer).append(PolicyUtils.Text.NEW_LINE).append('}');
 278 
 279         return buffer.toString();
 280     }
 281 
 282     @Override
 283     protected PolicySourceModel clone() throws CloneNotSupportedException {
 284         final PolicySourceModel clone = (PolicySourceModel) super.clone();
 285 
 286         clone.rootNode = this.rootNode.clone();
 287         try {
 288             clone.rootNode.setParentModel(clone);
 289         } catch (IllegalAccessException e) {
 290             throw LOGGER.logSevereException(new CloneNotSupportedException(LocalizationMessages.WSP_0013_UNABLE_TO_SET_PARENT_MODEL_ON_ROOT()), e);
 291         }
 292 
 293         return clone;
 294     }
 295 
 296     /**
 297      * Returns a boolean value indicating whether this policy source model contains references to another policy source models.
 298      * <p/>
 299      * Every source model that references other policies must be expanded before it can be translated into a Policy objects. See
 300      * {@link #expand(PolicySourceModelContext)} and {@link #isExpanded()} for more details.
 301      *
 302      * @return {@code true} or {@code false} depending on whether this policy source model contains references to another policy source models.
 303      */
 304     public boolean containsPolicyReferences() {
 305         return !references.isEmpty();
 306     }
 307 
 308     /**
 309      * Returns a boolean value indicating whether this policy source model contains is already expanded (i.e. contains no unexpanded
 310      * policy references) or not. This means that if model does not originally contain any policy references, it is considered as expanded,
 311      * thus this method returns {@code true} in such case. Also this method does not check whether the references policy source models are expanded
 312      * as well, so after expanding this model a value of {@code true} is returned even if referenced models are not expanded yet. Thus each model
 313      * can be considered to be fully expanded only if all policy source models stored in PolicySourceModelContext instance are expanded, provided the
 314      * PolicySourceModelContext instance contains full set of policy source models.
 315      * <p/>
 316      * Every source model that references other policies must be expanded before it can be translated into a Policy object. See
 317      * {@link #expand(PolicySourceModelContext)} and {@link #containsPolicyReferences()} for more details.
 318      *
 319      * @return {@code true} or {@code false} depending on whether this policy source model contains is expanded or not.
 320      */
 321     private boolean isExpanded() {
 322         return references.isEmpty() || expanded;
 323     }
 324 
 325     /**
 326      * Expands current policy model. This means, that if this model contains any (unexpanded) policy references, then the method expands those
 327      * references by placing the content of the referenced policy source models under the policy reference nodes. This operation merely creates
 328      * a link between this and referenced policy source models. Thus any change in the referenced models will be visible wihtin this model as well.
 329      * <p/>
 330      * Please, notice that the method does not check if the referenced models are already expanded nor does the method try to expand unexpanded
 331      * referenced models. This must be preformed manually within client's code. Consecutive calls of this method will have no effect.
 332      * <p/>
 333      * Every source model that references other policies must be expanded before it can be translated into a Policy object. See
 334      * {@link #isExpanded()} and {@link #containsPolicyReferences()} for more details.
 335      *
 336      * @param context a policy source model context holding the set of unmarshalled policy source models within the same context.
 337      * @throws PolicyException Thrown if a referenced policy could not be resolved
 338      */
 339     public synchronized void expand(final PolicySourceModelContext context) throws PolicyException {
 340         if (!isExpanded()) {
 341             for (ModelNode reference : references) {
 342                 final PolicyReferenceData refData = reference.getPolicyReferenceData();
 343                 final String digest = refData.getDigest();
 344                 PolicySourceModel referencedModel;
 345                 if (digest == null) {
 346                     referencedModel = context.retrieveModel(refData.getReferencedModelUri());
 347                 } else {
 348                     referencedModel = context.retrieveModel(refData.getReferencedModelUri(), refData.getDigestAlgorithmUri(), digest);
 349                 }
 350 
 351                 reference.setReferencedModel(referencedModel);
 352             }
 353             expanded = true;
 354         }
 355     }
 356 
 357     /**
 358      * Adds new policy reference to the policy source model. The method is used by
 359      * the ModelNode instances of type POLICY_REFERENCE that need to register themselves
 360      * as policy references in the model.
 361      *
 362      * @param node policy reference model node to be registered as a policy reference
 363      *        in this model.
 364      */
 365     void addNewPolicyReference(final ModelNode node) {
 366         if (node.getType() != ModelNode.Type.POLICY_REFERENCE) {
 367             throw new IllegalArgumentException(LocalizationMessages.WSP_0042_POLICY_REFERENCE_NODE_EXPECTED_INSTEAD_OF(node.getType()));
 368         }
 369 
 370         references.add(node);
 371     }
 372 
 373     /**
 374      * Iterates through policy vocabulary and extracts set of namespaces used in
 375      * the policy expression.
 376      *
 377      * @return collection of used namespaces within given policy instance
 378      * @throws PolicyException Thrown if internal processing failed.
 379      */
 380     private Collection<String> getUsedNamespaces() throws PolicyException {
 381         final Set<String> namespaces = new HashSet<String>();
 382         namespaces.add(getNamespaceVersion().toString());
 383 
 384         if (this.policyId != null) {
 385             namespaces.add(PolicyConstants.WSU_NAMESPACE_URI);
 386         }
 387 
 388         final Queue<ModelNode> nodesToBeProcessed = new LinkedList<ModelNode>();
 389         nodesToBeProcessed.add(rootNode);
 390 
 391         ModelNode processedNode;
 392         while ((processedNode = nodesToBeProcessed.poll()) != null) {
 393             for (ModelNode child : processedNode.getChildren()) {
 394                 if (child.hasChildren()) {
 395                     if (!nodesToBeProcessed.offer(child)) {
 396                         throw LOGGER.logSevereException(new PolicyException(LocalizationMessages.WSP_0081_UNABLE_TO_INSERT_CHILD(nodesToBeProcessed, child)));
 397                     }
 398                 }
 399 
 400                 if (child.isDomainSpecific()) {
 401                     final AssertionData nodeData = child.getNodeData();
 402                     namespaces.add(nodeData.getName().getNamespaceURI());
 403                     if (nodeData.isPrivateAttributeSet()) {
 404                         namespaces.add(PolicyConstants.SUN_POLICY_NAMESPACE_URI);
 405                     }
 406 
 407                     for (Entry<QName, String> attribute : nodeData.getAttributesSet()) {
 408                         namespaces.add(attribute.getKey().getNamespaceURI());
 409                     }
 410                 }
 411             }
 412         }
 413 
 414         return namespaces;
 415     }
 416 
 417     /**
 418      * Method retrieves default prefix for given namespace. Method returns null if
 419      * no default prefix is defined..
 420      *
 421      * @param namespace to get default prefix for.
 422      * @return default prefix for given namespace. May return {@code null} if the
 423      *         default prefix for given namespace is not defined.
 424      */
 425     private String getDefaultPrefix(final String namespace) {
 426         return namespaceToPrefix.get(namespace);
 427     }
 428 }