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