View Javadoc
1   /**
2    * Copyright 2005-2016 The Kuali Foundation
3    *
4    * Licensed under the Educational Community License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    * http://www.opensource.org/licenses/ecl2.php
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kim.lookup;
17  
18  import org.apache.commons.collections.CollectionUtils;
19  import org.apache.commons.collections.Predicate;
20  import org.apache.commons.lang.StringUtils;
21  import org.kuali.rice.core.api.criteria.QueryByCriteria;
22  import org.kuali.rice.kim.api.KimConstants;
23  import org.kuali.rice.kim.api.group.Group;
24  import org.kuali.rice.kim.api.group.GroupQueryResults;
25  import org.kuali.rice.kim.api.identity.entity.Entity;
26  import org.kuali.rice.kim.api.identity.entity.EntityQueryResults;
27  import org.kuali.rice.kim.api.identity.principal.Principal;
28  import org.kuali.rice.kim.api.services.KimApiServiceLocator;
29  import org.kuali.rice.kim.impl.role.RoleBo;
30  import org.kuali.rice.kim.impl.role.RoleMemberBo;
31  import org.kuali.rice.kns.web.ui.Field;
32  import org.kuali.rice.kns.web.ui.Row;
33  import org.kuali.rice.krad.bo.BusinessObject;
34  import org.kuali.rice.krad.lookup.CollectionIncomplete;
35  import org.kuali.rice.krad.util.KRADPropertyConstants;
36  
37  import java.sql.Timestamp;
38  import java.util.ArrayList;
39  import java.util.Arrays;
40  import java.util.HashMap;
41  import java.util.Iterator;
42  import java.util.List;
43  import java.util.Map;
44  
45  import static org.kuali.rice.core.api.criteria.PredicateFactory.and;
46  import static org.kuali.rice.core.api.criteria.PredicateFactory.like;
47  
48  public abstract class RoleMemberLookupableHelperServiceImpl extends KimLookupableHelperServiceImpl {
49  
50  	protected static final String DETAIL_CRITERIA = "detailCriteria";
51  	protected static final String WILDCARD = "*";
52      protected static final String TEMPLATE_NAMESPACE_CODE = "template." + KimConstants.UniqueKeyConstants.NAMESPACE_CODE;
53      protected static final String TEMPLATE_NAME = "template.name";
54      protected static final String TEMPLATE_ID = "template.id";
55      protected static final String NAMESPACE_CODE = KimConstants.UniqueKeyConstants.NAMESPACE_CODE;
56      protected static final String NAME = "name";
57      protected static final String GROUP_NAME = KimConstants.UniqueKeyConstants.GROUP_NAME;
58      protected static final String ASSIGNED_TO_PRINCIPAL_NAME = "assignedToPrincipal.principalName";
59      protected static final String ASSIGNED_TO_GROUP_NAMESPACE_CODE = "assignedToGroupNamespaceForLookup";
60      protected static final String ASSIGNED_TO_GROUP_NAME = "assignedToGroup." + KimConstants.UniqueKeyConstants.GROUP_NAME;
61      protected static final String ASSIGNED_TO_NAMESPACE_FOR_LOOKUP = "assignedToRoleNamespaceForLookup";
62      protected static final String ASSIGNED_TO_ROLE_NAME = "assignedToRole." + KimConstants.UniqueKeyConstants.ROLE_NAME;
63      protected static final String ATTRIBUTE_NAME = "attributeName";
64      protected static final String ATTRIBUTE_VALUE = "attributeValue";
65      protected static final String ASSIGNED_TO_ROLE_NAMESPACE_CODE = KimConstants.UniqueKeyConstants.NAMESPACE_CODE;
66      protected static final String ASSIGNED_TO_ROLE_ROLE_NAME = KimConstants.UniqueKeyConstants.ROLE_NAME;
67      protected static final String ASSIGNED_TO_ROLE_MEMBER_ID = "members.memberId";
68      protected static final String DETAIL_OBJECTS_ATTRIBUTE_VALUE = "attributeDetails.attributeValue";
69      protected static final String DETAIL_OBJECTS_ATTRIBUTE_NAME = "attributeDetails.kimAttribute.attributeName";
70      
71      @Override
72      protected List<? extends BusinessObject> getSearchResultsHelper(Map<String, String> fieldValues, boolean unbounded) {
73      	Map<String, String> searchCriteria = buildRoleSearchCriteria(fieldValues);
74      	if(searchCriteria == null) {
75      		return new ArrayList<BusinessObject>();
76          }
77          return getMemberSearchResults(fieldValues, unbounded);
78      }
79  
80      protected abstract List<? extends BusinessObject> getMemberSearchResults(Map<String, String> searchCriteria, boolean unbounded);
81      
82      protected Map<String, String> buildSearchCriteria(Map<String, String> fieldValues){
83          String templateNamespaceCode = fieldValues.get(TEMPLATE_NAMESPACE_CODE);
84          String templateName = fieldValues.get(TEMPLATE_NAME);
85          String templateId = fieldValues.get(TEMPLATE_ID);
86          String namespaceCode = fieldValues.get(NAMESPACE_CODE);
87          String name = fieldValues.get(NAME);
88          String attributeDetailValue = fieldValues.get(ATTRIBUTE_VALUE);
89          String attributeDetailName = fieldValues.get(ATTRIBUTE_NAME);
90          String detailCriteria = fieldValues.get( DETAIL_CRITERIA );
91          String active = fieldValues.get( KRADPropertyConstants.ACTIVE );
92  
93      	Map<String,String> searchCriteria = new HashMap<String, String>();
94      	if(StringUtils.isNotEmpty(templateNamespaceCode)) {
95      		searchCriteria.put(TEMPLATE_NAMESPACE_CODE, WILDCARD+templateNamespaceCode+WILDCARD);
96      	}
97          if(StringUtils.isNotEmpty(templateName)) {
98          	searchCriteria.put(TEMPLATE_NAME, WILDCARD+templateName+WILDCARD);
99          }
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 }