2 * Copyright 2011 Google Inc.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.google.ipc.invalidation.common;
18 import com.google.common.base.Preconditions;
19 import com.google.ipc.invalidation.common.ProtoValidator.FieldInfo.Presence;
20 import com.google.ipc.invalidation.util.BaseLogger;
21 import com.google.ipc.invalidation.util.TypedUtil;
22 import com.google.protobuf.MessageLite;
24 import java.util.Collection;
25 import java.util.HashSet;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.NoSuchElementException;
33 * Base class for writing protocol buffer validators. This is used in conjunction with
34 * {@code ProtoAccessorGenerator}.
37 public abstract class ProtoValidator {
38 /** Class providing access to protocol buffer fields in a generic way. */
39 public interface Accessor {
40 public boolean hasField(MessageLite message, Descriptor field);
41 public Object getField(MessageLite message, Descriptor field);
42 public Collection<String> getAllFieldNames();
45 /** Class naming a protocol buffer field in a generic way. */
46 public static class Descriptor {
47 private final String name;
49 public Descriptor(String name) {
52 /** Returns the name of the described field. */
53 public String getName() {
57 public String toString() {
58 return "Descriptor for field " + name;
62 /** Describes how to validate a message. */
63 public static class MessageInfo {
64 /** Protocol buffer descriptor for the message. */
65 private final Accessor messageAccessor;
67 /** Information about required and optional fields in this message. */
68 private final Set<FieldInfo> fieldInfo = new HashSet<FieldInfo>();
70 private int numRequiredFields;
73 * Constructs a message info.
75 * @param messageAccessor descriptor for the protocol buffer
76 * @param fields information about the fields
78 public MessageInfo(Accessor messageAccessor, FieldInfo... fields) {
79 // Track which fields in the message descriptor have not yet been covered by a FieldInfo.
80 // We'll use this to verify that we get a FieldInfo for every field.
81 Set<String> unusedDescriptors = new HashSet<String>();
82 unusedDescriptors.addAll(messageAccessor.getAllFieldNames());
84 this.messageAccessor = messageAccessor;
85 for (FieldInfo info : fields) {
86 // Lookup the field given the name in the FieldInfo.
87 boolean removed = TypedUtil.remove(unusedDescriptors, info.getFieldDescriptor().getName());
88 Preconditions.checkState(removed, "Bad field: %s", info.getFieldDescriptor().getName());
90 // Add the field info to the number -> info map.
93 if (info.getPresence() == Presence.REQUIRED) {
97 Preconditions.checkState(unusedDescriptors.isEmpty(), "Not all fields specified in %s: %s",
98 messageAccessor, unusedDescriptors);
101 /** Returns the stored field information. */
102 protected Collection<FieldInfo> getAllFields() {
107 * Function called after the presence/absence of all fields in this message and its child
108 * messages have been verified. Should be overridden to enforce additional semantic constraints
109 * beyond field presence/absence if needed.
111 protected boolean postValidate(MessageLite message) {
115 /** Returns the number of required fields for messages of this type. */
116 public int getNumRequiredFields() {
117 return numRequiredFields;
121 /** Describes a field in a message. */
122 protected static class FieldInfo {
124 * Whether the field is required or optional. A repeated field where at least one value
125 * must be set should use {@code REQUIRED}.
132 /** Name of the field in the containing message. */
133 private final Descriptor fieldDescriptor;
135 /** Whether the field is required or optional. */
136 private final Presence presence;
138 /** If not {@code null}, message info describing how to validate the field. */
139 private final MessageInfo messageInfo;
142 * Constructs an instance.
144 * @param fieldDescriptor identifier for the field
145 * @param presence required/optional
146 * @param messageInfo if not {@code null}, describes how to validate the field
148 FieldInfo(Descriptor fieldDescriptor, Presence presence,
149 MessageInfo messageInfo) {
150 this.fieldDescriptor = fieldDescriptor;
151 this.presence = presence;
152 this.messageInfo = messageInfo;
155 /** Returns the name of the field. */
156 public Descriptor getFieldDescriptor() {
157 return fieldDescriptor;
160 /** Returns the presence information for the field. */
161 Presence getPresence() {
165 /** Returns the validation information for the field. */
166 MessageInfo getMessageInfo() {
170 /** Returns whether the field needs additional validation. */
171 boolean requiresAdditionalValidation() {
172 return messageInfo != null;
176 * Returns a new instance describing a required field with name {@code fieldName} and validation
177 * specified by {@code messageInfo}.
179 public static FieldInfo newRequired(Descriptor fieldDescriptor, MessageInfo messageInfo) {
180 return new FieldInfo(fieldDescriptor, Presence.REQUIRED,
181 Preconditions.checkNotNull(messageInfo, "messageInfo cannot be null"));
185 * Returns a new instance describing a required field with name {@code fieldName} and no
186 * additional validation.
188 public static FieldInfo newRequired(Descriptor fieldDescriptor) {
189 return new FieldInfo(fieldDescriptor, Presence.REQUIRED, null);
193 * Returns a new instance describing an optional field with name {@code fieldName} and
194 * validation specified by {@code messageInfo}.
196 public static FieldInfo newOptional(Descriptor fieldDescriptor, MessageInfo messageInfo) {
197 return new FieldInfo(fieldDescriptor, Presence.OPTIONAL,
198 Preconditions.checkNotNull(messageInfo));
202 * Returns a new instance describing an optional field with name {@code fieldName} and no
203 * additional validation.
205 public static FieldInfo newOptional(Descriptor fieldDescriptor) {
206 return new FieldInfo(fieldDescriptor, Presence.OPTIONAL, null);
210 /** Logger for errors */
211 protected final BaseLogger logger;
213 protected ProtoValidator(BaseLogger logger) {
214 this.logger = logger;
218 * Returns an {@link Iterable} over the instance(s) of {@code field} in {@code message}. This
219 * provides a uniform way to handle both singleton and repeated fields in protocol buffers, which
220 * are accessed using different calls in the protocol buffer API.
222 @SuppressWarnings("unchecked")
224 protected static <FieldType> Iterable<FieldType> getFieldIterable(final MessageLite message,
225 final Accessor messageAccessor, final Descriptor fieldDescriptor) {
226 final Object obj = messageAccessor.getField(message, fieldDescriptor);
227 if (obj instanceof List) {
228 return (List<FieldType>) obj;
230 // Otherwise, just use a singleton iterator.
231 return new Iterable<FieldType>() {
233 public Iterator<FieldType> iterator() {
234 return new Iterator<FieldType>() {
237 public boolean hasNext() {
242 public FieldType next() {
244 throw new NoSuchElementException();
247 return (FieldType) obj;
251 public void remove() {
252 throw new UnsupportedOperationException("Not allowed");
261 * Returns whether {@code message} is valid.
262 * @param messageInfo specification of validity for {@code message}
265 protected boolean checkMessage(MessageLite message, MessageInfo messageInfo) {
266 for (FieldInfo fieldInfo : messageInfo.getAllFields()) {
267 Descriptor fieldDescriptor = fieldInfo.getFieldDescriptor();
268 boolean isFieldPresent =
269 messageInfo.messageAccessor.hasField(message, fieldDescriptor);
271 // If the field must be present but isn't, fail.
272 if ((fieldInfo.getPresence() == FieldInfo.Presence.REQUIRED) && !(isFieldPresent)) {
273 logger.warning("Required field not set: %s", fieldInfo.getFieldDescriptor().getName());
277 // If the field is present and requires its own validation, validate it.
278 if (isFieldPresent && fieldInfo.requiresAdditionalValidation()) {
279 for (MessageLite subMessage : TiclMessageValidator2.<MessageLite>getFieldIterable(
280 message, messageInfo.messageAccessor, fieldDescriptor)) {
281 if (!checkMessage(subMessage, fieldInfo.getMessageInfo())) {
288 // Once we've validated all fields, post-validate this message.
289 if (!messageInfo.postValidate(message)) {
290 logger.info("Failed post-validation of message (%s): %s",
291 message.getClass().getSimpleName(), message);