001/**
002 * Copyright 2005-2011 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.kim.impl.jaxb;
017
018import java.io.Serializable;
019import java.util.HashMap;
020import java.util.Map;
021
022import javax.xml.bind.Unmarshaller;
023import javax.xml.bind.annotation.XmlAccessType;
024import javax.xml.bind.annotation.XmlAccessorType;
025import javax.xml.bind.annotation.XmlElement;
026import javax.xml.bind.annotation.XmlTransient;
027import javax.xml.bind.annotation.XmlType;
028import javax.xml.bind.annotation.adapters.NormalizedStringAdapter;
029import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
030
031import org.apache.commons.lang.StringUtils;
032import org.joda.time.DateTime;
033import org.kuali.rice.core.api.membership.MemberType;
034import org.kuali.rice.core.api.util.jaxb.DateTimeAdapter;
035import org.kuali.rice.core.util.jaxb.NameAndNamespacePair;
036import org.kuali.rice.core.util.jaxb.NameAndNamespacePairValidatingAdapter;
037import org.kuali.rice.kim.api.group.GroupContract;
038import org.kuali.rice.kim.api.identity.principal.PrincipalContract;
039import org.kuali.rice.kim.api.jaxb.QualificationListAdapter;
040import org.kuali.rice.kim.api.role.RoleContract;
041import org.kuali.rice.kim.api.role.RoleMember;
042import org.kuali.rice.kim.api.services.KimApiServiceLocator;
043
044/**
045 * Base class representing an unmarshalled <roleMember> element.
046 * Refer to the static inner classes for more information about the specific contexts.
047 * 
048 * @author Kuali Rice Team (rice.collab@kuali.org)
049 */
050@XmlTransient
051public abstract class RoleMemberXmlDTO implements Serializable {
052
053    private static final long serialVersionUID = 1L;
054
055    @XmlElement(name="principalId")
056    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
057    private String principalId;
058    
059    @XmlElement(name="principalName")
060    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
061    private String principalName;
062    
063    @XmlElement(name="groupId")
064    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
065    private String groupId;
066    
067    @XmlElement(name="groupName")
068    @XmlJavaTypeAdapter(NameAndNamespacePairValidatingAdapter.class)
069    private NameAndNamespacePair groupName;
070    
071    @XmlElement(name="roleIdAsMember")
072    @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
073    private String roleIdAsMember;
074    
075    @XmlElement(name="roleNameAsMember")
076    @XmlJavaTypeAdapter(NameAndNamespacePairValidatingAdapter.class)
077    private NameAndNamespacePair roleNameAsMember;
078    
079    @XmlElement(name="activeFromDate")
080    @XmlJavaTypeAdapter(DateTimeAdapter.class)
081    private DateTime activeFromDate;
082    
083    @XmlElement(name="activeToDate")
084    @XmlJavaTypeAdapter(DateTimeAdapter.class)
085    private DateTime activeToDate;
086    
087    @XmlElement(name="qualifications")
088    @XmlJavaTypeAdapter(QualificationListAdapter.class)
089    private Map<String, String> qualifications;
090    
091    @XmlTransient
092    private MemberType memberType;
093    
094    /**
095     * Constructs an empty RoleMemberXmlDTO instance.
096     */
097    public RoleMemberXmlDTO() {}
098    
099    /**
100     * Constructs a RoleMemberXmlDTO instance that is populated with the info from the given role member.
101     * 
102     * @param roleMember The role member that this DTO should populate its data from.
103     * @param populateMemberId If true, the member principal/group/role ID will get populated; otherwise, only
104     * the member principal/group/role name and (if applicable) namespace will get populated.
105     * @throws IllegalArgumentException if roleMember is null, has an invalid member type, or refers to a nonexistent principal/group/role.
106     */
107    public RoleMemberXmlDTO(RoleMember roleMember, boolean populateMemberId) {
108        if (roleMember == null) {
109            throw new IllegalArgumentException("roleMember cannot be null");
110        }
111        this.memberType = roleMember.getType();
112        this.activeFromDate = roleMember.getActiveFromDate();
113        this.activeToDate = roleMember.getActiveToDate();
114        this.qualifications = (roleMember.getAttributes() != null) ? roleMember.getAttributes() : new HashMap<String, String>();
115        
116        if (MemberType.PRINCIPAL.equals(memberType)) {
117            if (populateMemberId) {
118                this.principalId = roleMember.getMemberId();
119            }
120            PrincipalContract principal = KimApiServiceLocator.getIdentityService().getPrincipal(
121                    roleMember.getMemberId());
122            if (principal == null) {
123                throw new IllegalArgumentException("Cannot find principal with ID \"" +  roleMember.getMemberId() + "\"");
124            }
125            this.principalName = principal.getPrincipalName();
126        } else if (MemberType.GROUP.equals(memberType)) {
127            if (populateMemberId) {
128                this.groupId = roleMember.getMemberId();
129            }
130            GroupContract group = KimApiServiceLocator.getGroupService().getGroup(roleMember.getMemberId());
131            if (group == null) {
132                throw new IllegalArgumentException("Cannot find group with ID \"" + roleMember.getMemberId() + "\"");
133            }
134            this.groupName = new NameAndNamespacePair(group.getNamespaceCode(), group.getName());
135        } else if (MemberType.ROLE.equals(memberType)) {
136            if (populateMemberId) {
137                this.roleIdAsMember = roleMember.getMemberId();
138            }
139            RoleContract role = KimApiServiceLocator.getRoleService().getRole(roleMember.getMemberId());
140            if (role == null) {
141                throw new IllegalArgumentException("Cannot find role with ID \"" + roleMember.getMemberId() + "\"");
142            }
143            this.roleNameAsMember = new NameAndNamespacePair(role.getNamespaceCode(), role.getName());
144        } else {
145            throw new IllegalArgumentException("Cannot construct a RoleMemberXmlDTO from a role member with an unrecognized member type code of \"" +
146                    memberType + "\"");
147        }    
148    }
149
150    /**
151     * @return the principalId
152     */
153    public String getPrincipalId() {
154        return this.principalId;
155    }
156
157    /**
158     * @param principalId the principalId to set
159     */
160    public void setPrincipalId(String principalId) {
161        this.principalId = principalId;
162    }
163
164    /**
165     * @return the principalName
166     */
167    public String getPrincipalName() {
168        return this.principalName;
169    }
170
171    /**
172     * @param principalName the principalName to set
173     */
174    public void setPrincipalName(String principalName) {
175        this.principalName = principalName;
176    }
177
178    /**
179     * @return the groupId
180     */
181    public String getGroupId() {
182        return this.groupId;
183    }
184
185    /**
186     * @param groupId the groupId to set
187     */
188    public void setGroupId(String groupId) {
189        this.groupId = groupId;
190    }
191
192    /**
193     * @return the groupName
194     */
195    public NameAndNamespacePair getGroupName() {
196        return this.groupName;
197    }
198
199    /**
200     * @param groupName the groupName to set
201     */
202    public void setGroupName(NameAndNamespacePair groupName) {
203        this.groupName = groupName;
204    }
205
206    /**
207     * @return the roleIdAsMember
208     */
209    public String getRoleIdAsMember() {
210        return this.roleIdAsMember;
211    }
212
213    /**
214     * @param roleIdAsMember the roleIdAsMember to set
215     */
216    public void setRoleIdAsMember(String roleIdAsMember) {
217        this.roleIdAsMember = roleIdAsMember;
218    }
219
220    /**
221     * @return the roleNameAsMember
222     */
223    public NameAndNamespacePair getRoleNameAsMember() {
224        return this.roleNameAsMember;
225    }
226
227    /**
228     * @param roleNameAsMember the roleNameAsMember to set
229     */
230    public void setRoleNameAsMember(NameAndNamespacePair roleNameAsMember) {
231        this.roleNameAsMember = roleNameAsMember;
232    }
233
234    /**
235     * @return the activeFromDate
236     */
237    public DateTime getActiveFromDate() {
238        return this.activeFromDate;
239    }
240
241    /**
242     * @param activeFromDate the activeFromDate to set
243     */
244    public void setActiveFromDate(DateTime activeFromDate) {
245        this.activeFromDate = activeFromDate;
246    }
247
248    /**
249     * @return the activeToDate
250     */
251    public DateTime getActiveToDate() {
252        return this.activeToDate;
253    }
254
255    /**
256     * @param activeToDate the activeToDate to set
257     */
258    public void setActiveToDate(DateTime activeToDate) {
259        this.activeToDate = activeToDate;
260    }
261
262    /**
263     * @return the qualifications
264     */
265    public Map<String, String> getQualifications() {
266        return this.qualifications;
267    }
268
269    /**
270     * @param qualifications the qualifications to set
271     */
272    public void setQualifications(Map<String, String> qualifications) {
273        this.qualifications = qualifications;
274    }
275
276    /**
277     * Retrieves the member type.
278     * 
279     * <p>If the member type is null at the time that this method is invoked, an attempt will be made to set its
280     * value based on any populated member principal/group/role ID/name information.
281     * 
282     * @return the member type, or null if no membership identification information has been set on this member.
283     * @throws IllegalStateException if the role member is populated simultaneously with multiple member ID/name information
284     */
285    public MemberType getMemberType() {
286        if (memberType == null) {
287            boolean foundMemberInfo = false;
288            
289            if (StringUtils.isNotBlank(principalId) || StringUtils.isNotBlank(principalName)) {
290                memberType = MemberType.PRINCIPAL;
291                foundMemberInfo = true;
292            }
293            
294            if (StringUtils.isNotBlank(groupId) || groupName != null) {
295                if (foundMemberInfo) {
296                    memberType = null;
297                    throw new IllegalStateException("Cannot have a role member that is simultaneously populated with member principal, member group, and/or member role information");
298                }
299                memberType = MemberType.GROUP;
300                foundMemberInfo = true;
301            }
302            
303            if (StringUtils.isNotBlank(roleIdAsMember) || roleNameAsMember != null) {
304                if (foundMemberInfo) {
305                    memberType = null;
306                    throw new IllegalStateException("Cannot have a role member that is simultaneously populated with member principal, member group, and/or member role information");
307                }
308                memberType = MemberType.ROLE;
309                foundMemberInfo = true;
310            }
311        }
312        return this.memberType;
313    }
314
315    /**
316     * Retrieves the role member's ID, based on the member type and any populated member principal/group/role IDs.
317     * 
318     * <p>If the member type is null at the time that this method is invoked, an attempt will be made to set its
319     * value based on any populated member principal/group/role ID/name information.
320     * 
321     * @return The member's ID, or null if the member type is null or the associated member ID information is null.
322     */
323    public String getMemberId() {
324        if (MemberType.PRINCIPAL.equals(getMemberType())) {
325            return principalId;
326        } else if (MemberType.GROUP.equals(getMemberType())) {
327            return groupId;
328        } else if (MemberType.ROLE.equals(getMemberType())) {
329            return roleIdAsMember;
330        }
331        return null;
332    }
333    
334    /**
335     * Retrieves the role member's name, based on the member type and any populated member principal/group/role names.
336     * 
337     * <p>If the member type is null at the time that this method is invoked, an attempt will be made to set its
338     * value based on any populated member principal/group/role ID/name information.
339     * 
340     * @return The member's name, or null if the member type is null or the associated member name information is null.
341     */
342    public String getMemberName() {
343        if (MemberType.PRINCIPAL.equals(getMemberType())) {
344            return principalName;
345        } else if (MemberType.GROUP.equals(getMemberType())) {
346            return (groupName != null) ? groupName.getName() : null;
347        } else if (MemberType.ROLE.equals(getMemberType())) {
348            return (roleNameAsMember != null) ? roleNameAsMember.getName() : null;
349        }
350        return null;
351    }
352    
353    /**
354     * Retrieves the role member's namespace code, based on the member type and any populated member principal/group/role names.
355     * 
356     * <p>If the member type is null at the time that this method is invoked, an attempt will be made to set its
357     * value based on any populated member principal/group/role ID/name information.
358     * 
359     * @return The member's namespace code, or null if the member type is null, the associated member name information is null,
360     * or the role member is a principal.
361     */
362    public String getMemberNamespaceCode() {
363        if (MemberType.PRINCIPAL.equals(getMemberType())) {
364            return null;
365        } else if (MemberType.GROUP.equals(getMemberType())) {
366            return (groupName != null) ? groupName.getName() : null;
367        } else if (MemberType.ROLE.equals(getMemberType())) {
368            return (roleNameAsMember != null) ? roleNameAsMember.getName() : null;
369        }
370        return null;
371    }
372    
373    /**
374     * Retrieves the ID of the role that this member belongs to.
375     * Subclasses are responsible for implementing this method so that it does so.
376     * 
377     * @return The role ID of the role that this member belongs to.
378     */
379    public abstract String getRoleId();
380    
381    // =======================================================================================================
382    
383    /**
384     * This class represents a &lt;roleMember&gt; element that is not a descendant of a &lt;role&gt; element.
385     * 
386     * @author Kuali Rice Team (rice.collab@kuali.org)
387     */
388    @XmlAccessorType(XmlAccessType.FIELD)
389    @XmlType(name="StandaloneRoleMemberType", propOrder={
390            "roleId", "roleNameAndNamespace", "principalId", "principalName", "groupId", "groupName", "roleIdAsMember",
391                    "roleNameAsMember", "activeFromDate", "activeToDate", "qualifications"
392    })
393    public static class OutsideOfRole extends RoleMemberXmlDTO {
394
395        private static final long serialVersionUID = 1L;
396
397        @XmlElement(name="roleId")
398        @XmlJavaTypeAdapter(NormalizedStringAdapter.class)
399        private String roleId;
400
401        @XmlElement(name="roleName")
402        @XmlJavaTypeAdapter(NameAndNamespacePairValidatingAdapter.class)
403        private NameAndNamespacePair roleNameAndNamespace;
404        
405        public OutsideOfRole() {
406            super();
407        }
408        
409        public OutsideOfRole(RoleMember roleMember, boolean populateMemberId) {
410            super(roleMember, populateMemberId);
411            this.roleId = roleMember.getRoleId();
412            RoleContract tempRole = KimApiServiceLocator.getRoleService().getRole(roleId);
413            if (tempRole == null) {
414                throw new IllegalArgumentException("Cannot find role with ID \"" + roleId + "\"");
415            }
416            this.roleNameAndNamespace = new NameAndNamespacePair(tempRole.getNamespaceCode(), tempRole.getName());
417        }
418        
419        /**
420         * @see org.kuali.rice.kim.impl.jaxb.RoleMemberXmlDTO#getRoleId()
421         */
422        @Override
423        public String getRoleId() {
424            return roleId;
425        }
426
427        /**
428         * @param roleId the roleId to set
429         */
430        public void setRoleId(String roleId) {
431            this.roleId = roleId;
432        }
433        
434        /**
435         * @return the roleNameAndNamespace
436         */
437        public NameAndNamespacePair getRoleNameAndNamespace() {
438            return this.roleNameAndNamespace;
439        }
440
441        /**
442         * @param roleNameAndNamespace the roleNameAndNamespace to set
443         */
444        public void setRoleNameAndNamespace(NameAndNamespacePair roleNameAndNamespace) {
445            this.roleNameAndNamespace = roleNameAndNamespace;
446        }
447
448        /**
449         * Retrieves the role name from the role-name-and-namespace combo.
450         * 
451         * @return The name of the role that this member belongs to, or null if the role-name-and-namespace combo is null.
452         */
453        public String getRoleName() {
454            return (roleNameAndNamespace != null) ? roleNameAndNamespace.getName() : null;
455        }
456
457        /**
458         * Retrieves the role namespace code from the role-name-and-namespace combo.
459         * 
460         * @return The namespace code of the role that this member belongs to, or null if the role-name-and-namespace combo is null.
461         */
462        public String getRoleNamespaceCode() {
463            return (roleNameAndNamespace != null) ? roleNameAndNamespace.getNamespaceCode() : null;
464        }
465    }
466    
467    // =======================================================================================================
468    
469    /**
470     * This class represents a &lt;roleMember&gt; element that is a descendant of a &lt;role&gt; element.
471     * 
472     * @author Kuali Rice Team (rice.collab@kuali.org)
473     */
474    @XmlAccessorType(XmlAccessType.FIELD)
475    @XmlType(name="RoleMemberType", propOrder={
476            "principalId", "principalName", "groupId", "groupName", "roleIdAsMember",
477                    "roleNameAsMember", "activeFromDate", "activeToDate", "qualifications"
478    })
479    public static class WithinRole extends RoleMemberXmlDTO {
480
481        private static final long serialVersionUID = 1L;
482        
483        @XmlTransient
484        private String roleId;
485
486        public WithinRole() {
487            super();
488        }
489        
490        public WithinRole(RoleMember roleMember, boolean populateMemberId) {
491            super(roleMember, populateMemberId);
492            this.roleId = roleMember.getRoleId();
493        }
494        
495        void beforeUnmarshal(Unmarshaller unmarshaller, Object parent) {
496            if (parent instanceof RoleMembersXmlDTO.WithinRole) {
497                this.roleId = ((RoleMembersXmlDTO.WithinRole)parent).getRoleId();
498            }
499        }
500        
501        /**
502         * @see org.kuali.rice.kim.impl.jaxb.RoleMemberXmlDTO#getRoleId()
503         */
504        @Override
505        public String getRoleId() {
506            return roleId;
507        }
508        
509    }
510}