001 /** 002 * Copyright 2005-2015 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.kim.lookup; 017 018 import org.apache.commons.collections.CollectionUtils; 019 import org.apache.commons.collections.Predicate; 020 import org.apache.commons.lang.StringUtils; 021 import org.kuali.rice.core.api.criteria.QueryByCriteria; 022 import org.kuali.rice.kim.api.KimConstants; 023 import org.kuali.rice.kim.api.group.Group; 024 import org.kuali.rice.kim.api.group.GroupQueryResults; 025 import org.kuali.rice.kim.api.identity.entity.Entity; 026 import org.kuali.rice.kim.api.identity.entity.EntityQueryResults; 027 import org.kuali.rice.kim.api.identity.principal.Principal; 028 import org.kuali.rice.kim.api.services.KimApiServiceLocator; 029 import org.kuali.rice.kim.impl.role.RoleBo; 030 import org.kuali.rice.kim.impl.role.RoleMemberBo; 031 import org.kuali.rice.kns.web.ui.Field; 032 import org.kuali.rice.kns.web.ui.Row; 033 import org.kuali.rice.krad.bo.BusinessObject; 034 import org.kuali.rice.krad.lookup.CollectionIncomplete; 035 import org.kuali.rice.krad.util.KRADPropertyConstants; 036 037 import java.sql.Timestamp; 038 import java.util.ArrayList; 039 import java.util.Arrays; 040 import java.util.HashMap; 041 import java.util.Iterator; 042 import java.util.List; 043 import java.util.Map; 044 045 import static org.kuali.rice.core.api.criteria.PredicateFactory.and; 046 import static org.kuali.rice.core.api.criteria.PredicateFactory.like; 047 048 public abstract class RoleMemberLookupableHelperServiceImpl extends KimLookupableHelperServiceImpl { 049 050 protected static final String DETAIL_CRITERIA = "detailCriteria"; 051 protected static final String WILDCARD = "*"; 052 protected static final String TEMPLATE_NAMESPACE_CODE = "template." + KimConstants.UniqueKeyConstants.NAMESPACE_CODE; 053 protected static final String TEMPLATE_NAME = "template.name"; 054 protected static final String TEMPLATE_ID = "template.id"; 055 protected static final String NAMESPACE_CODE = KimConstants.UniqueKeyConstants.NAMESPACE_CODE; 056 protected static final String NAME = "name"; 057 protected static final String GROUP_NAME = KimConstants.UniqueKeyConstants.GROUP_NAME; 058 protected static final String ASSIGNED_TO_PRINCIPAL_NAME = "assignedToPrincipal.principalName"; 059 protected static final String ASSIGNED_TO_GROUP_NAMESPACE_CODE = "assignedToGroupNamespaceForLookup"; 060 protected static final String ASSIGNED_TO_GROUP_NAME = "assignedToGroup." + KimConstants.UniqueKeyConstants.GROUP_NAME; 061 protected static final String ASSIGNED_TO_NAMESPACE_FOR_LOOKUP = "assignedToRoleNamespaceForLookup"; 062 protected static final String ASSIGNED_TO_ROLE_NAME = "assignedToRole." + KimConstants.UniqueKeyConstants.ROLE_NAME; 063 protected static final String ATTRIBUTE_NAME = "attributeName"; 064 protected static final String ATTRIBUTE_VALUE = "attributeValue"; 065 protected static final String ASSIGNED_TO_ROLE_NAMESPACE_CODE = KimConstants.UniqueKeyConstants.NAMESPACE_CODE; 066 protected static final String ASSIGNED_TO_ROLE_ROLE_NAME = KimConstants.UniqueKeyConstants.ROLE_NAME; 067 protected static final String ASSIGNED_TO_ROLE_MEMBER_ID = "members.memberId"; 068 protected static final String DETAIL_OBJECTS_ATTRIBUTE_VALUE = "attributeDetails.attributeValue"; 069 protected static final String DETAIL_OBJECTS_ATTRIBUTE_NAME = "attributeDetails.kimAttribute.attributeName"; 070 071 @Override 072 protected List<? extends BusinessObject> getSearchResultsHelper(Map<String, String> fieldValues, boolean unbounded) { 073 Map<String, String> searchCriteria = buildRoleSearchCriteria(fieldValues); 074 if(searchCriteria == null) { 075 return new ArrayList<BusinessObject>(); 076 } 077 return getMemberSearchResults(fieldValues, unbounded); 078 } 079 080 protected abstract List<? extends BusinessObject> getMemberSearchResults(Map<String, String> searchCriteria, boolean unbounded); 081 082 protected Map<String, String> buildSearchCriteria(Map<String, String> fieldValues){ 083 String templateNamespaceCode = fieldValues.get(TEMPLATE_NAMESPACE_CODE); 084 String templateName = fieldValues.get(TEMPLATE_NAME); 085 String templateId = fieldValues.get(TEMPLATE_ID); 086 String namespaceCode = fieldValues.get(NAMESPACE_CODE); 087 String name = fieldValues.get(NAME); 088 String attributeDetailValue = fieldValues.get(ATTRIBUTE_VALUE); 089 String attributeDetailName = fieldValues.get(ATTRIBUTE_NAME); 090 String detailCriteria = fieldValues.get( DETAIL_CRITERIA ); 091 String active = fieldValues.get( KRADPropertyConstants.ACTIVE ); 092 093 Map<String,String> searchCriteria = new HashMap<String, String>(); 094 if(StringUtils.isNotEmpty(templateNamespaceCode)) { 095 searchCriteria.put(TEMPLATE_NAMESPACE_CODE, WILDCARD+templateNamespaceCode+WILDCARD); 096 } 097 if(StringUtils.isNotEmpty(templateName)) { 098 searchCriteria.put(TEMPLATE_NAME, WILDCARD+templateName+WILDCARD); 099 } 100 if(StringUtils.isNotEmpty(templateId)) { 101 searchCriteria.put(TEMPLATE_ID, templateId); 102 } 103 if(StringUtils.isNotEmpty(namespaceCode)) { 104 searchCriteria.put(NAMESPACE_CODE, WILDCARD+namespaceCode+WILDCARD); 105 } 106 if(StringUtils.isNotEmpty(name)) { 107 searchCriteria.put(NAME, WILDCARD+name+WILDCARD); 108 } 109 if(StringUtils.isNotEmpty(attributeDetailValue)) { 110 searchCriteria.put(DETAIL_OBJECTS_ATTRIBUTE_VALUE, WILDCARD+attributeDetailValue+WILDCARD); 111 } 112 if(StringUtils.isNotEmpty(attributeDetailName)) { 113 searchCriteria.put(DETAIL_OBJECTS_ATTRIBUTE_NAME, WILDCARD+attributeDetailName+WILDCARD); 114 } 115 if ( StringUtils.isNotBlank( detailCriteria ) ) { 116 searchCriteria.put(DETAIL_CRITERIA, detailCriteria); 117 } 118 if ( StringUtils.isNotBlank( active ) ) { 119 searchCriteria.put(KRADPropertyConstants.ACTIVE, active); 120 } 121 122 return searchCriteria; 123 } 124 125 protected String getQueryString(String parameter){ 126 if(StringUtils.isEmpty(parameter)) { 127 return WILDCARD; 128 } 129 else { 130 return WILDCARD+parameter+WILDCARD; 131 } 132 } 133 134 @SuppressWarnings({ "unchecked" }) 135 protected Map<String, String> buildRoleSearchCriteria(Map<String, String> fieldValues){ 136 String assignedToPrincipalName = fieldValues.get(ASSIGNED_TO_PRINCIPAL_NAME); 137 Map<String, String> searchCriteria; 138 List<Principal> principals = new ArrayList<Principal>(); 139 140 if(StringUtils.isNotEmpty(assignedToPrincipalName)) { // if a principal name is specified in the search 141 // KULRICE-9153: Analyze IU patch for preventing role member lookup from causing out of memory exceptions 142 // Changing to exact match on principal name to prevent blowing up Rice by loading every user into memory 143 if (assignedToPrincipalName.contains("*")) { 144 return null; // bail out, wild cards are not allowed since 145 // IdentityServiceImpl.getPrincipalByPrincipalName has weird behavior around wildcards 146 } 147 148 Principal principal = KimApiServiceLocator.getIdentityService().getPrincipalByPrincipalName(assignedToPrincipalName); 149 150 if (principal == null) { 151 return null; // bail out, if no principal matched and a principal name was supplied, then there will be 152 // no valid matching roles. 153 } 154 155 principals.add(principal); 156 } 157 158 String assignedToGroupNamespaceCode = fieldValues.get(ASSIGNED_TO_GROUP_NAMESPACE_CODE); 159 String assignedToGroupName = fieldValues.get(ASSIGNED_TO_GROUP_NAME); 160 List<Group> groups = null; 161 if(StringUtils.isNotEmpty(assignedToGroupNamespaceCode) && StringUtils.isEmpty(assignedToGroupName) || 162 StringUtils.isEmpty(assignedToGroupNamespaceCode) && StringUtils.isNotEmpty(assignedToGroupName) || 163 StringUtils.isNotEmpty(assignedToGroupNamespaceCode) && StringUtils.isNotEmpty(assignedToGroupName)){ 164 QueryByCriteria.Builder builder = QueryByCriteria.Builder.create(); 165 builder.setPredicates(and( 166 like(NAMESPACE_CODE, getQueryString(assignedToGroupNamespaceCode)), 167 like(GROUP_NAME, getQueryString(assignedToGroupName)))); 168 GroupQueryResults qr = KimApiServiceLocator.getGroupService().findGroups(builder.build()); 169 if (qr.getResults() == null || qr.getResults().isEmpty()) { 170 return null; 171 } 172 groups = qr.getResults(); 173 } 174 175 String assignedToRoleNamespaceCode = fieldValues.get(ASSIGNED_TO_NAMESPACE_FOR_LOOKUP); 176 String assignedToRoleName = fieldValues.get(ASSIGNED_TO_ROLE_NAME); 177 178 searchCriteria = new HashMap<String, String>(); 179 if (StringUtils.isNotEmpty(assignedToRoleNamespaceCode)) { 180 searchCriteria.put(ASSIGNED_TO_ROLE_NAMESPACE_CODE, WILDCARD+assignedToRoleNamespaceCode+WILDCARD); 181 } 182 if(StringUtils.isNotEmpty(assignedToRoleName)) { 183 searchCriteria.put(ASSIGNED_TO_ROLE_ROLE_NAME, WILDCARD+assignedToRoleName+WILDCARD); 184 } 185 186 StringBuffer memberQueryString = null; 187 if(!principals.isEmpty()) { 188 memberQueryString = new StringBuffer(); 189 for(Principal principal: principals){ 190 memberQueryString.append(principal.getPrincipalId()+KimConstants.KimUIConstants.OR_OPERATOR); 191 } 192 if(memberQueryString.toString().endsWith(KimConstants.KimUIConstants.OR_OPERATOR)) { 193 memberQueryString.delete(memberQueryString.length()-KimConstants.KimUIConstants.OR_OPERATOR.length(), memberQueryString.length()); 194 } 195 } 196 if(groups!=null){ 197 if(memberQueryString==null) { 198 memberQueryString = new StringBuffer(); 199 } 200 else if(StringUtils.isNotEmpty(memberQueryString.toString())) { 201 memberQueryString.append(KimConstants.KimUIConstants.OR_OPERATOR); 202 } 203 for(Group group: groups){ 204 memberQueryString.append(group.getId()+KimConstants.KimUIConstants.OR_OPERATOR); 205 } 206 if(memberQueryString.toString().endsWith(KimConstants.KimUIConstants.OR_OPERATOR)) { 207 memberQueryString.delete(memberQueryString.length()-KimConstants.KimUIConstants.OR_OPERATOR.length(), memberQueryString.length()); 208 } 209 searchCriteria.put(ASSIGNED_TO_ROLE_MEMBER_ID, memberQueryString.toString()); 210 } 211 if (memberQueryString!=null && StringUtils.isNotEmpty(memberQueryString.toString())) { 212 searchCriteria.put(ASSIGNED_TO_ROLE_MEMBER_ID, memberQueryString.toString()); 213 } 214 215 return searchCriteria; 216 } 217 218 219 /** Checks whether the 2nd map is a subset of the first. */ 220 protected boolean isMapSubset( Map<String, String> mainMap, Map<String, String> subsetMap ) { 221 for ( Map.Entry<String, String> keyValue : subsetMap.entrySet() ) { 222 if ( !mainMap.containsKey(keyValue.getKey()) 223 || !StringUtils.equals( mainMap.get(keyValue.getKey()), keyValue.getValue() ) ) { 224 // if ( LOG.isDebugEnabled() ) { 225 // LOG.debug( "Maps did not match:\n" + mainMap + "\n" + subsetMap ); 226 // } 227 return false; 228 } 229 } 230 // if ( LOG.isDebugEnabled() ) { 231 // LOG.debug( "Maps matched:\n" + mainMap + "\n" + subsetMap ); 232 // } 233 return true; 234 } 235 236 /** Converts a special criteria string that is in the form key=value,key2=value2 into a map */ 237 protected Map<String, String> parseDetailCriteria( String detailCritiera ) { 238 if ( StringUtils.isBlank(detailCritiera) ) { 239 return new HashMap<String, String>(0); 240 } 241 String[] keyValuePairs = StringUtils.split(detailCritiera, ','); 242 if ( keyValuePairs == null || keyValuePairs.length == 0 ) { 243 return new HashMap<String, String>(0); 244 } 245 Map<String, String> parsedDetails = new HashMap<String, String>( keyValuePairs.length ); 246 for ( String keyValueStr : keyValuePairs ) { 247 String[] keyValue = StringUtils.split(keyValueStr, '='); 248 if ( keyValue.length >= 2 ) { 249 parsedDetails.put(keyValue[0], keyValue[1]); 250 } 251 } 252 return parsedDetails; 253 } 254 255 256 @Override 257 public List<Row> getRows() { 258 List<Row> rows = super.getRows(); 259 Iterator<Row> i = rows.iterator(); 260 while ( i.hasNext() ) { 261 Row row = i.next(); 262 int numFieldsRemoved = 0; 263 boolean rowIsBlank = true; 264 for (Iterator<Field> fieldIter = row.getFields().iterator(); fieldIter.hasNext();) { 265 Field field = fieldIter.next(); 266 String propertyName = field.getPropertyName(); 267 if ( propertyName.equals(DETAIL_CRITERIA) ) { 268 Object val = getParameters().get( propertyName ); 269 String propVal = null; 270 if ( val != null ) { 271 propVal = (val instanceof String)?(String)val:((String[])val)[0]; 272 } 273 if ( StringUtils.isBlank( propVal ) ) { 274 fieldIter.remove(); 275 numFieldsRemoved++; 276 } else { 277 field.setReadOnly(true); 278 rowIsBlank = false; 279 // leaving this in would prevent the "clear" button from resetting this value 280 // field.setDefaultValue( propVal ); 281 } 282 } else if (!Field.BLANK_SPACE.equals(field.getFieldType())) { 283 rowIsBlank = false; 284 } 285 } 286 // Check if any fields were removed from the row. 287 if (numFieldsRemoved > 0) { 288 // If fields were removed, check whether or not the remainder of the row is empty or only has blank space fields. 289 if (rowIsBlank) { 290 // If so, then remove the row entirely. 291 i.remove(); 292 } else { 293 // Otherwise, add one blank space for each field that was removed, using a technique similar to FieldUtils.createBlankSpace. 294 // It is safe to just add blank spaces as needed, since the removable field is the last of the visible ones in the list (or 295 // at least for the Permission and Responsibility lookups). 296 while (numFieldsRemoved > 0) { 297 Field blankSpace = new Field(); 298 blankSpace.setFieldType(Field.BLANK_SPACE); 299 blankSpace.setPropertyName(Field.BLANK_SPACE); 300 row.getFields().add(blankSpace); 301 numFieldsRemoved--; 302 } 303 } 304 } 305 } 306 return rows; 307 } 308 309 protected Long getActualSizeIfTruncated(List result){ 310 Long actualSizeIfTruncated = new Long(0); 311 if(result instanceof CollectionIncomplete) { 312 actualSizeIfTruncated = ((CollectionIncomplete)result).getActualSizeIfTruncated(); 313 } 314 return actualSizeIfTruncated; 315 } 316 317 @SuppressWarnings("unchecked") 318 protected List<RoleBo> searchRoles(Map<String, String> roleSearchCriteria, boolean unbounded){ 319 List<RoleBo> roles = (List<RoleBo>)getLookupService().findCollectionBySearchHelper( 320 RoleBo.class, roleSearchCriteria, unbounded); 321 String membersCrt = roleSearchCriteria.get("members.memberId"); 322 List<RoleBo> roles2Remove = new ArrayList<RoleBo>(); 323 if(StringUtils.isNotBlank(membersCrt)){ 324 List<String> memberSearchIds = new ArrayList<String>(); 325 List<String> memberIds = new ArrayList<String>(); 326 if(membersCrt.contains(KimConstants.KimUIConstants.OR_OPERATOR)) { 327 memberSearchIds = new ArrayList<String>(Arrays.asList(membersCrt.split("\\|"))); 328 } 329 else { 330 memberSearchIds.add(membersCrt); 331 } 332 for(RoleBo roleBo : roles){ 333 List<RoleMemberBo> roleMembers = roleBo.getMembers(); 334 memberIds.clear(); 335 CollectionUtils.filter(roleMembers, new Predicate() { 336 public boolean evaluate(Object object) { 337 RoleMemberBo member = (RoleMemberBo) object; 338 // keep active member 339 return member.isActive(new Timestamp(System.currentTimeMillis())); 340 } 341 }); 342 343 if(roleMembers != null && !roleMembers.isEmpty()){ 344 for(RoleMemberBo memberImpl : roleMembers) { 345 memberIds.add(memberImpl.getMemberId()); 346 } 347 if(((List<String>)CollectionUtils.intersection(memberSearchIds, memberIds)).isEmpty()) { 348 roles2Remove.add(roleBo); 349 } 350 } 351 else 352 { 353 roles2Remove.add(roleBo); 354 } 355 } 356 } 357 if(!roles2Remove.isEmpty()) { 358 roles.removeAll(roles2Remove); 359 } 360 return roles; 361 } 362 363 364 }