Posts Tagged ‘Continuous Delivery’
24
Jan

If you have to deploy to several environments, like DEV, TEST, PROD etc., and you don’t want to edit the config and property files (e.g. db url connect string, data source names, logging props, etc.) over and over again, then maybe it is time :

  1. for each environment have a separate version of the config / property files.
  2. Dependent on the environment, load the right version.

For 2. you need some kind of environment detection. There are a lot of ways of achieving this:

web.xml

If you have a WebApp then you could provide a context parameter:

<context-param>
  <description>Environment name for dependent configuration/description>
  <param-name>org.bigdev.env</param-name>
  <param-value>PROD</param-value>
</context-param>

with

ServletContext servletContext = ...;
String env = servletContext.getInitParameter("org.bigdev.env");
if ("DEV".equals(env)) {
    // DEV
} else if ("TEST".equals(env)) {
    // TEST
} else if ("PROD".equals(env)) {
    // PROD
}

There you trade editing one property, instead of many in different files.

Property File or just a empty Trigger File

if ((new File("WEB-INF/env/DEV.trigger")).exists()) {
    // DEV
} else if ((new File("WEB-INF/env/TEST.trigger")).exists()) {
    // TEST
} else if ((new File("WEB-INF/env/PROD.trigger")).exists()) {
    // PROD
}

Host Name

String hostName = InetAddress.getLocalHost().getHostName();
if ("localhost".equals(hostName)) {
    // DEV
} else if ("srv007".equals(hostName)) {
    // TEST
} else if ("srv008".equals(hostName)) {
    // PROD
}

IP address or range

String ip = InetAddress.getLocalHost().getHostAddress();
if ("1.2.3.4".equals(ip)) {
    // DEV
} else if (ip.startsWith("2.3.4.")) {
    // TEST
} else if (ip.startsWith("2.3.5.")) {
    // PROD
}

Operating System

String osName = System.getProperty("os.name");
if (osName.startsWith("Windows")) {
    // DEV
} else if (osName.startsWith("Linux")) {
    // TEST
} else if (osName.startsWith("AIX")) {
    // PROD
}

System Property

String env = System.getProperty("org.bigdev.env");
if ("DEV".equals(env)) {
    // DEV
} else if ("TEST".equals(env)) {
    // TEST
} else if ("PROD".equals(env)) {
    // PROD
}

Or any other system property that identifies your env.

There are many more use cases of the knowledge of the environment other than config/property files. You can en-/disable certain behavior/features of your application dependent on the current environment. E.g. in JPA overwrite the annotation object-relational mapping via orm.xml dependent on the env, see  here.

, ,

24
Jan

Meanwhile annotations are preferrred to XML configuration in Java EE. One still can use XML to override annotations. The same is true in JPA: one configures the object-relational mapping via entity annotations. I was in the situtation to make some mappings of the JPA entities and the database tables configurable, when deploying in different environments.

In my situation I needed to switch the ID generation strategy, auto vs. sequence, in all 30+ entites back and forth (building up different database structures for different environments in the same project is a bad thing, but happens in real life…). Either I could change the annotations in each entity file, like I did a few times, or:

  1. Specify with annotations the standard behavior.
  2. Override the annotations with orm.xml.

I will give an example to my situation. In the entity I specified the sequence generation via annotations:

@Id
@SequenceGenerator(name="TABLENAME_ID_GENERATOR", sequenceName="SEQUENCENAME_IN_DB")
@GeneratedValue(strategy=GenerationType.SEQUENCE, generator="TABLENAME_ID_GENERATOR")
@Column(name="TABLE_ID")
private Long id;

If you start overwriting a property, all annotations on the given property are ignored. So you also have to specify e.g. the @Column-configuration in the xml:
So to overwrite this with strategy=GenerationType.AUTO:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings  version="1.0"
  xmlns="http://java.sun.com/xml/ns/persistence/orm"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemalocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_1_0.xsd">

  <description>Override Annotations...</description>

  <!-- Set the default to FIELD-access, if you like -->
  <persistence-unit-metadata>
    <persistence-unit-defaults>
      <access>FIELD</access>
    </persistence-unit-defaults>
  </persistence-unit-metadata>

  <entity>
    <attributes>
      <id name="id">
        <column name="TABLE_ID" unique="true" nullable="false" precision="20"/>
        <generated-value strategy="AUTO" />
      </id>
    </attributes>
  </entity>

</entity-mappings>

You are done if you use the default location for this xml, beside the persistence.xml: META-INF/orm.xml.

If you want another name or another location you can register this file in the persistence unit in the persistence.xml:
We rename the file idGenerationStrategyAUTO.xml and then have to specify the mapping-file:

<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.0"
  xmlns="http://java.sun.com/xml/ns/persistence"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemalocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd">
  <persistence-unit name="bigDevPersistenceUnit_DEV" transaction-type="RESOURCE_LOCAL">
    <provider>org.hibernate.ejb.HibernatePersistence</provider>

    <mapping-file>META-INF/idGenerationStrategyAUTO.xml</mapping-file>

    <properties>
      ...
    </properties>
  </persistence-unit>
</persistence>

Finally, in the sense of “continous delivery” you could declare different persistence units, e.g. bigDevPersistenceUnit_DEV, bigDevPersistenceUnit_PROD, and create the right entity manager factory by using the detected environment DEV, PROD, see here.

, , ,