001    /**
002     * Copyright 2005-2014 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.impl.permission;
017    
018    import org.apache.commons.collections.CollectionUtils;
019    import org.apache.commons.lang.StringUtils;
020    import org.apache.commons.lang.exception.ExceptionUtils;
021    import org.apache.log4j.Logger;
022    import org.kuali.rice.core.api.cache.CacheKeyUtils;
023    import org.kuali.rice.core.api.criteria.CriteriaLookupService;
024    import org.kuali.rice.core.api.criteria.GenericQueryResults;
025    import org.kuali.rice.core.api.criteria.LookupCustomizer;
026    import org.kuali.rice.core.api.criteria.QueryByCriteria;
027    import org.kuali.rice.core.api.exception.RiceIllegalArgumentException;
028    import org.kuali.rice.core.api.exception.RiceIllegalStateException;
029    import org.kuali.rice.core.api.membership.MemberType;
030    import org.kuali.rice.core.api.resourceloader.GlobalResourceLoader;
031    import org.kuali.rice.kim.api.KimConstants;
032    import org.kuali.rice.kim.api.common.assignee.Assignee;
033    import org.kuali.rice.kim.api.common.delegate.DelegateType;
034    import org.kuali.rice.kim.api.common.template.Template;
035    import org.kuali.rice.kim.api.common.template.TemplateQueryResults;
036    import org.kuali.rice.kim.api.identity.principal.Principal;
037    import org.kuali.rice.kim.api.permission.Permission;
038    import org.kuali.rice.kim.api.permission.PermissionQueryResults;
039    import org.kuali.rice.kim.api.permission.PermissionService;
040    import org.kuali.rice.kim.api.role.RoleMembership;
041    import org.kuali.rice.kim.api.role.RoleService;
042    import org.kuali.rice.kim.api.services.KimApiServiceLocator;
043    import org.kuali.rice.kim.api.type.KimType;
044    import org.kuali.rice.kim.api.type.KimTypeInfoService;
045    import org.kuali.rice.kim.framework.permission.PermissionTypeService;
046    import org.kuali.rice.kim.impl.common.attribute.AttributeTransform;
047    import org.kuali.rice.kim.impl.common.attribute.KimAttributeDataBo;
048    import org.kuali.rice.kim.impl.role.RolePermissionBo;
049    import org.kuali.rice.krad.service.BusinessObjectService;
050    import org.kuali.rice.krad.util.KRADPropertyConstants;
051    import org.springframework.cache.Cache;
052    import org.springframework.cache.CacheManager;
053    import org.springframework.cache.support.NoOpCacheManager;
054    
055    import javax.xml.namespace.QName;
056    import java.util.ArrayList;
057    import java.util.Collection;
058    import java.util.Collections;
059    import java.util.Comparator;
060    import java.util.HashMap;
061    import java.util.List;
062    import java.util.Map;
063    import java.util.concurrent.CopyOnWriteArrayList;
064    
065    import static org.kuali.rice.core.api.criteria.PredicateFactory.equal;
066    import static org.kuali.rice.core.api.criteria.PredicateFactory.in;
067    
068    public class PermissionServiceImpl implements PermissionService {
069        private static final Logger LOG = Logger.getLogger( PermissionServiceImpl.class );
070    
071            private RoleService roleService;
072        private PermissionTypeService defaultPermissionTypeService;
073        private KimTypeInfoService kimTypeInfoService;
074            private BusinessObjectService businessObjectService;
075            private CriteriaLookupService criteriaLookupService;
076        private CacheManager cacheManager;
077    
078            private final CopyOnWriteArrayList<Template> allTemplates = new CopyOnWriteArrayList<Template>();
079    
080        // --------------------
081        // Authorization Checks
082        // --------------------
083    
084        public PermissionServiceImpl() {
085            this.cacheManager = new NoOpCacheManager();
086        }
087    
088        protected PermissionTypeService getPermissionTypeService(Template permissionTemplate) {
089            if ( permissionTemplate == null ) {
090                    throw new IllegalArgumentException( "permissionTemplate may not be null" );
091            }
092            KimType kimType = kimTypeInfoService.getKimType( permissionTemplate.getKimTypeId() );
093            String serviceName = kimType.getServiceName();
094            // if no service specified, return a default implementation
095            if ( StringUtils.isBlank( serviceName ) ) {
096                    return defaultPermissionTypeService;
097            }
098            try {
099                    Object service = GlobalResourceLoader.getService(QName.valueOf(serviceName));
100                    // if we have a service name, it must exist
101                    if ( service == null ) {
102                                    throw new RuntimeException("null returned for permission type service for service name: " + serviceName);
103                    }
104                    // whatever we retrieved must be of the correct type
105                    if ( !(service instanceof PermissionTypeService)  ) {
106                            throw new RuntimeException( "Service " + serviceName + " was not a PermissionTypeService.  Was: " + service.getClass().getName() );
107                    }
108                    return (PermissionTypeService)service;
109            } catch( Exception ex ) {
110                    // sometimes service locators throw exceptions rather than returning null, handle that
111                    throw new RuntimeException( "Error retrieving service: " + serviceName + " from the KimImplServiceLocator.", ex );
112            }
113        }
114    
115        @Override
116        public boolean hasPermission(String principalId, String namespaceCode,
117                                     String permissionName) throws RiceIllegalArgumentException  {
118            incomingParamCheck(principalId, "principalId");
119            incomingParamCheck(namespaceCode, "namespaceCode");
120            incomingParamCheck(permissionName, "permissionName");
121    
122            return isAuthorized( principalId, namespaceCode, permissionName, Collections.<String, String>emptyMap() );
123        }
124    
125        @Override
126        public boolean isAuthorized(String principalId, String namespaceCode,
127                                    String permissionName, Map<String, String> qualification ) throws RiceIllegalArgumentException {
128            incomingParamCheck(principalId, "principalId");
129            incomingParamCheck(namespaceCode, "namespaceCode");
130            incomingParamCheck(permissionName, "permissionName");
131            incomingParamCheck(qualification, "qualification");
132    
133            if ( LOG.isDebugEnabled() ) {
134                logAuthorizationCheck("Permission", principalId, namespaceCode, permissionName, qualification);
135            }
136    
137            List<String> roleIds = getRoleIdsForPermission(namespaceCode, permissionName);
138            if ( roleIds.isEmpty() ) {
139                    if ( LOG.isDebugEnabled() ) {
140                            LOG.debug( "Result: false");
141                    }
142                    return false;
143            }
144    
145            boolean isAuthorized = roleService.principalHasRole(principalId, roleIds, qualification);
146            
147            if ( LOG.isDebugEnabled() ) {
148                LOG.debug( "Result: " + isAuthorized );
149            }
150            return isAuthorized;
151    
152        }
153        @Override
154        public boolean hasPermissionByTemplate(String principalId, String namespaceCode, String permissionTemplateName,
155                Map<String, String> permissionDetails) throws RiceIllegalArgumentException {
156            incomingParamCheck(principalId, "principalId");
157            incomingParamCheck(namespaceCode, "namespaceCode");
158            incomingParamCheck(permissionTemplateName, "permissionTemplateName");
159    
160            return isAuthorizedByTemplate(principalId, namespaceCode, permissionTemplateName, permissionDetails,
161                    Collections.<String, String>emptyMap());
162        }
163        @Override
164        public boolean isAuthorizedByTemplate(String principalId, String namespaceCode, String permissionTemplateName,
165                Map<String, String> permissionDetails, Map<String, String> qualification) throws RiceIllegalArgumentException {
166            incomingParamCheck(principalId, "principalId");
167            incomingParamCheck(namespaceCode, "namespaceCode");
168            incomingParamCheck(permissionTemplateName, "permissionTemplateName");
169            incomingParamCheck(qualification, "qualification");
170    
171            if ( LOG.isDebugEnabled() ) {
172                logAuthorizationCheckByTemplate("Perm Templ", principalId, namespaceCode, permissionTemplateName, permissionDetails, qualification);
173            }
174    
175            List<String> roleIds = getRoleIdsForPermissionTemplate(namespaceCode, permissionTemplateName, permissionDetails);
176            if (roleIds.isEmpty()) {
177                if (LOG.isDebugEnabled()) {
178                    LOG.debug("Result: false");
179                }
180                    return false;
181            }
182            boolean isAuthorized = roleService.principalHasRole(principalId, roleIds, qualification);
183            if (LOG.isDebugEnabled()) {
184                LOG.debug( "Result: " + isAuthorized );
185            }
186                    return isAuthorized;
187            
188        }
189        @Override
190        public List<Permission> getAuthorizedPermissions( String principalId,
191                String namespaceCode, String permissionName,
192                Map<String, String> qualification ) throws RiceIllegalArgumentException {
193            incomingParamCheck(principalId, "principalId");
194            incomingParamCheck(namespaceCode, "namespaceCode");
195            incomingParamCheck(permissionName, "permissionName");
196            incomingParamCheck(qualification, "qualification");
197    
198            // get all the permission objects whose name match that requested
199            List<Permission> permissions = getPermissionsByName(namespaceCode, permissionName);
200            // now, filter the full list by the detail passed
201            List<Permission> applicablePermissions = getMatchingPermissions( permissions, null );
202            List<Permission> permissionsForUser = getPermissionsForUser(principalId, applicablePermissions, qualification);
203            return permissionsForUser;
204        }
205        @Override
206        public List<Permission> getAuthorizedPermissionsByTemplate(String principalId, String namespaceCode,
207                String permissionTemplateName, Map<String, String> permissionDetails, Map<String, String> qualification) throws RiceIllegalArgumentException {
208            incomingParamCheck(principalId, "principalId");
209            incomingParamCheck(namespaceCode, "namespaceCode");
210            incomingParamCheck(permissionTemplateName, "permissionTemplateName");
211            incomingParamCheck(qualification, "qualification");
212    
213            // get all the permission objects whose name match that requested
214            List<Permission> permissions = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
215            // now, filter the full list by the detail passed
216            List<Permission> applicablePermissions = getMatchingPermissions( permissions, permissionDetails );
217    
218            return getPermissionsForUser(principalId, applicablePermissions, qualification);
219        }
220        
221        /**
222         * Checks the list of permissions against the principal's roles and returns a subset of the list which match.
223         */
224        protected List<Permission> getPermissionsForUser( String principalId, List<Permission> permissions,
225                Map<String, String> qualification ) {
226            List<Permission> results = new ArrayList<Permission>();
227            for ( Permission perm : permissions ) {
228                    List<String> roleIds = getRoleIdsForPermissions( Collections.singletonList(perm) );
229                    if ( roleIds != null && !roleIds.isEmpty() ) {
230                            if ( roleService.principalHasRole( principalId, roleIds, qualification) ) {
231                                    results.add( perm );
232                            }
233                    }
234            }
235            return Collections.unmodifiableList(results);
236        }
237    
238        protected Map<String,PermissionTypeService> getPermissionTypeServicesByTemplateId( Collection<Permission> permissions ) {
239            Map<String,PermissionTypeService> permissionTypeServices = new HashMap<String, PermissionTypeService>( permissions.size() );
240            for (Permission perm : permissions) {
241                if(!permissionTypeServices.containsKey(perm.getTemplate().getId())) {
242                    permissionTypeServices.put(perm.getTemplate().getId(), getPermissionTypeService(perm.getTemplate()));
243                }
244            }
245            return permissionTypeServices;
246        }
247        
248            protected Map<String,List<Permission>> groupPermissionsByTemplate(Collection<Permission> permissions) {
249            Map<String,List<Permission>> results = new HashMap<String,List<Permission>>();
250            for (Permission perm : permissions) {
251                    List<Permission> perms = results.get(perm.getTemplate().getId());
252                    if (perms == null) {
253                            perms = new ArrayList<Permission>();
254                            results.put(perm.getTemplate().getId(), perms);
255                    }
256                    perms.add(perm);
257            }
258            return results;
259        }
260        
261            /**
262         * Compare each of the passed in permissions with the given permissionDetails.  Those that
263         * match are added to the result list.
264         */
265        protected List<Permission> getMatchingPermissions( List<Permission> permissions, Map<String, String> permissionDetails ) {
266            List<String> permissionIds = new ArrayList<String>(permissions.size());
267            for (Permission permission : permissions) {
268                permissionIds.add(permission.getId());
269            }
270            String cacheKey =  new StringBuilder("{getMatchingPermissions}")
271                    .append("permissionIds=").append(CacheKeyUtils.key(permissionIds)).append("|")
272                    .append("permissionDetails=").append(CacheKeyUtils.mapKey(permissionDetails)).toString();
273            Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
274            if (cachedValue != null && cachedValue.get() instanceof List) {
275                return ((List<Permission>)cachedValue.get());
276            }
277    
278            List<Permission> applicablePermissions = new ArrayList<Permission>();       
279            if ( permissionDetails == null || permissionDetails.isEmpty() ) {
280                    // if no details passed, assume that all match
281                    for ( Permission perm : permissions ) {
282                            applicablePermissions.add(perm);
283                    }
284            } else {
285                    // otherwise, attempt to match the permission details
286                    // build a map of the template IDs to the type services
287                    Map<String,PermissionTypeService> permissionTypeServices = getPermissionTypeServicesByTemplateId(permissions);
288                    // build a map of permissions by template ID
289                    Map<String, List<Permission>> permissionMap = groupPermissionsByTemplate(permissions);
290                    // loop over the different templates, matching all of the same template against the type
291                    // service at once
292                    for ( Map.Entry<String,List<Permission>> entry : permissionMap.entrySet() ) {
293                            PermissionTypeService permissionTypeService = permissionTypeServices.get( entry.getKey() );
294                            List<Permission> permissionList = entry.getValue();
295                                    applicablePermissions.addAll( permissionTypeService.getMatchingPermissions( permissionDetails, permissionList ) );                              
296                    }
297            }
298            applicablePermissions = Collections.unmodifiableList(applicablePermissions);
299            cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, applicablePermissions);
300            return applicablePermissions;
301        }
302    
303    
304        @Override
305        public List<Assignee> getPermissionAssignees( String namespaceCode, String permissionName,
306                Map<String, String> qualification ) throws RiceIllegalArgumentException {
307            incomingParamCheck(namespaceCode, "namespaceCode");
308            incomingParamCheck(permissionName, "permissionName");
309            incomingParamCheck(qualification, "qualification");
310    
311            List<String> roleIds = getRoleIdsForPermission( namespaceCode, permissionName);
312            if ( roleIds.isEmpty() ) {
313                    return Collections.emptyList();
314            }
315            Collection<RoleMembership> roleMembers = roleService.getRoleMembers( roleIds,qualification );
316            List<Assignee> results = new ArrayList<Assignee>();
317            for ( RoleMembership rm : roleMembers ) {
318                            List<DelegateType.Builder> delegateBuilderList = new ArrayList<DelegateType.Builder>();
319                            if (!rm.getDelegates().isEmpty()) {
320                            for (DelegateType delegate : rm.getDelegates()){
321                        delegateBuilderList.add(DelegateType.Builder.create(delegate));
322                            }
323                            }
324                    if ( MemberType.PRINCIPAL.equals(rm.getType()) ) {
325                            results.add (Assignee.Builder.create(rm.getMemberId(), null, delegateBuilderList).build());
326                    } else if ( MemberType.GROUP.equals(rm.getType()) ) {
327                            results.add (Assignee.Builder.create(null, rm.getMemberId(), delegateBuilderList).build());
328                    }
329            }
330    
331            return Collections.unmodifiableList(results);
332        }
333    
334        @Override
335        public List<Assignee> getPermissionAssigneesByTemplate(String namespaceCode, String permissionTemplateName,
336                Map<String, String> permissionDetails, Map<String, String> qualification) throws RiceIllegalArgumentException {
337            incomingParamCheck(namespaceCode, "namespaceCode");
338            incomingParamCheck(permissionTemplateName, "permissionTemplateName");
339            incomingParamCheck(qualification, "qualification");
340    
341            List<String> roleIds = getRoleIdsForPermissionTemplate( namespaceCode, permissionTemplateName, permissionDetails);
342            if ( roleIds.isEmpty() ) {
343                    return Collections.emptyList();
344            }
345            Collection<RoleMembership> roleMembers = roleService.getRoleMembers( roleIds,qualification);
346            List<Assignee> results = new ArrayList<Assignee>();
347            for ( RoleMembership rm : roleMembers ) {
348                            List<DelegateType.Builder> delegateBuilderList = new ArrayList<DelegateType.Builder>();
349                            if (!rm.getDelegates().isEmpty()) {
350                            for (DelegateType delegate : rm.getDelegates()){
351                        delegateBuilderList.add(DelegateType.Builder.create(delegate));
352                            }
353                            }
354                    if ( MemberType.PRINCIPAL.equals(rm.getType()) ) {
355                            results.add (Assignee.Builder.create(rm.getMemberId(), null, delegateBuilderList).build());
356                    } else { // a group membership
357                            results.add (Assignee.Builder.create(null, rm.getMemberId(), delegateBuilderList).build());
358                    }
359            }
360            return Collections.unmodifiableList(results);
361        }
362    
363        @Override
364        public boolean isPermissionDefined( String namespaceCode, String permissionName ) throws RiceIllegalArgumentException {
365            incomingParamCheck(namespaceCode, "namespaceCode");
366            incomingParamCheck(permissionName, "permissionName");
367    
368            // get all the permission objects whose name match that requested
369            List<Permission> permissions = getPermissionsByName(namespaceCode, permissionName);
370            // now, filter the full list by the detail passed
371            return !getMatchingPermissions(permissions, null).isEmpty();
372        }
373    
374        @Override
375        public boolean isPermissionDefinedByTemplate(String namespaceCode, String permissionTemplateName,
376                Map<String, String> permissionDetails) throws RiceIllegalArgumentException {
377    
378            incomingParamCheck(namespaceCode, "namespaceCode");
379            incomingParamCheck(permissionTemplateName, "permissionTemplateName");
380    
381            // get all the permission objects whose name match that requested
382            List<Permission> permissions = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
383            // now, filter the full list by the detail passed
384            return !getMatchingPermissions(permissions, permissionDetails).isEmpty();
385        }
386    
387        @Override
388        public List<String> getRoleIdsForPermission(String namespaceCode, String permissionName) throws RiceIllegalArgumentException {
389            incomingParamCheck(namespaceCode, "namespaceCode");
390            incomingParamCheck(permissionName, "permissionName");
391            // note...this method is cached at the RoleService interface level using an annotation, but it's called quite
392            // a bit internally, so we'll reproduce the caching here using the same key to help optimize
393            String cacheKey =  new StringBuilder("{RoleIds}")
394                    .append("namespaceCode=").append(namespaceCode).append("|")
395                    .append("name=").append(permissionName).toString();
396            Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
397            if (cachedValue != null && cachedValue.get() instanceof List) {
398                return ((List<String>)cachedValue.get());
399            }
400            // get all the permission objects whose name match that requested
401            List<Permission> permissions = getPermissionsByName(namespaceCode, permissionName);
402            // now, filter the full list by the detail passed
403            List<Permission> applicablePermissions = getMatchingPermissions(permissions, null);
404            List<String> roleIds = getRoleIdsForPermissions(applicablePermissions);
405            cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, roleIds);
406            return roleIds;
407        }
408    
409        protected List<String> getRoleIdsForPermissionTemplate(String namespaceCode,
410                String permissionTemplateName,
411                Map<String, String> permissionDetails) {
412            String cacheKey =  new StringBuilder("{getRoleIdsForPermissionTemplate}")
413                    .append("namespaceCode=").append(namespaceCode).append("|")
414                    .append("permissionTemplateName=").append(permissionTemplateName).append("|")
415                    .append("permissionDetails=").append(CacheKeyUtils.mapKey(permissionDetails)).toString();
416            Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
417            if (cachedValue != null && cachedValue.get() instanceof List) {
418                return ((List<String>)cachedValue.get());
419            }
420            // get all the permission objects whose name match that requested
421            List<Permission> permissions = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
422            // now, filter the full list by the detail passed
423            List<Permission> applicablePermissions = getMatchingPermissions(permissions, permissionDetails);
424            List<String> roleIds = getRoleIdsForPermissions(applicablePermissions);
425            cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, roleIds);
426            return roleIds;
427        }
428    
429        // --------------------
430        // Permission Data
431        // --------------------
432        
433        @Override
434        public Permission getPermission(String permissionId) throws RiceIllegalArgumentException {
435            incomingParamCheck(permissionId, "permissionId");
436            PermissionBo impl = getPermissionImpl(permissionId);
437            if (impl != null) {
438                    return PermissionBo.to(impl);
439            }
440            return null;
441        }
442        
443        @Override
444        public List<Permission> findPermissionsByTemplate(String namespaceCode, String permissionTemplateName) throws RiceIllegalArgumentException {
445            incomingParamCheck(namespaceCode, "namespaceCode");
446            incomingParamCheck(permissionTemplateName, "permissionTemplateName");
447    
448            List<Permission> perms = getPermissionsByTemplateName(namespaceCode, permissionTemplateName);
449            List<Permission> results = new ArrayList<Permission>(perms.size());
450            for (Permission perm : perms) {
451                results.add(perm);
452            }
453            return Collections.unmodifiableList(results);
454        }
455    
456            protected PermissionBo getPermissionImpl(String permissionId) throws RiceIllegalArgumentException {
457            incomingParamCheck(permissionId, "permissionId");
458    
459            HashMap<String,Object> pk = new HashMap<String,Object>( 1 );
460            pk.put( KimConstants.PrimaryKeyConstants.PERMISSION_ID, permissionId );
461            return businessObjectService.findByPrimaryKey( PermissionBo.class, pk );
462        }
463        
464        protected List<Permission> getPermissionsByTemplateName( String namespaceCode, String permissionTemplateName ){
465            String cacheKey =  new StringBuilder("{getPermissionsByTemplateName}")
466                    .append("namespaceCode=").append(namespaceCode).append("|")
467                    .append("permissionTemplateName=").append(permissionTemplateName).toString();
468            Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
469            if (cachedValue != null && cachedValue.get() instanceof List) {
470                return ((List<Permission>)cachedValue.get());
471            }
472            HashMap<String,Object> criteria = new HashMap<String,Object>(3);
473            criteria.put("template.namespaceCode", namespaceCode);
474            criteria.put("template.name", permissionTemplateName);
475            criteria.put("template.active", "Y");
476            criteria.put(KRADPropertyConstants.ACTIVE, "Y");
477            List<Permission> permissions =
478                    toPermissions(businessObjectService.findMatching(PermissionBo.class, criteria));
479            cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, permissions);
480            return permissions;
481        }
482    
483        protected List<Permission> getPermissionsByName( String namespaceCode, String permissionName ) {
484            String cacheKey =  new StringBuilder("{getPermissionsByName}")
485                    .append("namespaceCode=").append(namespaceCode).append("|")
486                    .append("permissionName=").append(permissionName).toString();
487            Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
488            if (cachedValue != null && cachedValue.get() instanceof List) {
489                return ((List<Permission>)cachedValue.get());
490            }
491            HashMap<String,Object> criteria = new HashMap<String,Object>(3);
492            criteria.put(KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode);
493            criteria.put(KimConstants.UniqueKeyConstants.PERMISSION_NAME, permissionName);
494            criteria.put(KRADPropertyConstants.ACTIVE, "Y");
495            List<Permission> permissions =
496                    toPermissions(businessObjectService.findMatching( PermissionBo.class, criteria ));
497            cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, permissions);
498            return permissions;
499        }
500            
501        @Override
502            public Template getPermissionTemplate(String permissionTemplateId) throws RiceIllegalArgumentException {
503            incomingParamCheck(permissionTemplateId, "permissionTemplateId");
504    
505            PermissionTemplateBo impl = businessObjectService.findBySinglePrimaryKey( PermissionTemplateBo.class, permissionTemplateId );
506                    if ( impl != null ) {
507                            return PermissionTemplateBo.to(impl);
508                    }
509                    return null;
510            }
511    
512        @Override
513            public Template findPermTemplateByNamespaceCodeAndName(String namespaceCode,
514                String permissionTemplateName) throws RiceIllegalArgumentException {
515                    incomingParamCheck(namespaceCode, "namespaceCode");
516            incomingParamCheck(permissionTemplateName, "permissionTemplateName");
517    
518            Map<String,String> criteria = new HashMap<String,String>(2);
519                    criteria.put( KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode );
520                    criteria.put( KimConstants.UniqueKeyConstants.PERMISSION_TEMPLATE_NAME, permissionTemplateName );
521                    PermissionTemplateBo impl = businessObjectService.findByPrimaryKey( PermissionTemplateBo.class, criteria );
522                    if ( impl != null ) {
523                            return PermissionTemplateBo.to(impl);
524                    }
525                    return null;
526            }
527    
528        @Override
529            public List<Template> getAllTemplates() {
530                    if ( allTemplates.isEmpty() ) {
531                            Map<String,String> criteria = new HashMap<String,String>(1);
532                            criteria.put( KRADPropertyConstants.ACTIVE, "Y" );
533                            List<PermissionTemplateBo> impls = (List<PermissionTemplateBo>) businessObjectService.findMatching( PermissionTemplateBo.class, criteria );
534                            List<Template> infos = new ArrayList<Template>( impls.size() );
535                            for ( PermissionTemplateBo impl : impls ) {
536                                    infos.add( PermissionTemplateBo.to(impl) );
537                            }
538                            Collections.sort(infos, new Comparator<Template>() {
539                                    @Override public int compare(Template tmpl1,
540                                                    Template tmpl2) {
541    
542                                            int result = tmpl1.getNamespaceCode().compareTo(tmpl2.getNamespaceCode());
543                                            if ( result != 0 ) {
544                                                    return result;
545                                            }
546                                            result = tmpl1.getName().compareTo(tmpl2.getName());
547                                            return result;
548                                    }
549                            });
550                            allTemplates.addAll(infos);
551                    }
552                    return Collections.unmodifiableList(allTemplates);
553        }
554    
555    
556            @Override
557            public Permission createPermission(Permission permission)
558                            throws RiceIllegalArgumentException, RiceIllegalStateException {
559            incomingParamCheck(permission, "permission");
560    
561            if (StringUtils.isNotBlank(permission.getId()) && getPermission(permission.getId()) != null) {
562                throw new RiceIllegalStateException("the permission to create already exists: " + permission);
563            }
564            List<PermissionAttributeBo> attrBos = Collections.emptyList();
565            if (permission.getTemplate() != null) {
566                attrBos = KimAttributeDataBo.createFrom(PermissionAttributeBo.class, permission.getAttributes(), permission.getTemplate().getKimTypeId());
567            }
568            PermissionBo bo = PermissionBo.from(permission);
569            if (bo.getTemplate() == null && bo.getTemplateId() != null) {
570                bo.setTemplate(PermissionTemplateBo.from(getPermissionTemplate(bo.getTemplateId())));
571            }
572            bo.setAttributeDetails(attrBos);
573            return PermissionBo.to(businessObjectService.save(bo));
574            }
575    
576            @Override
577            public Permission updatePermission(Permission permission)
578                            throws RiceIllegalArgumentException, RiceIllegalStateException {
579            incomingParamCheck(permission, "permission");
580    
581            PermissionBo oldPermission = getPermissionImpl(permission.getId());
582            if (StringUtils.isBlank(permission.getId()) || oldPermission == null) {
583                throw new RiceIllegalStateException("the permission does not exist: " + permission);
584            }
585    
586            //List<PermissionAttributeBo> attrBos = KimAttributeDataBo.createFrom(PermissionAttributeBo.class, permission.getAttributes(), permission.getTemplate().getKimTypeId());
587    
588            List<PermissionAttributeBo> oldAttrBos = oldPermission.getAttributeDetails();
589            //put old attributes in map for easier updating
590            Map<String, PermissionAttributeBo> oldAttrMap = new HashMap<String, PermissionAttributeBo>();
591            for (PermissionAttributeBo oldAttr : oldAttrBos) {
592                oldAttrMap.put(oldAttr.getKimAttribute().getAttributeName(), oldAttr);
593            }
594            List<PermissionAttributeBo> newAttrBos = new ArrayList<PermissionAttributeBo>();
595            for (String key : permission.getAttributes().keySet()) {
596                if (oldAttrMap.containsKey(key)) {
597                    PermissionAttributeBo updatedAttr = oldAttrMap.get(key);
598                    updatedAttr.setAttributeValue(permission.getAttributes().get(key));
599                    newAttrBos.add(updatedAttr);
600                } else { //new attribute
601                    newAttrBos.addAll(KimAttributeDataBo.createFrom(PermissionAttributeBo.class, Collections.singletonMap(key, permission.getAttributes().get(key)), permission.getTemplate().getKimTypeId()));
602                }
603            }
604            PermissionBo bo = PermissionBo.from(permission);
605            if (CollectionUtils.isNotEmpty(newAttrBos)) {
606                if(null!= bo.getAttributeDetails())  {
607                    bo.getAttributeDetails().clear();
608                }
609                bo.setAttributeDetails(newAttrBos);
610            }
611            if (bo.getTemplate() == null && bo.getTemplateId() != null) {
612                bo.setTemplate(PermissionTemplateBo.from(getPermissionTemplate(bo.getTemplateId())));
613            }
614    
615            return PermissionBo.to(businessObjectService.save(bo));         
616            }
617            
618        @Override
619        public Permission findPermByNamespaceCodeAndName(String namespaceCode, String permissionName)
620                throws RiceIllegalArgumentException {
621            incomingParamCheck(namespaceCode, "namespaceCode");
622            incomingParamCheck(permissionName, "permissionName");
623    
624            PermissionBo permissionBo = getPermissionBoByName(namespaceCode, permissionName);
625            if (permissionBo != null) {
626                return PermissionBo.to(permissionBo);
627            }
628            return null;
629        }
630        
631        protected PermissionBo getPermissionBoByName(String namespaceCode, String permissionName) {
632            if (StringUtils.isBlank(namespaceCode)
633                    || StringUtils.isBlank(permissionName)) {
634                return null;
635            }
636            Map<String, String> criteria = new HashMap<String, String>();
637            criteria.put(KimConstants.UniqueKeyConstants.NAMESPACE_CODE, namespaceCode);
638            criteria.put(KimConstants.UniqueKeyConstants.NAME, permissionName);
639            criteria.put(KRADPropertyConstants.ACTIVE, "Y");
640            // while this is not actually the primary key - there will be at most one row with these criteria
641            return businessObjectService.findByPrimaryKey(PermissionBo.class, criteria);
642        }
643    
644        @Override
645        public PermissionQueryResults findPermissions(final QueryByCriteria queryByCriteria)
646                throws RiceIllegalArgumentException {
647            incomingParamCheck(queryByCriteria, "queryByCriteria");
648    
649            LookupCustomizer.Builder<PermissionBo> lc = LookupCustomizer.Builder.create();
650            lc.setPredicateTransform(AttributeTransform.getInstance());
651    
652            GenericQueryResults<PermissionBo> results = criteriaLookupService.lookup(PermissionBo.class, queryByCriteria, lc.build());
653    
654            PermissionQueryResults.Builder builder = PermissionQueryResults.Builder.create();
655            builder.setMoreResultsAvailable(results.isMoreResultsAvailable());
656            builder.setTotalRowCount(results.getTotalRowCount());
657    
658            final List<Permission.Builder> ims = new ArrayList<Permission.Builder>();
659            for (PermissionBo bo : results.getResults()) {
660                ims.add(Permission.Builder.create(bo));
661            }
662    
663            builder.setResults(ims);
664            return builder.build();
665        }
666    
667        @Override
668        public TemplateQueryResults findPermissionTemplates(final QueryByCriteria queryByCriteria)
669                throws RiceIllegalArgumentException {
670            incomingParamCheck(queryByCriteria, "queryByCriteria");
671    
672            GenericQueryResults<PermissionTemplateBo> results = criteriaLookupService.lookup(PermissionTemplateBo.class, queryByCriteria);
673    
674            TemplateQueryResults.Builder builder = TemplateQueryResults.Builder.create();
675            builder.setMoreResultsAvailable(results.isMoreResultsAvailable());
676            builder.setTotalRowCount(results.getTotalRowCount());
677    
678            final List<Template.Builder> ims = new ArrayList<Template.Builder>();
679            for (PermissionTemplateBo bo : results.getResults()) {
680                ims.add(Template.Builder.create(bo));
681            }
682    
683            builder.setResults(ims);
684            return builder.build();
685        }
686    
687        private List<String> getRoleIdsForPermissions( Collection<Permission> permissions ) {
688            if (CollectionUtils.isEmpty(permissions)) {
689                return Collections.emptyList();
690            }
691            List<String> ids = new ArrayList<String>();
692            for (Permission p : permissions) {
693                ids.add(p.getId());
694            }
695    
696            return getRoleIdsForPermissionIds(ids);
697        }
698    
699        private List<String> getRoleIdsForPermissionIds(Collection<String> permissionIds) {
700            if (CollectionUtils.isEmpty(permissionIds)) {
701                return Collections.emptyList();
702            }
703            String cacheKey =  new StringBuilder("{getRoleIdsForPermissionIds}")
704                    .append("permissionIds=").append(CacheKeyUtils.key(permissionIds)).toString();
705            Cache.ValueWrapper cachedValue = cacheManager.getCache(Permission.Cache.NAME).get(cacheKey);
706            if (cachedValue != null && cachedValue.get() instanceof List) {
707                return ((List<String>)cachedValue.get());
708            }
709            QueryByCriteria query = QueryByCriteria.Builder.fromPredicates(equal("active", "true"), in("permissionId", permissionIds.toArray(new String[]{})));
710            GenericQueryResults<RolePermissionBo> results = criteriaLookupService.lookup(RolePermissionBo.class, query);
711            List<String> roleIds = new ArrayList<String>();
712            for (RolePermissionBo bo : results.getResults()) {
713                roleIds.add(bo.getRoleId());
714            }
715            roleIds = Collections.unmodifiableList(roleIds);
716            cacheManager.getCache(Permission.Cache.NAME).put(cacheKey, roleIds);
717            return roleIds;
718        }
719            
720            /**
721         * Sets the kimTypeInfoService attribute value.
722         *
723         * @param kimTypeInfoService The kimTypeInfoService to set.
724         */
725            public void setKimTypeInfoService(KimTypeInfoService kimTypeInfoService) {
726                    this.kimTypeInfoService = kimTypeInfoService;
727            }
728            
729            /**
730         * Sets the defaultPermissionTypeService attribute value.
731         *
732         * @param defaultPermissionTypeService The defaultPermissionTypeService to set.
733         */
734            public void setDefaultPermissionTypeService(PermissionTypeService defaultPermissionTypeService) {
735            this.defaultPermissionTypeService = defaultPermissionTypeService;
736            }
737            
738            /**
739         * Sets the roleService attribute value.
740         *
741         * @param roleService The roleService to set.
742         */
743            public void setRoleService(RoleService roleService) {
744                    this.roleService = roleService;
745            }
746    
747        /**
748         * Sets the businessObjectService attribute value.
749         *
750         * @param businessObjectService The businessObjectService to set.
751         */
752        public void setBusinessObjectService(final BusinessObjectService businessObjectService) {
753            this.businessObjectService = businessObjectService;
754        }
755    
756        /**
757         * Sets the criteriaLookupService attribute value.
758         *
759         * @param criteriaLookupService The criteriaLookupService to set.
760         */
761        public void setCriteriaLookupService(final CriteriaLookupService criteriaLookupService) {
762            this.criteriaLookupService = criteriaLookupService;
763        }
764    
765        /**
766         * Sets the cache manager which this service implementation can for internal caching.
767         * Calling this setter is optional, though the value passed to it must not be null.
768         *
769         * @param cacheManager the cache manager to use for internal caching, must not be null
770         * @throws IllegalArgumentException if a null cache manager is passed
771         */
772        public void setCacheManager(final CacheManager cacheManager) {
773            if (cacheManager == null) {
774                throw new IllegalArgumentException("cacheManager must not be null");
775            }
776            this.cacheManager = cacheManager;
777        }
778    
779        private List<Permission> toPermissions(Collection<PermissionBo> permissionBos) {
780            if (CollectionUtils.isEmpty(permissionBos)) {
781                return new ArrayList<Permission>();
782            }
783            List<Permission> permissions = new ArrayList<Permission>(permissionBos.size());
784            for (PermissionBo permissionBo : permissionBos) {
785                permissions.add(PermissionBo.to(permissionBo));
786            }
787            return permissions;
788        }
789        
790        protected void logAuthorizationCheck(String checkType, String principalId, String namespaceCode, String permissionName, Map<String, String> qualification ) {
791            StringBuilder sb = new StringBuilder();
792            sb.append(  '\n' );
793            sb.append( "Is AuthZ for " ).append( checkType ).append( ": " ).append( namespaceCode ).append( "/" ).append( permissionName ).append( '\n' );
794            sb.append( "             Principal:  " ).append( principalId );
795            if ( principalId != null ) {
796                Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(principalId);
797                if ( principal != null ) {
798                    sb.append( " (" ).append( principal.getPrincipalName() ).append( ')' );
799                }
800            }
801            sb.append( '\n' );
802            sb.append( "             Qualifiers:\n" );
803            if ( qualification != null && !qualification.isEmpty() ) {
804                sb.append( qualification );
805            } else {
806                sb.append( "                         [null]\n" );
807            }
808            if (LOG.isTraceEnabled()) {
809                LOG.trace( sb.append(ExceptionUtils.getStackTrace(new Throwable())));
810            } else {
811                LOG.debug(sb.toString());
812            }
813        }
814        
815        protected void logAuthorizationCheckByTemplate(String checkType, String principalId, String namespaceCode, String permissionName, 
816                                                       Map<String, String> permissionDetails, Map<String, String> qualification ) {
817            StringBuilder sb = new StringBuilder();
818            sb.append(  '\n' );
819            sb.append( "Is AuthZ for " ).append( checkType ).append( ": " ).append( namespaceCode ).append( "/" ).append( permissionName ).append( '\n' );
820            sb.append( "             Principal:  " ).append( principalId );
821            if ( principalId != null ) {
822                Principal principal = KimApiServiceLocator.getIdentityService().getPrincipal(principalId);
823                if ( principal != null ) {
824                    sb.append( " (" ).append( principal.getPrincipalName() ).append( ')' );
825                }
826            }
827            sb.append( '\n' );
828            sb.append( "             Details:\n" );
829            if ( permissionDetails != null ) {
830                sb.append( permissionDetails );
831            } else {
832                sb.append( "                         [null]\n" );
833            }
834            sb.append( "             Qualifiers:\n" );
835            if ( qualification != null && !qualification.isEmpty() ) {
836                sb.append( qualification );
837            } else {
838                sb.append( "                         [null]\n" );
839            }
840            if (LOG.isTraceEnabled()) {
841                LOG.trace( sb.append(ExceptionUtils.getStackTrace(new Throwable())));
842            } else {
843                LOG.debug(sb.toString());
844            }
845        }
846        
847        private void incomingParamCheck(Object object, String name) {
848            if (object == null) {
849                throw new RiceIllegalArgumentException(name + " was null");
850            } else if (object instanceof String
851                    && StringUtils.isBlank((String) object)) {
852                throw new RiceIllegalArgumentException(name + " was blank");
853            }
854        }
855    
856    
857    }