@Override public Value<FullEntity<?>> saveSafe(final P pojo, final boolean index, final SaveContext ctx, final Path path) throws SkipException { // check if we need to redirect to a different translator if (pojo.getClass() != declaredClass) { // Sometimes generics are more of a hindrance than a help @SuppressWarnings("unchecked") final ClassTranslator<P> translator = (ClassTranslator<P>)byClass.get(pojo.getClass()); if (translator == null) throw new IllegalStateException("Class '" + pojo.getClass() + "' is not a registered @Subclass"); else return translator.save(pojo, index, ctx, path); } else { // This is a normal save final FullEntity.Builder<IncompleteKey> into = FullEntity.newBuilder(); populator.save(pojo, index, ctx, path, into); if (discriminator != null) { into.set(DISCRIMINATOR_PROPERTY, StringValue.newBuilder(discriminator).setExcludeFromIndexes(true).build()); if (!indexedDiscriminators.isEmpty()) into.set(DISCRIMINATOR_INDEX_PROPERTY, ListValue.of(indexedDiscriminators)); } // The question of whether to index this is weird. In order for subthings to be indexed, the entity needs // to be indexed. But then lists with index-heterogeous values (say, nulls) get reordered (!) // by the datastore. So we always index EntityValues and force all the list translators homogenize their lists. // Gross but seems to be the only way. return EntityValue.of(into.build()); } }
@Override public Backfill store(Backfill backfill) throws IOException { final Key key = DatastoreStorage.backfillKey(tx.getDatastore().newKeyFactory(), backfill.id()); Entity.Builder builder = Entity.newBuilder(key) .set(PROPERTY_CONCURRENCY, backfill.concurrency()) .set(PROPERTY_START, instantToTimestamp(backfill.start())) .set(PROPERTY_END, instantToTimestamp(backfill.end())) .set(PROPERTY_COMPONENT, backfill.workflowId().componentId()) .set(PROPERTY_WORKFLOW, backfill.workflowId().id()) .set(PROPERTY_SCHEDULE, backfill.schedule().toString()) .set(PROPERTY_NEXT_TRIGGER, instantToTimestamp(backfill.nextTrigger())) .set(PROPERTY_ALL_TRIGGERED, backfill.allTriggered()) .set(PROPERTY_HALTED, backfill.halted()) .set(PROPERTY_REVERSE, backfill.reverse()); backfill.description().ifPresent(x -> builder.set(PROPERTY_DESCRIPTION, StringValue .newBuilder(x).setExcludeFromIndexes(true).build())); if (backfill.triggerParameters().isPresent()) { final String json = OBJECT_MAPPER.writeValueAsString(backfill.triggerParameters().get()); builder.set(PROPERTY_TRIGGER_PARAMETERS, StringValue.newBuilder(json).setExcludeFromIndexes(true).build()); } tx.put(builder.build()); return backfill; }
@Override public WorkflowId store(Workflow workflow) throws IOException { final Key componentKey = DatastoreStorage.componentKey(tx.getDatastore().newKeyFactory(), workflow.componentId()); if (tx.get(componentKey) == null) { tx.put(Entity.newBuilder(componentKey).build()); } final String json = OBJECT_MAPPER.writeValueAsString(workflow); final Key workflowKey = DatastoreStorage.workflowKey(tx.getDatastore().newKeyFactory(), workflow.id()); final Optional<Entity> workflowOpt = DatastoreStorage.getOpt(tx, workflowKey); final Entity workflowEntity = DatastoreStorage.asBuilderOrNew(workflowOpt, workflowKey) .set(PROPERTY_WORKFLOW_JSON, StringValue.newBuilder(json).setExcludeFromIndexes(true).build()) .build(); tx.put(workflowEntity); return workflow.id(); }
@Override public WorkflowId storeWorkflowWithNextNaturalTrigger(Workflow workflow, TriggerInstantSpec triggerSpec) throws IOException { final Key componentKey = DatastoreStorage.componentKey(tx.getDatastore().newKeyFactory(), workflow.componentId()); if (tx.get(componentKey) == null) { tx.put(Entity.newBuilder(componentKey).build()); } final String json = OBJECT_MAPPER.writeValueAsString(workflow); final Key workflowKey = DatastoreStorage.workflowKey(tx.getDatastore().newKeyFactory(), workflow.id()); final Optional<Entity> workflowOpt = DatastoreStorage.getOpt(tx, workflowKey); final Builder entity = DatastoreStorage.asBuilderOrNew(workflowOpt, workflowKey) .set(PROPERTY_WORKFLOW_JSON, StringValue.newBuilder(json).setExcludeFromIndexes(true).build()) .set(PROPERTY_NEXT_NATURAL_TRIGGER, instantToTimestamp(triggerSpec.instant())) .set(PROPERTY_NEXT_NATURAL_OFFSET_TRIGGER, instantToTimestamp(triggerSpec.offsetInstant())); tx.put(entity.build()); return workflow.id(); }
static StringValue jsonValue(Object o) throws JsonProcessingException { return StringValue .newBuilder(OBJECT_MAPPER.writeValueAsString(o)) .setExcludeFromIndexes(true) .build(); }
static StringValue valueOf(String input) { return StringValue.newBuilder(input) .setExcludeFromIndexes(true) .build(); } }
@Override public P loadSafe(final Value<FullEntity<?>> container, final LoadContext ctx, final Path path) throws SkipException { // check if we need to redirect to a different translator final String containerDiscriminator = container.get().contains(DISCRIMINATOR_PROPERTY) ? container.get().getString(DISCRIMINATOR_PROPERTY) : null; // wow no Optional or nullable get if (!Objects.equals(discriminator, containerDiscriminator)) { final ClassTranslator<? extends P> translator = byDiscriminator.get(containerDiscriminator); if (translator == null) { throw new IllegalStateException("Datastore object has discriminator value '" + containerDiscriminator + "' but no relevant @Subclass is registered"); } else { // This fixes alsoLoad names in discriminators by changing the discriminator to what the // translator expects for loading that subclass. Otherwise we'll get the error above since the // translator discriminator and the container discriminator won't match. final StringValue discriminatorValue = StringValue.newBuilder(translator.getDiscriminator()).setExcludeFromIndexes(true).build(); final FullEntity<?> updatedEntity = FullEntity.newBuilder(container.get()).set(DISCRIMINATOR_PROPERTY, discriminatorValue).build(); return translator.load(EntityValue.of(updatedEntity), ctx, path); } } else { // This is a normal load if (log.isTraceEnabled()) log.trace(LogUtils.msg(path, "Instantiating a " + declaredClass.getName())); final P into = forge.construct(declaredClass); populator.load(container.get(), ctx, path, into); return into; } }
/** * Adds a task entity to the Datastore. * * @param description The task description * @return The {@link Key} of the entity * @throws DatastoreException if the ID allocation or put fails */ Key addTask(String description) { Key key = datastore.allocateId(keyFactory.newKey()); Entity task = Entity.newBuilder(key) .set("description", StringValue.newBuilder(description).setExcludeFromIndexes(true).build()) .set("created", Timestamp.now()) .set("done", false) .build(); datastore.put(task); return key; } // [END datastore_add_entity]
@Test public void testNewTransactionRollback() { Transaction transaction = DATASTORE.newTransaction(); transaction.add(ENTITY3); Entity entity2 = Entity.newBuilder(ENTITY2) .clear() .setNull("bla") .set("list3", StringValue.of("bla"), StringValue.newBuilder("bla").build()) .build(); transaction.update(entity2); transaction.delete(KEY1); transaction.rollback(); transaction.rollback(); // should be safe to repeat rollback calls try { transaction.commit(); fail("Expecting a failure"); } catch (DatastoreException expected) { assertEquals("FAILED_PRECONDITION", expected.getReason()); } List<Entity> list = DATASTORE.fetch(KEY1, KEY2, KEY3); assertEquals(ENTITY1, list.get(0)); assertEquals(ENTITY2, list.get(1)); assertNull(list.get(2)); assertEquals(3, list.size()); }
@Test public void testNewTransactionRollback() { Transaction transaction = datastore.newTransaction(); transaction.add(ENTITY3); Entity entity2 = Entity.newBuilder(ENTITY2) .clear() .setNull("bla") .set("list3", StringValue.of("bla"), StringValue.newBuilder("bla").build()) .build(); transaction.update(entity2); transaction.delete(KEY1); transaction.rollback(); transaction.rollback(); // should be safe to repeat rollback calls try { transaction.commit(); fail("Expecting a failure"); } catch (DatastoreException ex) { // expected to fail } verifyNotUsable(transaction); List<Entity> list = datastore.fetch(KEY1, KEY2, KEY3); assertEquals(ENTITY1, list.get(0)); assertEquals(ENTITY2, list.get(1)); assertNull(list.get(2)); assertEquals(3, list.size()); }
@Test public void testToBuilder() throws Exception { StringValue value = StringValue.of(CONTENT); assertEquals(value, value.toBuilder().build()); }
@SuppressWarnings("deprecation") @Test public void testBuilder() throws Exception { StringValue.Builder builder = StringValue.newBuilder(CONTENT); StringValue value = builder.setMeaning(1).setExcludeFromIndexes(true).build(); assertEquals(CONTENT, value.get()); assertEquals(1, value.getMeaning()); assertTrue(value.excludeFromIndexes()); } }