View Javadoc

1   /*
2    * Copyright 2007-2009 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 responsibilities and
14   * limitations under the License.
15   */
16  package org.kuali.rice.kim.lookup;
17  
18  import java.util.ArrayList;
19  import java.util.HashMap;
20  import java.util.List;
21  import java.util.Map;
22  import java.util.Properties;
23  
24  import org.apache.commons.beanutils.PropertyUtils;
25  import org.apache.commons.lang.StringUtils;
26  import org.apache.log4j.Logger;
27  import org.kuali.rice.core.util.MaxAgeSoftReference;
28  import org.kuali.rice.kew.util.KEWConstants;
29  import org.kuali.rice.kim.bo.impl.ResponsibilityImpl;
30  import org.kuali.rice.kim.bo.impl.ReviewResponsibility;
31  import org.kuali.rice.kim.bo.impl.RoleImpl;
32  import org.kuali.rice.kim.bo.role.impl.KimResponsibilityImpl;
33  import org.kuali.rice.kim.bo.role.impl.RoleResponsibilityImpl;
34  import org.kuali.rice.kim.bo.types.dto.AttributeSet;
35  import org.kuali.rice.kim.service.KIMServiceLocator;
36  import org.kuali.rice.kim.util.KimConstants;
37  import org.kuali.rice.kns.bo.BusinessObject;
38  import org.kuali.rice.kns.lookup.CollectionIncomplete;
39  import org.kuali.rice.kns.lookup.HtmlData;
40  import org.kuali.rice.kns.service.KNSServiceLocator;
41  import org.kuali.rice.kns.service.LookupService;
42  import org.kuali.rice.kns.util.KNSConstants;
43  import org.kuali.rice.kns.util.UrlFactory;
44  
45  /**
46   * This is a description of what this class does - bhargavp don't forget to fill this in. 
47   * 
48   * @author Kuali Rice Team (rice.collab@kuali.org)
49   *
50   */
51  public class ResponsibilityLookupableHelperServiceImpl extends RoleMemberLookupableHelperServiceImpl {
52  
53  	private static final Logger LOG = Logger.getLogger( ResponsibilityLookupableHelperServiceImpl.class );
54  	
55  	private static final long serialVersionUID = -2882500971924192124L;
56  	
57  	private static LookupService lookupService;
58  
59  	private static boolean reviewResponsibilityDocumentTypeNameLoaded = false;
60  	private static String reviewResponsibilityDocumentTypeName = null;
61  	
62  	/**
63  	 * This overridden method ...
64  	 * 
65  	 * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#getCustomActionUrls(org.kuali.rice.kns.bo.BusinessObject, java.util.List)
66  	 */
67  	@SuppressWarnings("unchecked")
68  	@Override
69  	public List<HtmlData> getCustomActionUrls(BusinessObject businessObject, List pkNames) {
70      	List<HtmlData> htmlDataList = new ArrayList<HtmlData>();
71      	// convert the ResponsibilityImpl class into a ReviewResponsibility object
72          if ( ((ResponsibilityImpl)businessObject).getTemplate().getName().equals( KEWConstants.DEFAULT_RESPONSIBILITY_TEMPLATE_NAME ) ) {
73          	ReviewResponsibility reviewResp = new ReviewResponsibility( (ResponsibilityImpl)businessObject );
74          	businessObject = reviewResp;
75  	        if (allowsMaintenanceEditAction(businessObject)) {
76  	        	htmlDataList.add(getUrlData(businessObject, KNSConstants.MAINTENANCE_EDIT_METHOD_TO_CALL, pkNames));
77  	        }
78  	        if (allowsMaintenanceNewOrCopyAction()) {
79  	        	htmlDataList.add(getUrlData(businessObject, KNSConstants.MAINTENANCE_COPY_METHOD_TO_CALL, pkNames));
80  	        }
81          }
82          return htmlDataList;
83  	}
84  
85      @SuppressWarnings("unchecked")
86  	protected String getActionUrlHref(BusinessObject businessObject, String methodToCall, List pkNames){
87          Properties parameters = new Properties();
88          parameters.put(KNSConstants.DISPATCH_REQUEST_PARAMETER, methodToCall);
89          // TODO: why is this not using the businessObject parmeter's class?
90          parameters.put(KNSConstants.BUSINESS_OBJECT_CLASS_ATTRIBUTE, businessObject.getClass().getName());
91          parameters.put(KNSConstants.OVERRIDE_KEYS, KimConstants.PrimaryKeyConstants.RESPONSIBILITY_ID);
92          parameters.put(KNSConstants.COPY_KEYS, KimConstants.PrimaryKeyConstants.RESPONSIBILITY_ID);
93          if (StringUtils.isNotBlank(getReturnLocation())) {
94          	parameters.put(KNSConstants.RETURN_LOCATION_PARAMETER, getReturnLocation());	 
95  		}
96          parameters.putAll(getParametersFromPrimaryKey(businessObject, pkNames));
97          return UrlFactory.parameterizeUrl(KNSConstants.MAINTENANCE_ACTION, parameters);
98      }
99  	
100 	/**
101 	 * This overridden method ...
102 	 * 
103 	 * @see org.kuali.rice.kns.lookup.AbstractLookupableHelperServiceImpl#getMaintenanceDocumentTypeName()
104 	 */
105 	@Override
106 	protected String getMaintenanceDocumentTypeName() {
107 		if ( !reviewResponsibilityDocumentTypeNameLoaded ) {
108 			reviewResponsibilityDocumentTypeName = getMaintenanceDocumentDictionaryService().getDocumentTypeName(ReviewResponsibility.class);
109 			reviewResponsibilityDocumentTypeNameLoaded = true;
110 		}
111 		return reviewResponsibilityDocumentTypeName;
112 	}
113 	
114 	/**
115 	 * @see org.kuali.rice.kns.lookup.KualiLookupableHelperServiceImpl#getSearchResults(java.util.Map)
116 	 */
117 	@Override
118 	protected List<? extends BusinessObject> getMemberSearchResults(Map<String, String> searchCriteria, boolean unbounded) {
119 		Map<String, String> responsibilitySearchCriteria = buildSearchCriteria(searchCriteria);
120 		Map<String, String> roleSearchCriteria = buildRoleSearchCriteria(searchCriteria);
121 		boolean responsibilityCriteriaEmpty = responsibilitySearchCriteria==null || responsibilitySearchCriteria.isEmpty();
122 		boolean roleCriteriaEmpty = roleSearchCriteria==null || roleSearchCriteria.isEmpty();
123 		
124 		List<ResponsibilityImpl> responsibilitySearchResultsCopy = new CollectionIncomplete<ResponsibilityImpl>(new ArrayList<ResponsibilityImpl>(), new Long(0));
125 		if(!responsibilityCriteriaEmpty && !roleCriteriaEmpty){
126 			responsibilitySearchResultsCopy = getCombinedSearchResults(responsibilitySearchCriteria, roleSearchCriteria, unbounded);
127 		} else if(responsibilityCriteriaEmpty && !roleCriteriaEmpty){
128 			responsibilitySearchResultsCopy = getResponsibilitiesWithRoleSearchCriteria(roleSearchCriteria, unbounded);
129 		} else if(!responsibilityCriteriaEmpty && roleCriteriaEmpty){
130 			responsibilitySearchResultsCopy = getResponsibilitiesWithResponsibilitySearchCriteria(responsibilitySearchCriteria, unbounded);
131 		} else if(responsibilityCriteriaEmpty && roleCriteriaEmpty){
132 			return getAllResponsibilities(unbounded);
133 		}
134 		return responsibilitySearchResultsCopy;
135 	}
136 	
137 	private List<ResponsibilityImpl> getAllResponsibilities(boolean unbounded){
138 		List<ResponsibilityImpl> responsibilities = searchResponsibilities(new HashMap<String, String>(), unbounded);
139 		for(ResponsibilityImpl responsibility: responsibilities)
140 			populateAssignedToRoles(responsibility);
141 		return responsibilities;
142 	}
143 	
144 	private List<ResponsibilityImpl> getCombinedSearchResults(
145 			Map<String, String> responsibilitySearchCriteria, Map<String, String> roleSearchCriteria, boolean unbounded){
146 		List<ResponsibilityImpl> responsibilitySearchResults = searchResponsibilities(responsibilitySearchCriteria, unbounded);
147 		List<RoleImpl> roleSearchResults = searchRoles(roleSearchCriteria, unbounded);
148 		List<ResponsibilityImpl> responsibilitiesForRoleSearchResults = getResponsibilitiesForRoleSearchResults(roleSearchResults, unbounded);
149 		List<ResponsibilityImpl> matchedResponsibilities = new CollectionIncomplete<ResponsibilityImpl>(
150 				new ArrayList<ResponsibilityImpl>(), getActualSizeIfTruncated(responsibilitiesForRoleSearchResults));
151 		if((responsibilitySearchResults!=null && !responsibilitySearchResults.isEmpty()) && 
152 				(responsibilitiesForRoleSearchResults!=null && !responsibilitiesForRoleSearchResults.isEmpty())){
153 			for(ResponsibilityImpl responsibility: responsibilitySearchResults){
154 				for(ResponsibilityImpl responsibilityFromRoleSearch: responsibilitiesForRoleSearchResults){
155 					if(responsibilityFromRoleSearch.getResponsibilityId().equals(responsibility.getResponsibilityId()))
156 						matchedResponsibilities.add(responsibilityFromRoleSearch);
157 				}
158 			}
159 		}
160 		/*for(ResponsibilityImpl responsibility: responsibilitySearchResults){
161 			for(RoleResponsibilityImpl roleResponsibility: responsibility.getRoleResponsibilities()){
162 				for(RoleImpl roleImpl: roleSearchResults){
163 					if(roleImpl.getRoleId().equals(roleResponsibility.getRoleId())){
164 						responsibility.getAssignedToRoles().add(roleImpl);
165 						matchedResponsibilities.add(responsibility);
166 					}
167 				}
168 			}
169 		}*/
170 		return matchedResponsibilities;
171 	}
172 	
173 	@SuppressWarnings("unchecked")
174 	private List<ResponsibilityImpl> searchResponsibilities(Map<String, String> responsibilitySearchCriteria, boolean unbounded){
175 		return getResponsibilitiesSearchResultsCopy((List<KimResponsibilityImpl>)
176 					getLookupService().findCollectionBySearchHelper(
177 							KimResponsibilityImpl.class, responsibilitySearchCriteria, unbounded));	
178 	}
179 	
180 	private List<ResponsibilityImpl> getResponsibilitiesWithRoleSearchCriteria(Map<String, String> roleSearchCriteria, boolean unbounded){
181 		List<RoleImpl> roleSearchResults = searchRoles(roleSearchCriteria, unbounded);
182 		return getResponsibilitiesForRoleSearchResults(roleSearchResults, unbounded);
183 	}
184 
185 	private List<ResponsibilityImpl> getResponsibilitiesForRoleSearchResults(List<RoleImpl> roleSearchResults, boolean unbounded){
186 		Long actualSizeIfTruncated = getActualSizeIfTruncated(roleSearchResults);
187 		List<ResponsibilityImpl> responsibilities = new ArrayList<ResponsibilityImpl>();
188 		List<ResponsibilityImpl> tempResponsibilities;
189 		List<String> collectedResponsibilityIds = new ArrayList<String>();
190 		Map<String, String> responsibilityCriteria;
191 		
192 		for(RoleImpl roleImpl: roleSearchResults){
193 			responsibilityCriteria = new HashMap<String, String>();
194 			responsibilityCriteria.put("roleResponsibilities.roleId", roleImpl.getRoleId());
195 			tempResponsibilities = searchResponsibilities(responsibilityCriteria, unbounded);
196 			actualSizeIfTruncated += getActualSizeIfTruncated(tempResponsibilities);
197 			for(ResponsibilityImpl responsibility: tempResponsibilities){
198 				if(!collectedResponsibilityIds.contains(responsibility.getResponsibilityId())){
199 					populateAssignedToRoles(responsibility);
200 					collectedResponsibilityIds.add(responsibility.getResponsibilityId());
201 					responsibilities.add(responsibility);
202 				}
203 				//need to find roles that current role is a member of and build search string
204 				List<String> parentRoleIds = KIMServiceLocator.getRoleService().getMemberParentRoleIds(KimConstants.KimUIConstants.MEMBER_TYPE_ROLE_CODE, roleImpl.getRoleId());
205 				for (String parentRoleId : parentRoleIds) {
206 					Map<String, String> roleSearchCriteria = new HashMap<String, String>();
207 					roleSearchCriteria.put("roleId", parentRoleId);
208 					//get all parent role permissions and merge them with current permissions
209 					responsibilities = mergeResponsibilityLists(responsibilities, getResponsibilitiesWithRoleSearchCriteria(roleSearchCriteria, unbounded));
210 				}
211 			}
212 		}
213 		return new CollectionIncomplete<ResponsibilityImpl>(responsibilities, actualSizeIfTruncated);
214 	}
215 
216 	private void populateAssignedToRoles(ResponsibilityImpl responsibility){
217 		AttributeSet criteria = new AttributeSet();
218 		if ( responsibility.getAssignedToRoles().isEmpty() ) {
219 			for(RoleResponsibilityImpl roleResponsibility: responsibility.getRoleResponsibilities()){
220 				criteria.put(KimConstants.PrimaryKeyConstants.ROLE_ID, roleResponsibility.getRoleId());
221 				responsibility.getAssignedToRoles().add((RoleImpl)getBusinessObjectService().findByPrimaryKey(RoleImpl.class, criteria));
222 			}
223 		}
224 	}
225 	
226 	/* Since most queries will only be on the template namespace and name, cache the results for 30 seconds
227 	 * so that queries against the details, which are done in memory, do not require repeated database trips.
228 	 */
229     private static final Map<Map<String,String>,MaxAgeSoftReference<List<ResponsibilityImpl>>> respResultCache = new HashMap<Map<String,String>, MaxAgeSoftReference<List<ResponsibilityImpl>>>(); 
230 	private static final long RESP_CACHE_EXPIRE_SECONDS = 30L;
231 	
232 	private List<ResponsibilityImpl> getResponsibilitiesWithResponsibilitySearchCriteria(Map<String, String> responsibilitySearchCriteria, boolean unbounded){
233 		String detailCriteriaStr = responsibilitySearchCriteria.remove( DETAIL_CRITERIA );
234 		AttributeSet detailCriteria = parseDetailCriteria(detailCriteriaStr);
235 		MaxAgeSoftReference<List<ResponsibilityImpl>> cachedResult = respResultCache.get(responsibilitySearchCriteria);
236 		List<ResponsibilityImpl> responsibilities = null;
237 		if ( cachedResult == null || cachedResult.get() == null ) {
238 			responsibilities = searchResponsibilities(responsibilitySearchCriteria, unbounded);
239 			synchronized( respResultCache ) {
240 				respResultCache.put(responsibilitySearchCriteria, new MaxAgeSoftReference<List<ResponsibilityImpl>>( RESP_CACHE_EXPIRE_SECONDS, responsibilities ) ); 
241 			}
242 		} else {
243 			responsibilities = cachedResult.get();
244 		}
245 		List<ResponsibilityImpl> filteredResponsibilities = new CollectionIncomplete<ResponsibilityImpl>(
246 				new ArrayList<ResponsibilityImpl>(), getActualSizeIfTruncated(responsibilities)); 
247 		for(ResponsibilityImpl responsibility: responsibilities){
248 			if ( detailCriteria.isEmpty() ) {
249 				filteredResponsibilities.add(responsibility);
250 				populateAssignedToRoles(responsibility);
251 			} else {
252 				if ( isMapSubset( responsibility.getDetails(), detailCriteria ) ) {
253 					filteredResponsibilities.add(responsibility);
254 					populateAssignedToRoles(responsibility);
255 				}
256 			}
257 		}
258 		return filteredResponsibilities;
259 	}
260 	
261 	private List<ResponsibilityImpl> getResponsibilitiesSearchResultsCopy(List<KimResponsibilityImpl> responsibilitySearchResults){
262 		List<ResponsibilityImpl> responsibilitySearchResultsCopy = new CollectionIncomplete<ResponsibilityImpl>(
263 				new ArrayList<ResponsibilityImpl>(), getActualSizeIfTruncated(responsibilitySearchResults));
264 		for(KimResponsibilityImpl responsibilityImpl: responsibilitySearchResults){
265 			ResponsibilityImpl responsibilityCopy = new ResponsibilityImpl();
266 			try{
267 				PropertyUtils.copyProperties(responsibilityCopy, responsibilityImpl);
268 			} catch(Exception ex){
269 				LOG.error( "Unable to copy properties from KimResponsibilityImpl to ResponsibilityImpl, skipping.", ex );
270 				continue;
271 			}
272 			responsibilitySearchResultsCopy.add(responsibilityCopy);
273 		}
274 		return responsibilitySearchResultsCopy;
275 	}
276 	
277 
278 	/**
279 	 * @return the lookupService
280 	 */
281 	public LookupService getLookupService() {
282 		if ( lookupService == null ) {
283 			lookupService = KNSServiceLocator.getLookupService();
284 		}
285 		return lookupService;
286 	}
287  
288 	private List<ResponsibilityImpl> mergeResponsibilityLists(List<ResponsibilityImpl> perm1, List<ResponsibilityImpl> perm2) {
289 		List<ResponsibilityImpl> returnList = new ArrayList<ResponsibilityImpl>(perm1);
290 		List<String> responsibilityIds = new ArrayList<String>(perm1.size());
291 		for (ResponsibilityImpl perm : returnList) {
292 			responsibilityIds.add(perm.getResponsibilityId());
293 		}
294 		for (int i=0; i<perm2.size(); i++) {
295 		    if (!responsibilityIds.contains(perm2.get(i).getResponsibilityId())) {
296 		    	returnList.add(perm2.get(i));
297 		    }
298 		}
299 		return returnList;
300 	}
301 }