| /* |
| * 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; |
| } |
| } |