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