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    }