001/**
002 * Copyright 2005-2017 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.kew.xml;
017
018import org.jdom.Document;
019import org.jdom.Element;
020import org.jdom.JDOMException;
021import org.kuali.rice.core.api.util.xml.XmlException;
022import org.kuali.rice.core.api.util.xml.XmlHelper;
023import org.kuali.rice.kew.api.KewApiConstants;
024import org.kuali.rice.kew.util.Utilities;
025import org.kuali.rice.kim.api.KimConstants;
026import org.kuali.rice.kim.api.group.Group;
027import org.kuali.rice.kim.api.group.GroupService;
028import org.kuali.rice.kim.api.identity.IdentityService;
029import org.kuali.rice.kim.api.identity.principal.Principal;
030import org.kuali.rice.kim.api.services.KimApiServiceLocator;
031import org.kuali.rice.kim.api.type.KimType;
032import org.kuali.rice.kim.api.type.KimTypeAttribute;
033import org.xml.sax.SAXException;
034
035import javax.xml.parsers.ParserConfigurationException;
036import java.io.IOException;
037import java.io.InputStream;
038import java.util.ArrayList;
039import java.util.HashMap;
040import java.util.List;
041import java.util.Map;
042
043import 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 */
054public 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}