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 }