edu.psu.swe.scim.server.utility.AttributeUtil Maven / Gradle / Ivy
/*
* 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 keepAlwaysAttributesForDisplay(T resource) throws IllegalArgumentException, IllegalAccessException, AttributeDoesNotExistException, IOException {
return setAttributesForDisplayInternal(resource, Returned.DEFAULT, Returned.REQUEST, Returned.NEVER);
}
public T setAttributesForDisplay(T resource) throws IllegalArgumentException, IllegalAccessException, AttributeDoesNotExistException, IOException {
return setAttributesForDisplayInternal(resource, Returned.REQUEST, Returned.NEVER);
}
private 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 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 setAttributesForDisplay(T resource, Set 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 attributesToKeep = resolveAttributeReferences(attributes, true);
removeAttributesOfType(copy, schema, Returned.DEFAULT, attributesToKeep);
removeAttributesOfType(copy, schema, Returned.REQUEST, attributesToKeep);
removeAttributesOfType(copy, schema, Returned.NEVER);
for (Entry 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 setExcludedAttributesForDisplay(T resource, Set 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 attributesToRemove = resolveAttributeReferences(excludedAttributes, false);
removeAttributesOfType(copy, schema, Returned.REQUEST);
removeAttributesOfType(copy, schema, Returned.NEVER);
removeAttributes(copy, schema, attributesToRemove);
for (Entry 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 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 function = (attribute) -> returned == attribute.getReturned();
processAttributes(object, attributeContainer, function);
}
private void removeAttributesOfType(Object object, AttributeContainer attributeContainer, Returned returned, Set attributesToKeep) throws IllegalArgumentException, IllegalAccessException {
Function function = (attribute) -> !attributesToKeep.contains(attribute) && returned == attribute.getReturned();
processAttributes(object, attributeContainer, function);
}
private void removeAttributes(Object object, AttributeContainer attributeContainer, Set attributesToRemove) throws IllegalArgumentException, IllegalAccessException {
Function function = (attribute) -> attributesToRemove.contains(attribute);
processAttributes(object, attributeContainer, function);
}
private void processAttributes(Object object, AttributeContainer attributeContainer, Function 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 getAttributeReferences(String s) {
Set attributeReferences = new HashSet<>();
String[] split = StringUtils.split(s, ",");
for (String af : split) {
AttributeReference attributeReference = new AttributeReference(af);
attributeReferences.add(attributeReference);
}
return attributeReferences;
}
private Set resolveAttributeReferences(Set attributeReferences, boolean includeAttributeChain) throws AttributeDoesNotExistException {
Set attributes = new HashSet<>();
for (AttributeReference attributeReference : attributeReferences) {
Set findAttributes = findAttribute(attributeReference, includeAttributeChain);
if (!findAttributes.isEmpty()) {
attributes.addAll(findAttributes);
}
}
return attributes;
}
private Set findAttribute(AttributeReference attributeReference, boolean includeAttributeChain) throws AttributeDoesNotExistException {
String schemaUrn = attributeReference.getUrn();
Schema schema = null;
Set 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 findAttributeInSchema(Schema schema, AttributeReference attributeReference, boolean includeAttributeChain) {
AttributeContainer attributeContainer = schema;
if (attributeContainer == null) {
return Collections.emptySet();
}
Set 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 remaininAttributes = attribute.getAttributes();
attributes.addAll(remaininAttributes);
}
return attributes;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy