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 }