blob: 0e353f4e82d6c38715a9cf920fb036360211614c [file] [log] [blame]
/*
* Copyright (c) 2013, 2019 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0, which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the
* Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
* version 2 with the GNU Classpath Exception, which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
*/
package org.glassfish.jersey.message.filtering;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Spliterator;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
import javax.inject.Singleton;
import org.glassfish.jersey.internal.inject.InjectionManager;
import org.glassfish.jersey.internal.inject.Providers;
import org.glassfish.jersey.internal.util.ReflectionHelper;
import org.glassfish.jersey.message.filtering.spi.EntityGraph;
import org.glassfish.jersey.message.filtering.spi.EntityGraphProvider;
import org.glassfish.jersey.message.filtering.spi.EntityInspector;
import org.glassfish.jersey.message.filtering.spi.EntityProcessor;
import org.glassfish.jersey.message.filtering.spi.EntityProcessorContext;
import org.glassfish.jersey.message.filtering.spi.FilteringHelper;
import org.glassfish.jersey.model.internal.RankedComparator;
/**
* Class responsible for inspecting entity classes. This class invokes all available {@link EntityProcessor entity processors} in
* different {@link EntityProcessorContext contexts}.
*
* @author Michal Gajdos
*/
@Singleton
final class EntityInspectorImpl implements EntityInspector {
private final List<EntityProcessor> entityProcessors;
@Inject
private EntityGraphProvider graphProvider;
/**
* Constructor expecting {@link InjectionManager} to be injected.
*
* @param injectionManager injection manager to be injected.
*/
@Inject
public EntityInspectorImpl(final InjectionManager injectionManager) {
Spliterator<EntityProcessor> entities =
Providers.getAllProviders(injectionManager, EntityProcessor.class, new RankedComparator<>()).spliterator();
this.entityProcessors = StreamSupport.stream(entities, false).collect(Collectors.toList());
}
@Override
public void inspect(final Class<?> entityClass, final boolean forWriter) {
if (!graphProvider.containsEntityGraph(entityClass, forWriter)) {
final EntityGraph graph = new EntityGraphImpl(entityClass);
final Set<Class<?>> inspect = new HashSet<>();
// Class.
if (!inspectEntityClass(entityClass, graph, forWriter)) {
// Properties.
final Map<String, Method> unmatchedAccessors =
inspectEntityProperties(entityClass, graph, inspect, forWriter);
// Setters/Getters without fields.
inspectStandaloneAccessors(unmatchedAccessors, graph, forWriter);
graphProvider.putIfAbsent(entityClass, graph, forWriter);
// Inspect new classes.
for (final Class<?> clazz : inspect) {
inspect(clazz, forWriter);
}
} else {
graphProvider.putIfAbsent(entityClass, graph, forWriter);
}
}
}
/**
* Invoke available {@link EntityProcessor}s on given entity class.
*
* @param entityClass entity class to be examined.
* @param graph entity graph to be modified by examination.
* @param forWriter flag determining whether the class should be examined for reader or writer.
* @return {@code true} if the inspecting should be roll-backed, {@code false} otherwise.
*/
private boolean inspectEntityClass(final Class<?> entityClass, final EntityGraph graph, final boolean forWriter) {
final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
forWriter ? EntityProcessorContext.Type.CLASS_WRITER : EntityProcessorContext.Type.CLASS_READER,
entityClass, graph);
for (final EntityProcessor processor : entityProcessors) {
final EntityProcessor.Result result = processor.process(context);
if (EntityProcessor.Result.ROLLBACK == result) {
graphProvider.getOrCreateEmptyEntityGraph(entityClass, false);
return true;
}
}
return false;
}
/**
* Invoke available {@link EntityProcessor}s on fields of given entity class. Method returns a map ({@code fieldName},
* {@code method}) of unprocessed property accessors (getters/setters) and fills {@code inspect} set with entity classes
* that should be further processed.
*
* @param entityClass entity class to obtain properties to be examined.
* @param graph entity graph to be modified by examination.
* @param inspect non-null set of classes to-be-examined.
* @param forWriter flag determining whether the class should be examined for reader or writer.
* @return map of unprocessed property accessors.
*/
private Map<String, Method> inspectEntityProperties(final Class<?> entityClass, final EntityGraph graph,
final Set<Class<?>> inspect, final boolean forWriter) {
final Field[] fields = AccessController.doPrivileged(ReflectionHelper.getAllFieldsPA(entityClass));
final Map<String, Method> methods = FilteringHelper.getPropertyMethods(entityClass, forWriter);
for (final Field field : fields) {
// Ignore static fields.
if (Modifier.isStatic(field.getModifiers())) {
continue;
}
final String name = field.getName();
final Class<?> clazz = FilteringHelper.getEntityClass(field.getGenericType());
final Method method = methods.remove(name);
final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
forWriter ? EntityProcessorContext.Type.PROPERTY_WRITER : EntityProcessorContext.Type.PROPERTY_READER,
field, method, graph);
boolean rollback = false;
for (final EntityProcessor processor : entityProcessors) {
final EntityProcessor.Result result = processor.process(context);
if (EntityProcessor.Result.ROLLBACK == result) {
rollback = true;
graph.remove(name);
break;
}
}
if (!rollback && FilteringHelper.filterableEntityClass(clazz)) {
inspect.add(clazz);
}
}
return methods;
}
/**
* Invoke available {@link EntityProcessor}s on accessors (getter/setter) that has no match in classes' fields.
*
* @param unprocessedAccessors map of unprocessed accessors.
* @param graph entity graph to be modified by examination.
* @param forWriter flag determining whether the class should be examined for reader or writer.
*/
private void inspectStandaloneAccessors(final Map<String, Method> unprocessedAccessors, final EntityGraph graph,
final boolean forWriter) {
for (final Map.Entry<String, Method> entry : unprocessedAccessors.entrySet()) {
final EntityProcessorContextImpl context = new EntityProcessorContextImpl(
forWriter ? EntityProcessorContext.Type.METHOD_WRITER : EntityProcessorContext.Type.METHOD_READER,
entry.getValue(), graph);
for (final EntityProcessor processor : entityProcessors) {
final EntityProcessor.Result result = processor.process(context);
if (EntityProcessor.Result.ROLLBACK == result) {
graph.remove(entry.getKey());
break;
}
}
}
}
}