com.googlecode.objectify.Work Java Examples

The following examples show how to use com.googlecode.objectify.Work. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: ShardedCounterServiceShardIncrementInExistingTXTest.java    From appengine-counter with Apache License 2.0 6 votes vote down vote up
/**
 * Overidden so that all calls to {@link #increment} occur inside of an existing Transaction.
 *
 * @param counterName
 * @param requestedIncrementAmount
 * @return
 */
@Override
public CounterOperation increment(final String counterName, final long requestedIncrementAmount)
{
	return ObjectifyService.ofy().transact(new Work<CounterOperation>()
	{
		@Override
		public CounterOperation run()
		{
			// 1.) Create a random CounterShardData for simulation purposes. It doesn't do anything except
			// to allow us to do something else in the Datastore in the same transactional context whilst
			// performing all unit tests. This effectively allows us to simulate a parent transactional context
			// occuring with some other data operation being performed against the database.
			final CounterShardData counterShardData = new CounterShardData(UUID.randomUUID().toString(), 1);
			ObjectifyService.ofy().save().entity(counterShardData);

			// 2.) Operate on the counter and return.
			return ShardedCounterServiceTxWrapper.super.increment(counterName, requestedIncrementAmount);
		}
	});
}
 
Example #2
Source File: TransactionalInvocationHandler.java    From tech-gallery with Apache License 2.0 5 votes vote down vote up
@SuppressWarnings({"unchecked", "rawtypes"})
@Override
public Object invoke(final Object proxy, final Method method, final Object[] args)
    throws Throwable {
  try {
    final Method implementationMethod =
        implementation.getClass().getMethod(method.getName(), method.getParameterTypes());
    final Transactional transactional = implementationMethod.getAnnotation(Transactional.class);

    if (transactional != null) {

      final IdempotencyHandler idempotencyHandler =
          IdempotencyHandlerFactory.createHandlerAnnotationBased(transactional);

      return ObjectifyService.ofy().execute(transactional.type(), new Work() {
        @Override
        public Object run() {
          if (idempotencyHandler.shouldTransactionProceed(proxy, method, args)) {
            Object result = invokeMethod(args, implementationMethod);
            idempotencyHandler.setReturn(result);
          }
          return idempotencyHandler.getReturn();
        }
      });

    } else {
      return invokeMethod(args, implementationMethod);
    }
  } catch (RuntimeException err) {
    throw getRootCause(err, 5);
  }
}
 
Example #3
Source File: ConferenceApi.java    From ud859 with GNU General Public License v3.0 5 votes vote down vote up
/**
 * Unregister from the specified Conference.
 *
 * @param user An user who invokes this method, null when the user is not signed in.
 * @param websafeConferenceKey The String representation of the Conference Key to unregister
 *                             from.
 * @return Boolean true when success, otherwise false.
 * @throws UnauthorizedException when the user is not signed in.
 * @throws NotFoundException when there is no Conference with the given conferenceId.
 */
@ApiMethod(
        name = "unregisterFromConference",
        path = "conference/{websafeConferenceKey}/registration",
        httpMethod = HttpMethod.DELETE
)
public WrappedBoolean unregisterFromConference(final User user,
                                        @Named("websafeConferenceKey")
                                        final String websafeConferenceKey)
        throws UnauthorizedException, NotFoundException, ForbiddenException, ConflictException {
    // If not signed in, throw a 401 error.
    if (user == null) {
        throw new UnauthorizedException("Authorization required");
    }
    final String userId = getUserId(user);
    TxResult<Boolean> result = ofy().transact(new Work<TxResult<Boolean>>() {
        @Override
        public TxResult<Boolean> run() {
            Key<Conference> conferenceKey = Key.create(websafeConferenceKey);
            Conference conference = ofy().load().key(conferenceKey).now();
            // 404 when there is no Conference with the given conferenceId.
            if (conference == null) {
                return new TxResult<>(new NotFoundException(
                        "No Conference found with key: " + websafeConferenceKey));
            }
            // Un-registering from the Conference.
            Profile profile = getProfileFromUser(user, userId);
            if (profile.getConferenceKeysToAttend().contains(websafeConferenceKey)) {
                profile.unregisterFromConference(websafeConferenceKey);
                conference.giveBackSeats(1);
                ofy().save().entities(profile, conference).now();
                return new TxResult<>(true);
            } else {
                return new TxResult<>(false);
            }
        }
    });
    // NotFoundException is actually thrown here.
    return new WrappedBoolean(result.getResult());
}
 
Example #4
Source File: ShardedCounterServiceImpl.java    From appengine-counter with Apache License 2.0 5 votes vote down vote up
/**
 * Helper method to create the {@link CounterData} associated with the supplied counter information.
 *
 * @param counterName
 * @return
 * @throws IllegalArgumentException If the {@code counterName} is invalid.
 * @throws CounterExistsException If the counter with {@code counterName} already exists in the Datastore.
 */
@VisibleForTesting
protected CounterData createCounterData(final String counterName)
{
	this.counterNameValidator.validateCounterName(counterName);

	final Key<CounterData> counterKey = CounterData.key(counterName);

	// Perform a transactional GET to see if the counter exists. If it does, throw an exception. Otherwise, create
	// the counter in the same TX.
	return ObjectifyService.ofy().transact(new Work<CounterData>()
	{
		@Override
		public CounterData run()
		{
			final CounterData loadedCounterData = ObjectifyService.ofy().load().key(counterKey).now();
			if (loadedCounterData == null)
			{
				final CounterData counterData = new CounterData(counterName, config.getNumInitialShards());
				ObjectifyService.ofy().save().entity(counterData).now();
				return counterData;
			}
			else
			{
				throw new CounterExistsException(counterName);
			}
		}
	});
}
 
Example #5
Source File: ConferenceApi.java    From ud859 with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Register to attend the specified Conference.
 *
 * @param user An user who invokes this method, null when the user is not signed in.
 * @param websafeConferenceKey The String representation of the Conference Key.
 * @return Boolean true when success, otherwise false
 * @throws UnauthorizedException when the user is not signed in.
 * @throws NotFoundException when there is no Conference with the given conferenceId.
 */
@ApiMethod(
        name = "registerForConference",
        path = "conference/{websafeConferenceKey}/registration",
        httpMethod = HttpMethod.POST
)

public WrappedBoolean registerForConference(final User user,
        @Named("websafeConferenceKey") final String websafeConferenceKey)
        throws UnauthorizedException, NotFoundException,
        ForbiddenException, ConflictException {
    // If not signed in, throw a 401 error.
    if (user == null) {
        throw new UnauthorizedException("Authorization required");
    }

    // Get the userId
    final String userId = user.getUserId();

    WrappedBoolean result = ofy().transact(new Work<WrappedBoolean>() {
        @Override
        public WrappedBoolean run() {
            try {

            // Get the conference key
            // Will throw ForbiddenException if the key cannot be created
            Key<Conference> conferenceKey = Key.create(websafeConferenceKey);

            // Get the Conference entity from the datastore
            Conference conference = ofy().load().key(conferenceKey).now();

            // 404 when there is no Conference with the given conferenceId.
            if (conference == null) {
                return new WrappedBoolean (false,
                        "No Conference found with key: "
                                + websafeConferenceKey);
            }

            // Get the user's Profile entity
            Profile profile = getProfileFromUser(user);

            // Has the user already registered to attend this conference?
            if (profile.getConferenceKeysToAttend().contains(
                    websafeConferenceKey)) {
                return new WrappedBoolean (false, "Already registered");
            } else if (conference.getSeatsAvailable() <= 0) {
                return new WrappedBoolean (false, "No seats available");
            } else {
                // All looks good, go ahead and book the seat
                profile.addToConferenceKeysToAttend(websafeConferenceKey);
                conference.bookSeats(1);

                // Save the Conference and Profile entities
                ofy().save().entities(profile, conference).now();
                // We are booked!
                return new WrappedBoolean(true, "Registration successful");
            }

            }
            catch (Exception e) {
                return new WrappedBoolean(false, "Unknown exception");

            }
        }
    });
    // if result is false
    if (!result.getResult()) {
        if (result.getReason().contains("No Conference found with key")) {
            throw new NotFoundException (result.getReason());
        }
        else if (result.getReason() == "Already registered") {
            throw new ConflictException("You have already registered");
        }
        else if (result.getReason() == "No seats available") {
            throw new ConflictException("There are no seats available");
        }
        else {
            throw new ForbiddenException("Unknown exception");
        }
    }
    return result;
}
 
Example #6
Source File: ConferenceApi.java    From ud859 with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Unregister from the specified Conference.
 *
 * @param user An user who invokes this method, null when the user is not signed in.
 * @param websafeConferenceKey The String representation of the Conference Key to unregister
 *                             from.
 * @return Boolean true when success, otherwise false.
 * @throws UnauthorizedException when the user is not signed in.
 * @throws NotFoundException when there is no Conference with the given conferenceId.
 */
@ApiMethod(
        name = "unregisterFromConference",
        path = "conference/{websafeConferenceKey}/registration",
        httpMethod = HttpMethod.DELETE
)
public WrappedBoolean unregisterFromConference(final User user,
                                        @Named("websafeConferenceKey")
                                        final String websafeConferenceKey)
        throws UnauthorizedException, NotFoundException, ForbiddenException, ConflictException {
    // If not signed in, throw a 401 error.
    if (user == null) {
        throw new UnauthorizedException("Authorization required");
    }

    WrappedBoolean result = ofy().transact(new Work<WrappedBoolean>() {
        @Override
        public WrappedBoolean run() {
            Key<Conference> conferenceKey = Key.create(websafeConferenceKey);
            Conference conference = ofy().load().key(conferenceKey).now();
            // 404 when there is no Conference with the given conferenceId.
            if (conference == null) {
                return new  WrappedBoolean(false,
                        "No Conference found with key: " + websafeConferenceKey);
            }

            // Un-registering from the Conference.
            Profile profile = getProfileFromUser(user);
            if (profile.getConferenceKeysToAttend().contains(websafeConferenceKey)) {
                profile.unregisterFromConference(websafeConferenceKey);
                conference.giveBackSeats(1);
                ofy().save().entities(profile, conference).now();
                return new WrappedBoolean(true);
            } else {
                return new WrappedBoolean(false, "You are not registered for this conference");
            }
        }
    });
    // if result is false
    if (!result.getResult()) {
        if (result.getReason().contains("No Conference found with key")) {
            throw new NotFoundException (result.getReason());
        }
        else {
            throw new ForbiddenException(result.getReason());
        }
    }
    // NotFoundException is actually thrown here.
    return new WrappedBoolean(result.getResult());
}
 
Example #7
Source File: ConferenceApi.java    From ud859 with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Updates the existing Conference with the given conferenceId.
 *
 * @param user A user who invokes this method, null when the user is not signed in.
 * @param conferenceForm A ConferenceForm object representing user's inputs.
 * @param websafeConferenceKey The String representation of the Conference key.
 * @return Updated Conference object.
 * @throws UnauthorizedException when the user is not signed in.
 * @throws NotFoundException when there is no Conference with the given conferenceId.
 * @throws ForbiddenException when the user is not the owner of the Conference.
 */
@ApiMethod(
        name = "updateConference",
        path = "conference/{websafeConferenceKey}",
        httpMethod = HttpMethod.PUT
)
public Conference updateConference(final User user, final ConferenceForm conferenceForm,
                                   @Named("websafeConferenceKey")
                                   final String websafeConferenceKey)
        throws UnauthorizedException, NotFoundException, ForbiddenException, ConflictException {
    // If not signed in, throw a 401 error.
    if (user == null) {
        throw new UnauthorizedException("Authorization required");
    }
    final String userId = getUserId(user);
    // Update the conference with the conferenceForm sent from the client.
    // Need a transaction because we need to safely preserve the number of allocated seats.
    TxResult<Conference> result = ofy().transact(new Work<TxResult<Conference>>() {
        @Override
        public TxResult<Conference> run() {
            // If there is no Conference with the id, throw a 404 error.
            Key<Conference> conferenceKey = Key.create(websafeConferenceKey);
            Conference conference = ofy().load().key(conferenceKey).now();
            if (conference == null) {
                return new TxResult<>(
                        new NotFoundException("No Conference found with the key: "
                                + websafeConferenceKey));
            }
            // If the user is not the owner, throw a 403 error.
            Profile profile = ofy().load().key(Key.create(Profile.class, userId)).now();
            if (profile == null ||
                    !conference.getOrganizerUserId().equals(userId)) {
                return new TxResult<>(
                        new ForbiddenException("Only the owner can update the conference."));
            }
            conference.updateWithConferenceForm(conferenceForm);
            ofy().save().entity(conference).now();
            return new TxResult<>(conference);
        }
    });
    // NotFoundException or ForbiddenException is actually thrown here.
    return result.getResult();
}
 
Example #8
Source File: ConferenceApi.java    From ud859 with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Registers to the specified Conference.
 *
 * @param user An user who invokes this method, null when the user is not signed in.
 * @param websafeConferenceKey The String representation of the Conference Key.
 * @return Boolean true when success, otherwise false
 * @throws UnauthorizedException when the user is not signed in.
 * @throws NotFoundException when there is no Conference with the given conferenceId.
 */
@ApiMethod(
        name = "registerForConference",
        path = "conference/{websafeConferenceKey}/registration",
        httpMethod = HttpMethod.POST
)
public WrappedBoolean registerForConference(final User user,
                                     @Named("websafeConferenceKey")
                                     final String websafeConferenceKey)
    throws UnauthorizedException, NotFoundException, ForbiddenException, ConflictException {
    // If not signed in, throw a 401 error.
    if (user == null) {
        throw new UnauthorizedException("Authorization required");
    }
    final String userId = getUserId(user);
    TxResult<Boolean> result = ofy().transact(new Work<TxResult<Boolean>>() {
        @Override
        public TxResult<Boolean> run() {
            Key<Conference> conferenceKey = Key.create(websafeConferenceKey);
            Conference conference = ofy().load().key(conferenceKey).now();
            // 404 when there is no Conference with the given conferenceId.
            if (conference == null) {
                return new TxResult<>(new NotFoundException(
                        "No Conference found with key: " + websafeConferenceKey));
            }
            // Registration happens here.
            Profile profile = getProfileFromUser(user, userId);
            if (profile.getConferenceKeysToAttend().contains(websafeConferenceKey)) {
                return new TxResult<>(new ConflictException("You have already registered for this conference"));
            } else if (conference.getSeatsAvailable() <= 0) {
                return new TxResult<>(new ConflictException("There are no seats available."));
            } else {
                profile.addToConferenceKeysToAttend(websafeConferenceKey);
                conference.bookSeats(1);
                ofy().save().entities(profile, conference).now();
                return new TxResult<>(true);
            }
        }
    });
    // NotFoundException is actually thrown here.
    return new WrappedBoolean(result.getResult());
}
 
Example #9
Source File: ConferenceApi.java    From ud859 with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Register to attend the specified Conference.
 *
 * @param user An user who invokes this method, null when the user is not signed in.
 * @param websafeConferenceKey The String representation of the Conference Key.
 * @return Boolean true when success, otherwise false
 * @throws UnauthorizedException when the user is not signed in.
 * @throws NotFoundException when there is no Conference with the given conferenceId.
 */
@ApiMethod(
        name = "registerForConference",
        path = "conference/{websafeConferenceKey}/registration",
        httpMethod = HttpMethod.POST
)

public WrappedBoolean registerForConference(final User user,
        @Named("websafeConferenceKey") final String websafeConferenceKey)
        throws UnauthorizedException, NotFoundException,
        ForbiddenException, ConflictException {
    // If not signed in, throw a 401 error.
    if (user == null) {
        throw new UnauthorizedException("Authorization required");
    }

    // Get the userId
    final String userId = user.getUserId();

    WrappedBoolean result = ofy().transact(new Work<WrappedBoolean>() {
        @Override
        public WrappedBoolean run() {
            try {

            // Get the conference key
            // Will throw ForbiddenException if the key cannot be created
            Key<Conference> conferenceKey = Key.create(websafeConferenceKey);

            // Get the Conference entity from the datastore
            Conference conference = ofy().load().key(conferenceKey).now();

            // 404 when there is no Conference with the given conferenceId.
            if (conference == null) {
                return new WrappedBoolean (false,
                        "No Conference found with key: "
                                + websafeConferenceKey);
            }

            // Get the user's Profile entity
            Profile profile = getProfileFromUser(user);

            // Has the user already registered to attend this conference?
            if (profile.getConferenceKeysToAttend().contains(
                    websafeConferenceKey)) {
                return new WrappedBoolean (false, "Already registered");
            } else if (conference.getSeatsAvailable() <= 0) {
                return new WrappedBoolean (false, "No seats available");
            } else {
                // All looks good, go ahead and book the seat
                profile.addToConferenceKeysToAttend(websafeConferenceKey);
                conference.bookSeats(1);

                // Save the Conference and Profile entities
                ofy().save().entities(profile, conference).now();
                // We are booked!
                return new WrappedBoolean(true, "Registration successful");
            }

            }
            catch (Exception e) {
                return new WrappedBoolean(false, "Unknown exception");

            }
        }
    });
    // if result is false
    if (!result.getResult()) {
        if (result.getReason().contains("No Conference found with key")) {
            throw new NotFoundException (result.getReason());
        }
        else if (result.getReason() == "Already registered") {
            throw new ConflictException("You have already registered");
        }
        else if (result.getReason() == "No seats available") {
            throw new ConflictException("There are no seats available");
        }
        else {
            throw new ForbiddenException("Unknown exception");
        }
    }
    return result;
}
 
Example #10
Source File: ConferenceApi.java    From ud859 with GNU General Public License v3.0 4 votes vote down vote up
/**
 * Unregister from the specified Conference.
 *
 * @param user An user who invokes this method, null when the user is not signed in.
 * @param websafeConferenceKey The String representation of the Conference Key to unregister
 *                             from.
 * @return Boolean true when success, otherwise false.
 * @throws UnauthorizedException when the user is not signed in.
 * @throws NotFoundException when there is no Conference with the given conferenceId.
 */
@ApiMethod(
        name = "unregisterFromConference",
        path = "conference/{websafeConferenceKey}/registration",
        httpMethod = HttpMethod.DELETE
)
public WrappedBoolean unregisterFromConference(final User user,
                                        @Named("websafeConferenceKey")
                                        final String websafeConferenceKey)
        throws UnauthorizedException, NotFoundException, ForbiddenException, ConflictException {
    // If not signed in, throw a 401 error.
    if (user == null) {
        throw new UnauthorizedException("Authorization required");
    }

    WrappedBoolean result = ofy().transact(new Work<WrappedBoolean>() {
        @Override
        public WrappedBoolean run() {
            Key<Conference> conferenceKey = Key.create(websafeConferenceKey);
            Conference conference = ofy().load().key(conferenceKey).now();
            // 404 when there is no Conference with the given conferenceId.
            if (conference == null) {
                return new  WrappedBoolean(false,
                        "No Conference found with key: " + websafeConferenceKey);
            }

            // Un-registering from the Conference.
            Profile profile = getProfileFromUser(user);
            if (profile.getConferenceKeysToAttend().contains(websafeConferenceKey)) {
                profile.unregisterFromConference(websafeConferenceKey);
                conference.giveBackSeats(1);
                ofy().save().entities(profile, conference).now();
                return new WrappedBoolean(true);
            } else {
                return new WrappedBoolean(false, "You are not registered for this conference");
            }
        }
    });
    // if result is false
    if (!result.getResult()) {
        if (result.getReason().contains("No Conference found with key")) {
            throw new NotFoundException (result.getReason());
        }
        else {
            throw new ForbiddenException(result.getReason());
        }
    }
    // NotFoundException is actually thrown here.
    return new WrappedBoolean(result.getResult());
}
 
Example #11
Source File: ShardedCounterServiceImpl.java    From appengine-counter with Apache License 2.0 4 votes vote down vote up
/**
 * NOTE: We don't allow the counter's "count" to be updated by this method. Instead, {@link #increment} and
 * {@link #decrement} should be used.
 *
 * @param incomingCounter
 */
@Override
public void updateCounterDetails(final Counter incomingCounter)
{
	Preconditions.checkNotNull(incomingCounter);

	// First, assert the counter is in a proper state. If done consistently (i.e., in a TX, then this will function
	// as an effective CounterData lock).
	// Second, Update the counter details.

	ObjectifyService.ofy().transact(new Work<Void>()
	{
		@Override
		public Void run()
		{
			// First, load the incomingCounter from the datastore via a transactional get to ensure it has the
			// proper state.
			final Optional<CounterData> optCounterData = getCounterData(incomingCounter.getName());

			// //////////////
			// ShortCircuit: Can't update a counter that doesn't exist!
			// //////////////
			if (!optCounterData.isPresent())
			{
				throw new NoCounterExistsException(incomingCounter.getName());
			}

			final CounterData counterDataInDatastore = optCounterData.get();

			// Make sure the stored counter is currently in an updatable state!
			assertCounterDetailsMutatable(incomingCounter.getName(), counterDataInDatastore.getCounterStatus());

			// Make sure the incoming counter status is a validly settable status
			assertValidExternalCounterStatus(incomingCounter.getName(), incomingCounter.getCounterStatus());

			// NOTE: READ_ONLY_COUNT status means the count can't be incremented/decremented. However, it's details
			// can still be mutated.

			// NOTE: The counterName/counterId may not change!

			// Update the Description
			counterDataInDatastore.setDescription(incomingCounter.getDescription());

			// Update the numShards. Aside from setting this value, nothing explicitly needs to happen in the
			// datastore since shards will be created when a counter in incremented (if the shard doesn't already
			// exist). However, if the number of shards is being reduced, then throw an exception since this
			// requires counter shard reduction and some extra thinking. We can't allow the shard-count to go down
			// unless we collapse the entire counter's shards into a single shard or zero, and it's ambiguous if
			// this is even required. Note that if we allow this numShards value to decrease without capturing
			// the count from any of the shards that might no longer be used, then we might lose counts from the
			// shards that would no longer be factored into the #getCount method.
			if (incomingCounter.getNumShards() < counterDataInDatastore.getNumShards())
			{
				throw new IllegalArgumentException(
					"Reducing the number of counter shards is not currently allowed!  See https://github.com/instacount/appengine-counter/issues/4 for more details.");
			}

			counterDataInDatastore.setNumShards(incomingCounter.getNumShards());

			// The Exception above disallows any invalid states.
			counterDataInDatastore.setCounterStatus(incomingCounter.getCounterStatus());

			// Update the CounterDataIndexes
			counterDataInDatastore.setIndexes(
				incomingCounter.getIndexes() == null ? CounterIndexes.none() : incomingCounter.getIndexes());

			// Update the counter in the datastore.
			ObjectifyService.ofy().save().entity(counterDataInDatastore).now();

			// return null to satisfy Java...
			return null;
		}
	});
}
 
Example #12
Source File: ShardedCounterServiceImpl.java    From appengine-counter with Apache License 2.0 4 votes vote down vote up
/**
 * Does the work of incrementing or decrementing the value of a single shard for the counter named
 * {@code counterName}.
 *
 * @param counterName
 * @param amount The amount to mutate a counter shard by. This value will be negative for a decrement, and positive
 *            for an increment.
 * @param incrementOperationId
 * @param optShardNumber An optionally specified shard number to increment. If not specified, a random shard number
 *            will be chosen.
 * @return An instance of {@link CounterOperation} with information about the increment/decrement.
 */
private CounterOperation mutateCounterShard(final String counterName, final long amount,
		final Optional<Integer> optShardNumber, final UUID incrementOperationId)
{
	// ///////////
	// Precondition Checks are performed by calling methods.

	// This get is transactional and strongly consistent, but we perform it here so that the increment doesn't have
	// to be part of an XG transaction. Since the CounterData isn't expected to mutate that often, we want
	// increment/decrement operations to be as speedy as possibly. In this way, the IncrementWork can take place in
	// a non-XG transaction.
	final CounterDataGetCreateContainer counterDataGetCreateContainer = this.getOrCreateCounterData(counterName);
	final CounterData counterData = counterDataGetCreateContainer.getCounterData();

	// Create the Work to be done for this increment, which will be done inside of a TX. See
	// "https://developers.google.com/appengine/docs/java/datastore/transactions#Java_Isolation_and_consistency"
	final Work<CounterOperation> atomicIncrementShardWork = new IncrementShardWork(counterData,
		incrementOperationId, optShardNumber, amount, counterDataGetCreateContainer.isNewCounterDataCreated());

	// Note that this operation is idempotent from the perspective of a ConcurrentModificationException. In that
	// case, the increment operation will fail and will not have been applied. An Objectify retry of the
	// increment/decrement will occur, however and a successful increment/decrement will only ever happen once (if
	// the Appengine datastore is functioning properly). See the Javadoc in the API about DatastoreTimeoutException
	// or
	// DatastoreFailureException in cases where transactions have been committed and eventually will be applied
	// successfully."

	// We use the "counterShardOperationInTx" to force this thread to wait until the work inside of
	// "atomicIncrementShardWork" completes. This is because we don't want to increment memcache (below) until after
	// that operation's transaction successfully commits.
	final CounterOperation counterShardOperationInTx = ObjectifyService.ofy().transact(atomicIncrementShardWork);

	// /////////////////
	// Try to increment this counter in memcache atomically, but only if we're not inside of a parent caller's
	// transaction. If that's the case, then we can't know if the parent TX will fail upon commit, which would
	// happen after our call to memcache.
	// /////////////////
	if (isParentTransactionActive())
	{
		// If a parent-transaction is active, then don't update memcache. Instead, clear it out since we can't know
		// if the parent commit will actually stick.
		this.memcacheSafeDelete(counterName);
	}
	else
	{
		// Otherwise, try to increment memcache. If the memcache operation fails, it's ok because memcache is merely
		// a cache of the actual count data, and will eventually become accurate when the cache is reloaded via a
		// call to #getCount.
		long amountToMutateCache = counterShardOperationInTx.getAppliedAmount();
		if (amount < 0)
		{
			amountToMutateCache *= -1L;
		}
		this.incrementMemcacheAtomic(counterName, amountToMutateCache);
	}

	return counterShardOperationInTx;
}