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: COSManager.java,v 1.5 2009/01/28 05:34:51 ww203982 Exp $
026 *
027 */
028
029package com.iplanet.ums.cos;
030
031import java.security.Principal;
032import java.util.AbstractCollection;
033import java.util.ArrayList;
034import java.util.Arrays;
035import java.util.Collection;
036import java.util.StringTokenizer;
037
038import com.sun.identity.shared.ldap.LDAPException;
039import com.sun.identity.shared.ldap.util.DN;
040
041import com.iplanet.services.ldap.Attr;
042import com.iplanet.services.ldap.AttrSet;
043import com.iplanet.services.ldap.ModSet;
044import com.iplanet.services.util.I18n;
045import com.iplanet.sso.SSOException;
046import com.iplanet.sso.SSOToken;
047import com.iplanet.sso.SSOTokenManager;
048import com.iplanet.ums.Guid;
049import com.iplanet.ums.IUMSConstants;
050import com.iplanet.ums.PersistentObject;
051import com.iplanet.ums.SchemaManager;
052import com.iplanet.ums.SearchResults;
053import com.iplanet.ums.UMSException;
054import com.iplanet.ums.UMSObject;
055
056/**
057 * This class has the responsibility of adding, removing and replacing COS
058 * definitions. It also provides search capabilities for COS definitions.
059 * @supported.api
060 */
061public class COSManager {
062
063    /**
064     * This constructor sets the parent Directory entry which identifies the
065     * location of COS definitions which will be managed. It also gets an
066     * instance of a SchemaManager which will be used to update schema entries
067     * for COS assignments.
068     * 
069     * @param token Authenticated principal's single sign on token.
070     * @param guid The unique identifier specifying where COS definitions will
071     *        be managed.
072     * @throws UMSException if the token authentication fails, or if
073     *         the guid for the parent entry is not valid.
074     */
075    protected COSManager(SSOToken token, Guid guid) throws UMSException {
076        try {
077            SSOTokenManager.getInstance().validateToken(token);
078        } catch (SSOException se) {
079            throw new UMSException(i18n.getString(IUMSConstants.INVALID_TOKEN),
080                    se);
081        }
082        _parentObject = UMSObject.getObject(token, guid);
083        try {
084            _schemaManager = SchemaManager.getSchemaManager(token
085                    .getPrincipal());
086        } catch (SSOException se) {
087            throw new UMSException("Bad Authentication Token "
088                    + se.getMessage());
089        }
090    }
091
092    /**
093     * This constructor sets the parent Directory entry which identifies the
094     * location of COS definitions which will be managed. It also gets an
095     * instance of a SchemaManager which will be used to update schema entries
096     * for COS assignments.
097     * 
098     * @param principal
099     *            Authenticated principal
100     * @param guid
101     *            The unique identifier specifying where COS definitions will be
102     *            managed.
103     * 
104     * @throws UMSException
105     *             The exception thrown if there is a problem determining the
106     *             parent entry, or getting the SchemaManager instance.
107     */
108    protected COSManager(Principal principal, Guid guid) throws UMSException {
109        _parentObject = UMSObject.getObject(principal, guid);
110        _schemaManager = SchemaManager.getSchemaManager(principal);
111    }
112
113    /**
114     * This method returns an instance of a COS Manager.
115     * 
116     * @param token Authenticated principal's single sign on token.
117     * @param guid COS definitions will be managed under the level identified by
118     *        this guid.
119     * @throws UMSException
120     *             The exception thrown from the COSManager constructor.
121     * @supported.api
122     */
123    public static COSManager getCOSManager(SSOToken token, Guid guid)
124            throws UMSException {
125        return new COSManager(token, guid);
126    }
127
128    /**
129     * This method returns an instance of a COS Manager.
130     * 
131     * @param principal Authenticated principal.
132     * @param guid COS definitions will be managed under the level identified by
133     *        this guid.
134     * @throws UMSException
135     *             The exception thrown from the data layer.
136     */
137    public static COSManager getCOSManager(Principal principal, Guid guid)
138            throws UMSException {
139        return new COSManager(principal, guid);
140    }
141
142    /**
143     * This method adds a COS definition to the persistent store. The definition
144     * is added under the specified "guid" parameter.
145     * 
146     * @param cosDef
147     *            The COS definition to be added.
148     * 
149     * @throws UMSException
150     *             The exception thrown from the data layer.
151     * @supported.api
152     */
153    public void addDefinition(ICOSDefinition cosDef) throws UMSException {
154        if (!(cosDef instanceof DirectCOSDefinition)) {
155            String msg = i18n.getString(IUMSConstants.INVALID_COSDEFINITION);
156            throw new UMSException(msg);
157        }
158        String[] cosAttributes = cosDef.getCOSAttributes();
159        AbstractCollection aList = (AbstractCollection) Arrays
160                .asList(ICOSDefinition.qualifiers);
161        for (int i = 0; i < cosAttributes.length; i++) {
162            String cosAttribute = null;
163            String qualifier = null;
164            StringTokenizer st = new StringTokenizer(cosAttributes[i]);
165            if (st.hasMoreTokens()) {
166                cosAttribute = st.nextToken();
167            }
168            if (cosAttribute == null) {
169                String msg = i18n.getString(
170                        IUMSConstants.INVALID_COS_ATTRIBUTE_QUALIFIER);
171                throw new UMSException(msg);
172            }
173            if (st.hasMoreTokens())
174                qualifier = st.nextToken();
175            if (qualifier == null) {
176                qualifier = ICOSDefinition.qualifiers[ICOSDefinition.DEFAULT];
177                cosDef.removeCOSAttribute(cosAttribute);
178                cosDef.addCOSAttribute(cosAttribute, ICOSDefinition.DEFAULT);
179            }
180            if (!aList.contains(qualifier)) {
181                String msg = i18n.getString(
182                        IUMSConstants.INVALID_COS_ATTRIBUTE_QUALIFIER);
183                throw new UMSException(msg);
184            }
185        }
186        PersistentObject po = (PersistentObject) cosDef;
187        _parentObject.addChild(po);
188    }
189
190    /**
191     * Removes the COS definition.
192     * 
193     * @param name
194     *            The name of the definition to be removed.
195     * 
196     * @throws UMSException
197     *             The exception thrown from the data layer.
198     * @supported.api
199     */
200    public void removeDefinition(String name) throws UMSException {
201        Guid guid = new Guid(ICOSDefinition.DEFAULT_NAMING_ATTR + "=" + name
202                + "," + _parentObject.getGuid().getDn());
203        _parentObject.removeChild(guid);
204    }
205
206    /**
207     * Updates the contents of a COS definition with the new contents. The COS
208     * definition must already exist in the persistent layer, before its
209     * contents can be replaced.
210     * 
211     * @param cosDef
212     *            The COS definition containing new contents, which will replace
213     *            the same definition in the persistent layer.
214     * 
215     * @throws UMSException
216     *             The exception thrown from the data layer.
217     * @supported.api
218     */
219    public void updateDefinition(ICOSDefinition cosDef) throws UMSException {
220        PersistentObject pObject = (PersistentObject) cosDef;
221        if (pObject.getGuid() == null) {
222            String msg = i18n
223                    .getString(IUMSConstants.REPLACE_DEFINITION_NOT_PERSISTENT);
224            throw new UMSException(msg);
225        }
226        pObject.save();
227    }
228
229    /**
230     * Returns COS definition given the name.
231     * 
232     * @param name Name of the COS definition.
233     * @return A COS definition with the specified name.
234     * @throws UMSException if exception occurred at the data layer.
235     * @throws COSNotFoundException if the COS object is not found.
236     * @supported.api
237     */
238    public ICOSDefinition getDefinition(String name) throws UMSException,
239            COSNotFoundException {
240        ICOSDefinition cosDef = null;
241        SearchResults sr = _parentObject.getChildren(
242                ICOSDefinition.COSSUPERDEF_NAME_SEARCH + name + ")",
243                DEF_ATTRIBUTE_NAMES, null);
244        while (sr.hasMoreElements()) {
245            cosDef = (ICOSDefinition) sr.next();
246            if (cosDef.getName().equals(name)) {
247                break;
248            } else {
249                cosDef = null;
250            }
251        }
252        if (cosDef == null) {
253            String msg = i18n.getString(IUMSConstants.COS_DEFINITION_NOT_FOUND);
254            throw new COSNotFoundException(msg);
255        }
256        sr.abandon();
257        return cosDef;
258    }
259
260    /**
261     * Retrieves all COS definitions for the current organization. This
262     * COSManager instance applies to an organization.
263     * 
264     * @return A collection of COS definition objects.
265     * 
266     * @throws UMSException
267     *             The exception thrown from the data layer.
268     * @supported.api
269     */
270    public Collection getDefinitions() throws UMSException {
271        Collection cosDefinitions = new ArrayList();
272        SearchResults sr = _parentObject.search(
273                ICOSDefinition.COSSUPERDEF_SEARCH, DEF_ATTRIBUTE_NAMES, null);
274        while (sr.hasMoreElements()) {
275            cosDefinitions.add(sr.next());
276        }
277        return cosDefinitions;
278    }
279
280    /**
281     * Assigns a COS (as defined by a COS definition) to the persistent object.
282     * The COS target persistent object could be a user, group, organization,
283     * organizationalunit, etc. The COS target object must be persistent before
284     * this method can be used.
285     * 
286     * @param pObject
287     *            The COS target persistent object.
288     * @param cosDef
289     *            A COS definition.
290     * @param cosTemplate
291     *            A COS template. This only applies for COS and Indirect COS
292     *            definitions. For pointer COS definitions, this parameter can
293     *            be null.
294     * 
295     * @throws UMSException
296     *             If a data layer exception occurs.
297     * @supported.api
298     */
299    public void assignCOSDef(PersistentObject pObject, ICOSDefinition cosDef,
300            COSTemplate cosTemplate) throws UMSException {
301        if (pObject == null || cosDef == null) {
302            String msg = i18n
303                    .getString(IUMSConstants.COS_DEF_OR_TARGET_OBJECT_NULL);
304            throw new UMSException(msg);
305        }
306
307        // Do validation....
308        //
309        if (pObject.getGuid() == null) {
310            String msg = i18n
311                    .getString(IUMSConstants.COS_TARGET_OBJECT_NOT_PERSISTENT);
312            throw new UMSException(msg);
313        }
314
315        if (!(cosDef instanceof DirectCOSDefinition)) {
316            String msg = i18n.getString(IUMSConstants.INVALID_COSDEFINITION);
317            throw new UMSException(msg);
318        }
319
320        if (cosDef instanceof DirectCOSDefinition) {
321            assignDirectCOSDef(pObject, (DirectCOSDefinition) cosDef,
322                    cosTemplate, _schemaManager);
323        }
324    }
325
326    /**
327     * Removes COS assignment from the persistent object. The COS target
328     * persistent object could be a user, group, organization,
329     * organizationalunit, etc. The COS target object must be persistent before
330     * this method can be used.
331     * 
332     * @param pObject
333     *            The COS target persistent object.
334     * @param cosDef
335     *            A COS definition.
336     * @param cosTemplate
337     *            A COS template.
338     * 
339     * @throws UMSException
340     *             The exception thrown if any of the following occur: o the
341     *             target persistent object or COS definition parameter is null.
342     *             o the target object is not persistent. o the COS definition
343     *             is not one of the valid COS definitions. o an exception is
344     *             propagated from any of the "remove" methods.
345     * @supported.api
346     */
347    public void removeCOSAssignment(PersistentObject pObject,
348            ICOSDefinition cosDef, COSTemplate cosTemplate) throws UMSException 
349            {
350        if (pObject == null || cosDef == null) {
351            String msg = i18n
352                    .getString(IUMSConstants.COS_DEF_OR_TARGET_OBJECT_NULL);
353            throw new UMSException(msg);
354        }
355
356        // Do validation....
357        //
358        if (pObject.getGuid() == null) {
359            String msg = i18n
360                    .getString(IUMSConstants.COS_TARGET_OBJECT_NOT_PERSISTENT);
361            throw new UMSException(msg);
362        }
363
364        if (!(cosDef instanceof DirectCOSDefinition)) {
365            String msg = i18n.getString(IUMSConstants.INVALID_COSDEFINITION);
366            throw new UMSException(msg);
367        }
368
369        if (cosDef instanceof DirectCOSDefinition) {
370            removeDirectCOSAssignment(pObject, (DirectCOSDefinition) cosDef,
371                    cosTemplate, _schemaManager);
372        }
373    }
374
375    /**
376     * Removes a Direct COS assignment from a target persistent object. The COS
377     * target persistent object could be a user, group, organization,
378     * organizationalunit, etc. The COS target object must be persistent before
379     * this method can be used.
380     * 
381     * @param pObject
382     *            The COS target persistent object.
383     * @param cosDef
384     *            A COS definition.
385     * @param sMgr
386     *            A SchemaManager object, which is used to determine object
387     *            classes for attributes.
388     * 
389     * @throws UMSException
390     *             The exception thrown if any of the following occur: o an
391     *             exception occurs determining the object class for the COS
392     *             specifier. o an exception occurs determining the object class
393     *             for the COS attributes. o there is an exception thrown rom
394     *             the data layer.
395     */
396    private void removeDirectCOSAssignment(PersistentObject pObject,
397            DirectCOSDefinition cosDef, COSTemplate cosTemplate,
398            SchemaManager sMgr) throws UMSException {
399        ArrayList aList;
400        AttrSet attrSet = new AttrSet();
401
402        try {
403            // Include the attribute (whose name is the cosSpecifier)
404            // in the attribute set for removal (only if it exists).
405            //
406            if (pObject.getAttribute(cosDef.getCOSSpecifier()) != null)
407                attrSet.add(new Attr(cosDef.getCOSSpecifier(), cosTemplate
408                        .getName()));
409
410            // Get cosSpecifier object class - should only be one.
411            // Include the cosSpecifier object class in the attribute
412            // set for removal (only if itt exists).
413            //
414            aList = (ArrayList) sMgr.getObjectClasses(cosDef.getCOSSpecifier());
415            String cosSpecObjectClass = (String) aList.get(0);
416            if (objectClassExists(cosSpecObjectClass, pObject)) {
417                attrSet.add(new Attr("objectclass", cosSpecObjectClass));
418            }
419
420            // Get the cos attributes from the definition (ex. mailquota).
421            // For each of the attributes, get the objectclass. Include the
422            // object classes in the attribute set for removal (if they exist).
423            //
424            String[] cosAttributes = cosDef.getCOSAttributes();
425            String cosAttribute = null;
426            for (int i = 0; i < cosAttributes.length; i++) {
427                // Only get the attribute - not the qualifier
428                //
429                StringTokenizer st = new StringTokenizer(cosAttributes[i]);
430                cosAttribute = st.nextToken();
431                aList = (ArrayList) sMgr.getObjectClasses(cosAttribute);
432                String cosAttributeObjectClass = (String) aList.get(0);
433                if (objectClassExists(cosAttributeObjectClass, pObject)) {
434                    attrSet
435                            .add(new Attr("objectclass",
436                                    cosAttributeObjectClass));
437                }
438            }
439
440            if (attrSet.size() > 0) {
441                ModSet modSet = new ModSet(attrSet, ModSet.DELETE);
442                pObject.modify(modSet);
443                pObject.save();
444            }
445        } catch (UMSException e) {
446            LDAPException le = (LDAPException) e.getRootCause();
447            if (le.getLDAPResultCode() == LDAPException.OBJECT_CLASS_VIOLATION) 
448            {
449                // Ignore... It's not a COS generated attribute's
450                // object class.
451            } else {
452                throw e;
453            }
454        }
455    }
456
457    /**
458     * Assigns a direct (Classic) COS definition to a persistent object.
459     * 
460     * @param pObject
461     *            The target persistent object.
462     * @param cosDef
463     *            The direct (Classic) COS definition.
464     * @param cosTemplate
465     *            A COS template belonging to the definition.
466     * @param sMgr
467     *            A SchemaManager instance.
468     * 
469     * @throws UMSException
470     *             if an exception occurs
471     */
472    private void assignDirectCOSDef(PersistentObject pObject,
473            DirectCOSDefinition cosDef, COSTemplate cosTemplate,
474            SchemaManager sMgr) throws UMSException {
475
476        // Do validation....
477        //
478        if (cosDef.getGuid() == null) {
479            String msg = i18n
480                    .getString(IUMSConstants.COS_DEFINITION_NOT_PERSISTENT);
481            throw new UMSException(msg);
482        }
483
484        // Make sure target entry is in same tree as COS Def parent.
485        //
486        DN targetDN = new DN(pObject.getGuid().getDn());
487        DN cosParentDN = new DN(cosDef.getParentGuid().getDn());
488        if (!(targetDN.isDescendantOf(cosParentDN))) {
489            String msg = i18n
490                    .getString(IUMSConstants.COS_TARGET_OBJECT_DIFFERENT_TREE);
491            throw new UMSException(msg);
492        }
493
494        // If cosSpecifier is "nsRole", then we don't need to go
495        // any further (we don't need to update target entries).
496        //
497        if (cosDef.getCOSSpecifier().equalsIgnoreCase("nsrole"))
498            return;
499
500        ArrayList aList;
501        AttrSet attrSet = new AttrSet();
502
503        // Get cosSpecifier object class - should only be one.
504        // Update the target entry with cosSpecifier object class.
505        // Only add it if it doesn't already exist.
506        //
507        aList = (ArrayList) sMgr.getObjectClasses(cosDef.getCOSSpecifier());
508        String cosSpecObjectClass = (String) aList.get(0);
509        if (!objectClassExists(cosSpecObjectClass, pObject)) {
510            attrSet.add(new Attr("objectclass", cosSpecObjectClass));
511        }
512
513        // Get the cos attributes from the definition (ex. mailquota).
514        // For each of the attributes, get the objectclass. These
515        // will be used to attach to the target entry. This is only
516        // done if the cos attribute qualifier is not "operational"
517        // (you don't need to add cos attribute object classes for
518        // "operational" cos attribute qualifier.
519        //
520        String[] cosAttributes = cosDef.getCOSAttributes();
521        String qualifier = null;
522        Arrays.asList(ICOSDefinition.qualifiers);
523        Attr attr = cosTemplate.getAttribute("objectclass");
524        String[] cosTempObjClasses = attr.getStringValues();
525
526        for (int i = 0; i < cosAttributes.length; i++) {
527            StringTokenizer st = new StringTokenizer(cosAttributes[i]);
528            st.nextToken();
529            qualifier = st.nextToken();
530            if ((!qualifier.equals(
531                    ICOSDefinition.qualifiers[ICOSDefinition.OPERATIONAL]))) {
532                for (int j = 0; j < cosTempObjClasses.length; j++) {
533                    if (!cosTempObjClasses[j].equalsIgnoreCase("top")
534                       && !cosTempObjClasses[j].equalsIgnoreCase("costemplate")
535                       && !objectClassExists(cosTempObjClasses[j], pObject)) 
536                    {
537                        if (!attrSet.contains("objectclass",
538                                cosTempObjClasses[j])) {
539                            attrSet.add(new Attr("objectclass",
540                                    cosTempObjClasses[j]));
541                        }
542                    }
543                }
544            }
545        }
546
547        // Add the attribute name (cosSpecifier value) and attribute
548        // value (cosTemplate name) only if it doesn't exist.
549        //
550        if (pObject.getAttribute(cosDef.getCOSSpecifier()) == null)
551            attrSet.add(new Attr(cosDef.getCOSSpecifier(), cosTemplate
552                    .getName()));
553
554        if (attrSet.size() > 0) {
555            ModSet modSet = new ModSet(attrSet);
556            pObject.modify(modSet);
557            pObject.save();
558        }
559    }
560
561    /**
562     * Utility method to check if an object class exists in a persistent object.
563     * 
564     * @param objectclass
565     *            The object class.
566     * @param pObject
567     *            The persistent object.
568     */
569    private boolean objectClassExists(String objectClass,
570            PersistentObject pObject) {
571        Attr attr = pObject.getAttribute("objectclass");
572        String[] vals = attr.getStringValues();
573        for (int i = 0; i < vals.length; i++) {
574            if (objectClass.equalsIgnoreCase(vals[i])) {
575                return true;
576            }
577        }
578        return false;
579    }
580
581    //
582    // Definition Search Attributes
583    //
584    private static final String[] DEF_ATTRIBUTE_NAMES = { "objectclass",
585            ICOSDefinition.DEFAULT_NAMING_ATTR, ICOSDefinition.COSTEMPLATEDN,
586            ICOSDefinition.COSSPECIFIER, ICOSDefinition.COSATTRIBUTE,
587            ICOSDefinition.ICOSSPECIFIER };
588
589    private PersistentObject _parentObject;
590
591    private SchemaManager _schemaManager;
592
593    private static I18n i18n = I18n.getInstance(IUMSConstants.UMS_PKG);
594}