All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.api.tools.framework.processors.merger.Merger Maven / Gradle / Ivy

/*
 * Copyright (C) 2016 Google Inc.
 *
 * Licensed 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 com.google.api.tools.framework.processors.merger;

import com.google.api.Service;
import com.google.api.tools.framework.aspects.visibility.model.ScoperImpl;
import com.google.api.tools.framework.model.ConfigAspect;
import com.google.api.tools.framework.model.ConfigValidator;
import com.google.api.tools.framework.model.Diag;
import com.google.api.tools.framework.model.Element;
import com.google.api.tools.framework.model.Interface;
import com.google.api.tools.framework.model.Location;
import com.google.api.tools.framework.model.Model;
import com.google.api.tools.framework.model.Processor;
import com.google.api.tools.framework.model.ProtoElement;
import com.google.api.tools.framework.model.TypeRef;
import com.google.api.tools.framework.model.Visitor;
import com.google.api.tools.framework.model.stages.Merged;
import com.google.api.tools.framework.model.stages.Resolved;
import com.google.api.tools.framework.util.VisitsBefore;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Key;
import com.google.protobuf.Api;
import com.google.protobuf.DescriptorProtos.FieldDescriptorProto.Type;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

/**
 * A processor which establishes the {@link Merged} stage, in which service config and IDL are
 * combined and validated.
 *
 * 

The merger also derives interpreted values from the configuration, for example, the {@link * com.google.api.tools.framework.aspects.http.HttpConfigAspect}, and reports consistency errors * encountered during interpretation. */ public class Merger implements Processor { private static final Pattern SELECTOR_PATTERN = Pattern.compile("^(\\w+(\\.\\w+)*(\\.\\*)?)$"); @Override public ImmutableList> requires() { return ImmutableList.>of(Resolved.KEY); } @Override public Key establishes() { return Merged.KEY; } @Override public boolean run(Model model) { int oldErrorCount = model.getDiagReporter().getDiagCollector().getErrorCount(); if (model.getServiceConfig() == null) { // No service config defined; create a dummy one. model.setServiceConfig(Service.getDefaultInstance()); } // Resolve apis, computing which parts of the model are included. Attach apis to interfaces. Service config = model.getServiceConfig(); for (Api api : model.getServiceConfig().getApisList()) { Interface iface = model.getSymbolTable().lookupInterface(api.getName()); if (iface != null) { // Add interface to the roots. model.addRoot(iface); // Attach api proto to interface. iface.setConfig(api); } else { Location location = model.getLocationInConfig(api, "name"); model .getDiagReporter() .report(Diag.error(location, "Cannot resolve api '%s'.", api.getName())); } } List> orderedAspectGroup = sortForMerge(model.getConfigAspects()); // Merge-in config aspects. for (Set aspects : orderedAspectGroup) { for (ConfigAspect aspect : aspects) { aspect.startMerging(); } } for (Set aspects : orderedAspectGroup) { new ConfigAspectMerger(aspects).visit(model); } for (Set aspects : orderedAspectGroup) { for (ConfigAspect aspect : aspects) { aspect.endMerging(); } } runValidators(model); // Resolve types and enums specified in the service config as additional inclusions to // the tool chain, but not reachable from the service IDL, such as types associated with // Any type. for (com.google.protobuf.Type type : config.getTypesList()) { addAdditionalType( model, model.getLocationInConfig(type, "name"), type.getName(), Type.TYPE_MESSAGE); } for (com.google.protobuf.Enum enumType : config.getEnumsList()) { addAdditionalType( model, model.getLocationInConfig(enumType, "name"), enumType.getName(), Type.TYPE_ENUM); } // Set the initial scoper based on the roots. This will scope down further operation on the // model to those elements reachable via the roots. model.setScoper(ScoperImpl.create(model.getRoots())); if (oldErrorCount == model.getDiagReporter().getDiagCollector().getErrorCount()) { // No new errors produced -- success. model.putAttribute(Merged.KEY, new Merged()); return true; } return false; } private void runValidators(Model model) { final List> validators = model.getValidators(); new Visitor() { @SuppressWarnings("unchecked") @VisitsBefore void validate(Element element) { final Class elementType = element.getClass(); Iterable> validatorsToRun = getValidatorsToRun(validators, elementType); for (ConfigValidator validator : validatorsToRun) { ConfigValidator castedValidator = (ConfigValidator) validator; castedValidator.run(element); } } }.visit(model); } private static FluentIterable> getValidatorsToRun( List> validators, final Class elementType) { return FluentIterable.from(validators) .filter( new Predicate>() { @Override public boolean apply(ConfigValidator validator) { return validator.getElementClass().isAssignableFrom(elementType); } }); } /** * Resolve the additional type specified besides those that can be reached transitively from * service definition. It resolves the typeName into a {@link TypeRef} object. If typeName ends * with wildcard ".*", all the {@link TypeRef}s that is under typeName pattern path are added to * the root. */ private void addAdditionalType( Model model, Location location, final String typeName, final Type kind) { if (!SELECTOR_PATTERN.matcher(typeName).matches()) { model .getDiagReporter() .report( Diag.error( location, "Type selector '%s' specified in the config has bad syntax. " + "Valid format is \"('.' )*('.' '*')?\"", typeName)); return; } List typeRefs = model.getSymbolTable().lookupMatchingTypes(typeName, kind); if (typeRefs == null || typeRefs.isEmpty()) { model .getDiagReporter() .report( Diag.error( location, "Cannot resolve additional %s type '%s' specified in the config.", kind, typeName)); } else { for (TypeRef typeRef : typeRefs) { if (typeRef.isMessage()) { model.addRoot(typeRef.getMessageType()); } else if (typeRef.isEnum()) { model.addRoot(typeRef.getEnumType()); } } } } private static class ConfigAspectMerger extends Visitor { private final Iterable orderedAspects; private ConfigAspectMerger(Iterable orderedAspects) { this.orderedAspects = orderedAspects; } @VisitsBefore void merge(ProtoElement element) { for (ConfigAspect aspect : orderedAspects) { aspect.merge(element); } } } /** * Returns the given config aspects as list of group of aspects in merge dependency order. This * performs a 'longest path layering' algorithm by placing aspects at different levels (layers). * First place all sink nodes at level-1 and then each node n is placed at level level-p+1, where * p is the longest path from n to sink. Aspects in each level are independent of each other and * can only depend on aspects in lower levels. Detailed algorithm : 13.3.2 Layer Assignment * Algorithms : https://cs.brown.edu/~rt/gdhandbook/chapters/hierarchical.pdf */ private static List> sortForMerge(Iterable aspects) { Map, ConfigAspect> aspectsByType = HashBiMap.create( Maps.toMap( aspects, new Function>() { @Override public Class apply(ConfigAspect aspect) { return aspect.getClass(); } })) .inverse(); List> visiting = Lists.newArrayList(); Map aspectsToLevel = Maps.newLinkedHashMap(); for (ConfigAspect aspect : aspects) { assignLevelToAspect(aspect, aspectsByType, visiting, aspectsToLevel); } Map> aspectsByLevel = Maps.newLinkedHashMap(); for (ConfigAspect aspect : aspectsToLevel.keySet()) { Integer aspectLevel = aspectsToLevel.get(aspect); if (!aspectsByLevel.containsKey(aspectLevel)) { aspectsByLevel.put(aspectLevel, Sets.newLinkedHashSet()); } aspectsByLevel.get(aspectLevel).add(aspect); } List> aspectListByLevels = Lists.newArrayList(); for (int level = 1; level <= aspectsByLevel.size(); ++level) { aspectListByLevels.add(aspectsByLevel.get(level)); } return aspectListByLevels; } /** * Does a DFS traversal and computes the maximum height (level) of each node from the sink node. */ private static int assignLevelToAspect( ConfigAspect aspect, Map, ConfigAspect> aspectsByType, List> visiting, Map aspectToLevel) { Class aspectType = aspect.getClass(); if (aspectToLevel.containsKey(aspect)) { return aspectToLevel.get(aspect); } if (visiting.contains(aspectType)) { throw new IllegalStateException( String.format( "Cyclic dependency between config aspect attributes. Cycle is: %s <- %s", aspectType, Joiner.on(" <- ").join(visiting))); } visiting.add(aspectType); Integer childMaxHeight = 0; for (Class dep : aspect.mergeDependencies()) { if (aspectsByType.containsKey(dep)) { Integer childHeight = assignLevelToAspect(aspectsByType.get(dep), aspectsByType, visiting, aspectToLevel); childMaxHeight = childHeight > childMaxHeight ? childHeight : childMaxHeight; } else { throw new IllegalStateException( String.format( "config aspect %s depends on an unregistered aspect %s.", aspectType.getSimpleName(), dep.getSimpleName())); } } visiting.remove(aspectType); aspectToLevel.put(aspect, childMaxHeight + 1); return childMaxHeight + 1; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy