<commit_before>= isis-module-commchannel
:_imagesdir: ./

image:https://travis-ci.org/incodehq/incode-module-commchannel.png?branch=master[Build Status,link=https://travis-ci.org/incodehq/incode-module-commchannel]

This module, intended for use with link:http://isis.apache.org[Apache Isis], provides the ability to attach
communication channel objects (postal address, email or phone/fax number) to arbitrary domain objects.  There are
_no_ requirements for those objects to implement any interfaces.  A subclass of `CommunicationChannelOwnerLink`
abstract class is required (about link:https://github.com/incodehq/incode-module-commchannel/blob/master/fixture/src/main/java/org/incode/module/commchannel/fixture/dom/CommunicationChannelOwnerLinkForDemoObject.java[50 lines of boilerplate]), acting as the "glue" between the
communication channel and its associated "owner" domain object.

For postal address, the geocoding data can be looked up by invoking the
link:https://developers.google.com/maps/documentation/geocoding[Google Geocoding] API.  This allows free-form text
entry of an address to be converted into its geocoded equivalent, including latitude and longitude.  This in turn
allows each postal address object to implement the `Location` interface (from
link:http://github.com/isisaddons/isis-wicket-gmap3[Gmap3 wicket component]) meaning that they can be rendered in a
map view.

Each communication channel object can optionally be categorized with a predefined "purpose", where the available list
of "purposes" is defined by the optional `CommunicationChannelPurposeRepository` SPI service.  The list of purposes can
vary by both the communication channel type (postal address/email/phone) and also by the "owner" domain object; for
example address objects the list could be "billing address" and "shipping address", whereas for emails the list of
purposes might be "work email" or "home email".



== Domain Model

The following http://yuml.me[] class diagram highlights the main concepts:

image::http://yuml.me/5f9a2489[link="http://yuml.me/5f9a2489"]

(The colours used in the diagram are - approximately - from link:https://en.wikipedia.org/wiki/Object_Modeling_in_Color[Object Modeling in Color]).



== Screenshots

The following screenshots show an example app's usage of the module.

We start by installing some demo fixture data:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/020-recreate-fixtures.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/020-recreate-fixtures.png"]


The fixture data sets up a number of demo objects, along with some sample ``CommunicationChannel``s associated with
each.  These are shown in the contributed `communicationChannels` collection:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/030-communication-channels-collection-mixed-in.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/030-communication-channels-collection-mixed-in.png"]


Also contributed are actions to create new communication channels of each of the three main types.  For example, we
can create a new `PostalAddress`:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/040-new-postal-address-action.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/040-new-postal-address-action.png"]

Because postal addresses can vary so much around the world, the module defines only "address lines" (rather than house
street number, street address, district and so on).  Apart from the first address line, these are all optional.



Completing the action updates the list of communication channels for the communication channel owner:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/050-communication-channels-added-to.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/050-communication-channels-added-to.png"]



Each communication channel can be viewed and updated.  The `PostalAddress` is the most complex:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/060-postal-address-detail.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/060-postal-address-detail.png"]


Specifically, the postal address allows geocoding data to be looked up from the
link:https://developers.google.com/maps/documentation/geocoding[Google Geocoding] API:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/070-lookup-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/070-lookup-geocode.png"]


The address lines information entered previously is used for the search, but this can be adjusted as necessary by the
end-user:


image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/072-lookup-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/072-lookup-geocode.png"]



The geocoding data consists of the latitude and longitude, but also of a unique place Id.  It also contains various
other more detailed information, such as the various components that make up the address.

In addition, the raw JSON from the geocoding API request can be downloaded:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/080-download-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/080-download-geocode.png"]


specifying a filename:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/090-download-geocode-specify-filename.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/090-download-geocode-specify-filename.png"]


and viewed:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/110-geocode-file.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/110-geocode-file.png"]



All communication channel have a "purpose", which can be updated:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/120-update-postal-address-purpose.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/120-update-postal-address-purpose.png"]



The list of available purposes varies by channel type, for example "Billing Address" or "Shipping Address" for
``PostalAddress``es, "Home Email" or "Work Email" for ``EmailAddress``es, etc:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/130-purpose-defined-by-spi-service.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/130-purpose-defined-by-spi-service.png"]

The list itself is not hardcoded into the module, however; instead it is defined by the
`CommunicationChannelPurposeRepository` optional SPI.  If there is no implementation of this SPI service then a default
"purpose" is used



Every communication channel also allows adhoc notes to be added:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/140-update-notes.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/140-update-notes.png"]



When creating a new `PostalAddress`, the geocode information can be looked up at the same time using the final checkbox
parameter for the action:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/160-another-postal-address-lookup-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/160-another-postal-address-lookup-geocode.png"]



The `PostalAddress` entity implements the (non-ASF) http://github.com/isisaddons/isis-wicket-gmap3[Isis addons' gmap3]
wicket extension's `Locatable` interface, meaning that it can be rendered on a map.  Assuming that the extension has
been configured on the classpath:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/170-lookup-as-map.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/170-lookup-as-map.png"]



Then the map is rendered:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/180-map-view.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/180-map-view.png"]



The module also allows new ``EmailAddress``es to be created:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/190-new-email-address.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/190-new-email-address.png"]


And similarly allows new ``PhoneOrFaxNumber``s to be created:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/200-new-phone-or-fax-number.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/200-new-phone-or-fax-number.png"]



What can be created and modified can also be deleted.  Each of the `CommunicationChannel` objects provide an action
to remove them:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/210-remove-communication-channel.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/210-remove-communication-channel.png"]



As the screenshot shows, the end-user has the option of specifying some other communication channel (of the same type)
as a "replacement".   Because each action has a corresponding domain event, this allows for cascade updates scenarios
(where other objects that depend upon the channel being deleted can instead refer to its replacement).

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/220-are-you-sure-prompt.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/220-are-you-sure-prompt.png"]


Alternatively, subscribers can opt to veto the removal/deletion of a communication channel.  This is done using the
standard technique described in the link:http://isis.apache.org/guides/ug.html#_ug_more-advanced_decoupling_event-bus[Apache Isis user guide].




== How to run the Demo App

The prerequisite software is:

* Java JDK 8
* http://maven.apache.org[maven 3] (3.2.x or later is recommended).

To build the demo app:

[source]
----
git clone https://github.com/isisaddons/isis-module-commchannel.git
mvn clean install
----

To run the demo app:

[source]
----
cd webapp
mvn jetty:run
----

Then log on using user: `sven`, password: `<PASSWORD>`




== How to configure/use

You can either use this module "out-of-the-box", or you can fork this repo and extend to your own requirements. 

=== "Out-of-the-box"

To use "out-of-the-box":

* update your classpath by adding this dependency in your dom project's `pom.xml`: +
+
[source,xml]
----
<dependency>
    <groupId>org.incode.module.commchannel</groupId>
    <artifactId>incode-module-commchannel-dom</artifactId>
    <version>1.13.0</version>
</dependency>
----


* in the `AppManifest`, update its `getModules()` method: +
+
[source,java]
----
@Override
public List<Class<?>> getModules() {
    return Arrays.asList(
            ...
            org.incode.module.commchannel.dom.CommChannelModule.class,
    );
}
----


Check for later releases by searching http://search.maven.org/#search|ga|1|incode-module-commchannel-dom[Maven Central Repo].


==== "Out-of-the-box" (-SNAPSHOT)

If you want to use the current `-SNAPSHOT`, then the steps are the same as above, except:

* when updating the classpath, specify the appropriate -SNAPSHOT version:

[source,xml]
----
<version>1.14.0-SNAPSHOT</version>
----

* add the repository definition to pick up the most recent snapshot (we use the Cloudbees continuous integration service).  We suggest defining the repository in a `<profile>`:

[source,xml]
----
<profile>
    <id>cloudbees-snapshots</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <repositories>
        <repository>
            <id>snapshots-repo<;/id>
            <url>http://repository-estatio.forge.cloudbees.com/snapshot/</url>
            <releases>
                <enabled>false>/enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
</profile>
----


=== For each domain object...

In order to be able to add/remove communication channels for a domain object, you need to:

* implement a subclass of `CommunicationChannelOwnerLink` for the domain object's type.  +
+
This link acts as a type-safe tuple linking the domain object to the `CommunicationChannel`.

* implement the `CommunicationChannelOwnerLinkRepository.SubtypeProvider` SPI interface: +
+
[source,java]
----
public interface SubtypeProvider {
    Class<? extends CommunicationChannelOwnerLink> subtypeFor(
        Class<?> domainObject,
        CommunicationChannelType communicationChannelType);
}
----
+
This tells the module which subclass of `CommunicationChannelOwnerLink` to use to attach to the domain object.  The
`SubtypeProviderAbstract` adapter can be used to remove some boilerplate.

* subclass `T_addEmailAddress`, `T_addPostalAddress`, `T_addPhoneOrFaxNumber` and `T_communicationChannels` (abstract)
mixin classes for the domain object. +
+
These contribute the "communication channels" collection and actions to add communication channels of the various types.

Typically the SPI implementations and the mixin classes are nested static classes of the `CommunicationChannelOwnerLink`
subtype.


For example, in the demo app the `CommChannelDemoObject` domain object can "own" communication channels by virtue of
the `CommunicationChannelOwnerLinkForDemoObject` subclass:

[source,java]
----
@javax.jdo.annotations.PersistenceCapable(identityType= IdentityType.DATASTORE, schema="incodeCommChannelDemo")
@javax.jdo.annotations.Inheritance(strategy = InheritanceStrategy.NEW_TABLE)
@DomainObject
public class CommunicationChannelOwnerLinkForDemoObject extends CommunicationChannelOwnerLink { // <1>

    private CommChannelDemoObject demoObject;
    @Column( allowsNull = "false", name = "demoObjectId")
    public CommChannelDemoObject getDemoObject() {                                              // <2>
        return demoObject;
    }
    public void setDemoObject(final CommChannelDemoObject demoObject) {
        this.demoObject = demoObject;
    }

    public Object getOwner() {                                                                  // <3>
        return getDemoObject();
    }
    protected void setOwner(final Object object) {
        setDemoObject((CommChannelDemoObject) object);
    }

    @DomainService(nature = NatureOfService.DOMAIN)
    public static class SubtypeProvider
            extends CommunicationChannelOwnerLinkRepository.SubtypeProviderAbstract {           // <4>
        public SubtypeProvider() {
            super(CommChannelDemoObject.class, CommunicationChannelOwnerLinkForDemoObject.class);
        }
    }

    @Mixin
    public static class _communicationChannels                                                  // <5>
            extends T_communicationChannels<CommChannelDemoObject> {
        public _communicationChannels(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
    @Mixin
    public static class _addEmailAddress extends T_addEmailAddress<CommChannelDemoObject> {
        public _addEmailAddress(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
    @Mixin
    public static class _addPhoneOrFaxNumber extends T_addPhoneOrFaxNumber<CommChannelDemoObject> {
        public _addPhoneOrFaxNumber(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
    @Mixin
    public static class _addPostalAddress extends T_addPostalAddress<CommChannelDemoObject> {
        public _addPostalAddress(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
}
----
<1> extend from `CommunicationChannelOwnerLink`
<2> the type-safe reference property to the "owning" domain object (in this case `DemoObject`).  In the RDBMS
this will correspond to a regular foreign key with referential integrity constraints correctly applied.
<3> implement the hook `setOwner(...)` method to allow the type-safe reference property to the "owner" (in
this case `DemoObject`) to be set.  Also implemented `getOwner()` similarly
<4> implementation of the `SubtypeProvider` SPI domain service, telling the module which subclass of
`CommunicationChannelOwnerLink` to instantiate to attach to the owning domain object
<5> mixins for the collections and actions contributed to the owning domain object



=== SPI

The `CommunicationChannelPurposeRepository` interface can optionally be implemented to specify the available "purpose"
for each `CommunicationChannel` domain object.

For example, in the demo app this is implemented as:

[source,java]
----
@DomainService(nature = NatureOfService.DOMAIN)
public class CommunicationChannelPurposeRepositoryForDemo implements CommunicationChannelPurposeRepository {
    @Override
    public Collection<String> purposesFor(
            final CommunicationChannelType communicationChannelType,
            final Object owner) {
        switch (communicationChannelType) {
            case EMAIL_ADDRESS:
                return Arrays.asList("Home Email", "Work Email", "Other Email");
            case POSTAL_ADDRESS:
                return Arrays.asList("Billing Address", "Shipping Address");
            case PHONE_NUMBER:
                return Arrays.asList("Home Number", "Work Number", "Mobile Number");
            case FAX_NUMBER:
                return Arrays.asList("Home Fax", "Work Fax");
        }
        return null;
    }
}
----

If no implementation of this interface can be found, then the module provides a single "default" purpose for all
communication channels.


== UI Concerns

=== Suppressing/adding UI elements

Every property, collection and action has a corresponding domain event.  Thus, a subscriber can be used to hide or
disable UI representation of any domain object's members.

For example, the "notes" property could be suppressed using the following service:

[source,java]
----
@DomainService(nature = NatureOfService.DOMAIN)
public class CommChannelDemoSuppressNotesSubscriber extends AbstractSubscriber {
    @Subscribe
    public void on(CommunicationChannel.NotesDomainEvent ev) {
        switch (ev.getEventPhase()) {
        case HIDE:
            // uncomment as an example of how to influence the UI
            // (the 'note' property should disappear)
            // ev.hide();
        }
    }
}
----

Conversely, new UI elements can be added using
link:http://isis.apache.org/guides/ug.html#_ug_how-tos_contributed-members[contributions] and mixins.


=== Link class

The `CommunicationChannelOwnerLink` object is not intended to be rendered directly in the UI.  Rather, the
`T_communicationChannels` mixin renders the referenced ``CommunicationChannel``s instead.

Nevertheless (just in case there is a requirement to render the link object), the `CommunicationChannelOwnerLink` allows
its title, icon and CSS class to be specified using subscribers to UI event classes specific to the link class.



== Other Services

The module provides the following domain services for querying notes:

* `CommunicationChannelRepository` +
+
To search for ``CommunicationChannel``s by owner and type.

* `CommunicationChannelOwnerLinkRepository` +
+
To search for `CommunicationChannelOwnerLink``s, ie the tuple that links a `CommunicationChannel` to an
arbitrary `CommunicationChannelOwner`.  This repository is likely to be less useful than `CommunicationChannelRepository`,
but is crucial to the internal workings of the `incode-module-commchannel` module.





== Related Modules/Services

The module implements the http://github.com/isisaddons/isis-wicket-gmap3[Isis addons' gmap3]'s
 `LocationDereferencingService` SPI, so that clicking on a marker on a map will render the "owning" domain object,
 rather than the details of the `CommunicationChannel` itself.



== Known issues

None at this time.


== Change Log

* `1.13.0` - released against Isis 1.13.0; removed the `CommunicationChannelOwner` interface and changed
implementation to remove dependency on the http://github.com/isisaddons/isis-module-poly[Isis addons' poly] module;
also uses `.layout.xml` for layouts
* `1.12.0` - released against Isis 1.12.0
* `1.11.0` - released against Isis 1.11.0
* `1.10.0` - released against Isis 1.10.0




=== Forking the repo

If instead you want to extend this module's functionality, then we recommend that you fork this repo.  The repo is
structured as follows:

* `pom.xml   ` - parent pom
* `app`        - the demo webapp's `AppManifest`
* `dom       ` - the module implementation, depends on Isis applib
* `fixture   ` - fixtures, holding a sample domain objects and fixture scripts; depends on `dom`
* `integtests` - integration tests for the module; depends on `fixture`
* `webapp    ` - demo webapp (see above screenshots); depends on `dom` and `fixture`

Only the `dom` project is released to Maven Central Repo.  The versions of the other modules are purposely left at
`0.0.1-SNAPSHOT` because they are not intended to be released.

Note that the module uses link:https://projectlombok.org/[Project Lombok].  To compile the code within your IDE you will
therefore require the appropriate Lombok plugin.  See the link:https://projectlombok.org/download.html[Lombok download page] for more information.


== Legal Stuff

=== License

[source]
----
Copyright 2015~2016 <NAME>

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.
----

=== Dependencies

There are no third-party dependencies.

==  Maven deploy notes

Only the `dom` module is deployed, and is done so using Sonatype's OSS support (see
http://central.sonatype.org/pages/apache-maven.html[user guide]).

=== Release to Sonatype's Snapshot Repo

To deploy a snapshot, use:

[source]
----
pushd dom
mvn clean deploy
popd
----

The artifacts should be available in Sonatype's
https://oss.sonatype.org/content/repositories/snapshots[Snapshot Repo].



=== Release to Maven Central

The `release.sh` script automates the release process. It performs the following:

* performs a sanity check (`mvn clean install -o`) that everything builds ok
* bumps the `pom.xml` to a specified release version, and tag
* performs a double check (`mvn clean install -o`) that everything still builds ok
* releases the code using `mvn clean deploy`
* bumps the `pom.xml` to a specified release version

For example:

[source]
----
sh release.sh 1.14.0 \
              1.15.0-SNAPSHOT \
              <EMAIL> \
              "this is not really my passphrase"
----

where
* `$1` is the release version
* `$2` is the snapshot version
* `$3` is the email of the secret key (`~/.gnupg/secring.gpg`) to use for signing
* `$4` is the corresponding passphrase for that secret key.

Other ways of specifying the key and passphrase are available, see the `pgp-maven-plugin`'s<commit_msg>updates README with words on interim build process<commit_after>= isis-module-commchannel
:_imagesdir: ./

image:https://travis-ci.org/incodehq/incode-module-commchannel.png?branch=master[Build Status,link=https://travis-ci.org/incodehq/incode-module-commchannel]

This module, intended for use with link:http://isis.apache.org[Apache Isis], provides the ability to attach
communication channel objects (postal address, email or phone/fax number) to arbitrary domain objects.  There are
_no_ requirements for those objects to implement any interfaces.  A subclass of `CommunicationChannelOwnerLink`
abstract class is required (about link:https://github.com/incodehq/incode-module-commchannel/blob/master/fixture/src/main/java/org/incode/module/commchannel/fixture/dom/CommunicationChannelOwnerLinkForDemoObject.java[50 lines of boilerplate]), acting as the "glue" between the
communication channel and its associated "owner" domain object.

For postal address, the geocoding data can be looked up by invoking the
link:https://developers.google.com/maps/documentation/geocoding[Google Geocoding] API.  This allows free-form text
entry of an address to be converted into its geocoded equivalent, including latitude and longitude.  This in turn
allows each postal address object to implement the `Location` interface (from
link:http://github.com/isisaddons/isis-wicket-gmap3[Gmap3 wicket component]) meaning that they can be rendered in a
map view.

Each communication channel object can optionally be categorized with a predefined "purpose", where the available list
of "purposes" is defined by the optional `CommunicationChannelPurposeRepository` SPI service.  The list of purposes can
vary by both the communication channel type (postal address/email/phone) and also by the "owner" domain object; for
example address objects the list could be "billing address" and "shipping address", whereas for emails the list of
purposes might be "work email" or "home email".



== Domain Model

The following http://yuml.me[] class diagram highlights the main concepts:

image::http://yuml.me/5f9a2489[link="http://yuml.me/5f9a2489"]

(The colours used in the diagram are - approximately - from link:https://en.wikipedia.org/wiki/Object_Modeling_in_Color[Object Modeling in Color]).



== Screenshots

The following screenshots show an example app's usage of the module.

We start by installing some demo fixture data:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/020-recreate-fixtures.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/020-recreate-fixtures.png"]


The fixture data sets up a number of demo objects, along with some sample ``CommunicationChannel``s associated with
each.  These are shown in the contributed `communicationChannels` collection:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/030-communication-channels-collection-mixed-in.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/030-communication-channels-collection-mixed-in.png"]


Also contributed are actions to create new communication channels of each of the three main types.  For example, we
can create a new `PostalAddress`:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/040-new-postal-address-action.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/040-new-postal-address-action.png"]

Because postal addresses can vary so much around the world, the module defines only "address lines" (rather than house
street number, street address, district and so on).  Apart from the first address line, these are all optional.



Completing the action updates the list of communication channels for the communication channel owner:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/050-communication-channels-added-to.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/050-communication-channels-added-to.png"]



Each communication channel can be viewed and updated.  The `PostalAddress` is the most complex:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/060-postal-address-detail.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/060-postal-address-detail.png"]


Specifically, the postal address allows geocoding data to be looked up from the
link:https://developers.google.com/maps/documentation/geocoding[Google Geocoding] API:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/070-lookup-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/070-lookup-geocode.png"]


The address lines information entered previously is used for the search, but this can be adjusted as necessary by the
end-user:


image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/072-lookup-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/072-lookup-geocode.png"]



The geocoding data consists of the latitude and longitude, but also of a unique place Id.  It also contains various
other more detailed information, such as the various components that make up the address.

In addition, the raw JSON from the geocoding API request can be downloaded:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/080-download-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/080-download-geocode.png"]


specifying a filename:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/090-download-geocode-specify-filename.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/090-download-geocode-specify-filename.png"]


and viewed:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/110-geocode-file.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/110-geocode-file.png"]



All communication channel have a "purpose", which can be updated:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/120-update-postal-address-purpose.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/120-update-postal-address-purpose.png"]



The list of available purposes varies by channel type, for example "Billing Address" or "Shipping Address" for
``PostalAddress``es, "Home Email" or "Work Email" for ``EmailAddress``es, etc:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/130-purpose-defined-by-spi-service.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/130-purpose-defined-by-spi-service.png"]

The list itself is not hardcoded into the module, however; instead it is defined by the
`CommunicationChannelPurposeRepository` optional SPI.  If there is no implementation of this SPI service then a default
"purpose" is used



Every communication channel also allows adhoc notes to be added:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/140-update-notes.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/140-update-notes.png"]



When creating a new `PostalAddress`, the geocode information can be looked up at the same time using the final checkbox
parameter for the action:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/160-another-postal-address-lookup-geocode.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/160-another-postal-address-lookup-geocode.png"]



The `PostalAddress` entity implements the (non-ASF) http://github.com/isisaddons/isis-wicket-gmap3[Isis addons' gmap3]
wicket extension's `Locatable` interface, meaning that it can be rendered on a map.  Assuming that the extension has
been configured on the classpath:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/170-lookup-as-map.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/170-lookup-as-map.png"]



Then the map is rendered:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/180-map-view.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/180-map-view.png"]



The module also allows new ``EmailAddress``es to be created:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/190-new-email-address.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/190-new-email-address.png"]


And similarly allows new ``PhoneOrFaxNumber``s to be created:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/200-new-phone-or-fax-number.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/200-new-phone-or-fax-number.png"]



What can be created and modified can also be deleted.  Each of the `CommunicationChannel` objects provide an action
to remove them:

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/210-remove-communication-channel.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/210-remove-communication-channel.png"]



As the screenshot shows, the end-user has the option of specifying some other communication channel (of the same type)
as a "replacement".   Because each action has a corresponding domain event, this allows for cascade updates scenarios
(where other objects that depend upon the channel being deleted can instead refer to its replacement).

image::https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/220-are-you-sure-prompt.png[link="https://raw.githubusercontent.com/incodehq/incode-module-commchannel/master/images/220-are-you-sure-prompt.png"]


Alternatively, subscribers can opt to veto the removal/deletion of a communication channel.  This is done using the
standard technique described in the link:http://isis.apache.org/guides/ug.html#_ug_more-advanced_decoupling_event-bus[Apache Isis user guide].




== How to run the Demo App

The prerequisite software is:

* Java JDK 8
* http://maven.apache.org[maven 3] (3.2.x or later is recommended).

To build the demo app:

[source]
----
git clone https://github.com/isisaddons/isis-module-commchannel.git
mvn clean install
----

To run the demo app:

[source]
----
cd webapp
mvn jetty:run
----

Then log on using user: `sven`, password: `<PASSWORD>`




== How to configure/use

You can either use this module "out-of-the-box", or you can fork this repo and extend to your own requirements. 

=== "Out-of-the-box"

To use "out-of-the-box":

* update your classpath by adding this dependency in your dom project's `pom.xml`: +
+
[source,xml]
----
<dependency>
    <groupId>org.incode.module.commchannel</groupId>
    <artifactId>incode-module-commchannel-dom</artifactId>
    <version>1.13.0</version>
</dependency>
----


* in the `AppManifest`, update its `getModules()` method: +
+
[source,java]
----
@Override
public List<Class<?>> getModules() {
    return Arrays.asList(
            ...
            org.incode.module.commchannel.dom.CommChannelModule.class,
    );
}
----


Check for later releases by searching http://search.maven.org/#search|ga|1|incode-module-commchannel-dom[Maven Central Repo].


==== "Out-of-the-box" (-SNAPSHOT)

If you want to use the current `-SNAPSHOT`, then the steps are the same as above, except:

* when updating the classpath, specify the appropriate -SNAPSHOT version:

[source,xml]
----
<version>1.14.0-SNAPSHOT</version>
----

* add the repository definition to pick up the most recent snapshot (we use the Cloudbees continuous integration service).  We suggest defining the repository in a `<profile>`:

[source,xml]
----
<profile>
    <id>cloudbees-snapshots</id>
    <activation>
        <activeByDefault>true</activeByDefault>
    </activation>
    <repositories>
        <repository>
            <id>snapshots-repo<;/id>
            <url>http://repository-estatio.forge.cloudbees.com/snapshot/</url>
            <releases>
                <enabled>false>/enabled>
            </releases>
            <snapshots>
                <enabled>true</enabled>
            </snapshots>
        </repository>
    </repositories>
</profile>
----


=== For each domain object...

In order to be able to add/remove communication channels for a domain object, you need to:

* implement a subclass of `CommunicationChannelOwnerLink` for the domain object's type.  +
+
This link acts as a type-safe tuple linking the domain object to the `CommunicationChannel`.

* implement the `CommunicationChannelOwnerLinkRepository.SubtypeProvider` SPI interface: +
+
[source,java]
----
public interface SubtypeProvider {
    Class<? extends CommunicationChannelOwnerLink> subtypeFor(
        Class<?> domainObject,
        CommunicationChannelType communicationChannelType);
}
----
+
This tells the module which subclass of `CommunicationChannelOwnerLink` to use to attach to the domain object.  The
`SubtypeProviderAbstract` adapter can be used to remove some boilerplate.

* subclass `T_addEmailAddress`, `T_addPostalAddress`, `T_addPhoneOrFaxNumber` and `T_communicationChannels` (abstract)
mixin classes for the domain object. +
+
These contribute the "communication channels" collection and actions to add communication channels of the various types.

Typically the SPI implementations and the mixin classes are nested static classes of the `CommunicationChannelOwnerLink`
subtype.


For example, in the demo app the `CommChannelDemoObject` domain object can "own" communication channels by virtue of
the `CommunicationChannelOwnerLinkForDemoObject` subclass:

[source,java]
----
@javax.jdo.annotations.PersistenceCapable(identityType= IdentityType.DATASTORE, schema="incodeCommChannelDemo")
@javax.jdo.annotations.Inheritance(strategy = InheritanceStrategy.NEW_TABLE)
@DomainObject
public class CommunicationChannelOwnerLinkForDemoObject extends CommunicationChannelOwnerLink { // <1>

    private CommChannelDemoObject demoObject;
    @Column( allowsNull = "false", name = "demoObjectId")
    public CommChannelDemoObject getDemoObject() {                                              // <2>
        return demoObject;
    }
    public void setDemoObject(final CommChannelDemoObject demoObject) {
        this.demoObject = demoObject;
    }

    public Object getOwner() {                                                                  // <3>
        return getDemoObject();
    }
    protected void setOwner(final Object object) {
        setDemoObject((CommChannelDemoObject) object);
    }

    @DomainService(nature = NatureOfService.DOMAIN)
    public static class SubtypeProvider
            extends CommunicationChannelOwnerLinkRepository.SubtypeProviderAbstract {           // <4>
        public SubtypeProvider() {
            super(CommChannelDemoObject.class, CommunicationChannelOwnerLinkForDemoObject.class);
        }
    }

    @Mixin
    public static class _communicationChannels                                                  // <5>
            extends T_communicationChannels<CommChannelDemoObject> {
        public _communicationChannels(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
    @Mixin
    public static class _addEmailAddress extends T_addEmailAddress<CommChannelDemoObject> {
        public _addEmailAddress(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
    @Mixin
    public static class _addPhoneOrFaxNumber extends T_addPhoneOrFaxNumber<CommChannelDemoObject> {
        public _addPhoneOrFaxNumber(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
    @Mixin
    public static class _addPostalAddress extends T_addPostalAddress<CommChannelDemoObject> {
        public _addPostalAddress(final CommChannelDemoObject owner) {
            super(owner);
        }
    }
}
----
<1> extend from `CommunicationChannelOwnerLink`
<2> the type-safe reference property to the "owning" domain object (in this case `DemoObject`).  In the RDBMS
this will correspond to a regular foreign key with referential integrity constraints correctly applied.
<3> implement the hook `setOwner(...)` method to allow the type-safe reference property to the "owner" (in
this case `DemoObject`) to be set.  Also implemented `getOwner()` similarly
<4> implementation of the `SubtypeProvider` SPI domain service, telling the module which subclass of
`CommunicationChannelOwnerLink` to instantiate to attach to the owning domain object
<5> mixins for the collections and actions contributed to the owning domain object



=== SPI

The `CommunicationChannelPurposeRepository` interface can optionally be implemented to specify the available "purpose"
for each `CommunicationChannel` domain object.

For example, in the demo app this is implemented as:

[source,java]
----
@DomainService(nature = NatureOfService.DOMAIN)
public class CommunicationChannelPurposeRepositoryForDemo implements CommunicationChannelPurposeRepository {
    @Override
    public Collection<String> purposesFor(
            final CommunicationChannelType communicationChannelType,
            final Object owner) {
        switch (communicationChannelType) {
            case EMAIL_ADDRESS:
                return Arrays.asList("Home Email", "Work Email", "Other Email");
            case POSTAL_ADDRESS:
                return Arrays.asList("Billing Address", "Shipping Address");
            case PHONE_NUMBER:
                return Arrays.asList("Home Number", "Work Number", "Mobile Number");
            case FAX_NUMBER:
                return Arrays.asList("Home Fax", "Work Fax");
        }
        return null;
    }
}
----

If no implementation of this interface can be found, then the module provides a single "default" purpose for all
communication channels.


== UI Concerns

=== Suppressing/adding UI elements

Every property, collection and action has a corresponding domain event.  Thus, a subscriber can be used to hide or
disable UI representation of any domain object's members.

For example, the "notes" property could be suppressed using the following service:

[source,java]
----
@DomainService(nature = NatureOfService.DOMAIN)
public class CommChannelDemoSuppressNotesSubscriber extends AbstractSubscriber {
    @Subscribe
    public void on(CommunicationChannel.NotesDomainEvent ev) {
        switch (ev.getEventPhase()) {
        case HIDE:
            // uncomment as an example of how to influence the UI
            // (the 'note' property should disappear)
            // ev.hide();
        }
    }
}
----

Conversely, new UI elements can be added using
link:http://isis.apache.org/guides/ug.html#_ug_how-tos_contributed-members[contributions] and mixins.


=== Link class

The `CommunicationChannelOwnerLink` object is not intended to be rendered directly in the UI.  Rather, the
`T_communicationChannels` mixin renders the referenced ``CommunicationChannel``s instead.

Nevertheless (just in case there is a requirement to render the link object), the `CommunicationChannelOwnerLink` allows
its title, icon and CSS class to be specified using subscribers to UI event classes specific to the link class.



== Other Services

The module provides the following domain services for querying notes:

* `CommunicationChannelRepository` +
+
To search for ``CommunicationChannel``s by owner and type.

* `CommunicationChannelOwnerLinkRepository` +
+
To search for `CommunicationChannelOwnerLink``s, ie the tuple that links a `CommunicationChannel` to an
arbitrary `CommunicationChannelOwner`.  This repository is likely to be less useful than `CommunicationChannelRepository`,
but is crucial to the internal workings of the `incode-module-commchannel` module.





== Related Modules/Services

The module implements the http://github.com/isisaddons/isis-wicket-gmap3[Isis addons' gmap3]'s
 `LocationDereferencingService` SPI, so that clicking on a marker on a map will render the "owning" domain object,
 rather than the details of the `CommunicationChannel` itself.



== Known issues

None at this time.


== Change Log

* `1.13.0` - released against Isis 1.13.0; removed the `CommunicationChannelOwner` interface and changed
implementation to remove dependency on the http://github.com/isisaddons/isis-module-poly[Isis addons' poly] module;
also uses `.layout.xml` for layouts
* `1.12.0` - released against Isis 1.12.0
* `1.11.0` - released against Isis 1.11.0
* `1.10.0` - released against Isis 1.10.0




=== Forking the repo

If instead you want to extend this module's functionality, then we recommend that you fork this repo.  The repo is
structured as follows:

* `pom.xml   ` - parent pom
* `app`        - the demo webapp's `AppManifest`
* `dom       ` - the module implementation, depends on Isis applib
* `fixture   ` - fixtures, holding a sample domain objects and fixture scripts; depends on `dom`
* `integtests` - integration tests for the module; depends on `fixture`
* `webapp    ` - demo webapp (see above screenshots); depends on `dom` and `fixture`

Only the `dom` project is released to Maven Central Repo.  The versions of the other modules are purposely left at
`0.0.1-SNAPSHOT` because they are not intended to be released.

Note that the module uses link:https://projectlombok.org/[Project Lombok].  To compile the code within your IDE you will
therefore require the appropriate Lombok plugin.  See the link:https://projectlombok.org/download.html[Lombok download page] for more information.


== Legal Stuff

=== License

[source]
----
Copyright 2015~2016 <NAME>

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.
----

=== Dependencies

There are no third-party dependencies.

==  Maven deploy notes

Only the `dom` module is deployed, and is done so using Sonatype's OSS support (see
http://central.sonatype.org/pages/apache-maven.html[user guide]).

=== Release to Sonatype's Snapshot Repo

To deploy a snapshot, use:

[source]
----
pushd dom
mvn clean deploy
popd
----

The artifacts should be available in Sonatype's
https://oss.sonatype.org/content/repositories/snapshots[Snapshot Repo].


=== Release an Interim Build

If you have commit access to this project (or a fork of your own) then you can create interim releases using the `interim-release.sh` script.

The idea is that this will - in a new branch - update the `dom/pom.xml` with a timestamped version (eg `1.13.0.20161017-0738`).
It then pushes the branch (and a tag) to the specified remote.

A CI server such as Jenkins can monitor the branches matching the wildcard `origin/interim/*` and create a build.
These artifacts can then be published to a snapshot repository.

For example:

[source]
----
sh interim-release.sh 1.14.0 origin
----

where

* `1.14.0` is the base release
* `origin` is the name of the remote to which you have permissions to write to.





=== Release to Maven Central

The `release.sh` script automates the release process. It performs the following:

* performs a sanity check (`mvn clean install -o`) that everything builds ok
* bumps the `pom.xml` to a specified release version, and tag
* performs a double check (`mvn clean install -o`) that everything still builds ok
* releases the code using `mvn clean deploy`
* bumps the `pom.xml` to a specified release version

For example:

[source]
----
sh release.sh 1.14.0 \
              1.15.0-SNAPSHOT \
              <EMAIL> \
              "this is not really my passphrase"
----

where
* `$1` is the release version
* `$2` is the snapshot version
* `$3` is the email of the secret key (`~/.gnupg/secring.gpg`) to use for signing
* `$4` is the corresponding passphrase for that secret key.

Other ways of specifying the key and passphrase are available, see the `pgp-maven-plugin`'s
{'fraction_non_alphanumeric': 0.09634033828805741, 'fraction_numerical': 0.009595079446437725, 'mean_word_length': 4.191697711548696, 'pattern_counts': {'":': 2, '<': 115, '<?xml version=': 0, '>': 119, 'https://': 100, 'lorem ipsum': 0, 'www.': 2, 'xml': 17}, 'pii_count': 1, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 28, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5845668', 'n_tokens_mistral': 14417, 'n_tokens_neox': 13547, 'n_words': 4263}