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: ServiceSchemaManager.java,v 1.12 2009/07/25 05:11:55 qcheng Exp $
026 *
027 * Portions Copyrighted 2012-2016 ForgeRock AS.
028 */
029package com.sun.identity.sm;
030
031import com.iplanet.services.util.AMEncryption;
032import com.iplanet.sso.SSOException;
033import com.iplanet.sso.SSOToken;
034import com.iplanet.ums.IUMSConstants;
035import com.sun.identity.shared.debug.Debug;
036import com.sun.identity.shared.xml.XMLUtils;
037import java.io.IOException;
038import java.io.InputStream;
039import java.util.Collections;
040import java.util.HashSet;
041import java.util.Iterator;
042import java.util.Set;
043import org.forgerock.util.Function;
044import org.w3c.dom.Document;
045import org.w3c.dom.Node;
046
047/**
048 * The class <code>ServiceSchemaManager</code> provides interfaces to manage
049 * the service's schema. It provides access to <code>ServiceSchema</code>,
050 * which represents a single "schema" in the service.
051 *
052 * @supported.api
053 */
054public class ServiceSchemaManager {
055    
056    private SSOToken token;
057    
058    private String serviceName;
059    
060    private String version;
061    
062    private ServiceSchemaManagerImpl ssm;
063    
064    private static Debug debug = Debug.getInstance("amSMS");
065    
066    /**
067     * Constructor for service's schema manager to manage the attributes and
068     * sub configurations. Assumes service version number to be <class>1.0
069     * </class>.
070     *
071     * @throws SMSException
072     *             if an error occurred while trying to perform the operation
073     * @throws SSOException
074     *             if the single sign on token is invalid or expired
075     */
076    public ServiceSchemaManager(String serviceName, SSOToken token)
077    throws SMSException, SSOException {
078        this(token, serviceName, ServiceManager.isCoexistenceMode() ?
079            ServiceManager.serviceDefaultVersion(token, serviceName) :
080            ServiceManager.getVersion(serviceName));
081    }
082    
083    /**
084     * Creates an instance of
085     * <code>ServiceSchemaManager</code> for the given service and version
086     * pair. It requires an user identity, that will used to perform operations
087     * with. It is assumed that the application calling this constructor should
088     * authenticate the user.
089     *
090     * @param token
091     *            single sign on token of the user identity on whose behalf the
092     *            operations are performed.
093     * @param serviceName
094     *            the name of the service.
095     * @param version
096     *            the version of the service.
097     * @throws SMSException
098     *             if an error occurred while trying to perform the operation
099     * @throws SSOException
100     *             if the single sign on token is invalid or expired
101     *
102     * @supported.api
103     */
104    public ServiceSchemaManager(SSOToken token, String serviceName,
105        String version) throws SMSException, SSOException {
106        if (token == null || serviceName == null || version == null) {
107            throw new IllegalArgumentException(SMSEntry.bundle
108                .getString(IUMSConstants.SMS_INVALID_PARAMETERS));
109        }
110        SMSEntry.validateToken(token);
111        this.token = token;
112        this.serviceName = serviceName;
113        this.version = version;
114        ssm = ServiceSchemaManagerImpl.getInstance(token, serviceName, version);
115    }
116    
117    /**
118     * Returns the name of the service.
119     *
120     * @return the name of the service
121     *
122     * @supported.api
123     */
124    public String getName() {
125        return (serviceName);
126    }
127    
128    /**
129     * Returns the version of the service.
130     *
131     * @return the version of the service
132     *
133     * @supported.api
134     */
135    public String getVersion() {
136        return (version);
137    }
138    
139    /**
140     * Returns the I18N properties file name for the
141     * service.
142     *
143     * @return the I18N properties file name for the service
144     *
145     * @supported.api
146     */
147    public String getI18NFileName() {
148        validate();
149        return (ssm.getI18NFileName());
150    }
151    
152    /**
153     * Sets the I18N properties file name for the service
154     *
155     * @param url
156     *            properties file name
157     * @throws SMSException
158     *             if an error occurred while trying to perform the operation
159     * @throws SSOException
160     *             if the single sign on token is invalid or expired
161     *
162     * @supported.api
163     */
164    public void setI18NFileName(String url) throws SMSException, SSOException {
165        SMSEntry.validateToken(token);
166        validateServiceSchemaManagerImpl();
167        String tmpS = ssm.getI18NFileName();
168        ssm.setI18NFileName(url);
169        try {
170            replaceSchema(ssm.getDocument());
171        } catch (SMSException se) {
172            ssm.setI18NFileName(tmpS);
173            throw se;
174        }
175    }
176    
177    /**
178     * Returns the URL of the JAR file that contains the
179     * I18N properties file. The method could return null, in which case the
180     * properties file should be in <code>CLASSPATH</code>.
181     *
182     * @return the URL of the JAR file containing the <code>I18N</code>
183     *         properties file.
184     *
185     * @supported.api
186     */
187    public String getI18NJarURL() {
188        validate();
189        return (ssm.getI18NJarURL());
190    }
191    
192    /**
193     * Sets the URL of the JAR file that contains the I18N
194     * properties
195     *
196     * @param url
197     *            URL
198     * @throws SMSException
199     *             if an error occurred while trying to perform the operation
200     * @throws SSOException
201     *             if the single sign on token is invalid or expired
202     *
203     * @supported.api
204     */
205    
206    public void setI18NJarURL(String url) throws SMSException, SSOException {
207        SMSEntry.validateToken(token);
208        validateServiceSchemaManagerImpl();
209        String tmpS = ssm.getI18NJarURL();
210        ssm.setI18NJarURL(url);
211        try {
212            replaceSchema(ssm.getDocument());
213        } catch (SMSException se) {
214            ssm.setI18NJarURL(tmpS);
215            throw se;
216        }
217    }
218
219    /**
220     * Returns the service's hierarchy.
221     *
222     * @return service hierarchy in slash format.
223     *
224     * @supported.api
225     */
226    public String getServiceHierarchy() {
227        validate();
228        return (ssm.getServiceHierarchy());
229    }
230
231    /**
232     * Sets the service's hierarchy
233     *
234     * @param newhierarchy
235     *            service hierarchy
236     * @throws SMSException
237     *             if an error occurred while trying to perform the operation
238     * @throws SSOException
239     *             if the single sign on token is invalid or expired
240     *
241     * @supported.api
242     */
243    public void setServiceHierarchy(String newhierarchy) throws SMSException,
244            SSOException {
245        SMSEntry.validateToken(token);
246        validateServiceSchemaManagerImpl();
247        String tmpS = getServiceHierarchy();
248        ssm.setServiceHierarchy(newhierarchy);
249        try {
250            replaceSchema(ssm.getDocument());
251        } catch (SMSException e) {
252            ssm.setServiceHierarchy(tmpS);
253            throw e;
254        }
255    }
256
257    /**
258     * Returns i18nKey of the schema.
259     *
260     * @return i18nKey of the schema.
261     *
262     * @supported.api
263     */
264    public String getI18NKey() {
265        validate();
266        return (ssm.getI18NKey());
267    }
268    
269    /**
270     * Sets the i18nKey of the schema.
271     *
272     * @param i18nKey
273     *            <code>i18nKey</code> of the schema.
274     * @throws SMSException
275     *             if an error occurred while trying to perform the operation.
276     * @throws SSOException
277     *             if the single sign on token is invalid or expired.
278     *
279     * @supported.api
280     */
281    public void setI18NKey(String i18nKey) throws SMSException, SSOException {
282        SMSEntry.validateToken(token);
283        validateServiceSchemaManagerImpl();
284        String tmp = ssm.getI18NKey();
285        ssm.setI18NKey(i18nKey);
286        
287        try {
288            replaceSchema(ssm.getDocument());
289        } catch (SMSException e) {
290            ssm.setI18NKey(tmp);
291            throw e;
292        }
293    }
294    
295    /**
296     * Returns URL of the view bean for the service
297     *
298     * @return URL for view bean
299     *
300     * @supported.api
301     */
302    public String getPropertiesViewBeanURL() {
303        validate();
304        return (ssm.getPropertiesViewBeanURL());
305    }
306    
307    /**
308     * Sets the URL of the view bean for the service.
309     *
310     * @param url
311     *            of the view bean for the service.
312     * @throws SMSException
313     *             if an error occurred while trying to perform the operation.
314     * @throws SSOException
315     *             if the single sign on token is invalid or expired.
316     *
317     * @supported.api
318     */
319    public void setPropertiesViewBeanURL(String url) throws SMSException,
320        SSOException {
321        SMSEntry.validateToken(token);
322        validateServiceSchemaManagerImpl();
323        String tmpS = ssm.getPropertiesViewBeanURL();
324        ssm.setPropertiesViewBeanURL(url);
325        try {
326            replaceSchema(ssm.getDocument());
327        } catch (SMSException e) {
328            ssm.setPropertiesViewBeanURL(tmpS);
329            throw e;
330        }
331    }
332
333    /**
334     * Returns the service's resource name for CREST representation, or the
335     * service name if a resource name is not defined.
336     * @supported.api
337     */
338    public String getResourceName() {
339        validate();
340        String resourceName = ssm.getResourceName();
341        return resourceName == null ? getName() : resourceName;
342    }
343
344    /**
345     * Sets the service's  resource name for CREST representation.
346     *
347     * @param name
348     *             resource name for CREST representation
349     * @throws SMSException
350     *             if an error occurred while trying to perform the operation
351     * @throws SSOException
352     *             if the single sign on token is invalid or expired
353     *
354     * @supported.api
355     */
356    public void setResourceName(String name) throws SMSException,
357            SSOException {
358        SMSEntry.validateToken(token);
359        validateServiceSchemaManagerImpl();
360        String tmpS = getResourceName();
361        ssm.setResourceName(name);
362        try {
363            replaceSchema(ssm.getDocument());
364        } catch (SMSException e) {
365            ssm.setResourceName(tmpS);
366            throw e;
367        }
368    }
369
370    /**
371     * iPlanet_PUBLIC-METHOD Returns the revision number of the service schema.
372     *
373     * @return the revision number of the service schema
374     */
375    public int getRevisionNumber() {
376        validate();
377        return (ssm.getRevisionNumber());
378    }
379    
380    /**
381     * iPlanet_PUBLIC-METHOD Sets the revision number for the service schema.
382     *
383     * @param revisionNumber
384     *            revision number of the service schema.
385     * @throws SMSException
386     *             if there is a problem setting the value in the data store.
387     * @throws SSOException
388     *             If the user has an invalid SSO token.
389     */
390    public void setRevisionNumber(int revisionNumber) throws SMSException,
391        SSOException {
392        SMSEntry.validateToken(token);
393        validateServiceSchemaManagerImpl();
394        int tmpS = ssm.getRevisionNumber();
395        ssm.setRevisionNumber(revisionNumber);
396        try {
397            replaceSchema(ssm.getDocument());
398        } catch (SMSException e) {
399            ssm.setRevisionNumber(tmpS);
400            throw (e);
401        }
402    }
403    
404    /**
405     * Returns the schema types available with this
406     * service.
407     *
408     * @return set of <code>SchemaTypes</code> in this service.
409     * @throws SMSException
410     *             if an error occurred while trying to perform the operation
411     *
412     * @supported.api
413     */
414    public Set<SchemaType> getSchemaTypes() throws SMSException {
415        SMSEntry.validateToken(token);
416        validate();
417        return (ssm.getSchemaTypes());
418    }
419    
420    /**
421     * Returns the configuration schema for the given
422     * schema type
423     *
424     * @param type
425     *            schema type.
426     * @return service schema.
427     * @throws SMSException
428     *             if an error occurred while trying to perform the operation
429     *
430     * @supported.api
431     */
432    public ServiceSchema getSchema(String type) throws SMSException {
433        validate();
434        SchemaType t = null;
435        if (type.equalsIgnoreCase("role")
436        || type.equalsIgnoreCase("filteredrole")
437        || type.equalsIgnoreCase("realm")) {
438            t = SchemaType.DYNAMIC;
439        } else if (type.equalsIgnoreCase("user")) {
440            t = SchemaType.USER;
441        } else {
442            t = new SchemaType(type);
443        }
444        return (getSchema(t));
445    }
446    
447    /**
448     * Returns the configuration schema for the given
449     * schema type
450     *
451     * @param type
452     *            schema type.
453     * @return service schema.
454     * @throws SMSException
455     *             if an error occurred while trying to perform the operation
456     *
457     * @supported.api
458     */
459    public ServiceSchema getSchema(SchemaType type) throws SMSException {
460        SMSEntry.validateToken(token);
461        validate();
462        ServiceSchemaImpl ss = ssm.getSchema(type);
463        if ((ss == null) && type.equals(SchemaType.USER)) {
464            type = SchemaType.DYNAMIC;
465            ss = ssm.getSchema(type);
466        }
467        if (ss != null) {
468            return (new ServiceSchema(ss, "", type, this));
469        }
470        return (null);
471    }
472    
473    /**
474     * Returns the organization creation configuration schema if present; else
475     * returns <code>null</code>
476     *
477     * @return service schema.
478     * @throws SMSException
479     *             if an error occurred while trying to perform the operation
480     */
481    public ServiceSchema getOrganizationCreationSchema() throws SMSException {
482        SMSEntry.validateToken(token);
483        validate();
484        ServiceSchemaImpl ss = ssm.getSchema(SchemaType.ORGANIZATION);
485        if (ss != null) {
486            ServiceSchemaImpl ssi = ss.getOrgAttrSchema();
487            if (ssi != null) {
488                return (new ServiceSchema(ssi, "", SchemaType.ORGANIZATION,
489                    this, true));
490            }
491        }
492        return (null);
493    }
494    
495    /**
496     * Returns the attribute schemas for the given schema
497     * type excluding status and service identifier attributes.
498     *
499     * @param type
500     *            schema type.
501     * @return service schema.
502     * @throws SMSException
503     *             if an error occurred while trying to perform the operation
504     *
505     * @supported.api
506     */
507    public Set getServiceAttributeNames(SchemaType type) throws SMSException {
508        SMSEntry.validateToken(token);
509        validate();
510        ServiceSchema ss = getSchema(type);
511        return (ss.getServiceAttributeNames());
512    }
513    
514    /**
515     * Returns the global service configuration schema.
516     *
517     * @return the global service configuration schema
518     * @throws SMSException
519     *             if an error occurred while trying to perform the operation
520     *
521     * @supported.api
522     */
523    public ServiceSchema getGlobalSchema() throws SMSException {
524        return (getSchema(SchemaType.GLOBAL));
525    }
526    
527    /**
528     * Returns the organization service configuration
529     * schema.
530     *
531     * @return the organization service configuration schema
532     * @throws SMSException
533     *             if an error occurred while trying to perform the operation
534     *
535     * @supported.api
536     */
537    public ServiceSchema getOrganizationSchema() throws SMSException {
538        return (getSchema(SchemaType.ORGANIZATION));
539    }
540    
541    /**
542     * Returns the dynamic service configuration schema.
543     *
544     * @return the dynamic service configuration schema
545     * @throws SMSException
546     *             if an error occurred while trying to perform the operation
547     *
548     * @supported.api
549     */
550    public ServiceSchema getDynamicSchema() throws SMSException {
551        return (getSchema(SchemaType.DYNAMIC));
552    }
553    
554    /**
555     * Returns the user service configuration schema.
556     *
557     * @return the user service configuration schema
558     * @throws SMSException
559     *             if an error occurred while trying to perform the operation
560     *
561     * @supported.api
562     */
563    public ServiceSchema getUserSchema() throws SMSException {
564        return (getSchema(SchemaType.USER));
565    }
566    
567    /**
568     * Returns the policy service configuration schema.
569     *
570     * @return the policy service configuration schema
571     * @throws SMSException
572     *             if an error occurred while trying to perform the operation
573     *
574     * @supported.api
575     */
576    public ServiceSchema getPolicySchema() throws SMSException {
577        return (getSchema(SchemaType.POLICY));
578    }
579    
580    /**
581     * Returns the service schema in XML for this service.
582     *
583     * @return the service schema in XML for this service
584     * @throws SMSException
585     *             if an error occurred while trying to perform the operation
586     *
587     * @supported.api
588     */
589    public InputStream getSchema() throws SMSException {
590        SMSEntry.validateToken(token);
591        validate();
592        return (ssm.getSchema());
593    }
594    
595    /**
596     * Replaces the existing service schema with the given
597     * schema defined by the XML input stream that follows the SMS DTD.
598     *
599     * @param xmlServiceSchema
600     *            the XML format of the service schema
601     * @throws SMSException
602     *             if an error occurred while trying to perform the operation
603     * @throws SSOException
604     *             if the single sign on token is invalid or expired
605     * @throws IOException
606     *             if an error occurred with the <code> InputStream </code>
607     *
608     * @supported.api
609     */
610    public void replaceSchema(InputStream xmlServiceSchema)
611    throws SSOException, SMSException, IOException {
612        SMSEntry.validateToken(token);
613        validateServiceSchemaManagerImpl();
614        CachedSMSEntry smsEntry = ssm.getCachedSMSEntry();
615        smsEntry.writeXMLSchema(token, xmlServiceSchema);
616    }
617
618    // @Override
619    public int hashCode() {
620        int hash = 7;
621        hash = 67 * hash + (serviceName != null ? serviceName.hashCode() : 0);
622        hash = 67 * hash + (version != null ? version.hashCode() : 0);
623        return hash;
624    }
625    
626    /**
627     * Returns true if the given object equals this
628     * object.
629     *
630     * @param o
631     *            object for comparison.
632     * @return true if the given object equals this object.
633     *
634     * @supported.api
635     */
636    public boolean equals(Object o) {
637        if (o instanceof ServiceSchemaManager) {
638            ServiceSchemaManager ossm = (ServiceSchemaManager) o;
639            if (serviceName.equals(ossm.serviceName)
640            && version.equals(ossm.version)) {
641                return (true);
642            }
643        }
644        return (false);
645    }
646    
647    /**
648     * Returns the string representation of the Service
649     * Schema.
650     *
651     * @return the string representation of the Service Schema.
652     *
653     * @supported.api
654     */
655    public String toString() {
656        validate();
657        return (ssm.toString());
658    }
659    
660    /**
661     * Registers for changes to service's schema. The
662     * object will be called when schema for this service and version is
663     * changed.
664     *
665     * @param listener
666     *            callback object that will be invoked when schema changes.
667     * @return an ID of the registered listener.
668     *
669     * @supported.api
670     */
671    public String addListener(ServiceListener listener) {
672        validate();
673        return (ssm.addListener(listener));
674    }
675    
676    /**
677     * Removes the listener from the service for the given
678     * listener ID. The ID was issued when the listener was registered.
679     *
680     * @param listenerID
681     *            the listener ID issued when the listener was registered
682     *
683     * @supported.api
684     */
685    public void removeListener(String listenerID) {
686        if (ssm !=null ) {
687            ssm.removeListener(listenerID);
688        }
689    }
690    
691    /**
692     * Returns the last modified time stamp of this service schema. This method
693     * is expensive because it does not cache the modified time stamp but goes
694     * directly to the data store to obtain the value of this entry
695     *
696     * @return The last modified time stamp as a string with the format of
697     *         <code>yyyyMMddhhmmss</code>
698     * @throws SMSException if there is an error trying to read from the
699     *         datastore.
700     * @throws SSOException if the single sign-on token of the user is invalid.
701     */
702    public String getLastModifiedTime() throws SMSException, SSOException {
703        validateServiceSchemaManagerImpl();
704        CachedSMSEntry ce = ssm.getCachedSMSEntry();
705        if (ce.isDirty()) {
706            ce.refresh();
707        }
708        SMSEntry e = ce.getSMSEntry();
709        String vals[] = e.getAttributeValues(SMSEntry.ATTR_MODIFY_TIMESTAMP,
710            true);
711        String mTS = null;
712        if (vals != null) {
713            mTS = vals[0];
714        }
715        return mTS;
716    }
717    
718    // ================= Plugin Interface Methods ========
719    
720    /**
721     * Returns the names of the plugin interfaces used by the service
722     *
723     * @return service's plugin interface names
724     */
725    public Set getPluginInterfaceNames() {
726        validate();
727        return (ssm.getPluginInterfaceNames());
728    }
729    
730    /**
731     * Returns the <code>PluginInterface</code> object of the service for the
732     * specified plugin interface name
733     *
734     * @param pluginInterfaceName
735     *            name of the plugin interface
736     * @return plugin interface configured for the service; else
737     *         <code>null</code>
738     */
739    public PluginInterface getPluginInterface(String pluginInterfaceName) {
740        validate();
741        return (ssm.getPluginInterface(pluginInterfaceName));
742    }
743    
744    /**
745     * Adds a new plugin interface objct to service's schema.
746     *
747     * @param interfaceName
748     *            name for the plugin interface
749     * @param interfaceClass
750     *            fully qualified interface class name
751     * @param i18nKey
752     *            I18N key that will by used by UI to get messages to display
753     *            the interface name
754     */
755    public void addPluginInterface(String interfaceName, String interfaceClass,
756        String i18nKey) throws SMSException, SSOException {
757        SMSEntry.validateToken(token);
758        validateServiceSchemaManagerImpl();
759        if ((interfaceName == null) || (interfaceClass == null)) {
760            throw (new IllegalArgumentException());
761        }
762        StringBuilder sb = new StringBuilder(100);
763        sb.append("<").append(SMSUtils.PLUGIN_INTERFACE).append(" ").append(
764            SMSUtils.NAME).append("=\"").append(interfaceName)
765            .append("\" ").append(SMSUtils.PLUGIN_INTERFACE_CLASS).append(
766            "=\"").append(interfaceClass).append("\"");
767        if (i18nKey != null) {
768            sb.append(" ").append(SMSUtils.I18N_KEY).append("=\"").append(
769                i18nKey).append("\"");
770        }
771        sb.append("></").append(SMSUtils.PLUGIN_INTERFACE).append(">");
772        // Construct XML document
773        Document pluginDoc = SMSSchema.getXMLDocument(sb.toString(), false);
774        Node node = XMLUtils.getRootNode(pluginDoc, SMSUtils.PLUGIN_INTERFACE);
775        
776        // Added to XML document and write it
777        Document schemaDoc = ssm.getDocumentCopy();
778        Node pluginNode = schemaDoc.importNode(node, true);
779        Node schemaNode = XMLUtils.getRootNode(schemaDoc, SMSUtils.SCHEMA);
780        schemaNode.appendChild(pluginNode);
781        replaceSchema(schemaDoc);
782    }
783    
784    /**
785     * Removes the plugin interface object from the service schema.
786     *
787     * @param interfacename Name of the plugin class.
788     */
789    public void removePluginInterface(String interfacename)
790    throws SMSException, SSOException {
791        SMSEntry.validateToken(token);
792        validateServiceSchemaManagerImpl();
793        Document schemaDoc = ssm.getDocumentCopy();
794        Node schemaNode = XMLUtils.getRootNode(schemaDoc, SMSUtils.SCHEMA);
795        // Get the plugin interface node
796        Node pluginNode = XMLUtils.getNamedChildNode(schemaNode,
797            SMSUtils.PLUGIN_INTERFACE, SMSUtils.NAME, interfacename);
798        if (pluginNode != null) {
799            schemaNode.removeChild(pluginNode);
800            replaceSchema(schemaDoc);
801        }
802    }
803    
804    // -----------------------------------------------------------
805    // Plugin Schema
806    // -----------------------------------------------------------
807    /**
808     * Returns the names of plugins configured for the plugin interface. If
809     * organization is <code>null</code>, returns the plugins configured for
810     * the "root" organization.
811     */
812    public Set getPluginSchemaNames(String interfaceName, String orgName)
813    throws SMSException {
814        SMSEntry.validateToken(token);
815        validate();
816        // Construct the DN to get CachedSubEntries
817        StringBuilder sb = new StringBuilder(100);
818        sb.append("ou=").append(interfaceName).append(",").append(
819            CreateServiceConfig.PLUGIN_CONFIG_NODE).append("ou=").append(
820            version).append(",").append("ou=").append(serviceName).append(
821            ",").append(SMSEntry.SERVICES_RDN).append(",").append(
822            DNMapper.orgNameToDN(orgName));
823        CachedSubEntries cse = CachedSubEntries.getInstance(token, sb
824            .toString());
825        try {
826            return (cse.getSubEntries(token));
827        } catch (SSOException s) {
828            debug.error("ServiceSchemaManager: Unable to get "
829                + "Plugin Schema Names", s);
830        }
831        return (Collections.EMPTY_SET);
832    }
833    
834    /**
835     * Returns the PluginSchema object given the schema name and the interface
836     * name for the specified organization. If organization is
837     * <code>null</code>, returns the PluginSchema for the "root" organization.
838     */
839    public PluginSchema getPluginSchema(String pluginSchemaName,
840        String interfaceName, String orgName) throws SMSException {
841        SMSEntry.validateToken(token);
842        validate();
843        return (new PluginSchema(token, serviceName, version, pluginSchemaName,
844            interfaceName, orgName));
845    }
846
847    /**
848     * Returns true if admin token  cached within this class is valid
849     *
850     * @return true is admin token is valid
851     */
852    public boolean isSSOTokenValid() {
853        try {
854            SMSEntry.validateToken(token);
855            return true;
856        } catch (SMSException smse) {
857            debug.warning("ServiceSchemaManager: token is not valid.", smse);
858        }
859        return false;
860    }
861    
862    // -----------------------------------------------------------
863    // Internal protected method
864    // -----------------------------------------------------------
865    SSOToken getSSOToken() {
866        return (token);
867    }
868    
869    protected Document getDocumentCopy() throws SMSException {
870        validate();
871        return (ssm.getDocumentCopy());
872    }
873    
874    protected synchronized void replaceSchema(Document document)
875    throws SSOException, SMSException {
876        validate();
877        CachedSMSEntry smsEntry = ssm.getCachedSMSEntry();
878        SMSSchema smsSchema = new SMSSchema(document);
879        smsEntry.writeXMLSchema(token, smsSchema.getSchema());
880    }
881
882    public <E extends Exception> void modifySchema(Function<Document, Boolean, E> modifier) throws E, SMSException, SSOException {
883        Document schema = getDocumentCopy();
884        if (modifier.apply(schema)) {
885            replaceSchema(schema);
886        }
887    }
888    
889    private void validate() {
890        try {
891            validateServiceSchemaManagerImpl();
892        } catch (SSOException e) {
893            // Since method signatures cannot be changed, a runtime
894            // exception is thrown. This conditions would happen only
895            // when SSOToken has become invalid or service has been
896            // removed.
897            debug.error("ServiceSchemaManager:validate failed for SN: " +
898                serviceName, e);
899            throw (new RuntimeException(e.getMessage()));
900        } catch (SMSException e) {
901            // Ignore the exception
902        }
903    }
904    
905    private void validateServiceSchemaManagerImpl()
906        throws SMSException, SSOException {
907        if (ssm == null || !ssm.isValid()) {
908            // Recreate the SSM
909            ssm = ServiceSchemaManagerImpl.getInstance(token,
910                serviceName, version);
911        }
912    }
913    
914    // -----------------------------------------------------------
915    // Static method to create a new service schema
916    // -----------------------------------------------------------
917    static void createService(SSOToken token, SMSSchema smsSchema)
918    throws SMSException, SSOException {
919        // Service node
920        SMSEntry smsEntry = new SMSEntry(token, ServiceManager
921            .getServiceNameDN(smsSchema.getServiceName()));
922        
923        if (smsEntry.isNewEntry()) {
924            // create this entry
925            smsEntry.addAttribute(SMSEntry.ATTR_OBJECTCLASS, SMSEntry.OC_TOP);
926            smsEntry.addAttribute(SMSEntry.ATTR_OBJECTCLASS,
927                SMSEntry.OC_SERVICE);
928            smsEntry.save();
929        }
930        // Version node
931        CachedSMSEntry cEntry = CachedSMSEntry.getInstance(token,
932            ServiceManager.getServiceNameDN(smsSchema.getServiceName(),
933            smsSchema.getServiceVersion()));
934        if (cEntry.isDirty()) {
935            cEntry.refresh();
936        }
937        smsEntry = cEntry.getSMSEntry();
938        String[] schema = new String[1];
939        if ((smsEntry.getAttributeValues(SMSEntry.ATTR_SCHEMA) == null)
940        || ((smsEntry.getAttributeValues(SMSEntry.ATTR_SCHEMA))[0]
941            .equalsIgnoreCase(SMSSchema.getDummyXML(smsSchema
942            .getServiceName(), smsSchema
943            .getServiceVersion())))) {
944            schema[0] = smsSchema.getSchema();
945            smsEntry.setAttribute(SMSEntry.ATTR_SCHEMA, schema);
946        } else {
947            // Throw service already exists exception
948            Object[] args = { smsSchema.getServiceName(),
949            smsSchema.getServiceVersion() };
950            throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
951                IUMSConstants.SMS_service_already_exists, args));
952        }
953        if (smsEntry.isNewEntry()) {
954            // add object classes
955            smsEntry.addAttribute(SMSEntry.ATTR_OBJECTCLASS, SMSEntry.OC_TOP);
956            smsEntry.addAttribute(SMSEntry.ATTR_OBJECTCLASS,
957                SMSEntry.OC_SERVICE);
958        }
959        smsEntry.save(token);
960        cEntry.refresh(smsEntry);
961    }
962    
963    public String toXML(AMEncryption encryptObj)
964        throws SMSException {
965        validate();
966        String xml = ssm.toXML(encryptObj);
967        int idx = xml.lastIndexOf("</" + SMSUtils.SERVICE + ">");
968        StringBuffer buff = new StringBuffer();
969        buff.append(xml.substring(0, idx));
970
971        Set realms = new HashSet();
972        realms.add("/");
973
974        for (Iterator i = getPluginInterfaceNames().iterator(); i.hasNext(); ) {
975            String iName = (String)i.next();
976            getPlugSchemaXML(buff, iName, realms);
977        }
978
979        buff.append("</" + SMSUtils.SERVICE + ">");
980        return buff.toString();
981    }
982
983    private void getPlugSchemaXML(
984        StringBuffer buff,
985        String interfaceName,
986        Set realms
987    ) throws SMSException {
988        for (Iterator i = realms.iterator(); i.hasNext(); ){
989            String realm = (String)i.next();
990            Set schemaNames = getPluginSchemaNames(interfaceName, realm);
991            for (Iterator j = schemaNames.iterator(); j.hasNext();) {
992                String pName = (String)j.next();
993                PluginSchema pSchema = getPluginSchema(
994                    pName, interfaceName, realm);
995                buff.append(pSchema.toXML());
996            }
997        }
998    }
999}