001    /**
002     * Copyright 2005-2012 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     */
016    package org.kuali.rice.kew.xml;
017    
018    import org.jdom.Document;
019    import org.jdom.Element;
020    import org.jdom.JDOMException;
021    import org.kuali.rice.core.api.util.xml.XmlException;
022    import org.kuali.rice.core.api.util.xml.XmlHelper;
023    import org.kuali.rice.kew.api.KewApiConstants;
024    import org.kuali.rice.kew.util.Utilities;
025    import org.kuali.rice.kim.api.KimConstants;
026    import org.kuali.rice.kim.api.group.Group;
027    import org.kuali.rice.kim.api.group.GroupService;
028    import org.kuali.rice.kim.api.identity.IdentityService;
029    import org.kuali.rice.kim.api.identity.principal.Principal;
030    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
031    import org.kuali.rice.kim.api.type.KimType;
032    import org.kuali.rice.kim.api.type.KimTypeAttribute;
033    import org.xml.sax.SAXException;
034    
035    import javax.xml.parsers.ParserConfigurationException;
036    import java.io.IOException;
037    import java.io.InputStream;
038    import java.util.ArrayList;
039    import java.util.HashMap;
040    import java.util.List;
041    import java.util.Map;
042    
043    import static org.kuali.rice.core.api.impex.xml.XmlConstants.*;
044    
045    
046    /**
047     * Parses groups from XML.
048     *
049     * @see Group
050     *
051     * @author Kuali Rice Team (rice.collab@kuali.org)
052     *
053     */
054    public class GroupXmlParser {
055        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(GroupXmlParser.class);
056        private static final boolean DEFAULT_ACTIVE_VALUE = true;
057        private static final String DEFAULT_GROUP_DESCRIPTION = "";
058        private HashMap<String, List<String>> memberGroupIds = new HashMap<String, List<String>>();
059        private HashMap<String, List<String>> memberGroupNames = new HashMap<String, List<String>>();
060        private HashMap<String, List<String>> memberPrincipalIds = new HashMap<String, List<String>>();
061        private Map<String, String> groupAttributes = new HashMap<String, String>();
062    
063        public List<Group> parseGroups(InputStream input) throws IOException, XmlException {
064            try {
065                Document doc = XmlHelper.trimSAXXml(input);
066                Element root = doc.getRootElement();
067                return parseGroups(root);
068            } catch (JDOMException e) {
069                throw new XmlException("Parse error.", e);
070            } catch (SAXException e){
071                throw new XmlException("Parse error.",e);
072            } catch(ParserConfigurationException e){
073                throw new XmlException("Parse error.",e);
074            }
075        }
076    
077    
078        /**
079         * Parses and saves groups
080         * @param element top-level 'data' element which should contain a <groups> child element
081         * @return a list of parsed and saved, current, groups;
082         * @throws XmlException
083         */
084        @SuppressWarnings("unchecked")
085            public List<Group> parseGroups(Element element) throws XmlException {
086            List<Group> groups = new ArrayList<Group>();
087            for (Element groupsElement: (List<Element>) element.getChildren(GROUPS, GROUP_NAMESPACE)) {
088    
089                for (Element groupElement: (List<Element>) groupsElement.getChildren(GROUP, GROUP_NAMESPACE)) {
090                    groups.add(parseGroup(groupElement));
091                }
092            }
093            for (Group group : groups) {
094                GroupService groupService = KimApiServiceLocator.getGroupService();
095                // check if group already exists
096                Group foundGroup = groupService.getGroupByNamespaceCodeAndName(group.getNamespaceCode(), group.getName());
097    
098                if (foundGroup == null) {
099                    if ( LOG.isInfoEnabled() ) {
100                            LOG.info("Group named '" + group.getName() + "' not found, creating new group named '" + group.getName() + "'");
101                    }
102                    try {
103                        Group newGroup =  groupService.createGroup(group);
104    
105                        String key = newGroup.getNamespaceCode().trim() + KewApiConstants.KIM_GROUP_NAMESPACE_NAME_DELIMITER_CHARACTER + newGroup.getName().trim();
106                        addGroupMembers(newGroup, key);
107                    } catch (Exception e) {
108                        throw new RuntimeException("Error creating group with name '" + group.getName() + "'", e);
109                    }
110                } else {
111                    if ( LOG.isInfoEnabled() ) {
112                            LOG.info("Group named '" + group.getName() + "' found, creating a new version");
113                    }
114                    try {
115                        Group.Builder builder = Group.Builder.create(foundGroup);
116                        builder.setActive(group.isActive());
117                        builder.setDescription(group.getDescription());
118                        builder.setKimTypeId(group.getKimTypeId());
119    
120                        //builder.setVersionNumber(foundGroup.getVersionNumber());
121                        group = builder.build();
122                        groupService.updateGroup(foundGroup.getId(), group);
123    
124                        //delete existing group members and replace with new
125                        groupService.removeAllMembers(foundGroup.getId());
126    
127                        String key = group.getNamespaceCode().trim() + KewApiConstants.KIM_GROUP_NAMESPACE_NAME_DELIMITER_CHARACTER + group.getName().trim();
128                        addGroupMembers(group, key);
129    
130                    } catch (Exception e) {
131                        throw new RuntimeException("Error updating group.", e);
132                    }
133                }
134            }
135            return groups;
136        }
137    
138        @SuppressWarnings("unchecked")
139            private Group parseGroup(Element element) throws XmlException {
140    
141    
142            // Type element and children (namespace and name)
143    
144            String typeId = null;
145            KimType kimTypeInfo;
146            List<KimTypeAttribute> kimTypeAttributes = new ArrayList<KimTypeAttribute>();
147            if (element.getChild(TYPE, GROUP_NAMESPACE) != null) {
148                Element typeElement = element.getChild(TYPE, GROUP_NAMESPACE);
149                String typeNamespace = typeElement.getChildText(NAMESPACE, GROUP_NAMESPACE);
150                String typeName = typeElement.getChildText(NAME, GROUP_NAMESPACE);
151                kimTypeInfo = KimApiServiceLocator.getKimTypeInfoService().findKimTypeByNameAndNamespace(typeNamespace, typeName);
152                if (kimTypeInfo != null) {
153                    typeId = kimTypeInfo.getId();
154                    kimTypeAttributes = kimTypeInfo.getAttributeDefinitions();
155                } else  {
156                    throw new XmlException("Invalid type name and namespace specified.");
157                }
158            } else { //set to default type
159                kimTypeInfo = KimApiServiceLocator.getKimTypeInfoService().findKimTypeByNameAndNamespace(KimConstants.KIM_TYPE_DEFAULT_NAMESPACE, KimConstants.KIM_TYPE_DEFAULT_NAME);
160                if (kimTypeInfo != null) {
161                    typeId = kimTypeInfo.getId();
162                    kimTypeAttributes = kimTypeInfo.getAttributeDefinitions();
163                } else {
164                    throw new RuntimeException("Failed to locate the 'Default' group type!  Please ensure that it's in your database.");
165                }
166            }
167            //groupInfo.setKimTypeId(typeId);
168    
169            String groupNamespace = element.getChildText(NAMESPACE, GROUP_NAMESPACE);
170            if (groupNamespace == null) {
171                throw new XmlException("Namespace must have a value.");
172            }
173    
174            String groupName = element.getChildText(NAME, GROUP_NAMESPACE);
175            if (groupName == null) {
176                throw new XmlException("Name must have a value.");
177            }
178    
179            Group.Builder groupInfo = Group.Builder.create(groupNamespace, groupName, typeId);
180            IdentityService identityService = KimApiServiceLocator.getIdentityService();
181            //groupInfo.setGroupName(element.getChildText(NAME, GROUP_NAMESPACE));
182    
183            String id = element.getChildText(ID, GROUP_NAMESPACE);
184            if (id != null) {
185                groupInfo.setId(id.trim());
186            } else {
187                    
188            }
189    
190            String description = element.getChildText(DESCRIPTION, GROUP_NAMESPACE);
191            if (description != null && !description.trim().equals("")) {
192                groupInfo.setDescription(description);
193            }
194    
195            //Active Indicator
196            groupInfo.setActive(DEFAULT_ACTIVE_VALUE);
197            if (element.getChildText(ACTIVE, GROUP_NAMESPACE) != null) {
198                String active = element.getChildText(ACTIVE, GROUP_NAMESPACE).trim();
199                if (active.toUpperCase().equals("N") || active.toUpperCase().equals("FALSE")) {
200                    groupInfo.setActive(false);
201                }
202            }
203    
204            //Get list of attribute keys
205            List<String> validAttributeKeys = new ArrayList<String>();
206            for (KimTypeAttribute attribute : kimTypeAttributes) {
207                validAttributeKeys.add(attribute.getKimAttribute().getAttributeName());
208            }
209            //Group attributes
210            if (element.getChild(ATTRIBUTES, GROUP_NAMESPACE) != null) {
211                List<Element> attributes = element.getChild(ATTRIBUTES, GROUP_NAMESPACE).getChildren();
212    
213                Map<String, String> attrMap = new HashMap<String, String>();
214                for (Element attr : attributes ) {
215                    attrMap.put(attr.getAttributeValue(KEY), attr.getAttributeValue(VALUE));
216                    if (!validAttributeKeys.contains(attr.getAttributeValue(KEY))) {
217                        throw new XmlException("Invalid attribute specified.");
218                    }
219                }
220                Map<String, String> groupAttributes = attrMap;
221                if (!groupAttributes.isEmpty()) {
222                    groupInfo.setAttributes(groupAttributes);
223                }
224            }
225    
226            //Group members
227    
228            List<Element> members = null;
229            if(element.getChild(MEMBERS, GROUP_NAMESPACE) == null) {
230                members = new ArrayList<Element>();
231            }
232            else {
233                members = element.getChild(MEMBERS, GROUP_NAMESPACE).getChildren();
234            }
235            for (Element member : members) {
236                String elementName = member.getName().trim();
237                if (elementName.equals(PRINCIPAL_NAME)) {
238                    String principalName = member.getText().trim();
239                    Principal principal = identityService.getPrincipalByPrincipalName(principalName);
240                    if (principal != null) {
241                        addPrincipalToGroup(groupInfo.getNamespaceCode(), groupInfo.getName(), principal.getPrincipalId());
242                    } else {
243                        throw new XmlException("Principal Name "+principalName+" cannot be found.");
244                    }
245                } else if (elementName.equals(PRINCIPAL_ID)) {
246                    String xmlPrincipalId = member.getText().trim();
247                    Principal principal = identityService.getPrincipal(xmlPrincipalId);
248                    if (principal != null) {
249                        addPrincipalToGroup(groupInfo.getNamespaceCode(), groupInfo.getName(), principal.getPrincipalId());
250                    } else {
251                        throw new XmlException("Principal Id "+xmlPrincipalId+" cannot be found.");
252                    }
253                // Groups are handled differently since the member group may not be saved yet.  Therefore they need to be validated after the groups are saved.
254                } else if (elementName.equals(GROUP_ID)) {
255                    String xmlGroupId = member.getText().trim();
256                    addGroupToGroup(groupInfo.getNamespaceCode(), groupInfo.getName(), xmlGroupId);
257                } else if (elementName.equals(GROUP_NAME)) {
258                    String xmlGroupName = member.getChildText(NAME, GROUP_NAMESPACE).trim();
259                    String xmlGroupNamespace = member.getChildText(NAMESPACE, GROUP_NAMESPACE).trim();
260                    addGroupNameToGroup(groupInfo.getNamespaceCode(), groupInfo.getName(), xmlGroupNamespace, xmlGroupName);
261                } else {
262                    LOG.error("Unknown member element: " + elementName);
263                }
264    
265    
266            }
267    
268            return groupInfo.build();
269    
270        }
271    
272        private void addPrincipalToGroup(String groupNamespace, String groupName, String principalId) {
273            String key = groupNamespace.trim() + KewApiConstants.KIM_GROUP_NAMESPACE_NAME_DELIMITER_CHARACTER + groupName.trim();
274            List<String> principalIds = memberPrincipalIds.get(key);
275            if (principalIds == null) {
276                principalIds = new ArrayList<String>();
277            }
278            principalIds.add(principalId);
279            memberPrincipalIds.put(key, principalIds);
280        }
281    
282        private void addGroupToGroup(String groupNamespace, String groupName, String groupId) {
283            String key = groupNamespace.trim() + KewApiConstants.KIM_GROUP_NAMESPACE_NAME_DELIMITER_CHARACTER + groupName.trim();
284            List<String> groupIds = memberGroupIds.get(key);
285            if (groupIds == null) {
286                groupIds = new ArrayList<String>();
287            }
288            groupIds.add(groupId);
289            memberGroupIds.put(key, groupIds);
290        }
291    
292        private void addGroupNameToGroup(String groupNamespace, String groupName, String memberGroupNamespace, String memberGroupName) {
293            String key = groupNamespace.trim() + KewApiConstants.KIM_GROUP_NAMESPACE_NAME_DELIMITER_CHARACTER + groupName.trim();
294            List<String> groupNames = memberGroupNames.get(key);
295            if (groupNames == null) {
296                groupNames = new ArrayList<String>();
297            }
298            groupNames.add(memberGroupNamespace.trim() + KewApiConstants.KIM_GROUP_NAMESPACE_NAME_DELIMITER_CHARACTER + memberGroupName.trim());
299            memberGroupNames.put(key, groupNames);
300        }
301    
302        private void addGroupMembers(Group groupInfo, String key) throws XmlException {
303            GroupService groupService = KimApiServiceLocator.getGroupService();
304            List<String> groupIds = memberGroupIds.get(key);
305            if (groupIds != null) {
306                for (String groupId : groupIds) {
307                    Group group = groupService.getGroup(groupId);
308                    if (group != null) {
309                        groupService.addGroupToGroup(group.getId(), groupInfo.getId());
310                    } else {
311                        throw new XmlException("Group Id "+groupId+" cannot be found.");
312                    }
313                }
314            }
315            List<String> groupNames = memberGroupNames.get(key);
316            if (groupNames != null) {
317                for (String groupName : groupNames) {
318                    Group group = groupService.getGroupByNamespaceCodeAndName(Utilities.parseGroupNamespaceCode(groupName),
319                            Utilities.parseGroupName(groupName));
320                    if (group != null) {
321                            groupService.addGroupToGroup(group.getId(), groupInfo.getId());
322                    } else {
323                        throw new XmlException("Group "+groupName+" cannot be found.");
324                    }
325                }
326            }
327            List<String> principalIds = memberPrincipalIds.get(key);
328            if (principalIds != null) {
329                for (String principalId : principalIds) {
330                    groupService.addPrincipalToGroup(principalId, groupInfo.getId());
331                }
332            }
333    
334        }
335    }