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