001/*
002 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003 *
004 * Copyright (c) 2005 Sun Microsystems Inc. All Rights Reserved
005 *
006 * The contents of this file are subject to the terms
007 * of the Common Development and Distribution License
008 * (the License). You may not use this file except in
009 * compliance with the License.
010 *
011 * You can obtain a copy of the License at
012 * https://opensso.dev.java.net/public/CDDLv1.0.html or
013 * opensso/legal/CDDLv1.0.txt
014 * See the License for the specific language governing
015 * permission and limitations under the License.
016 *
017 * When distributing Covered Code, include this CDDL
018 * Header Notice in each file and include the License file
019 * at opensso/legal/CDDLv1.0.txt.
020 * If applicable, add the following below the CDDL Header,
021 * with the fields enclosed by brackets [] replaced by
022 * your own identifying information:
023 * "Portions Copyrighted [year] [name of copyright owner]"
024 *
025 * $Id: ServiceSchema.java,v 1.12 2008/08/30 16:46:47 goodearth Exp $
026 *
027 * Portions Copyrighted 2011-2016 ForgeRock AS.
028 */
029package com.sun.identity.sm;
030
031import com.iplanet.sso.SSOException;
032import com.iplanet.ums.IUMSConstants;
033import com.sun.identity.shared.Constants;
034import com.sun.identity.common.CaseInsensitiveHashSet;
035import com.sun.identity.shared.debug.Debug;
036import com.sun.identity.shared.xml.XMLUtils;
037import java.io.InputStream;
038import java.util.Collections;
039import java.util.HashMap;
040import java.util.HashSet;
041import java.util.Iterator;
042import java.util.Map;
043import java.util.Set;
044import java.util.StringTokenizer;
045import org.w3c.dom.Document;
046import org.w3c.dom.Element;
047import org.w3c.dom.Node;
048import org.w3c.dom.NodeList;
049
050
051/**
052 * The class <code>ServiceSchema</code> provides interfaces to manage the
053 * schema information of a service. The schema for a service can be one of the
054 * following types: GLOBAL, ORGANIZATION, DYNAMIC, USER, and POLICY.
055 *
056 * @supported.all.api
057 */
058public class ServiceSchema {
059
060    // Pointer to service's schema manager & type
061    ServiceSchemaImpl ss;
062
063    String componentName;
064
065    SchemaType type;
066
067    ServiceSchemaManager ssm;
068
069    boolean orgAttrSchema;
070
071    /**
072     * Default constructor (private).
073     */
074    private ServiceSchema() {
075        // do nothing
076    }
077
078    protected ServiceSchema(ServiceSchemaImpl ssi, String compName,
079            SchemaType type, ServiceSchemaManager ssm) {
080        this(ssi, compName, type, ssm, false);
081    }
082
083    protected ServiceSchema(ServiceSchemaImpl ssi, String compName,
084            SchemaType type, ServiceSchemaManager ssm, boolean isOrgAttrSchema) 
085    {
086        this.ss = ssi;
087        this.componentName = compName;
088        this.type = type;
089        this.ssm = ssm;
090        this.orgAttrSchema = isOrgAttrSchema;
091        this.ss.isOrgAttrSchema = isOrgAttrSchema;
092        this.ss.serviceName = ssm.getName();
093    }
094
095    /**
096     * Returns the name of the service.
097     * 
098     * @return the name of the schema
099     */
100    public String getServiceName() {
101        return (ssm.getName());
102    }
103
104    /**
105     * Returns the version of the service.
106     * 
107     * @return version of the service schema
108     */
109    public String getVersion() {
110        return (ssm.getVersion());
111    }
112
113    /**
114     * Returns the name of the schema.
115     * 
116     * @return the name of the schema
117     */
118    public String getName() {
119        return (ss.getName());
120    }
121
122    /**
123     * Returns the schema type.
124     * 
125     * @return the schema type.
126     */
127    public SchemaType getServiceType() {
128        return (type);
129    }
130
131    /**
132     * Returns the I18N key that points to the description of the service.
133     * 
134     * @return the I18N key that points to the description of the service
135     */
136    public String getI18NKey() {
137        return (ss.getI18NKey());
138    }
139
140    /**
141     * Returns <code>true</code> if service schema supports multiple
142     * configurations; <code>false</code> otherwise
143     * 
144     * @return <code>true</code> if service schema supports multiple
145     *         configurations; <code>false</code> otherwise
146     */
147    public boolean supportsMultipleConfigurations() {
148        return (ss.supportsMultipleConfigurations());
149    }
150
151    /**
152     * Sets the value of the I18N key in the service schema.
153     * 
154     * @param key
155     *            Value to be set for the I18N key of the service schema.
156     * @throws SMSException
157     *             if there is a problem setting the value in the data store.
158     * @throws SSOException
159     *             If the user has an invalid SSO token.
160     */
161    public void setI18Nkey(String key) throws SMSException, SSOException {
162        SMSEntry.validateToken(ssm.getSSOToken());
163        Node sNode = ss.getSchemaNode();
164        ((Element) sNode).setAttribute(SMSUtils.I18N_KEY, key);
165        ssm
166                .replaceSchema((ServiceSchemaManagerImpl.getInstance(ssm
167                        .getSSOToken(), ssm.getName(), ssm.getVersion()))
168                        .getDocument());
169        ss.i18nKey = key;
170    }
171
172    /**
173     * Set the value of inheritance attribute in service schema.
174     * 
175     * @param value
176     *            New value of inheritance attribute.
177     * @throws SMSException
178     *             if there is a problem setting the value in the data store.
179     * @throws SSOException
180     *             if the user has an invalid single sign on token.
181     */
182    public void setInheritance(String value) throws SMSException, SSOException {
183        if (!value.equals("single") && !value.equals("multiple")) {
184            String[] arg = { value };
185            throw new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
186                    "sms-invalid-inheritance-value", arg);
187        }
188
189        SMSEntry.validateToken(ssm.getSSOToken());
190        Node sNode = ss.getSchemaNode();
191        ((Element) sNode).setAttribute(SMSUtils.INHERITANCE, value);
192        ssm
193                .replaceSchema((ServiceSchemaManagerImpl.getInstance(ssm
194                        .getSSOToken(), ssm.getName(), ssm.getVersion()))
195                        .getDocument());
196        ss.inheritance = value;
197    }
198
199    /**
200     * Returns the view bean URL for this service
201     * 
202     * @return file name that contains I18N messages
203     */
204    public String getPropertiesViewBeanURL() {
205        return (ssm.getPropertiesViewBeanURL());
206    }
207
208    /**
209     * Returns the name of the status attribute, as defined in the Service
210     * schema
211     * 
212     * @return String name of status attribute
213     */
214    public String getStatusAttribute() {
215        return (ss.getStatusAttribute());
216    }
217
218    /**
219     * Returns <code>true</code> if the service configuration created can be
220     * exported to other organizations.
221     * 
222     * @return <code>true</code> if service configurations for this schema can
223     *         be exported to other organizations; <code>false</code>
224     *         otherwise.
225     */
226    public boolean isExportable() {
227        return (false);
228    }
229
230    /**
231     * Sets the exportable nature of the service configurations created for this
232     * schema. Setting it to <code>true</code> allows the configurations to be
233     * exported to other organizations and a value of <code>false</code>
234     * disables exporting of configuration data.
235     * 
236     * @param exportable
237     *            <code>true</code> if service configurations for this schema
238     *            can be exported to other organizations; <code>false</code>
239     *            otherwise.
240     */
241    public void setExportable(boolean exportable) {
242    }
243
244    /**
245     * Returns the I18N properties file name for the service schema.
246     *
247     * @return the I18N properties file name for the service schema
248     */
249    public String getI18NFileName() {
250        String i18nFileName = ss.getI18NFileName();
251        return i18nFileName == null ? ssm.getI18NFileName() : i18nFileName;
252    }
253
254    /**
255     * Sets the I18N properties file name for the service schema
256     *
257     * @param url
258     *            properties file name
259     * @throws SMSException
260     *             if an error occurred while trying to perform the operation
261     * @throws SSOException
262     *             if the single sign on token is invalid or expired
263     */
264    public void setI18NFileName(String url) throws SMSException, SSOException {
265        ssm.setI18NFileName(url);
266    }
267
268    /**
269     * Returns the name for the service schema when used in a CREST representation.
270     */
271    public String getResourceName() {
272        String resourceName = ss.getResourceName();
273        return resourceName == null ? getName() : resourceName;
274    }
275
276    /**
277     * Should this service schema be hidden in the Configuration UI.
278     * @return True if it should be hidden.
279     */
280    public boolean isHiddenInConfigUI() {
281        return ss.isHiddenInConfigUI();
282    }
283
284    /**
285     * During the creation of a new organisation/realm the services assigned to the parent realm are copied to the
286     * child realm. This will include the sub configs for that service, which in some cases are realm specific and will
287     * fail validation if copied. The schemas of these sub configs should set {@code realmCloneable} to {@code no} to
288     * avoid being copied.
289     *
290     * @return {@code true} if the config is cloneable between realms.
291     */
292    public boolean isRealmCloneable() {
293        return ss.isRealmCloneable();
294    }
295
296    /**
297     * Sets the CREST resource name for the service schema.
298     *
299     * @param name
300     *            resource name.
301     * @throws SMSException
302     *             if an error occurred while trying to perform the operation
303     * @throws SSOException
304     *             if the single sign on token is invalid or expired
305     */
306    public void setResourceName(String name) throws SMSException, SSOException {
307        SMSEntry.validateToken(ssm.getSSOToken());
308        Node sNode = ss.getSchemaNode();
309        ((Element) sNode).setAttribute(SMSUtils.RESOURCE_NAME, name);
310        ssm.replaceSchema((ServiceSchemaManagerImpl.getInstance(ssm.getSSOToken(), ssm.getName(), ssm.getVersion()))
311                .getDocument());
312        ss.resourceName = name;
313    }
314
315    /**
316     * Returns the names of the schema attributes defined for the service. It
317     * does not return the schema attributes defined for the sub-schema.
318     * 
319     * @return the names of schema attributes defined for the service
320     */
321    public Set getAttributeSchemaNames() {
322        return (ss.getAttributeSchemaNames());
323    }
324
325    /**
326     * Returns the names of the schema attributes defined for the service which
327     * are searchable. It does not return the schema attributes defined for the
328     * sub-schema.
329     * 
330     * @return the names of schema attributes defined for the service which are
331     *         searchable attributes.
332     */
333    protected Set getSearchableAttributeNames() {
334        return (ss.getSearchableAttributeNames());
335    }
336
337    /**
338     * Returns the schema for an attribute given the name of the attribute,
339     * defined for this service. It returns only the attribute schema defined at
340     * the top level for the service and not from the sub-schema.
341     * 
342     * @param attributeName
343     *            the name of the schema attribute
344     * @return the schema for the attribute
345     */
346    public AttributeSchema getAttributeSchema(String attributeName) {
347        AttributeSchemaImpl as = ss.getAttributeSchema(attributeName);
348        return ((as == null) ? null : new AttributeSchema(as, ssm, this));
349    }
350
351    /**
352     * Returns the attribute schemas defined for the service. It does not return
353     * the schema attributes defined for the sub-schema.
354     * 
355     * @return attribute schemas defined for the service
356     */
357    public Set getAttributeSchemas() {
358        Set answer = new HashSet();
359        for (Iterator items = getAttributeSchemaNames().iterator(); items
360                .hasNext();) {
361            String attrName = (String) items.next();
362            answer.add(getAttributeSchema(attrName));
363        }
364        return (answer);
365    }
366
367    /**
368     * Returns the attribute schemas defined for the service that is not a
369     * status attribute and is not a service attribute. It does not return the
370     * schema attributes defined for the sub-schema.
371     * 
372     * @return attribute schemas defined for the service
373     */
374    public Set getServiceAttributeNames() {
375        return (ss.getServiceAttributeNames());
376    }
377
378    /**
379     * Validates the <code>attrMap</code> against the attributes defined in
380     * this schema of the service. It will throw an exception if the map
381     * contains any attribute not listed in the schema. It will also pick up
382     * default values for any attributes not in the map but which are listed in
383     * the schema, if the boolean <Code>inherit</Code> is set to true.
384     * 
385     * @param attrMap
386     *            map of attributes
387     * @param inherit
388     *            if true, then inherit the default values
389     * @return Map of validated attributes with default values
390     * @throws SMSException
391     *             if invalid attribute names are present in the
392     *             <code>attrMap</code>.
393     */
394    public Map validateAndInheritDefaults(Map attrMap, boolean inherit)
395            throws SMSException {
396        return (validateAndInheritDefaults(attrMap, null, inherit));
397    }
398
399    /**
400     * Validates the <code>attrMap</code> against the attributes defined in
401     * this schema of the service for the given organization. It will throw an
402     * exception if the map contains any attribute not listed in the schema. It
403     * will also pick up default values for any attributes not in the map but
404     * which are listed in the schema, if the boolean <Code>inherit</Code> is
405     * set to true.
406     * 
407     * @param attrMap
408     *            map of attributes
409     * @param inherit
410     *            if true, then inherit the default values
411     * @return Map of validated attributes with default values
412     * @throws SMSException
413     *             if invalid attribute names are present in the
414     *             <code>attrMap</code>.
415     */
416    public Map validateAndInheritDefaults(Map attrMap, String orgName,
417            boolean inherit) throws SMSException {
418        SMSEntry.validateToken(ssm.getSSOToken());
419
420        if (attrMap == null || attrMap.isEmpty()) {
421            attrMap = new HashMap();
422        }
423        Iterator keys = attrMap.keySet().iterator();
424        Set defAttrSet = ss.getAttributeSchemaNames();
425        while (keys.hasNext()) {
426            String attr = (String) keys.next();
427            if (!defAttrSet.contains(attr)) {
428                // This attribute is not listed in the service.
429                debug.error("ServiceSchema.validateAndInheritDefaults: " + attr
430                        + " is not listed in the service " + getServiceName());
431                throw new InvalidAttributeNameException(
432                        IUMSConstants.UMS_BUNDLE_NAME,
433                        "services_validator_invalid_attr_name", null);
434            }
435        }
436
437        // If orgName is not null, populate the envMap
438        Map envMap = Collections.EMPTY_MAP;
439        if (orgName != null) {
440            envMap = new HashMap();
441            envMap.put(Constants.ORGANIZATION_NAME, orgName);
442            envMap.put(Constants.SSO_TOKEN, ssm.getSSOToken());
443        }
444        Iterator ass = ss.getAttributeSchemaNames().iterator();
445        while (ass.hasNext()) {
446            String attr = (String) ass.next();
447            AttributeSchemaImpl as = ss.getAttributeSchema(attr);
448            AttributeValidator av = ss.getAttributeValidator(attr);
449            String anyValue = as.getAny();
450            if (inherit && (anyValue != null) &&
451                (anyValue.indexOf("required") > -1)) {
452                // Inherit default values of this attribute, if
453                // required
454                attrMap = av.inheritDefaults(attrMap);
455                Set attrVals = (Set) attrMap.get(attr);
456                if (attrVals == null || attrVals.isEmpty()) {
457                    // A required attribute is being deleted
458                    // throw an exception.
459                    debug.error("ServiceSchema.validateAndInheritDefaults: "
460                            + attr + " is a required attribute and cannot"
461                            + " be deleted");
462                    Object args[] = { attr };
463                    throw new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
464                            "sms-required-attribute-delete", args);
465                }
466            } else if (inherit) {
467                attrMap = av.inheritDefaults(attrMap);
468            }
469            // validate the attribute against Schema
470            Set valSet = (Set) attrMap.get(attr);
471            if (valSet != null) {
472                String i18nFileName = (ssm != null) ? ssm.getI18NFileName()
473                        : null;
474                av.validate(valSet, i18nFileName, false, envMap);
475            }
476        }
477        if (debug.messageEnabled()) {
478            debug.error("ServiceSchema.validate&InheritDef: "
479                    + " returning attrMap: " + attrMap.toString());
480        }
481        return attrMap;
482    }
483
484    /**
485     * Adds the attribute schema to this service. The schema is defined in XML
486     * input stream that follows the SMS DTD.
487     * 
488     * @param xmlAttrSchema
489     *            the XML format of the attribute schema
490     * @throws SMSException
491     *             if an error occurred while performing the operation
492     * @throws SSOException
493     *             if the single sign on token is invalid or expired
494     */
495    public void addAttributeSchema(InputStream xmlAttrSchema)
496            throws SSOException, SMSException {
497        SMSEntry.validateToken(ssm.getSSOToken());
498        // Check if attribute exists
499        Document doc = SMSSchema.getXMLDocument(xmlAttrSchema, false);
500        NodeList nl = doc.getElementsByTagName(SMSUtils.SCHEMA_ATTRIBUTE);
501        CaseInsensitiveHashSet asNames =
502            new CaseInsensitiveHashSet(ss.getAttributeSchemaNames());
503        for (int i = 0; i < nl.getLength(); i++) {
504            Node node = nl.item(i);
505            AttributeSchemaImpl as = new AttributeSchemaImpl(node);
506            if (asNames.contains(as.getName())) {
507                Object[] args = { as.getName() };
508                throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
509                        "sms-attributeschema-already-exists", args));
510            }
511        }
512        appendAttributeSchema(nl);
513    }
514
515    /**
516     * Removes the attribute schema from this service.
517     * 
518     * @param attrName
519     *            the name of the attribute schema
520     * @throws SMSException
521     *             if an error occurred while performing the operation
522     * @throws SSOException
523     *             if the single sign on token is invalid or expired
524     */
525    public void removeAttributeSchema(String attrName) throws SSOException,
526            SMSException {
527        removeChildNode(SMSUtils.SCHEMA_ATTRIBUTE, attrName);
528    }
529
530    /**
531     * Returns a map of all the attribute and their default values in this
532     * schema.
533     * 
534     * @return Map of Attribute Names and Sets of their default values as
535     *         defined in the Schema
536     */
537    public Map getAttributeDefaults() {
538        return (ss.getAttributeDefaults());
539    }
540
541    /**
542     * Returns an unmodifiable map of all the attribute and their default values
543     * in this schema.
544     * 
545     * @return Map of Attribute Names and Sets of their default values as
546     *         defined in the Schema
547     */
548    public Map getReadOnlyAttributeDefaults() {
549        return (ss.getReadOnlyAttributeDefaults());
550    }
551
552    /**
553     * Method to change the default values of attributes in the schema.
554     * 
555     * @param attrs
556     *            A map of the names of <code>AttributeSchema</code> to
557     *            modify, and a Set of Values which should replace the default
558     *            values of the current schema.
559     * @throws SMSException
560     *             if an error occurred while performing the operation
561     * @throws SSOException
562     *             if the single sign on token is invalid or expired
563     */
564    public void setAttributeDefaults(Map attrs) throws SSOException,
565            SMSException {
566        SMSEntry.validateToken(ssm.getSSOToken());
567        // Get a copy of the XML document
568        Document document = ssm.getDocumentCopy();
569        Iterator items = attrs.keySet().iterator();
570        while (items.hasNext()) {
571            String attrName = (String) items.next();
572            AttributeSchema as = getAttributeSchema(attrName);
573            if (as == null) {
574                Object[] args = { attrName };
575                throw (new SchemaException(IUMSConstants.UMS_BUNDLE_NAME,
576                    "sms-invalid-attr-name", args)); 
577            }
578            as.setDefaultValues((Set) attrs.get(attrName), document);
579        }
580        // Save the document
581        ssm.replaceSchema(document);
582    }
583
584    /**
585     * Method to change default value for a specific attribute.
586     * 
587     * @param attrName
588     *            Name of the attribute for which defaults values need to be
589     *            replaced.
590     * @param values
591     *            Set of new values to replace the old ones.
592     * @throws SchemaException
593     *             if an error occurred while parsing the XML
594     * @throws SMSException
595     *             if an error occurred while performing the operation
596     * @throws SSOException
597     *             if the single sign on token is invalid or expired
598     */
599    public void setAttributeDefaults(String attrName, Set values)
600            throws SchemaException, SMSException, SSOException {
601        SMSEntry.validateToken(ssm.getSSOToken());
602        AttributeSchema as = getAttributeSchema(attrName);
603        if (as == null) {
604            Object[] args = { attrName };
605            throw (new SchemaException(IUMSConstants.UMS_BUNDLE_NAME,
606                    "sms-invalid-attr-name", args));
607        }
608        as.setDefaultValues(values);
609    }
610
611    /**
612     * Removes the default values of attributes in the schema.
613     * 
614     * @param attrs
615     *            A set of the names of <code>AttributeSchema</code>.
616     * @throws SMSException
617     *             if an error occurred while performing the operation
618     * @throws SSOException
619     *             if the single sign on token is invalid or expired
620     */
621    public void removeAttributeDefaults(Set attrs) throws SMSException,
622            SSOException {
623        SMSEntry.validateToken(ssm.getSSOToken());
624        Iterator it = attrs.iterator();
625        while (it.hasNext()) {
626            String asName = (String) it.next();
627            AttributeSchema as = getAttributeSchema(asName);
628            if (as == null) {
629                throw (new InvalidAttributeNameException(
630                        IUMSConstants.UMS_BUNDLE_NAME,
631                        IUMSConstants.services_validator_invalid_attr_name,
632                        null));
633            }
634            as.removeDefaultValues();
635        }
636    }
637
638    /**
639     * Returns the names of sub-schemas for the service.
640     * 
641     * @return the names of service's sub-schemas
642     */
643    public Set getSubSchemaNames() {
644        return (ss.getSubSchemaNames());
645    }
646
647    /**
648     * Returns <code>ServiceSchema</code> object given the name of the
649     * service's sub-schema.
650     * 
651     * @param subSchemaName
652     *            the name of the service's sub-schema
653     * @return <code>ServiceSchema</code> object
654     * @throws SMSException
655     *             if an error occurred while performing the operation
656     * 
657     */
658    public ServiceSchema getSubSchema(String subSchemaName) throws SMSException 
659    {
660        SMSEntry.validateToken(ssm.getSSOToken());
661        ServiceSchema answer = null;
662        ServiceSchemaImpl ssi = ss.getSubSchema(subSchemaName);
663        if (ssi != null) {
664            answer = new ServiceSchema(ssi,
665                    componentName + "/" + subSchemaName, type, ssm);
666        }
667        return (answer);
668    }
669
670    /**
671     * Adds the service's sub-schema given the XML input stream that follows the
672     * SMS DTD.
673     * 
674     * @param xmlSubSchema
675     *            the XML format of the sub-schema
676     * @throws SMSException
677     *             if an error occurred while performing the operation
678     * @throws SSOException
679     *             if the single sign on token is invalid or expired
680     */
681    public void addSubSchema(InputStream xmlSubSchema) throws SSOException,
682            SMSException {
683        SMSEntry.validateToken(ssm.getSSOToken());
684        // Check if attribute exists
685        Document doc = SMSSchema.getXMLDocument(xmlSubSchema, false);
686        NodeList nl = doc.getElementsByTagName(SMSUtils.SUB_SCHEMA);
687        Set asNames = ss.getSubSchemaNames();
688        for (int i = 0; i < nl.getLength(); i++) {
689            Node node = nl.item(i);
690            String nodeName = XMLUtils.getNodeAttributeValue(node,
691                    SMSUtils.NAME);
692            if (asNames.contains(nodeName)) {
693                Object[] args = { nodeName };
694                throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
695                        "sms-subschema-already-exists", args));
696            }
697        }
698
699        appendSubSchema(doc);
700    }
701
702    /**
703     * Removes the service's sub-schema from the service.
704     * 
705     * @param subSchemaName
706     *            the name of the service's sub-schema
707     * @throws SMSException
708     *             if an error occurred while performing the operation
709     * @throws SSOException
710     *             if the single sign on token is invalid or expired
711     */
712    public void removeSubSchema(String subSchemaName) throws SSOException,
713            SMSException {
714        SMSEntry.validateToken(ssm.getSSOToken());
715        removeChildNode(SMSUtils.SUB_SCHEMA, subSchemaName);
716    }
717
718    /**
719     * Determines whether each attribute in the attribute set is valid. Iterates
720     * though the set checking each element to see if there is a validator that
721     * needs to execute.
722     * 
723     * @param attributeSet
724     *            the <code>Map</code> where key is the attribute name and
725     *            value is the <code>Set</code> of attribute values
726     * @return true if all attributes are valid
727     * @throws SMSException
728     *             if an error occurred while performing the operation
729     */
730    public boolean validateAttributes(Map attributeSet) throws SMSException {
731        SMSEntry.validateToken(ssm.getSSOToken());
732        return (ss.validateAttributes(ssm.getSSOToken(), attributeSet, false,
733            null));
734    }
735
736    /**
737     * Determines whether each attribute in the attribute set is valid for the
738     * given organization. Iterates though the set checking each element to see
739     * if there is a validator that needs to execute.
740     * 
741     * @param attributeSet
742     *            the <code>Map</code> where key is the attribute name and
743     *            value is the <code>Set</code> of attribute values
744     * @param orgName
745     *            organization name
746     * @return true if all attributes are valid
747     * @throws SMSException
748     *             if an error occurred while performing the operation
749     */
750    public boolean validateAttributes(Map attributeSet, String orgName)
751            throws SMSException {
752        SMSEntry.validateToken(ssm.getSSOToken());
753        return (ss.validateAttributes(ssm.getSSOToken(), attributeSet, false, 
754            orgName));
755    }
756
757    /**
758     * Returns string representation of the schema.
759     * 
760     * @return string representation of the schema.
761     */
762    public String toString() {
763        return (ss.toString());
764    }
765
766    /**
767     * Returns the Node of this schema element. Used by Policy component's
768     * <code>ServiceType</code> to get <code>ActionSchema</code>.
769     * 
770     * @return the Node of this schema element. Used by Policy component's
771     *         <code>ServiceType</code> to get <code>ActionSchema</code>.
772     */
773    public Node getSchemaNode() {
774        Node node = null;
775        try {
776            node = getSchemaNode(ssm.getDocumentCopy());
777        } catch (SMSException ssme) {
778            debug.error("ServiceSchema::getSchemaNode: invalid schema");
779        }
780        return (node);
781    }
782
783    /**
784     * Removes the attribute schema from this service.
785     * 
786     * @param attrName
787     *            the name of the attribute schema
788     * @throws SMSException
789     *             if an error occurred while performing the operation
790     * @throws SSOException
791     *             if the single sign on token is invalid or expired
792     */
793    public void replaceAttributeSchema(String attrName, Node attributeSchemaNode) throws SSOException,
794            SMSException {
795        replaceChildNode(SMSUtils.SCHEMA_ATTRIBUTE, attrName, attributeSchemaNode);
796    }
797
798    private void appendAttributeSchema(NodeList nodes) throws SSOException, SMSException {
799        if (nodes == null || nodes.getLength() == 0) {
800            throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
801                    IUMSConstants.SMS_SMSSchema_no_schema_element, null));
802        }
803        Document schemaDoc = ssm.getDocumentCopy();
804        try {
805            Node schemaNode = getSchemaNode(schemaDoc);
806            NodeList childrens = schemaNode.getChildNodes();
807            Node nextSibling = null;
808            for (int i = 0; i < childrens.getLength(); i++) {
809                Node child = childrens.item(i);
810                if (Node.ELEMENT_NODE == child.getNodeType()
811                        && !SMSUtils.SCHEMA_ATTRIBUTE.equals(child.getNodeName())) {
812                    nextSibling = child;
813                    break;
814                }
815            }
816            for (int i = 0; i < nodes.getLength(); i++) {
817                Node node = nodes.item(i);
818                Node iNode = schemaDoc.importNode(node, true);
819                schemaNode.insertBefore(iNode, nextSibling);
820            }
821        } catch (Exception e) {
822            throw (new SMSException(e.getMessage(), e, "sms-cannot_append_NODE"));
823        }
824        ssm.replaceSchema(schemaDoc);
825    }
826
827    private void appendSubSchema(Document doc) throws SSOException, SMSException {
828        if (doc == null) {
829            throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
830                    IUMSConstants.SMS_SMSSchema_no_schema_element, null));
831        }
832        
833        Document schemaDoc = ssm.getDocumentCopy();
834        
835        try {
836            Node schemaNode = getSchemaNode(schemaDoc);
837            Node node = XMLUtils.getRootNode(doc, SMSUtils.SUB_SCHEMA);
838            NodeList childrens = schemaNode.getChildNodes();
839            Node nextSibling = null;
840            for (int i = 0; i < childrens.getLength(); i++) {
841                Node child = childrens.item(i);
842                if (Node.ELEMENT_NODE == child.getNodeType()
843                        && !SMSUtils.SCHEMA_ATTRIBUTE.equals(child.getNodeName())) {
844                    //In this case we've found an entry that is not an AttributeSchema, so this is definitely an
845                    //element before which we can place a SubSchema. (i.e. the found element was a SubSchema or
846                    //an OrganizationAttributeSchema). Note that this will change the order of the SubSchema
847                    //elements in the service description, however the order should not matter for SMS.
848                    nextSibling = child;
849                    break;
850                }
851            }
852            Node iNode = schemaDoc.importNode(node, true);
853            schemaNode.insertBefore(iNode, nextSibling);
854        } catch (Exception ex) {
855            throw (new SMSException(ex.getMessage(), ex, "sms-cannot_append_NODE"));
856        }
857       
858        ssm.replaceSchema(schemaDoc);
859    }
860
861    // -----------------------------------------------------------
862    // Protected methods
863    // -----------------------------------------------------------
864    void removeChildNode(String nodeType, String nodeName) throws SSOException,
865            SMSException {
866        if (debug.messageEnabled()) {
867            debug.message("ServiceSchema::removeChildNode called for: "
868                    + getServiceName() + "(" + ssm.getVersion() + ") "
869                    + componentName);
870        }
871        Document schemaDoc = ssm.getDocumentCopy();
872        Node schemaNode = getSchemaNode(schemaDoc);
873        if (schemaNode != null) {
874            Node node = XMLUtils.getNamedChildNode(schemaNode, nodeType,
875                    SMSUtils.NAME, nodeName);
876            if (node != null) {
877                schemaNode.removeChild(node);
878                ssm.replaceSchema(schemaDoc);
879            }
880        }
881    }
882
883    void replaceChildNode(String nodeType, String nodeName, Node attributeSchemaNode) throws SSOException,
884            SMSException {
885        if (debug.messageEnabled()) {
886            debug.message("ServiceSchema::replaceChildNode called for: "
887                    + getServiceName() + "(" + ssm.getVersion() + ") "
888                    + componentName);
889        }
890        Document schemaDoc = ssm.getDocumentCopy();
891        Node schemaNode = getSchemaNode(schemaDoc);
892        if (schemaNode != null) {
893            Node oldNode = XMLUtils.getNamedChildNode(schemaNode, nodeType,
894                    SMSUtils.NAME, nodeName);
895            if (oldNode != null) {
896                Node newNode = schemaDoc.importNode(attributeSchemaNode, true);
897                schemaNode.replaceChild(newNode, oldNode);
898                ssm.replaceSchema(schemaDoc);
899            }
900        } else {
901            if (debug.messageEnabled()) {
902                debug.message("ServiceSchema::replaceChildNode failed to retrieve service schema for : "
903                        + getServiceName() + "(" + ssm.getVersion() + ") "
904                        + componentName);
905            }
906        }
907    }
908
909    // -----------------------------------------------------------
910    // Method to obtain schema node
911    // -----------------------------------------------------------
912    Node getSchemaNode(Document document) throws SMSException {
913        NodeList nodes = document.getElementsByTagName(SMSUtils.SCHEMA);
914        if ((nodes == null) || (nodes.getLength() == 0)) {
915            throwInvalidSchemaException();
916        }
917        Node rNode = nodes.item(0);
918
919        // Get the schema type node
920        String schemaType = SMSUtils.GLOBAL_SCHEMA;
921        if (type.equals(SchemaType.ORGANIZATION)) {
922            schemaType = SMSUtils.ORG_SCHEMA;
923        } else if (type.equals(SchemaType.DYNAMIC)) {
924            schemaType = SMSUtils.DYNAMIC_SCHEMA;
925        } else if (type.equals(SchemaType.USER)) {
926            schemaType = SMSUtils.USER_SCHEMA;
927        } else if (type.equals(SchemaType.POLICY)) {
928            schemaType = SMSUtils.POLICY_SCHEMA;
929        } else if (type.equals(SchemaType.GROUP)) {
930            schemaType = SMSUtils.GROUP_SCHEMA;
931        } else if (type.equals(SchemaType.DOMAIN)) {
932            schemaType = SMSUtils.DOMAIN_SCHEMA;
933        }
934
935        Node stNode = XMLUtils.getChildNode(rNode, schemaType);
936        if (stNode == null) {
937            throwInvalidSchemaException();
938        }
939
940        // Walk the component name
941        if ((componentName == null) || (componentName.length() == 0)) {
942            return (stNode);
943        } else if (orgAttrSchema) {
944            // OrganizationAttributeSchema
945            return (XMLUtils
946                    .getChildNode(stNode, SMSUtils.ORG_ATTRIBUTE_SCHEMA));
947        }
948
949        StringTokenizer st = new StringTokenizer(componentName, "/");
950        while (st.hasMoreTokens()) {
951            String tokenName = st.nextToken();
952            if ((tokenName == null) || (tokenName.length() == 0)) {
953                continue;
954            }
955            if ((stNode = XMLUtils.getNamedChildNode(stNode,
956                    SMSUtils.SUB_SCHEMA, SMSUtils.NAME, tokenName)) == null) {
957                throwInvalidSchemaException();
958            }
959        }
960        return (stNode);
961    }
962
963    // -----------------------------------------------------------
964    // Method to obtain organizationattributeschema node
965    // -----------------------------------------------------------
966    Node getOrgAttrSchemaNode(Document doc) throws SMSException {
967        NodeList nodes = doc.getElementsByTagName(
968            SMSUtils.ORG_ATTRIBUTE_SCHEMA);
969        if (nodes == null || (nodes.getLength() == 0)) {
970            // Throw an exception
971            throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
972                "sms-invalid-orgattr-schema-document", null));
973        }
974        Node rNode = nodes.item(0);
975        if (rNode == null) {
976            throwInvalidSchemaException();
977        }
978        return (rNode);
979    }
980
981    void throwInvalidSchemaException() throws SMSException {
982        SMSEntry.debug.error("ServiceSchema::getSchemaNode: "
983                + "Invalid service schema XML: " + getServiceName() + "("
984                + ssm.getVersion() + ")");
985        throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
986                IUMSConstants.SMS_SMSSchema_no_service_element, null));
987    }
988
989    Debug debug = Debug.getInstance("amSMS");
990}