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