/* * 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.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import java.util.stream.Collectors; import org.glassfish.jersey.internal.guava.HashBasedTable; import org.glassfish.jersey.internal.guava.HashMultimap; import org.glassfish.jersey.internal.guava.Table; import org.glassfish.jersey.message.filtering.spi.EntityGraph; import org.glassfish.jersey.message.filtering.spi.ScopeProvider; /** * Default implementation of {@link EntityGraph}. * * @author Michal Gajdos */ final class EntityGraphImpl implements EntityGraph { private final Class<?> entityClass; private final Set<String> globalScopes; private final Set<String> localScopes; // <FilteringScope, FieldName> private final HashMultimap<String, String> fields; // <FilteringScope, FieldName, Class> private final Table<String, String, Class<?>> subgraphs; /** * Create an entity graph for given class. * * @param entityClass entity class the graph should be created for. */ public EntityGraphImpl(final Class<?> entityClass) { this.entityClass = entityClass; this.fields = HashMultimap.create(); this.subgraphs = HashBasedTable.create(); this.globalScopes = new HashSet<>(); this.localScopes = new HashSet<>(); } @Override public EntityGraphImpl addField(final String fieldName) { return addField(fieldName, globalScopes); } @Override public EntityGraphImpl addField(final String fieldName, final String... filteringScopes) { return addField(fieldName, Arrays.stream(filteringScopes).collect(Collectors.toSet())); } @Override public EntityGraphImpl addField(final String fieldName, final Set<String> filteringScopes) { for (final String filteringScope : filteringScopes) { createFilteringScope(filteringScope); fields.get(filteringScope).add(fieldName); } return this; } @Override public EntityGraphImpl addFilteringScopes(final Set<String> filteringScopes) { this.globalScopes.addAll(filteringScopes); return this; } @Override public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass) { return addSubgraph(fieldName, fieldClass, globalScopes); } @Override public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass, final String... filteringScopes) { return addSubgraph(fieldName, fieldClass, Arrays.stream(filteringScopes).collect(Collectors.toSet())); } @Override public EntityGraphImpl addSubgraph(final String fieldName, final Class<?> fieldClass, final Set<String> filteringScopes) { for (final String filteringScope : filteringScopes) { createFilteringScope(filteringScope); subgraphs.put(filteringScope, fieldName, fieldClass); } return this; } @Override public Class<?> getEntityClass() { return entityClass; } @Override public Set<String> getFields(final String filteringScope) { return fields.containsKey(filteringScope) ? Collections.unmodifiableSet(fields.get(filteringScope)) : Collections.<String>emptySet(); } @Override public Set<String> getFields(final String... filteringScopes) { return filteringScopes.length == 0 ? Collections.<String>emptySet() : (filteringScopes.length == 1 ? getFields(filteringScopes[0]) : getFields(Arrays.stream(filteringScopes).collect(Collectors.toSet()))); } @Override public Set<String> getFields(final Set<String> filteringScopes) { final Set<String> matched = new HashSet<>(); for (final String filteringContext : filteringScopes) { matched.addAll(fields.get(filteringContext)); } return matched; } @Override public Set<String> getFilteringScopes() { HashSet<String> strings = new HashSet<>(globalScopes); strings.addAll(localScopes); return Collections.unmodifiableSet(strings); } @Override public Set<String> getClassFilteringScopes() { return Collections.unmodifiableSet(globalScopes); } @Override public Map<String, Class<?>> getSubgraphs(final String filteringScope) { return subgraphs.containsRow(filteringScope) ? Collections.unmodifiableMap(subgraphs.row(filteringScope)) : Collections.<String, Class<?>>emptyMap(); } @Override public Map<String, Class<?>> getSubgraphs(final String... filteringScopes) { return filteringScopes.length == 0 ? Collections.<String, Class<?>>emptyMap() : (filteringScopes.length == 1 ? getSubgraphs(filteringScopes[0]) : getSubgraphs(Arrays.stream(filteringScopes).collect(Collectors.toSet()))); } @Override public Map<String, Class<?>> getSubgraphs(final Set<String> filteringScopes) { final Map<String, Class<?>> matched = new HashMap<>(); for (final String filteringContext : filteringScopes) { matched.putAll(subgraphs.row(filteringContext)); } return matched; } @Override public boolean presentInScopes(final String name) { return fields.containsValue(name) || subgraphs.containsColumn(name); } @Override public boolean presentInScope(final String field, final String filteringScope) { return fields.containsEntry(filteringScope, field) || subgraphs.contains(filteringScope, field); } @Override public EntityGraphImpl remove(final String fieldName) { for (final String scope : getFilteringScopes()) { if (fields.containsEntry(scope, fieldName)) { fields.remove(scope, fieldName); } if (subgraphs.containsColumn(fieldName)) { subgraphs.remove(scope, fieldName); } } return this; } /** * Create a new entity-filtering scope based on the {@link ScopeProvider#DEFAULT_SCOPE default one}. * * @param filteringScope entity-filtering scope to be created. */ private void createFilteringScope(final String filteringScope) { // Do not create a scope if it already exists. if (!getFilteringScopes().contains(filteringScope)) { // Copy contents of default scope into the new one. if (localScopes.contains(ScopeProvider.DEFAULT_SCOPE)) { fields.putAll(filteringScope, fields.get(ScopeProvider.DEFAULT_SCOPE)); final Map<String, Class<?>> row = subgraphs.row(ScopeProvider.DEFAULT_SCOPE); for (final Map.Entry<String, Class<?>> entry : row.entrySet()) { subgraphs.put(filteringScope, entry.getKey(), entry.getValue()); } } localScopes.add(filteringScope); } } @Override public boolean equals(final Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } final EntityGraphImpl that = (EntityGraphImpl) o; return entityClass.equals(that.entityClass) && fields.equals(that.fields) && globalScopes.equals(that.globalScopes) && localScopes.equals(that.localScopes) && subgraphs.equals(that.subgraphs); } @Override public int hashCode() { int result = entityClass.hashCode(); result = 53 * result + globalScopes.hashCode(); result = 53 * result + localScopes.hashCode(); result = 53 * result + fields.hashCode(); result = 53 * result + subgraphs.hashCode(); return result; } }