// Copyright 2017 The Nomulus Authors. All Rights Reserved. // // 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 google.registry.mapreduce.inputs; import static google.registry.model.EntityClasses.ALL_CLASSES; import static google.registry.model.ofy.ObjectifyService.ofy; import com.google.appengine.api.datastore.Cursor; import com.google.appengine.api.datastore.QueryResultIterator; import com.google.appengine.tools.mapreduce.InputReader; import com.google.appengine.tools.mapreduce.ShardContext; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableSet; import com.googlecode.objectify.Key; import com.googlecode.objectify.annotation.Entity; import google.registry.model.EppResource; import google.registry.model.ImmutableObject; import google.registry.model.index.EppResourceIndex; import google.registry.model.index.EppResourceIndexBucket; import java.io.IOException; import java.util.NoSuchElementException; import javax.annotation.Nullable; /** * Reader that maps over {@link EppResourceIndex} and returns resources that are children of * {@link EppResource} objects. */ class ChildEntityReader extends InputReader { private static final long serialVersionUID = 7481761146349663848L; /** This reader uses an EppResourceEntityReader under the covers to iterate over EPP resources. */ private final EppResourceEntityReader eppResourceEntityReader; /** The child resource classes to postfilter for. */ private final ImmutableList> childResourceClasses; /** The index within the list above for the next ofy query. */ private int childResourceClassIndex; /** A reader used to go over children of the current eppResourceEntity and childResourceClass. */ @Nullable private ChildReader childReader; public ChildEntityReader( Key bucketKey, ImmutableSet> resourceClasses, ImmutableSet> childResourceClasses) { this.childResourceClasses = expandPolymorphicClasses(childResourceClasses); this.eppResourceEntityReader = new EppResourceEntityReader<>(bucketKey, resourceClasses); } /** Expands non-entity polymorphic classes into their child types. */ @SuppressWarnings("unchecked") private ImmutableList> expandPolymorphicClasses( ImmutableSet> resourceClasses) { ImmutableList.Builder> builder = new ImmutableList.Builder<>(); for (Class clazz : resourceClasses) { if (clazz.isAnnotationPresent(Entity.class)) { builder.add(clazz); } else { for (Class entityClass : ALL_CLASSES) { if (clazz.isAssignableFrom(entityClass)) { builder.add((Class) entityClass); } } } } return builder.build(); } /** * Get the next {@link ImmutableObject} (i.e. child element) from the query. * * @throws NoSuchElementException if there are no more EPP resources to iterate over. */ I nextChild() throws NoSuchElementException { // This code implements a single iteration over a triple-nested loop. It returns the next // innermost item of that 3-nested loop. The entire loop would look like this: // // NOTE: I'm treating eppResourceEntityReader and childReader as if they were iterables for // brevity, although they aren't - they are Readers // // I'm also using the python 'yield' command to show we're returning this item one by one. // // for (eppResourceEntity : eppResourceEntityReader) { // for (childResourceClass : childResourceClasses) { // for (I child : ChildReader.create(childResourceClass, Key.create(eppResourceEntity)) { // yield child; // returns the 'child's one by one. // } // } // } // First, set all the variables if they aren't set yet. This should only happen on the first // time in the function. // // This can be merged with the calls in the "catch" below to avoid code duplication, but it // makes the code harder to read. if (childReader == null) { childResourceClassIndex = 0; childReader = ChildReader.create( childResourceClasses.get(childResourceClassIndex), Key.create(eppResourceEntityReader.next())); } // Then continue advancing the 3-nested loop until we find a value while (true) { try { // Advance the inner loop and return the next value. return childReader.next(); } catch (NoSuchElementException e) { // If we got here it means the inner loop (childQueryIterator) is done - we need to advance // the middle loop by one, and then reset the inner loop. childResourceClassIndex++; // Check if the middle loop is done as well if (childResourceClassIndex < childResourceClasses.size()) { // The middle loop is not done. Reset the inner loop. childReader = childReader.withType(childResourceClasses.get(childResourceClassIndex)); } else { // We're done with the middle loop as well! Advance the outer loop, and reset the middle // loop and inner loops childResourceClassIndex = 0; childReader = ChildReader.create( childResourceClasses.get(childResourceClassIndex), Key.create(eppResourceEntityReader.next())); } // Loop back up the while, to try reading reading a value again } } } @Override public I next() throws NoSuchElementException { while (true) { I entity = nextChild(); if (entity != null) { // Postfilter to distinguish polymorphic types. for (Class resourceClass : childResourceClasses) { if (resourceClass.isInstance(entity)) { return entity; } } } } } @Override public void beginSlice() { eppResourceEntityReader.beginSlice(); if (childReader != null) { childReader.beginSlice(); } } @Override public void endSlice() { eppResourceEntityReader.endSlice(); if (childReader != null) { childReader.endSlice(); } } @Override public Double getProgress() { return eppResourceEntityReader.getProgress(); } @Override public long estimateMemoryRequirement() { return eppResourceEntityReader.estimateMemoryRequirement(); } @Override public ShardContext getContext() { return eppResourceEntityReader.getContext(); } @Override public void setContext(ShardContext context) { eppResourceEntityReader.setContext(context); } @Override public void beginShard() { eppResourceEntityReader.beginShard(); } @Override public void endShard() throws IOException { eppResourceEntityReader.endShard(); } private static class ChildReader extends RetryingInputReader { private static final long serialVersionUID = -8443132445119657998L; private final Class type; private final Key ancestor; /** Create a reader that goes over all the children of a given type to the given ancestor. */ public ChildReader(Class type, Key ancestor) { this.type = type; this.ancestor = ancestor; // This reader isn't initialized by mapreduce, so we need to initialize it ourselves beginShard(); beginSlice(); } /** * Create a reader that goes over all the children of a given type to the given ancestor. * *

We need this function in addition to the constructor so that we can create a ChildReader. */ public static ChildReader create(Class type, Key ancestor) { return new ChildReader(type, ancestor); } /** Query for children of the current resource and of the current child class. */ @Override public QueryResultIterator getQueryIterator(Cursor cursor) { return startQueryAt(ofy().load().type(type).ancestor(ancestor), cursor).iterator(); } @Override public int getTotal() { return 0; } @Override public I next() { return nextQueryResult(); } /** Returns a new ChildReader of the same ancestor for the given type. */ public ChildReader withType(Class type) { return create(type, ancestor); } } }