Jello Framework

Jello Entity is the core component which the application model is based on. Jello uses Objectify as the underlying ORM layer to map and store entities in the database. If you are familiar with Objectify or your application is already using Objectify, you will find it very easy and intuitive to migrate your app to Jello.

Jello has an improved persistence mapping model over the basic Objectify implementation. Its superior model using the entity full name to maps entities in different packages to the database, providing a better way to organize the code and shape your application authorization model.

You define a Jello Entity by extending the JelloEntity class. To expose it via RESTfull API, you will need to add the @Accessible annotation.


Entity declaration

1
2
3
4
5
6
package app;
					
@Accessible
public class Person extends JelloEntity {
   ...
}

JelloEntity (jello.model.JelloEntity)

The base class for all Jello persistence entities.


@Accessible (jello.security.Accessible)

Marks the entity as accessible to all via REST requests. The @Accessible annotation can get authorization parameters (for data-access-control options, refer to the Jello Authorization Model).


@Index, @Unindex (com.googlecode.objectify.annotation.Index)

Placed on a field or class. When placed on a class, sets the default state for all fields of the class. Indexed fields cost more space and cpu time to store, but can be queried on. Unless otherwise specified, all classes default to @Index. For more options like partial indexing, refer to Objectify annotation reference


@Searchable, @NotSearchable (jello.annotation.Searchable)

Determine whether to add the entity to the search index. By default, Jello will treat all* entities as searchable. To override this behavior, add the following to app.yaml. (See also the application configuration file.)

    servlet: jello.JelloServlet
    name: Jello
    init_params:
      jello.search.enabledByDefault: false					

* For security reasons, an entity which contains instance level access will not be treated as searchable.




Entity Key

The entity key is composed of all fields annotated with @KeyElement and @GroupElement. In addition to all fields specified by the user, Jello will add implicit __ID key field to hold the entity key value.

In case no key element is specified, an auto generated UID will be assigned as the entity key.

In case one key element is specified, the entity key value will be the same as the key element value.

1
2
3
// example of entity key:  John Doe
@KeyElement
public String fullName;

In case of multiple key elements:

1
2
3
4
// example of entity key:  Toyota::123456-CA
@GroupElement String model
@KeyElement String licensePlate;
@KeyElement String registeredAtState;

Field annotated with @groupElement is a special type of key element typically used to establish strong association and/or define strong persistence and transactions. Only one field annotated with @GroupElement can be defined per Entity. (read more about strong association ad transactions).





Field Element declaration

Most of the data types supported by Google App Engine datastore are also supported by Jello.

In addition, Jello provides superior handling for the following types:

Enum type

Reference type

Association type

Calculated type

Attachment type

Owned embedded type

Collection types



new field dialog

Field annotations modifiers:


@Expose(jello.annotation.Expose)

Marks the field as accessible to all via REST requests. This annotation affects only the REST API, while the Java access level is determined, as usual, by the field java access modifiers (public/private).

The @Expose annotation can get authorization parameters (for data-access-control options, refer to the Data Access Control section).

In case of exposure of a private field, the Jello annotation processor will issue a warning, letting you know that this field will be exposed via REST calls regardless of its 'private' modifier. To mute this warning, go to the project settings » Jello Framework » Annotation settings.


@Required(jello.annotation.Required)

Marks the field as required. This annotation affects both REST (Create, Update) operations and the Java API create(), save(), update().


@Cardinality(jello.annotation.Cardinality)

Specifies the field cardinality. This annotation affects both REST (Create, Update) operations and the Java API create(), save(), update().

This annotation is valid only for multiple values fields (Set or List). Cardinality has priority over @Required and will override it.

Syntax:
@Cardinality("number..number|*") // for example: @Cardinality("2..*")

@ReadOnly(jello.annotation.ReadOnly)

Marks the field as read-only by REST API. This annotation affects only the REST (Create, Update) operations while the Java access level is determined, as usual, by the field java access modifiers (public/private, final).


@Final (jello.annotation.Final)

Marks the field as final by REST API. This annotation affects only the REST (Update) operation. It allows one to specify a value for the field when creating the instance. Any attempt to update the field's value after instance creation will fail. The Java field access behavior is determined, as usual, by the field Java modifier (final).


@Ignore (com.googlecode.objectify.annotation.Ignore)

Marks the field as as not persistent. The field value will not be saved in the database.
By default, all entity fields are persistent.


Audit fields

Jello entities maintain Audit fields for all records. Each entity includes the following audit fields by default:

@Expose @Audit @ReadOnly
private Date createdOn;
	
@Expose @Audit @ReadOnly
private User createdBy;
	
@Expose @Audit @ReadOnly
private Date changedOn;
	
@Expose @Audit @ReadOnly
private User changedBy;

Audit fields are not included in the REST response by default. To include Audit fields in the response, use the $audit=true query option (see REST Query Options - $audit). Note: Audit fields will be exposed only if the logged-in user has CREATE permission for the entity. (For data-access-control options, refer to the Data Access Control section.)

  • @Audit (jello.annotation.Audit)
    Marks the field as an Audit field.


  • Special field types:


    Enum field type

    Any enumeration type can be used as a field type. (The @label annotation is optional).
    @Expose public Color background;
    
    public enum Color { @Label("Red") RED, @Label("Green") GREEN, @Label("Blue") BLUE };
    

    The Entity metadata will include the enumeration definition as shown below:

    {
      name: "background",
      type: "app.Box.Color"
    }
    ...
    enums: {
      app.Box.Color: [
        {
          value: "RED",
          label: "Red"
        },
        {
          value: "GREEN",
          label: "Green"
        },
        {
          value: "BLUE",
          label: "Blue"
        }
      ]
    }
    

    Calculated field

    Calculated fields are not saved in the database but rather are calculated on demand whenever an entity's instance is being fetched. From REST call perspective, calculated fields look like any other read only field.

    import java.util.function.Function;
    import java.util.function.Supplier;
    import com.googlecode.objectify.annotation.Ignore;
    
    public class EntityWithCalcField extends JelloEntity {
    	// Simple Lambda expression
    	@Expose Supplier<String> calcField = () -> "I am a calculated field";
    	
    	// Lambda expression with 'this' instance context
    	@Expose Function<EntityWithCalcField, String> calcFieldWithContext = 
               (x) -> "I am a calculated field: " + x.getID();
    	
    	// The old way: 
            // Adding a method with the same name as the field with no parameters
            // The method must return the same type as the field's type
            @Ignore
    	@Expose private String calculatedField;
    	public String calculatedField() {
    	    return "I am calculated field: " + getID();
    	}
    }
    

    Collection field

    Both Set and List collections are supported for the following types: any core type, enums, reference type, and managed embedded type.

       @Expose public List<String> messages;
    
       @Expose public Address managedAddress;
       @Expose public List<Color> colorsList;
       @Expose public Set<Color> colorsSet;
       enum Color {RED, GREEN}
       
       @Reference(Product.class)
       @Expose public List<Key> productsList;
       @Expose public Set<Key> productsSet;
       
       @Expose public List<Address> addressesList;
       @Expose public Set<Address> addressesSet;
       
       static class Address {
          String street;
          String city;
          String state;
          String zipCode;
       }
    

    UX annotations

    Entity elements can be annotated with UX annotations. You can add any of the jello.ux annotations such as Label, Hint, Control, etc. (see list of all available UX annotation).

    In addition, you can define custom UX annotations which will be added to the element meta data object (see Define Custom UX Annotations).


    Before and After Save methods

    Overrides the default JelloEntity empty methods. These functions are used for adding custom validation (see Server-Side validation) and records manipulation before and after saving the record. It applies to both REST (create, update) API calls and JAVA create(), save(), update() API.

    These methods are handy especially since only the default empty constructor is invoked during REST call.

    @Override
    protected Car beforeSave() throws IllegalRequestResource {
       // Here you can add code that will be executed before saving the record.
       if(this.text.contains("somethingfishy"))
          throw new IllegalRequestResource( "something fishy while trying to save " +  this.getID() )
             .forField("text");
       return this;
    }
       
    @Override
    protected Car afterSave() {
       // Here you can add code that will be executed after saving the record.
       return this;
    }
    

    Before and After Delete methods

    @Override
    protected Car beforeDelete() throws IllegalRequestResource {
       // Here you can add code that will be executed before deleting the record.
       if(something-fishy)
          throw new IllegalRequestResource( "something fishy while trying to delete " +  this.getID() );
       return this;
    }
       
    @Override
    protected Car afterDelete() {
       // Here you can add code that will be executed after deleting the record.
       return this;
    }
    


    Static Entity

    Static entity serves as a service endpoint that can only expose static actions via REST calls. Any attempt to persist an instance of this entity in the database will fail.

    @Accessible
    public class AppService extends StaticEntity {
       // Static actions only
    }
    

    Last updated March 11, 2019.