/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright (c) 2013-2017 Oracle and/or its affiliates. All rights reserved.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common Development
* and Distribution License("CDDL") (collectively, the "License"). You
* may not use this file except in compliance with the License. You can
* obtain a copy of the License at
* http://glassfish.java.net/public/CDDL+GPL_1_1.html
* or packager/legal/LICENSE.txt. See the License for the specific
* language governing permissions and limitations under the License.
*
* When distributing the software, include this License Header Notice in each
* file and include the License file at packager/legal/LICENSE.txt.
*
* GPL Classpath Exception:
* Oracle designates this particular file as subject to the "Classpath"
* exception as provided by Oracle in the GPL Version 2 section of the License
* file that accompanied this code.
*
* Modifications:
* If applicable, add the following below the License Header, with the fields
* enclosed by brackets [] replaced by your own identifying information:
* "Portions Copyright [year] [name of copyright owner]"
*
* Contributor(s):
* If you wish your version of this file to be governed by only the CDDL or
* only the GPL Version 2, indicate your decision by adding "[Contributor]
* elects to include this software in this distribution under the [CDDL or GPL
* Version 2] license." If you don't indicate a single choice of license, a
* recipient has the option to distribute your version of this file under
* either the CDDL, the GPL Version 2 or to extend the choice of license to
* its licensees as provided above. However, if you add GPL Version 2 code
* and therefore, elected the GPL Version 2 license, then the option applies
* only if the new code is made subject to such option by the copyright
* holder.
*/
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 = graphProvider.getOrCreateEntityGraph(entityClass, forWriter);
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);
// Inspect new classes.
for (final Class<?> clazz : inspect) {
inspect(clazz, 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;
}
}
}
}
}