/* * Copyright 2015 The Apache Software Foundation. * * 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 org.apache.avro.compiler.idl; import com.google.common.base.Function; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; import org.apache.avro.Protocol; import org.apache.avro.Schema; import org.apache.avro.compiler.schema.Schemas; /** * Utility class to resolve schemas that are unavailable at the time they are referenced in the IDL. */ final class SchemaResolver { private SchemaResolver() { } private static final String UR_SCHEMA_ATTR = "org.apache.avro.compiler.idl.unresolved.name"; private static final String UR_SCHEMA_NAME = "UnresolvedSchema"; private static final String UR_SCHEMA_NS = "org.apache.avro.compiler"; /** * Create a schema to represent a "unresolved" schema. * (used to represent a schema where the definition is not known at the time) * This concept might be generalizable... * * @param name * @return */ static Schema unresolvedSchema(final String name) { Schema schema = Schema.createRecord(UR_SCHEMA_NAME, "unresolved schema", UR_SCHEMA_NS, false, Collections.EMPTY_LIST); schema.addProp(UR_SCHEMA_ATTR, name); return schema; } /** * Is this a unresolved schema. * * @param schema * @return */ static boolean isUnresolvedSchema(final Schema schema) { return (schema.getType() == Schema.Type.RECORD && schema.getProp(UR_SCHEMA_ATTR) != null && UR_SCHEMA_NAME.equals(schema.getName()) && UR_SCHEMA_NS.equals(schema.getNamespace())); } /** * get the unresolved schema name. * * @param schema * @return */ static String getUnresolvedSchemaName(final Schema schema) { if (!isUnresolvedSchema(schema)) { throw new IllegalArgumentException("Not a unresolved schema: " + schema); } return schema.getProp(UR_SCHEMA_ATTR); } /** * Will clone the provided protocol while resolving all unreferenced schemas * * @param protocol * @return */ static Protocol resolve(final Protocol protocol) { Protocol result = new Protocol(protocol.getName(), protocol.getDoc(), protocol.getNamespace()); final Collection<Schema> types = protocol.getTypes(); // replace unresolved schemas. List<Schema> newSchemas = new ArrayList(types.size()); IdentityHashMap<Schema, Schema> replacements = new IdentityHashMap<Schema, Schema>(); for (Schema schema : types) { newSchemas.add(Schemas.visit(schema, new ResolvingVisitor(schema, replacements, new SymbolTable(protocol)))); } result.setTypes(newSchemas); // replace types with resolved ones // Resolve all schemas refferenced by protocol Messages. for (Map.Entry<String, Protocol.Message> entry : protocol.getMessages().entrySet()) { Protocol.Message value = entry.getValue(); Protocol.Message nvalue; if (value.isOneWay()) { Schema replacement = resolve(replacements, value.getRequest(), protocol); nvalue = result.createMessage(value.getName(), value.getDoc(), value.getObjectProps(), replacement); } else { Schema request = resolve(replacements, value.getRequest(), protocol); Schema response = resolve(replacements, value.getResponse(), protocol); Schema errors = resolve(replacements, value.getErrors(), protocol); nvalue = result.createMessage(value.getName(), value.getDoc(), value.getObjectProps(), request, response, errors); } result.getMessages().put(entry.getKey(), nvalue); } Schemas.copyProperties(protocol, result); return result; } private static Schema resolve(final IdentityHashMap<Schema, Schema> replacements, final Schema request, final Protocol protocol) { Schema replacement = replacements.get(request); if (replacement == null) { replacement = Schemas.visit(request, new ResolvingVisitor(request, replacements, new SymbolTable(protocol))); } return replacement; } private static class SymbolTable implements Function<String, Schema> { private final Protocol symbolTable; public SymbolTable(Protocol symbolTable) { this.symbolTable = symbolTable; } @Override public Schema apply(final String f) { return symbolTable.getType(f); } } }