/* * Copyright 2011 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.ipc.invalidation.common; import com.google.common.base.Preconditions; import com.google.ipc.invalidation.common.ProtoValidator.FieldInfo.Presence; import com.google.ipc.invalidation.util.BaseLogger; import com.google.ipc.invalidation.util.TypedUtil; import com.google.protobuf.MessageLite; import java.util.Collection; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.NoSuchElementException; import java.util.Set; /** * Base class for writing protocol buffer validators. This is used in conjunction with * {@code ProtoAccessorGenerator}. * */ public abstract class ProtoValidator { /** Class providing access to protocol buffer fields in a generic way. */ public interface Accessor { public boolean hasField(MessageLite message, Descriptor field); public Object getField(MessageLite message, Descriptor field); public Collection<String> getAllFieldNames(); } /** Class naming a protocol buffer field in a generic way. */ public static class Descriptor { private final String name; public Descriptor(String name) { this.name = name; } /** Returns the name of the described field. */ public String getName() { return name; } @Override public String toString() { return "Descriptor for field " + name; } } /** Describes how to validate a message. */ public static class MessageInfo { /** Protocol buffer descriptor for the message. */ private final Accessor messageAccessor; /** Information about required and optional fields in this message. */ private final Set<FieldInfo> fieldInfo = new HashSet<FieldInfo>(); private int numRequiredFields; /** * Constructs a message info. * * @param messageAccessor descriptor for the protocol buffer * @param fields information about the fields */ public MessageInfo(Accessor messageAccessor, FieldInfo... fields) { // Track which fields in the message descriptor have not yet been covered by a FieldInfo. // We'll use this to verify that we get a FieldInfo for every field. Set<String> unusedDescriptors = new HashSet<String>(); unusedDescriptors.addAll(messageAccessor.getAllFieldNames()); this.messageAccessor = messageAccessor; for (FieldInfo info : fields) { // Lookup the field given the name in the FieldInfo. boolean removed = TypedUtil.remove(unusedDescriptors, info.getFieldDescriptor().getName()); Preconditions.checkState(removed, "Bad field: %s", info.getFieldDescriptor().getName()); // Add the field info to the number -> info map. fieldInfo.add(info); if (info.getPresence() == Presence.REQUIRED) { ++numRequiredFields; } } Preconditions.checkState(unusedDescriptors.isEmpty(), "Not all fields specified in %s: %s", messageAccessor, unusedDescriptors); } /** Returns the stored field information. */ protected Collection<FieldInfo> getAllFields() { return fieldInfo; } /** * Function called after the presence/absence of all fields in this message and its child * messages have been verified. Should be overridden to enforce additional semantic constraints * beyond field presence/absence if needed. */ protected boolean postValidate(MessageLite message) { return true; } /** Returns the number of required fields for messages of this type. */ public int getNumRequiredFields() { return numRequiredFields; } } /** Describes a field in a message. */ protected static class FieldInfo { /** * Whether the field is required or optional. A repeated field where at least one value * must be set should use {@code REQUIRED}. */ enum Presence { REQUIRED, OPTIONAL } /** Name of the field in the containing message. */ private final Descriptor fieldDescriptor; /** Whether the field is required or optional. */ private final Presence presence; /** If not {@code null}, message info describing how to validate the field. */ private final MessageInfo messageInfo; /** * Constructs an instance. * * @param fieldDescriptor identifier for the field * @param presence required/optional * @param messageInfo if not {@code null}, describes how to validate the field */ FieldInfo(Descriptor fieldDescriptor, Presence presence, MessageInfo messageInfo) { this.fieldDescriptor = fieldDescriptor; this.presence = presence; this.messageInfo = messageInfo; } /** Returns the name of the field. */ public Descriptor getFieldDescriptor() { return fieldDescriptor; } /** Returns the presence information for the field. */ Presence getPresence() { return presence; } /** Returns the validation information for the field. */ MessageInfo getMessageInfo() { return messageInfo; } /** Returns whether the field needs additional validation. */ boolean requiresAdditionalValidation() { return messageInfo != null; } /** * Returns a new instance describing a required field with name {@code fieldName} and validation * specified by {@code messageInfo}. */ public static FieldInfo newRequired(Descriptor fieldDescriptor, MessageInfo messageInfo) { return new FieldInfo(fieldDescriptor, Presence.REQUIRED, Preconditions.checkNotNull(messageInfo, "messageInfo cannot be null")); } /** * Returns a new instance describing a required field with name {@code fieldName} and no * additional validation. */ public static FieldInfo newRequired(Descriptor fieldDescriptor) { return new FieldInfo(fieldDescriptor, Presence.REQUIRED, null); } /** * Returns a new instance describing an optional field with name {@code fieldName} and * validation specified by {@code messageInfo}. */ public static FieldInfo newOptional(Descriptor fieldDescriptor, MessageInfo messageInfo) { return new FieldInfo(fieldDescriptor, Presence.OPTIONAL, Preconditions.checkNotNull(messageInfo)); } /** * Returns a new instance describing an optional field with name {@code fieldName} and no * additional validation. */ public static FieldInfo newOptional(Descriptor fieldDescriptor) { return new FieldInfo(fieldDescriptor, Presence.OPTIONAL, null); } } /** Logger for errors */ protected final BaseLogger logger; protected ProtoValidator(BaseLogger logger) { this.logger = logger; } /** * Returns an {@link Iterable} over the instance(s) of {@code field} in {@code message}. This * provides a uniform way to handle both singleton and repeated fields in protocol buffers, which * are accessed using different calls in the protocol buffer API. */ @SuppressWarnings("unchecked") protected static <FieldType> Iterable<FieldType> getFieldIterable(final MessageLite message, final Accessor messageAccessor, final Descriptor fieldDescriptor) { final Object obj = messageAccessor.getField(message, fieldDescriptor); if (obj instanceof List) { return (List<FieldType>) obj; } else { // Otherwise, just use a singleton iterator. return new Iterable<FieldType>() { @Override public Iterator<FieldType> iterator() { return new Iterator<FieldType>() { boolean done; @Override public boolean hasNext() { return !done; } @Override public FieldType next() { if (done) { throw new NoSuchElementException(); } done = true; return (FieldType) obj; } @Override public void remove() { throw new UnsupportedOperationException("Not allowed"); } }; } }; } } /** * Returns whether {@code message} is valid. * @param messageInfo specification of validity for {@code message} */ protected boolean checkMessage(MessageLite message, MessageInfo messageInfo) { for (FieldInfo fieldInfo : messageInfo.getAllFields()) { Descriptor fieldDescriptor = fieldInfo.getFieldDescriptor(); boolean isFieldPresent = messageInfo.messageAccessor.hasField(message, fieldDescriptor); // If the field must be present but isn't, fail. if ((fieldInfo.getPresence() == FieldInfo.Presence.REQUIRED) && !(isFieldPresent)) { logger.warning("Required field not set: %s", fieldInfo.getFieldDescriptor().getName()); return false; } // If the field is present and requires its own validation, validate it. if (isFieldPresent && fieldInfo.requiresAdditionalValidation()) { for (MessageLite subMessage : TiclMessageValidator2.<MessageLite>getFieldIterable( message, messageInfo.messageAccessor, fieldDescriptor)) { if (!checkMessage(subMessage, fieldInfo.getMessageInfo())) { return false; } } } } // Once we've validated all fields, post-validate this message. if (!messageInfo.postValidate(message)) { logger.info("Failed post-validation of message (%s): %s", message.getClass().getSimpleName(), message); return false; } return true; } }