Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you 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 edu.psu.swe.scim.server.utility; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map.Entry; import java.util.Set; import java.util.function.Function; import javax.annotation.PostConstruct; import javax.ejb.Stateless; import javax.inject.Inject; import org.apache.commons.lang3.StringUtils; import com.fasterxml.jackson.annotation.JsonInclude.Include; import com.fasterxml.jackson.databind.AnnotationIntrospector; import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.introspect.AnnotationIntrospectorPair; import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; import com.fasterxml.jackson.databind.module.SimpleModule; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationIntrospector; import com.fasterxml.jackson.module.jaxb.JaxbAnnotationModule; import edu.psu.swe.scim.server.exception.AttributeDoesNotExistException; import edu.psu.swe.scim.server.rest.ScimResourceDeserializer; import edu.psu.swe.scim.server.schema.Registry; import edu.psu.swe.scim.spec.protocol.attribute.AttributeReference; import edu.psu.swe.scim.spec.resources.ScimExtension; import edu.psu.swe.scim.spec.resources.ScimGroup; import edu.psu.swe.scim.spec.resources.ScimResource; import edu.psu.swe.scim.spec.resources.ScimUser; import edu.psu.swe.scim.spec.schema.AttributeContainer; import edu.psu.swe.scim.spec.schema.Schema; import edu.psu.swe.scim.spec.schema.Schema.Attribute; import edu.psu.swe.scim.spec.schema.Schema.Attribute.Returned; import edu.psu.swe.scim.spec.schema.Schema.Attribute.Type; import lombok.extern.slf4j.Slf4j; @Stateless @Slf4j public class AttributeUtil { @Inject Registry registry; ObjectMapper objectMapper; @PostConstruct public void init() { // TODO move this to a CDI producer objectMapper = new ObjectMapper(); JaxbAnnotationModule jaxbAnnotationModule = new JaxbAnnotationModule(); objectMapper.registerModule(jaxbAnnotationModule); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); AnnotationIntrospector jaxbIntrospector = new JaxbAnnotationIntrospector(objectMapper.getTypeFactory()); AnnotationIntrospector jacksonIntrospector = new JacksonAnnotationIntrospector(); AnnotationIntrospector pair = new AnnotationIntrospectorPair(jacksonIntrospector, jaxbIntrospector); objectMapper.setAnnotationIntrospector(pair); objectMapper.setSerializationInclusion(Include.NON_NULL); SimpleModule module = new SimpleModule(); module.addDeserializer(ScimResource.class, new ScimResourceDeserializer(this.registry, this.objectMapper)); objectMapper.registerModule(module); } public <T extends ScimResource> T keepAlwaysAttributesForDisplay(T resource) throws IllegalArgumentException, IllegalAccessException, AttributeDoesNotExistException, IOException { return setAttributesForDisplayInternal(resource, Returned.DEFAULT, Returned.REQUEST, Returned.NEVER); } public <T extends ScimResource> T setAttributesForDisplay(T resource) throws IllegalArgumentException, IllegalAccessException, AttributeDoesNotExistException, IOException { return setAttributesForDisplayInternal(resource, Returned.REQUEST, Returned.NEVER); } private <T extends ScimResource> T setAttributesForDisplayInternal(T resource, Returned... removeAttributesOfTypes) throws IllegalArgumentException, IllegalAccessException, AttributeDoesNotExistException, IOException { T copy = cloneScimResource(resource); String resourceType = copy.getResourceType(); Schema schema = registry.getBaseSchemaOfResourceType(resourceType); // return always and default, exclude never and requested for (Returned removeAttributesOfType : removeAttributesOfTypes) { removeAttributesOfType(copy, schema, removeAttributesOfType); } for (Entry<String, ScimExtension> extensionEntry : copy.getExtensions().entrySet()) { String extensionUrn = extensionEntry.getKey(); ScimExtension scimExtension = extensionEntry.getValue(); Schema extensionSchema = registry.getSchema(extensionUrn); for (Returned removeAttributesOfType : removeAttributesOfTypes) { removeAttributesOfType(scimExtension, extensionSchema, removeAttributesOfType); } } return copy; } public <T extends ScimResource> T setAttributesForDisplay(T resource, Set<AttributeReference> attributes) throws IllegalArgumentException, IllegalAccessException, AttributeDoesNotExistException, IOException { if (attributes.isEmpty()) { return setAttributesForDisplay(resource); } else { T copy = cloneScimResource(resource); String resourceType = copy.getResourceType(); Schema schema = registry.getBaseSchemaOfResourceType(resourceType); // return always and specified attributes, exclude never Set<Attribute> attributesToKeep = resolveAttributeReferences(attributes, true); removeAttributesOfType(copy, schema, Returned.DEFAULT, attributesToKeep); removeAttributesOfType(copy, schema, Returned.REQUEST, attributesToKeep); removeAttributesOfType(copy, schema, Returned.NEVER); for (Entry<String, ScimExtension> extensionEntry : copy.getExtensions().entrySet()) { String extensionUrn = extensionEntry.getKey(); ScimExtension scimExtension = extensionEntry.getValue(); Schema extensionSchema = registry.getSchema(extensionUrn); removeAttributesOfType(scimExtension, extensionSchema, Returned.DEFAULT, attributesToKeep); removeAttributesOfType(scimExtension, extensionSchema, Returned.REQUEST, attributesToKeep); removeAttributesOfType(scimExtension, extensionSchema, Returned.NEVER); } return copy; } } public <T extends ScimResource> T setExcludedAttributesForDisplay(T resource, Set<AttributeReference> excludedAttributes) throws IllegalArgumentException, IllegalAccessException, AttributeDoesNotExistException, IOException { if (excludedAttributes.isEmpty()) { return setAttributesForDisplay(resource); } else { T copy = cloneScimResource(resource); String resourceType = copy.getResourceType(); Schema schema = registry.getBaseSchemaOfResourceType(resourceType); // return always and default, exclude never and specified attributes Set<Attribute> attributesToRemove = resolveAttributeReferences(excludedAttributes, false); removeAttributesOfType(copy, schema, Returned.REQUEST); removeAttributesOfType(copy, schema, Returned.NEVER); removeAttributes(copy, schema, attributesToRemove); for (Entry<String, ScimExtension> extensionEntry : copy.getExtensions().entrySet()) { String extensionUrn = extensionEntry.getKey(); ScimExtension scimExtension = extensionEntry.getValue(); Schema extensionSchema = registry.getSchema(extensionUrn); removeAttributesOfType(scimExtension, extensionSchema, Returned.REQUEST); removeAttributesOfType(scimExtension, extensionSchema, Returned.NEVER); removeAttributes(scimExtension, extensionSchema, attributesToRemove); } return copy; } } @SuppressWarnings("unchecked") private <T extends ScimResource> T cloneScimResource(T original) throws IOException { ByteArrayOutputStream boas = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(boas); oos.writeObject(original); ByteArrayInputStream bais = new ByteArrayInputStream(boas.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bais); T copy = null; try { copy = (T) ois.readObject(); } catch (ClassNotFoundException e) { // Should never happen log.error("", e); } return copy; } private void removeAttributesOfType(Object object, AttributeContainer attributeContainer, Returned returned) throws IllegalArgumentException, IllegalAccessException { Function<Attribute, Boolean> function = (attribute) -> returned == attribute.getReturned(); processAttributes(object, attributeContainer, function); } private void removeAttributesOfType(Object object, AttributeContainer attributeContainer, Returned returned, Set<Attribute> attributesToKeep) throws IllegalArgumentException, IllegalAccessException { Function<Attribute, Boolean> function = (attribute) -> !attributesToKeep.contains(attribute) && returned == attribute.getReturned(); processAttributes(object, attributeContainer, function); } private void removeAttributes(Object object, AttributeContainer attributeContainer, Set<Attribute> attributesToRemove) throws IllegalArgumentException, IllegalAccessException { Function<Attribute, Boolean> function = (attribute) -> attributesToRemove.contains(attribute); processAttributes(object, attributeContainer, function); } private void processAttributes(Object object, AttributeContainer attributeContainer, Function<Attribute, Boolean> function) throws IllegalArgumentException, IllegalAccessException { if (attributeContainer != null && object != null) { for (Attribute attribute : attributeContainer.getAttributes()) { Field field = attribute.getField(); if (function.apply(attribute)) { field.setAccessible(true); if (!field.getType().isPrimitive()) { Object obj = field.get(object); if (obj == null) { continue; } log.info("field to be set to null = " + field.getType().getName()); field.set(object, null); } } else if (!attribute.isMultiValued() && attribute.getType() == Type.COMPLEX) { String name = field.getName(); log.debug("### Processing single value complex field " + name); field.setAccessible(true); Object subObject = field.get(object); if (subObject == null) { continue; } Attribute subAttribute = attributeContainer.getAttribute(name); log.debug("### container type = " + attributeContainer.getClass().getName()); if (subAttribute == null) { log.debug("#### subattribute == null"); } processAttributes(subObject, subAttribute, function); } else if (attribute.isMultiValued() && attribute.getType() == Type.COMPLEX) { String name = field.getName(); log.debug("### Processing multi-valued complex field " + name); field.setAccessible(true); Object subObject = field.get(object); if (subObject == null) { continue; } if (Collection.class.isAssignableFrom(subObject.getClass())) { Collection<?> collection = (Collection<?>) subObject; for (Object o : collection) { Attribute subAttribute = attributeContainer.getAttribute(name); processAttributes(o, subAttribute, function); } } else if (field.getType().isArray()) { Object[] array = (Object[]) subObject; for (Object o : array) { Attribute subAttribute = attributeContainer.getAttribute(name); processAttributes(o, subAttribute, function); } } } } } } public Set<AttributeReference> getAttributeReferences(String s) { Set<AttributeReference> attributeReferences = new HashSet<>(); String[] split = StringUtils.split(s, ","); for (String af : split) { AttributeReference attributeReference = new AttributeReference(af); attributeReferences.add(attributeReference); } return attributeReferences; } private Set<Attribute> resolveAttributeReferences(Set<AttributeReference> attributeReferences, boolean includeAttributeChain) throws AttributeDoesNotExistException { Set<Attribute> attributes = new HashSet<>(); for (AttributeReference attributeReference : attributeReferences) { Set<Attribute> findAttributes = findAttribute(attributeReference, includeAttributeChain); if (!findAttributes.isEmpty()) { attributes.addAll(findAttributes); } } return attributes; } private Set<Attribute> findAttribute(AttributeReference attributeReference, boolean includeAttributeChain) throws AttributeDoesNotExistException { String schemaUrn = attributeReference.getUrn(); Schema schema = null; Set<Attribute> attributes; if (!StringUtils.isEmpty(schemaUrn)) { schema = registry.getSchema(schemaUrn); attributes = findAttributeInSchema(schema, attributeReference, includeAttributeChain); if (attributes.isEmpty()) { log.error("Attribute " + attributeReference.getFullyQualifiedAttributeName() + "not found in schema " + schemaUrn); throw new AttributeDoesNotExistException(attributeReference.getFullyQualifiedAttributeName()); } return attributes; } // Handle unqualified attributes, look in the core schemas schema = registry.getSchema(ScimUser.SCHEMA_URI); attributes = findAttributeInSchema(schema, attributeReference, includeAttributeChain); if (!attributes.isEmpty()) { return attributes; } schema = registry.getSchema(ScimGroup.SCHEMA_URI); attributes = findAttributeInSchema(schema, attributeReference, includeAttributeChain); if (!attributes.isEmpty()) { return attributes; } log.error("Attribute " + attributeReference.getFullyQualifiedAttributeName() + "not found in any schema."); throw new AttributeDoesNotExistException(attributeReference.getFullyQualifiedAttributeName()); } private Set<Attribute> findAttributeInSchema(Schema schema, AttributeReference attributeReference, boolean includeAttributeChain) { AttributeContainer attributeContainer = schema; if (attributeContainer == null) { return Collections.emptySet(); } Set<Attribute> attributes = new HashSet<>(); String attributeName = attributeReference.getAttributeName(); String subAttributeName = attributeReference.getSubAttributeName(); Attribute attribute = attributeContainer.getAttribute(attributeName); if (attribute == null) { return Collections.emptySet(); } if (includeAttributeChain || subAttributeName == null) { attributes.add(attribute); } if (subAttributeName != null) { attribute = attribute.getAttribute(subAttributeName); if (attribute == null) { return Collections.emptySet(); } attributes.add(attribute); } if (attribute.getType() == Type.COMPLEX && includeAttributeChain) { List<Attribute> remaininAttributes = attribute.getAttributes(); attributes.addAll(remaininAttributes); } return attributes; } }