Java tutorial
/** * Copyright 2012-2013 Trento RISE * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package eu.trentorise.smartcampus.permissionprovider.manager; import java.io.UnsupportedEncodingException; import java.net.URI; import java.net.URLDecoder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import javax.annotation.PostConstruct; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBElement; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.transform.stream.StreamSource; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import org.springframework.web.util.UriTemplate; import eu.trentorise.smartcampus.permissionprovider.Config; import eu.trentorise.smartcampus.permissionprovider.Config.AUTHORITY; import eu.trentorise.smartcampus.permissionprovider.Config.RESOURCE_VISIBILITY; import eu.trentorise.smartcampus.permissionprovider.jaxbmodel.ResourceDeclaration; import eu.trentorise.smartcampus.permissionprovider.jaxbmodel.ResourceMapping; import eu.trentorise.smartcampus.permissionprovider.jaxbmodel.Service; import eu.trentorise.smartcampus.permissionprovider.jaxbmodel.Services; import eu.trentorise.smartcampus.permissionprovider.model.ClientDetailsEntity; import eu.trentorise.smartcampus.permissionprovider.model.Resource; import eu.trentorise.smartcampus.permissionprovider.model.ResourceParameter; import eu.trentorise.smartcampus.permissionprovider.model.ResourceParameterKey; import eu.trentorise.smartcampus.permissionprovider.oauth.ResourceStorage; import eu.trentorise.smartcampus.permissionprovider.repository.ClientDetailsRepository; import eu.trentorise.smartcampus.permissionprovider.repository.ResourceParameterRepository; import eu.trentorise.smartcampus.permissionprovider.repository.ResourceRepository; /** * Class used to operate resource model. * @author raman * */ @Component @Transactional public class ResourceAdapter { private static Log logger = LogFactory.getLog(ResourceAdapter.class); @Autowired private ResourceStorage resourceStorage; @Autowired private ResourceParameterRepository resourceParameterRepository; @Autowired private ResourceRepository resourceRepository; @Autowired private ClientDetailsRepository clientDetailsRepository; private Map<String, Service> serviceMap = new HashMap<String, Service>(); private Map<String, ResourceDeclaration> resourceDeclarationMap = new HashMap<String, ResourceDeclaration>(); private Map<String, ResourceMapping> resourceMappingMap = new HashMap<String, ResourceMapping>(); private Map<String, String> resourceTreeMap = new HashMap<String, String>(); private Map<String, String> resourceServiceMap = new HashMap<String, String>(); private Map<String, List<ResourceMapping>> flatServiceMappings = new HashMap<String, List<ResourceMapping>>(); @PostConstruct public void init() { processServiceResourceTemplates(); } /** * Save resource parameter. Check the uniqueness of the parameter value across all the * parameters with the same resource parameter definition ID. Instantiate * all the derived resources. * @param rp */ public void storeResourceParameter(ResourceParameter rp) { ResourceParameterKey pk = new ResourceParameterKey(); pk.resourceId = rp.getResourceId(); pk.value = rp.getValue(); ResourceParameter rpold = resourceParameterRepository.findOne(pk); // check uniqueness String clientId = rp.getClientId(); if (rpold != null && !clientId.equals(clientId)) { throw new IllegalArgumentException("A parameter already used by another app"); } else if (rpold == null) { resourceParameterRepository.save(rp); // derived resources Map<String, ResourceMapping> mappings = findResourceURIs(rp); // store new resources entailed by the resource parameter if (mappings != null) { Set<String> newSet = new HashSet<String>(); Set<String> newScopes = new HashSet<String>(); for (String uri : mappings.keySet()) { ResourceMapping resourceMapping = mappings.get(uri); Resource r = prepareResource(clientId, rp, uri, resourceMapping, rp.getVisibility()); resourceRepository.save(r); newSet.add(r.getResourceId().toString()); newScopes.add(r.getResourceUri()); } // add automatically the resources entailed by own resource parameters to the client resourceIds ClientDetailsEntity cd = clientDetailsRepository.findByClientId(clientId); Set<String> oldSet = cd.getResourceIds(); if (oldSet != null) newSet.addAll(oldSet); cd.setResourceIds(StringUtils.collectionToCommaDelimitedString(newSet)); // add automatically the resources entailed by own resource parameters to the client scope oldSet = cd.getScope(); if (oldSet != null) newScopes.addAll(oldSet); cd.setScope(StringUtils.collectionToCommaDelimitedString(newScopes)); clientDetailsRepository.save(cd); } } else { throw new IllegalArgumentException("A parameter already exists"); } } /** * Update the visibility of the resource parameter. This only affects the child resources * in case of more restrictive policy applied. If the changes are in conflict with the * clients that use the corresponding resources, an exception is thrown. Also, if the new * visibility is more relaxing than that of the parent, the exception is thrown. * * @param resourceId id of the changed resource parameter definition * @param value parameter value * @param clientId owning client id * @param visibility new visibility value * @return changed parameter */ public ResourceParameter updateResourceParameterVisibility(String resourceId, String value, String clientId, RESOURCE_VISIBILITY visibility) { assert visibility != null; ResourceParameterKey pk = new ResourceParameterKey(); pk.resourceId = resourceId; pk.value = value; ResourceParameter rpdb = resourceParameterRepository.findOne(pk); if (rpdb != null && !rpdb.getClientId().equals(clientId)) { throw new IllegalArgumentException("Can update only own resource parameters"); } if (rpdb != null) { ClientDetailsEntity client = clientDetailsRepository.findByClientId(clientId); Map<String, RESOURCE_VISIBILITY> visibilityMap = new HashMap<String, Config.RESOURCE_VISIBILITY>(); rpdb.setVisibility(visibility); for (String uri : findResourceURIs(rpdb).keySet()) { visibilityMap.put(uri, visibility); Resource res = resourceRepository.findByResourceUri(uri); res.setVisibility(visibility); resourceRepository.save(res); } if (!checkUsages(visibilityMap, clientId, client.getDeveloperId())) { throw new IllegalArgumentException("Resource is in use, cannot reduce visibility"); } ResourceDeclaration rd = resourceDeclarationMap.get(rpdb.getResourceId()); List<Resource> resources = resourceRepository.findByClientId(clientId); if (resources != null) { for (Resource r : resources) { ResourceMapping rm = resourceMappingMap.get(r.getResourceType()); Map<String, String> params = new UriTemplate(rm.getUri()).match(r.getResourceUri()); if (params != null && rpdb.getValue().equals(params.get(rd.getName()))) { r.setVisibility(rpdb.getVisibility()); } } } } else { throw new IllegalArgumentException("No resource parameter found"); } return rpdb; } /** * Check the visibility of the specified resources managed by the specified client and its owner by all the clients registered * @param visibilityMap contains the resource URI and its target visibility * @return true if at least one of the specified resources violates visibility constraint */ private boolean checkUsages(Map<String, RESOURCE_VISIBILITY> visibilityMap, String clientId, Long developerId) { List<ClientDetailsEntity> clients = clientDetailsRepository.findAll(); for (ClientDetailsEntity client : clients) { // owned resources are visible, skip if (clientId.equals(client.getClientId())) continue; // Set<String> uris = client.getScope(); for (String uri : visibilityMap.keySet()) { if (uris.contains(uri)) { switch (visibilityMap.get(uri)) { // if should be visible only by the owning client app - violation case CLIENT_APP: return false; // if should be visible only by the owning developer - violation case DEVELOPER: if (!client.getDeveloperId().equals(developerId)) { return false; } default: break; } } } } return true; } /** * Try to remove the resource parameter and its children. Operation is recursive. * If one of the derived resources is already in use, an exception is thrown. * @param resourceId * @param value * @param clientId */ public void removeResourceParameter(String resourceId, String value, String clientId) { ResourceParameterKey pk = new ResourceParameterKey(); pk.resourceId = resourceId; pk.value = value; // main parameter ResourceParameter rpdb = resourceParameterRepository.findOne(pk); if (rpdb != null && !rpdb.getClientId().equals(clientId)) { throw new IllegalArgumentException("Can delete only own resource parameters"); } if (rpdb != null) { Set<String> ids = new HashSet<String>(); Set<String> scopes = new HashSet<String>(); // aggregate all derived resource uris Collection<String> uris = findResourceURIs(rpdb).keySet(); for (String uri : uris) { Resource r = resourceRepository.findByResourceUri(uri); if (r != null) { ids.add(r.getResourceId().toString()); scopes.add(r.getResourceUri()); } } ClientDetailsEntity owner = null; // check the resource uri usages for (ClientDetailsEntity cd : clientDetailsRepository.findAll()) { if (cd.getClientId().equals(clientId)) { owner = cd; continue; } if (!Collections.disjoint(cd.getResourceIds(), ids)) { throw new IllegalArgumentException("Resource is in use by other client app."); } } // delete main and its children for (String id : ids) { resourceRepository.delete(Long.parseLong(id)); } if (owner != null) { Set<String> oldScopes = new HashSet<String>(owner.getScope()); oldScopes.removeAll(scopes); owner.setScope(StringUtils.collectionToCommaDelimitedString(oldScopes)); Set<String> oldIds = new HashSet<String>(owner.getResourceIds()); oldIds.removeAll(ids); owner.setResourceIds(StringUtils.collectionToCommaDelimitedString(oldIds)); clientDetailsRepository.save(owner); } resourceParameterRepository.delete(rpdb); } } /** * Find all the resource uris derived from the specified resource parameter and its parent parameters. * @param rpdb * @return map with URIs as the keys and mapping definitions as values. */ private Map<String, ResourceMapping> findResourceURIs(ResourceParameter rpdb) { Map<String, ResourceMapping> res = new HashMap<String, ResourceMapping>(); Map<String, String> params = new HashMap<String, String>(); params.put(rpdb.getResourceId(), rpdb.getValue()); // the service where parameter is defined Service service = serviceMap.get(rpdb.getServiceId()); if (service == null) { throw new IllegalArgumentException("Service " + rpdb.getServiceId() + " is not found."); } // all the service resource mappings List<ResourceMapping> list = flatServiceMappings.get(service.getId()); if (list != null) { for (ResourceMapping rm : list) { UriTemplate template = new UriTemplate(rm.getUri()); // if the extracted parameters contain all the template parameters, the mapping is updated if (template.getVariableNames() != null) { if (new HashSet<String>(template.getVariableNames()).equals(params.keySet())) { URI uri = template.expand(params); res.put(uri.toString(), rm); } } } } return res; } /** * Read resource parameters owned by the specified client, optionally restricting * to the specified service and resource parameter ID * @param clientId * @param serviceId * @param resourceId * @return */ public List<ResourceParameter> getOwnResourceParameters(String clientId, String serviceId, String resourceId) { if (clientId == null) { return Collections.emptyList(); } if (resourceId != null) { return resourceParameterRepository.findByClientIdAndResourceId(clientId, resourceId); } if (serviceId != null) { return resourceParameterRepository.findByClientIdAndServiceId(clientId, serviceId); } return resourceParameterRepository.findByClientId(clientId); } /** * Read the resources from the XML descriptor * @return */ private List<Service> loadResourceTemplates() { try { JAXBContext jaxb = JAXBContext.newInstance(Service.class, Services.class, ResourceMapping.class, ResourceDeclaration.class); Unmarshaller unm = jaxb.createUnmarshaller(); JAXBElement<Services> element = (JAXBElement<Services>) unm.unmarshal( new StreamSource(getClass().getResourceAsStream("resourceTemplates.xml")), Services.class); return element.getValue().getService(); } catch (JAXBException e) { logger.error("Failed to load resource templates: " + e.getMessage(), e); return Collections.emptyList(); } } /** * Parse the resource template descriptor */ private void processServiceResourceTemplates() { List<Service> services = loadResourceTemplates(); for (Service s : services) { // service descriptor serviceMap.put(s.getId(), s); if (s.getResource() != null) { // process resource parameter declarations for (ResourceDeclaration rd : s.getResource()) { // resource parameter declaration resourceDeclarationMap.put(rd.getId(), rd); // map resource parameter to service resourceServiceMap.put(rd.getId(), s.getId()); // extract parameters recursively extractRDs(rd.getResource(), rd.getId(), s.getId()); } } // process resource mappings if (s.getResourceMapping() != null) { List<Resource> resources = new ArrayList<Resource>(); for (ResourceMapping rm : s.getResourceMapping()) { // extract resource mappings recursively extractResources(rm, resources, s); } // store the extracted non-parametric resource mappings if (!resources.isEmpty()) { resourceStorage.storeResources(resources); } } // TODO validate service data } } /** * Extract resource mappings recursively * @param rm * @param resources * @param s */ private void extractResources(ResourceMapping rm, List<Resource> resources, Service s) { // resource mapping resourceMappingMap.put(rm.getId(), rm); // flat list of resource mappings associated to the service List<ResourceMapping> list = flatServiceMappings.get(s.getId()); if (list == null) { list = new ArrayList<ResourceMapping>(); flatServiceMappings.put(s.getId(), list); } list.add(rm); // add non-parametric resources to the target list if (!isParametric(rm)) { resources.add(prepareResource(null, null, rm.getUri(), rm, RESOURCE_VISIBILITY.PUBLIC)); } // recursion if (rm.getResourceMapping() != null) { for (ResourceMapping child : rm.getResourceMapping()) { extractResources(child, resources, s); } } } /** * @param rm * @return true if the mapping definition is parametric to the service resource parameters */ private boolean isParametric(ResourceMapping rm) { UriTemplate template = new UriTemplate(rm.getUri()); return template.getVariableNames() != null && template.getVariableNames().size() > 0; } /** * @param clientId * @param rp * @param uri * @param rm * @param visibility * @return {@link Resource} instance out of mapping, clientID, and resource URI. */ protected Resource prepareResource(String clientId, ResourceParameter rp, String uri, ResourceMapping rm, RESOURCE_VISIBILITY visibility) { Resource r = new Resource(); r.setAccessibleByOthers(rm.isAccessibleByOthers()); r.setApprovalRequired(rm.isApprovalRequired()); r.setAuthority(AUTHORITY.valueOf(rm.getAuthority().value())); r.setClientId(clientId); r.setResourceParameter(rp); UriTemplate template = new UriTemplate(rm.getUri()); Map<String, String> params = template.match(uri); template = new UriTemplate(rm.getDescription()); try { r.setDescription(URLDecoder.decode(template.expand(params).toString(), "utf8")); } catch (UnsupportedEncodingException e) { r.setDescription(rm.getDescription()); } template = new UriTemplate(rm.getName()); try { r.setName(URLDecoder.decode(template.expand(params).toString(), "utf8")); } catch (UnsupportedEncodingException e) { r.setName(rm.getName()); } r.setResourceType(rm.getId()); r.setResourceUri(uri); r.setVisibility(visibility); return r; } /** * Recursively extract resource parameter declarations * @param list * @param id */ private void extractRDs(List<ResourceDeclaration> list, String parent, String serviceId) { if (list != null) { for (ResourceDeclaration rd : list) { // resource declaration map resourceDeclarationMap.put(rd.getId(), rd); // map property to its parent if any resourceTreeMap.put(rd.getId(), parent); // map resource to the service resourceServiceMap.put(rd.getId(), serviceId); // recursion extractRDs(rd.getResource(), rd.getId(), serviceId); } } } /** * @return */ public Collection<Service> getServices() { return Collections.unmodifiableCollection(serviceMap.values()); } /** * read the resource that the client app may request permissions for * @param clientId * @return */ public List<Resource> getAvailableResources(String clientId, Long userId) { if (clientId != null) { // check all the resources List<Resource> list = resourceRepository.findAll(); for (Iterator<Resource> iterator = list.iterator(); iterator.hasNext();) { Resource resource = iterator.next(); // interested only in non-owned resources if (!clientId.equals(resource.getClientId()) && resource.getClientId() != null) { if (!resource.isAccessibleByOthers()) { iterator.remove(); continue; } // check the resource visibility for the current client switch (resource.getVisibility()) { case CLIENT_APP: iterator.remove(); break; case DEVELOPER: ClientDetailsEntity cd = clientDetailsRepository.findByClientId(resource.getClientId()); if (cd == null || !cd.getDeveloperId().equals(userId)) { iterator.remove(); } default: break; } } } return list; } return Collections.emptyList(); } }