View Javadoc

1   package org.kuali.student.common.ui.server.gwt;
2   
3   import java.util.HashMap;
4   import java.util.LinkedHashMap;
5   import java.util.List;
6   import java.util.Map;
7   import java.util.UUID;
8   
9   import java.net.URLDecoder;
10  import org.apache.commons.lang.StringUtils;
11  import org.apache.log4j.Logger;
12  import org.kuali.rice.kew.api.document.DocumentDetail;
13  import org.kuali.rice.kew.api.document.WorkflowDocumentService;
14  import org.kuali.rice.kim.api.permission.PermissionService;
15  import org.kuali.student.common.ui.client.service.DataSaveResult;
16  import org.kuali.student.common.ui.server.gwt.DataService;
17  import org.kuali.student.common.ui.shared.IdAttributes;
18  import org.kuali.student.common.util.security.SecurityUtils;
19  import org.kuali.student.core.assembly.transform.ProposalWorkflowFilter;
20  import org.kuali.student.r1.common.assembly.data.Data;
21  import org.kuali.student.r1.common.assembly.data.Metadata;
22  import org.kuali.student.r1.common.assembly.transform.AuthorizationFilter;
23  import org.kuali.student.r1.common.assembly.transform.MetadataFilter;
24  import org.kuali.student.r1.common.assembly.transform.TransformFilter;
25  import org.kuali.student.r1.common.assembly.transform.TransformFilter.TransformFilterAction;
26  import org.kuali.student.r1.common.assembly.transform.TransformationManager;
27  import org.kuali.student.r2.common.dto.DtoConstants;
28  import org.kuali.student.r1.common.rice.StudentIdentityConstants;
29  import org.kuali.student.r1.common.rice.authorization.PermissionType;
30  import org.kuali.student.r2.common.dto.ContextInfo;
31  import org.kuali.student.r2.common.dto.ValidationResultInfo;
32  import org.kuali.student.r2.common.exceptions.DataValidationErrorException;
33  import org.kuali.student.r2.common.exceptions.DoesNotExistException;
34  import org.kuali.student.r2.common.exceptions.OperationFailedException;
35  import org.kuali.student.r2.common.exceptions.VersionMismatchException;
36  import org.kuali.student.r1.core.proposal.dto.ProposalInfo;
37  import org.kuali.student.r1.core.proposal.service.ProposalService;
38  import org.springframework.transaction.annotation.Transactional;
39  
40  @Transactional(readOnly=true,noRollbackFor={DoesNotExistException.class},rollbackFor={Throwable.class})
41  public abstract class AbstractDataService implements DataService{
42  
43  	private static final long serialVersionUID = 1L;
44  
45  	final Logger LOG = Logger.getLogger(AbstractDataService.class);
46  
47  	private TransformationManager transformationManager;
48  	
49  	private PermissionService permissionService;
50  	
51  	//TODO: why do we have this reference in the base class????
52  	private ProposalService proposalService;
53  	
54  	private WorkflowDocumentService workflowDocumentService;
55  
56  	@Override
57  	public Data getData(String id, ContextInfo contextInfo) throws OperationFailedException {
58  		Map<String, Object> filterProperties = getDefaultFilterProperties(contextInfo);
59  		filterProperties.put(TransformFilter.FILTER_ACTION, TransformFilterAction.GET);
60  		filterProperties.put(MetadataFilter.METADATA_ID_VALUE, id);
61  		
62  		String dtoId = URLDecoder.decode(id);
63  		//First check if this is a proposal id
64          //TODO: Igor : Why do we check for this when getting the data for programs?
65  		try{
66  			if (proposalService != null){
67  			    ProposalInfo proposalInfo = proposalService.getProposal(dtoId);
68  				filterProperties.put(ProposalWorkflowFilter.PROPOSAL_INFO, proposalInfo);
69  				dtoId = proposalInfo.getProposalReference().get(0);
70  			}			
71  
72  			Object dto = get(dtoId, contextInfo);
73  			if (dto != null){
74  				return transformationManager.transform(dto, getDtoClass().getName(), filterProperties);
75  			}
76  		} catch(DoesNotExistException e){
77  			return null;
78  		} catch (Exception e) {
79  			LOG.error("Error getting data",e);
80  			throw new OperationFailedException("Error getting data",e);
81  		}
82  		return null;
83  	}
84  
85  	@Override
86  	public Metadata getMetadata(String id, Map<String, String> attributes,ContextInfo contextInfo) {
87  		Map<String, Object> filterProperties = getDefaultFilterProperties(contextInfo);
88  		filterProperties.put(MetadataFilter.METADATA_ID_VALUE, id);
89  		
90  		//Place id attributes into filter properties
91  		String idType = (attributes != null? attributes.get(IdAttributes.ID_TYPE):null);
92  		String docType = (attributes != null ? attributes.get(StudentIdentityConstants.DOCUMENT_TYPE_NAME):null);
93  		String dtoState = (attributes != null ? attributes.get(DtoConstants.DTO_STATE):null);
94  		String dtoNextState = (attributes != null ? attributes.get(DtoConstants.DTO_NEXT_STATE):null);
95  		String workflowNode = (attributes != null ? attributes.get(DtoConstants.DTO_WORKFLOW_NODE):null);
96  				
97  		if (idType == null){
98  			filterProperties.remove(MetadataFilter.METADATA_ID_TYPE);
99  		} else {
100 			filterProperties.put(MetadataFilter.METADATA_ID_TYPE, idType);
101 		}
102 	
103 		if (docType == null){
104 			filterProperties.put(ProposalWorkflowFilter.WORKFLOW_DOC_TYPE, getDefaultWorkflowDocumentType());
105 		} else {
106 			filterProperties.put(ProposalWorkflowFilter.WORKFLOW_DOC_TYPE, docType);
107 		}
108 
109 		if (dtoState != null){
110 			filterProperties.put(DtoConstants.DTO_STATE, dtoState);			
111 		}
112 		
113 		if (dtoNextState != null){
114 			filterProperties.put(DtoConstants.DTO_NEXT_STATE, dtoNextState);			
115 		}
116 
117 		if (workflowNode != null){
118 			filterProperties.put(DtoConstants.DTO_WORKFLOW_NODE, workflowNode);			
119 		}
120 
121 		if (checkDocumentLevelPermissions()){
122 			filterProperties.put(AuthorizationFilter.DOC_LEVEL_PERM_CHECK, Boolean.TRUE.toString());
123 		}
124 		
125 		Metadata metadata = transformationManager.getMetadata(getDtoClass().getName(), filterProperties); 
126 		return metadata;
127 	}
128 
129 	@Override
130 	@Transactional(readOnly=false,noRollbackFor={DoesNotExistException.class},rollbackFor={Throwable.class})
131 	public DataSaveResult saveData(Data data, ContextInfo contextInfo) throws OperationFailedException, DataValidationErrorException, VersionMismatchException{
132 		Map<String, Object> filterProperties = getDefaultFilterProperties(contextInfo);
133 		filterProperties.put(TransformFilter.FILTER_ACTION, TransformFilterAction.SAVE);
134 		
135 		DataSaveResult saveResult = new DataSaveResult();
136 		try {
137 			//Convert data object to dto object
138 			Object dto = transformationManager.transform(data, getDtoClass(), filterProperties);
139 			
140 			//This calls save method for DataService impl, which makes the needed service calls to persist dto
141 			//The service call should do it's own validation, any errors will cause DataValidationErrorException
142 			//and is handled in the catch below.
143 			dto = save(dto, filterProperties, contextInfo);
144 			
145 			//Validate saved data again to get validation warnings that may exist on the data
146 			List<ValidationResultInfo> validationResults = validate(dto, contextInfo);
147 			
148 			//Convert saved data object back to data object to send to UI
149 			Data persistedData = transformationManager.transform(dto, getDtoClass().getName(), filterProperties);			
150 			
151 			saveResult.setValue(persistedData);
152 			saveResult.setValidationResults(validationResults);			
153 		}catch (DataValidationErrorException dvee){
154 			//Throw the error, we need the the transaction to be rolled back when service throws an error.
155 			throw dvee;
156 		}catch (OperationFailedException ofe){
157 		    throw ofe;
158 		}catch (VersionMismatchException vme){
159 		    throw vme;
160 		}catch (Exception e) {
161 			LOG.error("Failed to save data",e);
162 			throw new OperationFailedException("Failed to save data",e);
163 		}
164 		
165 		return saveResult;
166 	}
167 	
168 	
169 
170 	@Override
171 	public List<ValidationResultInfo> validateData(Data data, ContextInfo contextInfo) throws OperationFailedException {
172 		List<ValidationResultInfo> validationResults;
173 		
174 		try {
175 			Metadata metadata = transformationManager.getUnfilteredMetadata(getDtoClass().getName());
176 			Object dto = null;
177 			dto = transformationManager.getMapper().convertFromData(data, getDtoClass(), metadata);
178 			validationResults = validate(dto, contextInfo);
179 		} catch (Exception e) {
180 			throw new OperationFailedException("Unable to validate data", e);
181 		}
182 
183 		return validationResults;
184 	}
185 
186 	@Override
187     public Boolean isAuthorized(PermissionType type, Map<String,String> attributes, ContextInfo contextInfo) {
188         String user = SecurityUtils.getCurrentUserId();
189         boolean result = false;
190         if (checkDocumentLevelPermissions()) {
191             if (type == null) {
192                 return null;
193             }
194             String namespaceCode = type.getPermissionNamespace();
195             String permissionTemplateName = type.getPermissionTemplateName();
196             
197             Map<String,String> roleQuals = new LinkedHashMap<String,String>();
198             if (attributes != null) {
199                 //Determine permission details and role qualifiers to pass into permission service.
200                 //We will use same attributes for permission details and role qualifiers (never hurts to use more than needed)
201                 
202                 if (proposalService != null){
203                     ProposalInfo proposalInfo = null;
204                     try {
205                         //Retrieve the proposal info provided the proposal id (passed in as KS_JEW_OBJECT_ID) or the workflow id
206                         if (attributes.containsKey(IdAttributes.IdType.KS_KEW_OBJECT_ID.toString())){
207                             proposalInfo = proposalService.getProposal(attributes.get(IdAttributes.IdType.KS_KEW_OBJECT_ID.toString()));
208                         } else if (attributes.containsKey(IdAttributes.IdType.DOCUMENT_ID.toString())){
209                             proposalInfo = proposalService.getProposalByWorkflowId(attributes.get(IdAttributes.IdType.DOCUMENT_ID.toString()));
210                         }
211                         
212                         //Check if the route status is in the list of allowed statuses
213                         DocumentDetail docDetail = getWorkflowDocumentService().getDocumentDetail(proposalInfo.getWorkflowId());
214                         String docStatusCode = getWorkflowDocumentService().getDocumentStatus(proposalInfo.getWorkflowId()).getCode();
215 
216                         //Populate attributes with additional attributes required for permission check
217                         if (proposalInfo != null){
218                             attributes.put(IdAttributes.IdType.KS_KEW_OBJECT_ID.toString(), proposalInfo.getId());
219                             attributes.put(StudentIdentityConstants.QUALIFICATION_DATA_ID, proposalInfo.getId()); // this is what most of the permissions/roles check
220                             attributes.put(IdAttributes.IdType.DOCUMENT_ID.toString(), proposalInfo.getWorkflowId());
221                             attributes.put(StudentIdentityConstants.DOCUMENT_TYPE_NAME, proposalInfo.getType());
222                             attributes.put(StudentIdentityConstants.ROUTE_STATUS_CODE, docStatusCode);
223                             
224                             //Call t his to add any additional attributes that child classes need
225                             addAdditionalAttributes(attributes,proposalInfo,docDetail);
226                         }
227                     } catch (Exception e){
228                         LOG.error("Could not retrieve proposal to determine permission qualifiers:" + e.toString());
229                     }
230                 }
231                 
232                 //Put in additional random number for role qualifiers. This is to avoid this request from being cached. 
233                 //Might want to do this only for specific templates to take advantage of caching
234                 attributes.put("RAND_NO_CACHE", UUID.randomUUID().toString());
235                 roleQuals.putAll(attributes);
236             }
237             if (StringUtils.isNotBlank(namespaceCode) && StringUtils.isNotBlank(permissionTemplateName)) {
238                 LOG.info("Checking Permission '" + namespaceCode + "/" + permissionTemplateName + "' for user '" + user + "'");
239                 result = getPermissionService().isAuthorizedByTemplate(user, namespaceCode, permissionTemplateName, new LinkedHashMap<String,String>(), roleQuals);
240             }
241             else {
242                 LOG.info("Can not check Permission with namespace '" + namespaceCode + "' and template name '" + permissionTemplateName + "' for user '" + user + "'");
243                 return Boolean.TRUE;
244             }
245         }
246         else {
247             LOG.info("Will not check for document level permissions. Defaulting authorization to true.");
248             result = true;
249         }
250         LOG.info("Result of authorization check for user '" + user + "': " + result);
251         return Boolean.valueOf(result);
252     }
253 	
254 	protected void addAdditionalAttributes(Map<String, String> attributes,
255             ProposalInfo proposalInfo, DocumentDetail docDetail) {
256         return;
257     }
258 	
259 	public Map<String, Object> getDefaultFilterProperties(ContextInfo contextInfo){
260 		Map<String, Object> filterProperties = new HashMap<String,Object>();
261 		filterProperties.put(MetadataFilter.METADATA_ID_TYPE, StudentIdentityConstants.QUALIFICATION_KEW_OBJECT_ID);
262 		filterProperties.put(ProposalWorkflowFilter.WORKFLOW_USER, SecurityUtils.getCurrentUserId());
263 		
264 		return filterProperties;
265 	}
266 	
267 	protected DataSaveResult _saveData(Data data, Map<String, Object> filterProperties, ContextInfo contextInfo) throws OperationFailedException{
268 		try {
269 			filterProperties.put(MetadataFilter.METADATA_ID_VALUE, (String)data.query("id"));	
270 
271 			Object dto = transformationManager.transform(data, getDtoClass(),filterProperties);
272 			dto = save(dto, filterProperties, contextInfo);
273 				
274 			Data persistedData = transformationManager.transform(dto,getDtoClass().getName(), filterProperties);
275 			return new DataSaveResult(null, persistedData);
276 		} catch (DataValidationErrorException dvee){
277 			return new DataSaveResult(dvee.getValidationResults(), null);
278 		} catch (Exception e) {
279 			LOG.error("Unable to save", e);
280 			throw new OperationFailedException("Unable to save");
281 		}		
282 	}
283 	
284 	protected boolean checkDocumentLevelPermissions() {
285 		return false;
286 	}
287 
288 
289 
290 	public TransformationManager getTransformationManager() {
291 		return transformationManager;
292 	}
293 
294 	public void setTransformationManager(TransformationManager transformationManager) {
295 		this.transformationManager = transformationManager;
296 	}
297 
298 	public PermissionService getPermissionService() {
299 		return permissionService;
300 	}
301 
302 	public void setPermissionService(PermissionService permissionService) {
303 		this.permissionService = permissionService;
304 	}
305 	
306 	public ProposalService getProposalService() {
307 		return proposalService;
308 	}
309 
310 	public void setProposalService(ProposalService proposalService) {
311 		this.proposalService = proposalService;
312 	}
313 	
314 	public WorkflowDocumentService getWorkflowDocumentService() {
315         return workflowDocumentService;
316     }
317 
318     public void setWorkflowDocumentService(WorkflowDocumentService workflowDocumentService) {
319         this.workflowDocumentService = workflowDocumentService;
320     }
321 	
322 	protected abstract String getDefaultWorkflowDocumentType();
323 	
324 	protected abstract String getDefaultMetaDataState();
325 	
326 	/**
327 	 * Implement this method to make to make service call to get DTO object. The method is called
328 	 * by the get(Data) method before it invokes transformationManager to convert DTO to a Data map 
329 	 * 
330 	 * @param id DTO id
331 	 * @param contextInfo
332 	 * @return the dto retrieved by calling the appropriate service method
333 	 * @throws Exception
334 	 */
335 	protected abstract Object get(String id, ContextInfo contextInfo) throws Exception;
336 	
337 	/**
338 	 * Implement this method to make a service call to get DTO object. The method is called	 
339 	 * by the save(Data) method after it invokes transformationManager to convert Data map to DTO 
340 	 * 
341 	 * @param dto
342 	 * @param properties
343 	 * @return the persisted dto object
344 	 * @throws Exception
345 	 */
346 	protected abstract Object save(Object dto, Map<String, Object> properties, ContextInfo contextInfo) throws Exception;
347 	
348 	/**
349 	 * Implement this method to make a service call to get DTO object. The method is called	 
350 	 * in the save(data) method before calling the save(dto,properties) method to validate the data
351  
352 	 * @param dto
353 	 * @return
354 	 * @throws Exception
355 	 */
356 	protected abstract List<ValidationResultInfo> validate(Object dto, ContextInfo contextInfo) throws Exception;
357 
358 	/**
359 	 * Implement this method to return the type of the dto object.
360 	 * 
361 	 * @return The object type returned and expected by the get & save dto methods
362 	 */
363 	protected abstract Class<?> getDtoClass();
364     
365 }