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: ServiceConfig.java,v 1.18 2009/01/28 05:35:03 ww203982 Exp $
026 *
027 * Portions Copyrighted 2011-2018 ForgeRock AS.
028 * Portions Copyrighted 2012 Open Source Solution Technology Corporation
029 */
030package com.sun.identity.sm;
031
032import java.util.Collections;
033import java.util.HashSet;
034import java.util.Iterator;
035import java.util.Map;
036import java.util.Set;
037
038import org.forgerock.opendj.ldap.DN;
039
040import com.iplanet.services.util.AMEncryption;
041import com.iplanet.sso.SSOException;
042import com.iplanet.sso.SSOToken;
043import com.iplanet.ums.IUMSConstants;
044
045/**
046 * The class <code>ServiceConfig</code> provides interfaces to manage the
047 * configuration information of a service configuration. It provides methods to
048 * get and set configuration parameters for this service configuration.
049 *
050 * @supported.all.api
051 */
052public class ServiceConfig {
053    // Instance variables
054    private SSOToken token;
055
056    private ServiceConfigImpl sc;
057
058    private ServiceSchemaImpl ss;
059
060    private ServiceConfigManager scm;
061
062    /**
063     * Default constructor. Makes it private so that it can not be instantiated.
064     */
065    private ServiceConfig() {
066        // hence can not be instantiated
067    }
068
069    /**
070     * Protected constructor
071     */
072    protected ServiceConfig(ServiceConfigManager scm, ServiceConfigImpl sc)
073            throws SMSException, SSOException {
074        this.scm = scm;
075        token = scm.getSSOToken();
076        this.sc = sc;
077        this.ss = sc.getServiceSchemaImpl();
078    }
079
080    /**
081     * Returns the name of this service configuration.
082     * 
083     * @return the name of this service configuration
084     */
085    public String getServiceName() {
086        return (scm.getName());
087    }
088
089    /**
090     * Returns the service version
091     * 
092     * @return service version
093     */
094    public String getVersion() {
095        return (scm.getVersion());
096    }
097
098    /**
099     * Returns the service component name. It is "/" separated and the root
100     * component name is "/".
101     *
102     * @return service component name
103     */
104    public String getComponentName() {
105        validate();
106        return (sc.getComponentName());
107    }
108
109    /**
110     * Returns the service name.
111     *
112     * @return service name
113     */
114    public String getName() {
115        validate();
116        return sc.getName();
117    }
118
119    /**
120     * Returns the service component's schema ID. For global and organization's
121     * root configurations it returns an empty string.
122     * 
123     * @return service component's schema ID
124     */
125    public String getSchemaID() {
126        validate();
127        return (sc.getSchemaID());
128    }
129
130    /**
131     * Returns the priority assigned to the service configuration.
132     * 
133     * @return the priority assigned to the service configuration
134     */
135    public int getPriority() {
136        validate();
137        return (sc.getPriority());
138    }
139
140    /**
141     * Sets the priority to the service configuration.
142     * 
143     * @param priority
144     *            the priority to be assigned to the configuration
145     * @throws SMSException
146     *             if there is an error occurred while performing the operation
147     * @throws SSOException
148     *             if the user's single sign-on is invalid or expired
149     */
150    public void setPriority(int priority) throws SSOException, SMSException {
151        validateServiceConfigImpl();
152        StringBuilder sb = new StringBuilder(8);
153        String[] priorities = { sb.append(priority).toString() };
154        SMSEntry e = sc.getSMSEntry();
155        e.setAttribute(SMSEntry.ATTR_PRIORITY, priorities);
156        saveSMSEntry(e);
157    }
158
159    /**
160     * Returns the labeled uri assigned to the service configuration.
161     * 
162     * @return the labeled uri assigned to the service configuration
163     * @deprecated The labeledURI setting shall not be used for storing configuration data.
164     */
165    public String getLabeledUri() {
166        validate();
167        return (sc.getLabeledUri());
168    }
169
170    /**
171     * Sets the labeled uri to the service configuration.
172     * 
173     * @param luri the labeled uri to be assigned to the configuration
174     * @throws SMSException
175     *             if there is an error occurred while performing the operation
176     * @throws SSOException
177     *             if the user's single sign-on is invalid or expired
178     * @deprecated The labeledURI setting shall not be used for storing configuration data.
179     */
180    public void setLabeledUri(String luri) throws SSOException, SMSException {
181        validateServiceConfigImpl();
182        StringBuilder sb = new StringBuilder(8);
183        String[] lUris = { sb.append(luri).toString() };
184        SMSEntry e = sc.getSMSEntry();
185        e.setAttribute(SMSEntry.ATTR_LABELED_URI, lUris);
186        saveSMSEntry(e);
187    }
188
189    /**
190     * delete the labeled uri to the service configuration.
191     * 
192     * @param luri the labeled uri to be assigned to the configuration
193     * @throws SMSException
194     *             if there is an error occurred while performing the operation
195     * @throws SSOException
196     *             if the user's single sign-on is invalid or expired
197     * @deprecated The labeledURI setting shall not be used for storing configuration data.
198     */
199    public void deleteLabeledUri(String luri) throws SSOException, SMSException {
200        validateServiceConfigImpl();
201        SMSEntry e = sc.getSMSEntry();
202        sc.setLabeledUri(null);
203        e.removeAttribute(SMSEntry.ATTR_LABELED_URI, luri);
204        saveSMSEntry(e);
205    }
206
207    /**
208     * Returns the names of all service's sub-configurations.
209     * 
210     * @return set of names of all service's sub-configurations
211     * @throws SMSException
212     *             if there is an error accessing the data store
213     */
214    public Set<String> getSubConfigNames() throws SMSException {
215        validateServiceConfigImpl();
216        try {
217            return sc.getSubConfigNames(token);
218        } catch (SSOException s) {
219            SMSEntry.debug.error("ServiceConfig: Unable to "
220                    + "get subConfig Names", s);
221        }
222        return (Collections.EMPTY_SET);
223    }
224
225    /**
226     * Method to get names of service's sub-configurations that match the given
227     * pattern.
228     * 
229     * @param pattern
230     *            pattern to match for sub-configuration names
231     * @return names of the service sub-configuration
232     * @throws SMSException
233     *             if an error occurred while performing the operation.
234     */
235    public Set<String> getSubConfigNames(String pattern) throws SMSException {
236        validateServiceConfigImpl();
237        try {
238            return (sc.getSubConfigNames(token, pattern));
239        } catch (SSOException s) {
240            SMSEntry.debug.error("ServiceConfigManager: Unable to "
241                    + "get subConfig Names for filter: " + pattern, s);
242        }
243        return Collections.emptySet();
244
245    }
246
247    /**
248     * Method to get names of service's sub-configurations that match the given
249     * pattern and belongs to the specified service schema name.
250     * 
251     * @param pattern
252     *            pattern to match for other entities.
253     * @param schemaName
254     *            service schema name.
255     * @return names of the service sub-configuration
256     * @throws SMSException
257     *             if an error occurred while performing the operation.
258     */
259    public Set<String> getSubConfigNames(String pattern, String schemaName)
260            throws SMSException {
261        validateServiceConfigImpl();
262        try {
263            return sc.getSubConfigNames(token, pattern, schemaName);
264        } catch (SSOException s) {
265            SMSEntry.debug.error("ServiceConfigManager: Unable to "
266                    + "get subConfig Names for filters: " + pattern + "AND"
267                    + schemaName, s);
268        }
269        return Collections.emptySet();
270
271    }
272
273    /**
274     * Method to retrieve a single service sub-configuration entry by name.
275     * Avoids otherwise having to filter through a large list of entries using
276     * getSubConfigNames(String pattern)
277     *
278     * @param entityName
279     *            name to match for entity
280     * @return Details of the entry found
281     * @throws SMSException
282     *            if an error occurred while performing the operation
283     */
284    public Map<String, Set<String>> getSubConfigEntity(String entityName) throws SMSException {
285        validateServiceConfigImpl();
286        try {
287            return sc.getSubConfigEntity(token, entityName);
288        } catch (SSOException ssoe) {
289            SMSEntry.debug.error("ServiceConfigManager: Unable to "
290                    + "get subConfig entity for: {}", entityName, ssoe);
291        }
292        return null;
293    }
294
295    /**
296     * Returns a set of exported fully qualified sub-configuration names that
297     * can be imported used locally as service configuration
298     * 
299     * @param serviceId
300     *            service schema identifier
301     * @return names of fully qualified applicable service sub-configurations
302     * @throws SMSException
303     *             if an error occurred while performing the operation.
304     */
305    public Set getExportedSubConfigNames(String serviceId) throws SMSException {
306        return (null);
307    }
308
309    /**
310     * Returns the service's sub-configuration given the service's
311     * sub-configuration name.
312     * 
313     * @param subConfigName
314     *            The name of the service's sub-configuration to retrieve.
315     * @return The <code>ServiceConfig</code> object corresponding to the
316     *         specified name of the service's sub-configuration.
317     * @throws SMSException
318     *             if there is an error occurred while performing the operation
319     * @throws SSOException
320     *             if the user's single sign-on is invalid or expired
321     */
322    public ServiceConfig getSubConfig(String subConfigName)
323            throws SSOException, SMSException {
324        ServiceConfigImpl sci = sc.getSubConfig(token, subConfigName);
325        return ((sci == null) ? null : new ServiceConfig(scm, sci));
326    }
327
328    /**
329     * Adds a service sub-configuration with configuration parameters.
330     * 
331     * @param subConfigName
332     *            the name of service sub-configuration to add
333     * @param subConfigId
334     *            type of service sub-configuration
335     * @param priority
336     *            the priority of the configuration
337     * @param attrs
338     *            configuration parameters for the sub-configuration
339     * @throws SMSException
340     *             if there is an error occurred while performing the operation
341     * @throws SSOException
342     *             if the user's single sign-on is invalid or expired
343     */
344    public void addSubConfig(String subConfigName, String subConfigId,
345            int priority, Map attrs) throws SMSException, SSOException {
346        validateServiceConfigImpl();
347        // Check if this entry exists
348        if (sc.isNewEntry()) {
349            // Ideally these nodes should have been created, since they
350            // are not present we need to create them
351            scm.createOrganizationConfig(sc.getOrganizationName(), null);
352            // Check if rest of the component names are present
353            checkAndCreateComponents(sc.getDN());
354        }
355
356        // Get service schemas
357        String subSchemaIdentifier = subConfigId == null ? subConfigName : subConfigId;
358        ServiceSchemaImpl nss = ss.getSubSchema(subSchemaIdentifier);
359
360        if (nss == null) {
361            String[] args = { subSchemaIdentifier };
362            throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
363                    "sms-invalid-add-sub-config-unknown-schema-name", args));
364        }
365
366        if (!nss.supportsMultipleConfigurations()
367                && !getSubConfigNames().isEmpty()) {
368            String[] args = { subConfigName };
369            throw (new SMSException(IUMSConstants.UMS_BUNDLE_NAME,
370                    "sms-invalid-add-sub-config", args));
371        }
372
373        // Convert priority to string
374        StringBuilder sb = new StringBuilder(8);
375        sb.append(priority);
376
377        // Create the entry
378        CreateServiceConfig.createSubConfigEntry(token, ("ou=" + subConfigName
379                + "," + sc.getDN()), nss, subConfigId, sb.toString(),
380                SMSUtils.copyAttributes(attrs), sc.getOrganizationName());
381    }
382
383    /**
384     * Removes the service sub-configuration.
385     * 
386     * @param subConfigName
387     *            name of service sub-configuration to remove
388     * @throws SMSException
389     *             if there is an error occurred while performing the operation
390     * @throws SSOException
391     *             if the user's single sign-on is invalid or expired
392     */
393    public void removeSubConfig(String subConfigName) throws SMSException,
394            SSOException {
395        validateServiceConfigImpl();
396        // Obtain the SMSEntry for the subconfig and delete it
397        // unescape in case users provide such a subConfigName for deletion.
398        // "http:&amp;#47;&amp;#47;abc.east.sun.com:58080"
399
400        subConfigName = SMSSchema.unescapeName(subConfigName);
401
402        // First remove the entry from ServiceConfigImpl Cache.
403
404        // Construct subconfig DN
405        String sdn = "ou=" + subConfigName + "," + sc.getDN();
406
407        // Construct ServiceConfigManagerImpl
408        ServiceConfigManagerImpl scmImpl = ServiceConfigManagerImpl.
409            getInstance(token, getServiceName(), getVersion());
410
411        // Construct ServiceConfigImpl of the removed subconfig.
412        ServiceConfigImpl sConfigImpl =
413            sc.getSubConfig(token, subConfigName);
414        
415        // Call ServiceConfigImpl's deleteInstance() to remove from cache.
416        if (sConfigImpl != null) {
417            ServiceConfigImpl.deleteInstance(token, scmImpl, null, sdn, "/", 
418                sConfigImpl.getGroupName(), (getComponentName() + "/" 
419                + SMSSchema.escapeSpecialCharacters(subConfigName)), false, 
420                ss);
421        }
422        // Remove this entry from smsentry.
423        CachedSMSEntry cEntry = CachedSMSEntry.getInstance(token, sdn);
424        if (cEntry.isDirty()) {
425            cEntry.refresh();
426        }
427        SMSEntry entry = cEntry.getClonedSMSEntry();
428        entry.delete(token);
429        cEntry.refresh(entry);
430
431        // Remove the entry from CachedSubEntries
432        CachedSubEntries cse = CachedSubEntries.getInstance(token, sc.getDN());
433        cse.remove(subConfigName);
434    }
435
436    /**
437     * Imports a service sub-configuration to the list of localy defined
438     * sub-configuration. The imported sub-configuration name must be fully
439     * qualified, as obtained from <code>getExportedSubConfigNames</code>.
440     * 
441     * @param subConfigName
442     *            the name of service sub-configuration to add locally
443     * @param exportedSubConfigName
444     *            the fully qualified name of the exported sub-configuration
445     *            name
446     * @throws SMSException
447     *             if there is an error occurred while performing the operation
448     * @throws SSOException
449     *             if the user's single sign-on is invalid or expired
450     */
451    public void importSubConfig(String subConfigName,
452            String exportedSubConfigName) throws SMSException, SSOException {
453    }
454
455    /**
456     * Returns the service configuration parameters. The keys in the
457     * <code>Map</code> contains the attribute names and their corresponding
458     * values in the <code>Map</code> is a <code>Set</code> that contains
459     * the values for the attribute. This method picks up the default values for
460     * any attributes not defined in the <code>ServiceConfig</code>. The
461     * default values for these attributes are picked up from the Service
462     * Schema. If there is no default value defined, then this method will still
463     * return the attribute-value pair, except that the Set will be a
464     * Collections.EMPTY_SET. This is distinct from an empty Set with no entries
465     * in it. AN empty set represents an attribute whose value has been set to
466     * an empty value by the application using the <code>setAttributes()</code>
467     * method.
468     * 
469     * @return the <code>Map</code> where key is the attribute name and value
470     *         is the <code>Set</code> of attribute values
471     */
472    public Map getAttributes() {
473        validate();
474        return (sc.getAttributes());
475    }
476
477        /**
478     * Returns the service configuration parameters for read only.
479     * The keys in the <code>Map</code> contains the attribute names and
480     * their corresponding values in the <code>Map</code> is a
481     * <code>Set</code> that contains the values for the attribute.
482     */
483
484    /**
485     * Returns the service configuration parameters without inheriting the
486     * default values from service's schema. The keys in the <code>Map</code>
487     * contains the attribute names and their corresponding values in the
488     * <code>Map</code> is a <code>Set</code> that contains the values for
489     * the attribute.
490     */
491    public Map getAttributesWithoutDefaults() {
492        validate();
493        return (sc.getAttributesWithoutDefaults());
494    }
495    
496    /**
497     * Returns the service configuration parameters for read only,
498     * modification cannot be performed on the return <code>Map</code>.
499     * The keys in the
500     * <code>Map</code> contains the attribute names and their
501     * corresponding values in the <code>Map</code> is a
502     * <code>Set</code> that contains the values for the attribute.
503     * This method picks up the default values for any attributes
504     * not defined in the <code>ServiceConfig</code>. The default values for
505     * these attributes are picked up from the Service Schema.
506     * If there is no default value defined, then this method
507     * will still return the attribute-value pair, except that
508     * the Set will be a Collections.EMPTY_SET.
509     * This is distinct from an empty Set with no entries in it.
510     * AN empty set represents an attribute whose value has
511     * been set to an empty value by the application using
512     * the <code>setAttributes()</code> method.
513     * 
514     * @return the <code>Map</code> where key is the attribute name
515     *   and value is the <code>Set</code> of attribute values
516     */
517    public Map<String, Set<String>> getAttributesForRead() {
518        validate();
519        return (sc.getAttributesForRead());
520    }
521
522    /**
523     * Returns the service configuration parameters for read only without
524     * inheriting the default values from service's schema. The keys
525     * in the  <code>Map</code> contains the attribute names and their
526     * corresponding values in the <code>Map</code> is a
527     * <code>Set</code> that contains the values for the attribute.
528     */
529    public Map<String, Set<String>> getAttributesWithoutDefaultsForRead() {
530        validate();
531        return (sc.getAttributesWithoutDefaultsForRead());
532    }
533
534    /**
535     * Sets the service configuration parameters. The keys in the
536     * <code>Map</code> contains the attribute names and their corresponding
537     * values in the <code>Map</code> is a <code>Set</code> that contains
538     * the values for the attribute. This method will replace the existing
539     * attribute values with the given one. For attributes that are not
540     * specified in <code>attrs</code>, it will not be modified.
541     * 
542     * @param attrs
543     *            the <code>Map</code> where key is the attribute name and
544     *            value is the <code>Set</code> of attribute values
545     * @throws SMSException
546     *             if there is an error occurred while performing the operation
547     * @throws SSOException
548     *             if the user's single sign-on is invalid or expired
549     */
550    public void setAttributes(Map attrs) throws SMSException, SSOException {
551        validateServiceConfigImpl();
552        Map oldAttrs = sc.getAttributesWithoutDefaults();
553        Iterator it = oldAttrs.keySet().iterator();
554        Map newAttrs = SMSUtils.copyAttributes(attrs);
555        while (it.hasNext()) {
556            String s = (String) it.next();
557            if (!newAttrs.containsKey(s)) {
558                newAttrs.put(s, oldAttrs.get(s));
559            }
560        }
561        /*
562         * For validation using ChoiceValues plugin we need to pass in
563         * OrganizationName, since the plugins use organization names to compute
564         * the choice values
565         */
566        ss.validateAttributes(token, newAttrs, true, sc.getOrganizationName());
567        SMSEntry e = sc.getSMSEntry();
568        SMSUtils.setAttributeValuePairs(e, newAttrs, ss
569                .getSearchableAttributeNames());
570        saveSMSEntry(e);
571    }
572
573    /**
574     * Adds a configuration parameter to the service configuration.
575     * 
576     * @param attrName
577     *            the name of the attribute to add
578     * @param values
579     *            the set of values to add
580     * @throws SMSException
581     *             if there is an error occurred while performing the operation
582     * @throws SSOException
583     *             if the user's single sign-on is invalid or expired
584     */
585    public void addAttribute(String attrName, Set values) throws SMSException,
586            SSOException {
587        validateServiceConfigImpl();
588        // Get current attributes
589        Map attributes = getAttributes();
590        // Validate attribute values
591        Set newVals = values;
592        Set oldVals = (Set) attributes.get(attrName);
593        if (oldVals != null) {
594            newVals = new HashSet();
595            newVals.addAll(values);
596            newVals.addAll(oldVals);
597        }
598        ss
599                .validateAttrValues(token, attrName, newVals, true, sc
600                        .getOrganizationName());
601        // Store the entry
602        SMSEntry e = sc.getSMSEntry();
603        SMSUtils.addAttribute(e, attrName, values, ss
604                .getSearchableAttributeNames());
605        saveSMSEntry(e);
606    }
607
608    /**
609     * Removes a configuration parameter from the service configuration.
610     * 
611     * @param attrName
612     *            the name of the attribute to remove
613     * @throws SMSException
614     *             if there is an error occurred while performing the operation
615     * @throws SSOException
616     *             if the user's single sign-on is invalid or expired
617     */
618    public void removeAttribute(String attrName) throws SMSException,
619            SSOException {
620        validateServiceConfigImpl();
621        SMSEntry e = sc.getSMSEntry();
622        SMSUtils.removeAttribute(e, attrName);
623        saveSMSEntry(e);
624    }
625
626    /**
627     * Removes a configuration parameters from the service configuration.
628     * 
629     * @param attrNames
630     *            <code>Set</code> of attribute names to remove
631     * @throws SMSException
632     *             if there is an error occurred while performing the operation
633     * @throws SSOException
634     *             if the user's single sign-on is invalid or expired
635     */
636    public void removeAttributes(Set attrNames) throws SMSException,
637            SSOException {
638        validateServiceConfigImpl();
639        SMSEntry e = sc.getSMSEntry();
640        if (attrNames != null && !attrNames.isEmpty()) {
641            for (Iterator items = attrNames.iterator(); items.hasNext();) {
642                SMSUtils.removeAttribute(e, (String) items.next());
643            }
644            saveSMSEntry(e);
645        }
646    }
647
648    /**
649     * Removes the specific values for the given configuration parameter.
650     * 
651     * @param attrName
652     *            the name of the attribute
653     * @param values
654     *            set of attribute values to remove from the given attribute
655     * @throws SMSException
656     *             if there is an error occurred while performing the operation
657     * @throws SSOException
658     *             if the user's single sign-on is invalid or expired
659     */
660    public void removeAttributeValues(String attrName, Set values)
661            throws SMSException, SSOException {
662        validateServiceConfigImpl();
663        SMSEntry e = sc.getSMSEntry();
664        SMSUtils.removeAttributeValues(e, attrName, values, ss
665                .getSearchableAttributeNames());
666        saveSMSEntry(e);
667    }
668
669    /**
670     * Replaces old value of the configuration parameter with new value.
671     * 
672     * @param attrName
673     *            the name of the attribute
674     * @param oldValue
675     *            the old value to remove from the attribute
676     * @param newValue
677     *            the new value to add to the attribute
678     * @throws SMSException
679     *             if there is an error occurred while performing the operation
680     * @throws SSOException
681     *             if the user's single sign-on is invalid or expired
682     */
683    public void replaceAttributeValue(String attrName, String oldValue,
684            String newValue) throws SMSException, SSOException {
685        validateServiceConfigImpl();
686        // Get current attributes
687        Map attributes = getAttributes();
688        // Validate values
689
690        Set currentValues = (Set) attributes.get(attrName);
691        if (currentValues != null && !currentValues.contains(oldValue)) {
692            throw (new SMSException("Current value doesn't match supplied value",
693                    "sms-INVALID_PARAMETERS"));
694        }
695
696        Set newVals = new HashSet();
697        Set oldVals = (Set) attributes.get(attrName);
698        if (oldVals != null) {
699            newVals.addAll(oldVals);
700            newVals.remove(oldValue);
701        }
702        newVals.add(newValue);
703        ss
704                .validateAttrValues(token, attrName, newVals, true, sc
705                        .getOrganizationName());
706        // Store the entry
707        SMSEntry e = sc.getSMSEntry();
708        SMSUtils.replaceAttributeValue(e, attrName, oldValue, newValue, ss
709                .getSearchableAttributeNames());
710        saveSMSEntry(e);
711    }
712
713    /**
714     * Replaces the old values of the configuration parameter with the new
715     * values.
716     * 
717     * @param attrName
718     *            the name of the attribute
719     * @param oldValues
720     *            the set of old values to remove from the attribute
721     * @param newValues
722     *            the set of new values to add to the attribute
723     * @throws SMSException
724     *             if there is an error occurred while performing the operation
725     * @throws SSOException
726     *             if the user's single sign-on is invalid or expired
727     */
728    public void replaceAttributeValues(String attrName, Set oldValues,
729            Set newValues) throws SMSException, SSOException {
730        validateServiceConfigImpl();
731        // Get current attributes
732        Map attributes = getAttributes();
733        // Validate values
734        Set newVals = new HashSet();
735        Set oldVals = (Set) attributes.get(attrName);
736        if (oldVals != null) {
737            newVals.addAll(oldVals);
738            newVals.removeAll(oldValues);
739        }
740        newVals.addAll(newValues);
741        ss.validateAttrValues(token, attrName, newVals, true,
742            sc.getOrganizationName());
743        // Store the entry
744        SMSEntry e = sc.getSMSEntry();
745        SMSUtils.replaceAttributeValues(e, attrName, oldValues, newValues, ss
746                .getSearchableAttributeNames());
747        saveSMSEntry(e);
748    }
749
750    /**
751     * Returns the LDAP DN represented by this <code>ServiceConfig</code>
752     * object.
753     * 
754     * @return the LDAP DN represented by this <code>ServiceConfig</code>
755     *         object.
756     */
757    public String getDN() {
758        validate();
759        return (sc.getDN());
760    }
761
762    /**
763     * Returns the last modified time stamp of this configuration This method is
764     * expensive because it does not cache the modified time stamp but goes
765     * directly to the data store to obtain the value of this entry
766     * 
767     * @return The last modified time stamp as a string with the format of
768     *         <code> yyyyMMddhhmmss </code>
769     * @throws SMSException
770     *             if there is an error trying to read from the data store
771     * @throws SSOException
772     *             if the single sign-on token of the user is invalid.
773     */
774
775    public String getLastModifiedTime() throws SMSException, SSOException {
776        validateServiceConfigImpl();
777        SMSEntry e = sc.getSMSEntry();
778        String vals[] = e.getAttributeValues(SMSEntry.ATTR_MODIFY_TIMESTAMP,
779                true);
780        String mTS = null;
781        if (vals != null) {
782            mTS = vals[0];
783        }
784        return mTS;
785    }
786
787    /**
788     * Returns the organization names to which the service configuration is
789     * being exported. The organization names would be fully qualified starting
790     * with a forward slash "/". To specify an entire sub-tree that can use the
791     * service configuration, a "*" would have to be appended after the final
792     * forward slash. For example "/a/b/c/*" would imply all sub-organization
793     * under "/a/b/c" can use this service configuration. Exporting implies
794     * privileges to read the service configuration data, but not to modify or
795     * delete.
796     * 
797     * @return names of organizations to which service configuration
798     *         configuration is exported
799     */
800    public Set getExportedOrganizationNames() {
801        return (null);
802    }
803
804    /**
805     * Sets the organization names that can import the service configuration.
806     * The organization names must be fully qualified, starting with a forward
807     * slash "/". To specify an entire sub-tree that can use the service
808     * configuration, a "*" would have to be appended after the final forward
809     * slash. For example "/a/b/c/*" would imply all sub-organization under
810     * "/a/b/c" can use this service configuration. Exporting implies privileges
811     * to read the service configuration data and not to modify or delete.
812     * 
813     * @param names
814     *            names of the organizations that can import the service
815     *            configuration
816     */
817    public void setExportedOrganizationNames(Set names) throws SMSException,
818            SSOException {
819    }
820
821    /**
822     * Adds the organization names to the list of organization names that can
823     * import this service configutation. If one does not exist it will be
824     * created. The organization names must be fully qualified, starting with a
825     * forward slash "/". To specify an entire sub-tree that can use the service
826     * configuration, a "*" would have to be appended after the final forward
827     * slash. For example "/a/b/c/*" would imply all sub-organization under
828     * "/a/b/c" can use this service configuration. Exporting implies privileges
829     * to read the service configuration data and not to modify or delete.
830     * 
831     * @param names
832     *            names of the organizations that can import the service
833     *            configuration
834     */
835    public void addExportedOrganizationNames(Set names) throws SMSException,
836            SSOException {
837    }
838
839    /**
840     * Removes the organization names from the list of organization names that
841     * can import the service configuration. If the organization has already
842     * imported the service configutation, it would have to be undone before the
843     * organization name can be removed from the list. The organization names
844     * must be fully qualified, starting with a forward slash "/". To specify an
845     * entire sub-tree that can use the service configuration, a "*" would have
846     * to be appended after the final forward slash. For example "/a/b/c/*"
847     * would imply all sub-organization under "/a/b/c" can use this service
848     * configuration.
849     * 
850     * @param names
851     *            names of the organizations that will be removed from the list
852     *            of organization names that can import the service
853     *            configutation
854     */
855    public void removeSharedOrganizationNames(Set names) throws SMSException,
856            SSOException {
857    }
858
859    /**
860     * Returns String representation of the <code>ServiceConfig</code> object.
861     * It returns attributes defined and sub configurations.
862     * 
863     * @return String representation of the <code>ServiceConfig</code> object.
864     */
865    public String toString() {
866        StringBuilder sb = new StringBuilder();
867        // Print the attributes
868        sb.append("Service Component name: " + getComponentName());
869        sb.append("\n\tAttributes: " + getAttributes()).append("\n");
870
871        // Try sub-configs
872        try {
873            Iterator subConfigNames = getSubConfigNames().iterator();
874            while (subConfigNames.hasNext()) {
875                ServiceConfig ssc = getSubConfig((String)subConfigNames.next());
876                sb.append(ssc);
877            }
878        } catch (Exception e) {
879            sb.append(e.getMessage());
880        }
881        return (sb.toString());
882    }
883
884    // Protected methods
885    void saveSMSEntry(SMSEntry e) throws SMSException, SSOException {
886        if (e.isNewEntry()) {
887            // Check if base nodes exists
888            CreateServiceConfig.checkBaseNodesForOrg(token, DNMapper
889                    .orgNameToDN(sc.getOrganizationName()), getServiceName(),
890                    getVersion());
891            // Check if parent DN is present
892            String parentDN = DN.valueOf(e.getDN()).parent().toString();
893            checkAndCreateComponents(parentDN);
894            // Add object classses to this entry
895            e.addAttribute(SMSEntry.ATTR_OBJECTCLASS, SMSEntry.OC_TOP);
896            e.addAttribute(SMSEntry.ATTR_OBJECTCLASS, SMSEntry.OC_SERVICE_COMP);
897        }
898        e.save(token);
899        sc.refresh(e);
900    }
901
902    public void checkAndCreateGroup(String dn, String groupName) 
903        throws SMSException, SSOException {
904
905        CachedSMSEntry entry = CachedSMSEntry.getInstance(token, dn);
906        if (entry.isDirty()) {
907            entry.refresh();
908        }
909        if (entry.isNewEntry()) {
910            // Check if parent exisits
911            String pDN = DN.valueOf(dn).parent().toString();
912            CachedSMSEntry pEntry = CachedSMSEntry.getInstance(token, pDN);
913            if (pEntry.isDirty()) {
914                pEntry.refresh();
915            }
916            if (pEntry.isNewEntry()) {
917                checkAndCreateComponents(pDN);
918            }
919            // Create this entry
920            SMSEntry e = entry.getClonedSMSEntry();
921            e.addAttribute(SMSEntry.ATTR_OBJECTCLASS, SMSEntry.OC_TOP);
922            e.addAttribute(SMSEntry.ATTR_OBJECTCLASS,SMSEntry.OC_SERVICE_COMP);
923            e.addAttribute(SMSEntry.ATTR_SERVICE_ID, groupName);
924            e.save(token);
925            entry.refresh(e);
926        }
927    }
928
929    void checkAndCreateComponents(String dn) throws SMSException, SSOException {
930        CachedSMSEntry entry = CachedSMSEntry.getInstance(token, dn);
931        if (entry.isDirty()) {
932            entry.refresh();
933        }
934        if (entry.isNewEntry()) {
935            // Check if parent exisits
936            String pDN = DN.valueOf(dn).parent().toString();
937            CachedSMSEntry pEntry = CachedSMSEntry.getInstance(token, pDN);
938            if (pEntry.isDirty()) {
939                pEntry.refresh();
940            }
941            if (pEntry.isNewEntry()) {
942                checkAndCreateComponents(pDN);
943            }
944            // Create this entry
945            SMSEntry e = entry.getClonedSMSEntry();
946            e.addAttribute(SMSEntry.ATTR_OBJECTCLASS, SMSEntry.OC_TOP);
947            e.addAttribute(SMSEntry.ATTR_OBJECTCLASS, SMSEntry.OC_SERVICE_COMP);
948            e.save(token);
949            entry.refresh(e);
950        }
951    }
952    
953    private void validate() {
954        try {
955            validateServiceConfigImpl();
956        } catch (SMSException e) {
957            // Ignore the exception
958        }
959    }
960    
961    private void validateServiceConfigImpl() throws SMSException {
962        if (!sc.isValid()) {
963            throw (new SMSException("service-config: " + sc.getDN() +
964                " No longer valid. Cache has been cleared. Recreate from" +
965                "ServiceConfigManager"));
966        }
967    }
968    
969    /**
970     * Returns the status of this Service Configuration Object.
971     * Must be used by classes that cache ServiceConfig.
972     * 
973     * @return <code>true</code> if this object is still valid.
974     */
975    public boolean isValid() {
976        return (sc.isValid());
977    }
978    
979    /**
980     * Returns <code>true</code> if the entry exist
981     */
982    public boolean exists() {
983        return (!sc.isNewEntry());
984    }
985    
986    public String toXML(String NodeTag, AMEncryption encryptObj)
987        throws SMSException, SSOException {
988        validateServiceConfigImpl();
989        return sc.toXML(token, NodeTag, encryptObj);
990    }
991
992    public String toXML(String NodeTag, AMEncryption encryptObj, String orgName)
993        throws SMSException, SSOException {
994        validateServiceConfigImpl();
995        return sc.toXML(token, NodeTag, encryptObj, orgName);
996    }
997}