[#properties]
= Properties

:Notice: Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file distributed with this work for additional information regarding copyright ownership. The ASF licenses this file to you 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.
:page-partial:


A property is an instance variable of a domain object, of a scalar type, that holds some state in either a xref:userguide:fun:domain-entities-and-services.adoc#domain-entities[domain entity] or a xref:userguide:fun:view-models.adoc[view model].

For example, a ``Customer``'s `firstName` would be a property, as would their `accountCreationDate` that they created their account.
All properties have at least a "getter" method, and most properties have also a "setter" method (meaning that they are mutable).
Properties that do _not_ have a setter method are derived properties, and so are not persisted.

Formally speaking, a property is simply a regular JavaBean getter, returning a scalar value recognized by the framework.
Most properties (those that are editable/modifiable) will also have a setter and, if persisted, a backing instance field.
And most properties will also have a number of annotations:

* Apache Isis defines its own xref:refguide:applib:index/annotation/Property.adoc[@Property] annotation for capturing domain semantics.
It also provides a xref:refguide:applib:index/annotation/PropertyLayout.adoc[@PropertyLayout] for UI hints (though the information in this annotation may instead be provided by a supplementary xref:userguide:fun:ui.adoc#object-layout[.layout.xml] file)

* the properties of domain entities are usually also annotated with an ORM annotation.
+
In the case of xref:pjdo:ROOT:about.adoc[JDO/DataNucleus] this would be the link:http://www.datanucleus.org/products/datanucleus/jdo/annotations.html#Column[@javax.jdo.annotations.Column] annotation.
For property references, there may be other annotations to indicate whether the reference is bidirectional.
It's also possible (using annotations) to define a link table to hold foreign key columns.

* for the properties of view models, then JAXB annotations such as  link:https://docs.oracle.com/javase/8/docs/api/javax/xml/bind/annotation/XmlElement.html[@XmlElement] may be present

Apache Isis recognises some of these annotations from the persistence layer and infers some domain semantics from them (for example, the maximum allowable length of a string property).

Since writing getter and setter methods adds quite a bit of boilerplate, it's common to use link:https://projectlombok.org/[Project Lombok] to code generate these methods at compile time (using Java's annotation processor) simply by adding the `@lombok.Getter` and `@lombok.Setter` annotations to the field.
The xref:docs:starters:simpleapp.adoc[SimpleApp] starter app uses this approach.

== Value vs Reference Types

Properties can be either a value type (strings, int, date and so on) or be a reference to another object (for example, an `Order` referencing the `Customer` that placed it).

For example, to map a string value type:

[source,java]
----
import lombok.Getter;
import lombok.Setter;

@Getter @Setter        // <.>
private String notes;
----
<1> using link:https://projectlombok.org/[Project Lombok] annotations to reduce boilerplate

You could also add the `@Property` annotation if you wished:

[source,java]
----
import lombok.Getter;
import lombok.Setter;

@Property
@Getter @Setter
private String notes;
----

Although in this case it is not required (none of its attributes have been set).

Or to map a reference type:

[source,java]
----
import lombok.Getter;
import lombok.Setter;

@Property
@Getter @Setter
private Customer customer;
----

It's ok for a xref:userguide:fun:view-models.adoc[view model] to reference other view models and also to reference domain entities.
However, it _isn't_ valid for a domain entity to hold a reference to view model, because the ORM will not know how to persist the view model object.

[TIP]
====
For further details on mapping associations using xref:pjdo:ROOT:about.adoc[JDO/DataNucleus], see their link:http://www.datanucleus.org:15080/products/accessplatform_5_2/jdo/mapping.html[mapping guide].
====

[#optional-properties]
== Optional Properties

(For domain entities) JDO/DataNucleus' default is that a property is assumed to be mandatory if it is a primitive type (eg `int`, `boolean`), but optional if a reference type (eg `String`, `BigDecimal` etc).
To override optionality in JDO/DataNucleus the `@Column(allowsNull="...")` annotations is used.

Apache Isis on the other hand assumes that all properties (and action parameters, for that matter) are mandatory, not optional.
These defaults can also be overridden using Apache Isis' own annotations, specifically `@Property(optionality=...)`, or (because it's much less verbose) using `@javax.annotation.Nullable`.

These different defaults can lead to incompatibilities between the two frameworks.
To counteract that, Apache Isis also recognizes and honours JDO's `@Column(allowsNull=...)`.

For example, you can write:

[source,java]
----
import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(allowsNull="true")
@Property
@Getter @Setter
private LocalDate date;
----

rather than the more verbose:

[source,java]
----
import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(allowsNull="true")
@Property(optionality=Optionality.OPTIONAL)
@Getter @Setter
private LocalDate date;
----

The framework will search for any incompatibilities in optionality (whether specified explicitly or defaulted implicitly) between Apache Isis' defaults and DataNucleus, and will refuse to boot if any are found.

[#editable-properties]
== Editable Properties

Apache Isis provides the capability to allow individual properties to be modified.
This is specified using the `@Property(editing=...)` attribute.

For example:

[source,java]
----
import lombok.Getter;
import lombok.Setter;

@Property(editing = Editing.ENABLED)
@Getter @Setter
private String notes;
----

If this is omitted then whether editing is enabled or disabled is inherited from the domain object level, xref:refguide:applib:index/annotation/DomainObject.adoc#editing[@DomainObject#editing()].

If that isn't specified (or is set to "AS_CONFIGURED"), then the configuration is taken from the `application.properties` configuration file:

* xref:refguide:config:sections/isis.applib.adoc#isis.applib.annotation.domain-object.editing[isis.applib.annotation.domain-object.editing]
+
for all properties in the domain object.

And, finally, if there is no configuration property set at all, then the default is for editing to be DISABLED.


For entities, editable properties are not necessarily persistable.
In such a case the setter would not write to a field, but would (presumably) mutate the object in some other way.
In such a case you will need a getter and a setter, but the property annotated for the ORM as non-persistent.
(If using JDO/DataNucleus, this is done using `@NotPersistent`).

For example:

[source,java]
----
import javax.inject.Inject;
import javax.jdo.annotations.Column
import javax.jdo.annotations.NotPersistent;
import lombok.Getter;
import lombok.Setter;

@javax.jdo.annotations.NotPersistent
@Property(editing=Editing.ENABLED)
public String getAddress() {
    return addressService.toAddress( getLatLong() );                // <.>
}
public void setAddress(String address) {
    setLatLong(addressService.toLatLong(address));
}

@Column
@Programmatic
@Getter @Setter
private String latLong;                                             // <.>

@Inject
AddressService addressService;                                      // <.>
----
<.> the representation of the address, in human readable form, eg "10 Downing Street, London, UK"
<.> the lat/long representation of the address, eg "51.503363;-0.127625".
Excluded from the Apache Isis metamodel.
<.> an injected service that can convert to/from address and latLong.

== Ignoring Properties

By default Apache Isis will automatically render all properties in the xref:vw:ROOT:about.adoc[Wicket UI] or in the xref:vro:ROOT:about.adoc[REST API].
To get Apache Isis to ignore a property (exclude it from its metamodel), annotate the getter using xref:refguide:applib:index/annotation/Programmatic.adoc[@Programmatic].

Similarly, for the JDO/DataNucleus ORM, ignore a property using the `@javax.jdo.annotations.NotPersistent` annotation.
This is independent of Apache Isis; in other words that property will still be rendered in the UI (unless also annotated with `@Programmatic`).

For view models, you can tell JAXB to ignore a property using the `@javax.xml.bind.annotation.XmlTransient` annotation.
Again, this is independent of Apache Isis.

You can also suppress a property from the UI using xref:refguide:applib:index/annotation/Property.adoc#hidden[@Property#hidden()] or indeed xref:refguide:applib:index/annotation/PropertyLayout.adoc#hidden[@PropertyLayout#hidden()].
However, this doesn't exclude the property from the metamodel.

== Derived Properties

Derived properties are those with a getter but no setter.
These will still be rendered in the UI, but they will be read-only (not editable).

For entities these may or may not be persisted; that depends on whether the ORM annotations are specified on the field or on the property.


== Data types (ORM considerations)

This section shows specific considerations for various datatypes, in particular how to annotate them for the ORM.

This section covers the xref:pjdo:ROOT:about.adoc[JDO/DataNucleus] object store.

=== ``String``s (Length)

By default JDO/DataNucleus will map string properties to a `VARCHAR(255)`.
To limit the length, use the `@Column(length=...)` annotation.

For example:

[source,java]
----
import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(length=50)
@Property
@Getter @Setter
private String firstName
----

This is a good example of a case where Apache Isis infers domain semantics from the JDO annotation.

=== JODA Dates

Apache Isis' bundles DataNucleus' http://www.datanucleus.org/documentation/products/plugins.html[built-in support] for Joda `LocalDate` and `LocalDateTime` datatypes, meaning that entity properties of these types will be persisted as appropriate data types in the database tables.

It is, however, necessary to annotate your properties with `@javax.jdo.annotations.Persistent`, otherwise the data won't actually be persisted.
See the link:http://db.apache.org/jdo/field_types.html[JDO docs] for more details on this.

Moreover, these datatypes are _not_ in the default fetch group, meaning that JDO/DataNucleus will perform an additional `SELECT` query for each attribute.
To avoid this extra query, the annotation should indicate that the property is in the default fetch group.

For example:

[source,java]
----
import javax.jdo.annotations.Persistent;
import javax.jdo.annotations.Column;
import org.joda.time.LocalDate;

@Persistent(defaultFetchGroup="true")
@Column(allowsNull="true")
@Property
@Getter @Setter
private LocalDate dueBy;
----

=== ``BigDecimal``s (Precision)

Working with `java.math.BigDecimal` properties takes a little care due to scale/precision issues.

For example, suppose we have:

[source,java]
----
import lombok.Getter;
import lombok.Setter;

@Property
@Getter @Setter
private BigDecimal impact;
----

Without any constraints, the length of the corresponding column is database specific.
For example, with HSQL JDO/DataNucleus maps to a column NUMERIC(19);
no decimal digits are admitted.
(Further details http://hsqldb.org/doc/2.0/guide/sqlgeneral-chapt.html#sgc_numeric_types[here]).

What this implies is that, when a record is inserted, a log entry similar to this one appears:

[source,java]
----
INSERT INTO ENTITY(..., IMPACT, ....) VALUES (...., 0.5, ....)
----

But when that same record is retrieved, the log will show that a value of "0" is returned, instead of 0.5.

The solution is to explicitly add the scale to the field like this:

[source,java]
----
import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(scale=2)
@Getter @Setter
private BigDecimal impact;
----

In addition, you should also set the scale of the `BigDecimal`, using `setScale(scale, roundingMode)`.

More information can be found http://www.opentaps.org/docs/index.php/How_to_Use_Java_BigDecimal:_A_Tutorial[here] and http://www.tutorialspoint.com/java/math/bigdecimal_setscale_rm_roundingmode.htm[here].

=== ``Blob``s

Apache Isis configures JDO/DataNucleus so that the properties of type `org.apache.isis.applib.value.Blob` and `org.apache.isis.applib.value.Clob` can also be persisted.

As for Joda dates, this requires the `@javax.jdo.annotations.Persistent` annotation.
However, whereas for dates one would always expect this value to be retrieved eagerly, for blobs and clobs it is not so clear cut.

For example:

[source,java]
----
import javax.jdo.annotations.Column;
import javax.jdo.annotations.Persistent;
import lombok.Getter;
import lombok.Setter;

@Persistent(defaultFetchGroup="false", columns = {
    @Column(name = "attachment_name"),
    @Column(name = "attachment_mimetype"),
    @Column(name = "attachment_bytes",
            jdbcType="BLOB", sqlType = "LONGVARBINARY")
})
@Property(optionality = Optionality.OPTIONAL)
@Getter @Setter
private Blob attachment;
----

The three `@Column` annotations are required because the mapping classes that Apache Isis provides (https://github.com/apache/isis/blob/isis-1.4.0/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/valuetypes/IsisBlobMapping.java#L59[IsisBlobMapping] and https://github.com/apache/isis/blob/isis-1.4.0/component/objectstore/jdo/jdo-datanucleus/src/main/java/org/apache/isis/objectstore/jdo/datanucleus/valuetypes/IsisClobMapping.java#L59[IsisClobMapping]) map to 3 columns.
(It is not an error to omit these `@Column` annotations, but without them the names of the table columns are simply suffixed `_0`, `_1`, `_2` etc.

If the `Blob` is mandatory, then use:

[source,java]
----
import javax.jdo.annotations.Column;
import javax.jdo.annotations.Persistent;
import lombok.Getter;
import lombok.Setter;

@Persistent(defaultFetchGroup="false", columns = {
    @Column(name = "attachment_name", allowsNull="false"),
    @Column(name = "attachment_mimetype", allowsNull="false"),
    @Column(name = "attachment_bytes",
            jdbcType="BLOB", sqlType = "LONGVARBINARY", allowsNull="false")
})
@Property(optionality = Optionality.MANDATORY )
@Getter @Setter
private Blob attachment;
----

[NOTE]
====
If specifying a `sqlType` of "LONGVARBINARY" does not work, try instead "BLOB".
There can be differences in behaviour between JDBC drivers.
====

=== ``Clob``s

Mapping ``Clob``s works in a very similar way to ``Blob``s, but the `jdbcType` and `sqlType` attributes will, respectively, be `CLOB` and `LONGVARCHAR`:

[source,java]
----
import javax.jdo.annotations.Column;
import javax.jdo.annotations.Persistent;
import lombok.Getter;
import lombok.Setter;

@Persistent(defaultFetchGroup="false", columns = {
    @Column(name = "attachment_name"),
    @Column(name = "attachment_mimetype"),
    @Column(name = "attachment_chars",
            jdbcType="CLOB", sqlType = "LONGVARCHAR")
})
@Property( optionality = Optionality.OPTIONAL )
@Getter @Setter
private Clob doc;
----

[NOTE]
====
If specifying a `sqlType` of "LONGVARCHAR" does not work, try instead "CLOB".
There can be differences in behaviour between JDBC drivers.
====

=== Mapping to VARBINARY or VARCHAR

Instead of mapping to a sqlType of `LONGVARBINARY` (or perhaps `BLOB`), you might instead decide to map to a `VARBINARY`.
The difference is whether the binary data is held "on-row" or as a pointer "off-row"; with a `VARBINARY` the data is held on-row and so you will need to specify a length.

For example:

[source,java]
----
import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(
        name = "attachment_bytes",
        jdbcTypr="BLOB", sqlType = "VARBINARY", length=2048
)
@Getter @Setter
private Blob image;
----

The same argument applies to `LONGVARCHAR` (or `CLOB`); you could instead map to a regular `VARCHAR`:

[source,java]
----
import javax.jdo.annotations.Column;
import lombok.Getter;
import lombok.Setter;

@Column(
        name = "attachment_chars",
        sqlType = "VARCHAR", length=2048
)
@Getter @Setter
private Clob letter;
----

Support and maximum allowed length will vary by database vendor.




