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

=== Custom distributions

As Karaf is an OSGi container, it's heavily used as as application and middleware kernel.

You may wish to construct your own Karaf distribution preconfigured to your requirements.

This custom distribution could contain:

* branding to change the Karaf console look-and-feel
* configuration files (in the etc folder) altered to your requirements
* pre-packaged artifacts in the deploy folder
* a pre-populated system repository (containing your own bundle and features descriptor)
* renamed or specific scripts in the bin folder
* system documentation files

See https://github.com/apache/karaf/tree/master/examples/karaf-maven-example/karaf-maven-example-assembly for a custom distribution example.

==== Maven assembly

The recommended way to create a Karaf server assembly is to use the karaf-assembly packaging with the karaf-maven-plugin.
This assembles a server from the maven dependencies in the project pom.  After explanation of the configuration options
we present an example.

The Karaf project effectively uses this packaging to assemble the official Karaf distributions, but due to maven
limitations we have to simulate rather than use the karaf-assembly packaging.

This packaging creates tar.gz and zip archives containing the assembled server.
They are identical except that zip archives don't unpack with appropriate unix file permissions for the scripts.

===== Maven dependencies

Maven dependencies in a karaf-assembly project can be feature repositories (classifier "features") or kar archives.
Feature repositories are installed in the internal "system" Maven structured repository.
Kar archives have their content unpacked on top of the server as well as contained feature repositories installed.

The Maven scope of a dependency determines whether its feature repository is listed in the features service
configuration file `etc/org.apache.karaf.features.cfg` `featuresRepositories` property:

* compile (default): All the features in the repository (or for a kar repositories) will be installed into the
`startup.properties`. The feature repo is not listed in the features service configuration file.
* runtime: feature installation is controlled by `<startupFeature>`, `<bootFeature>`, and `<installedFeature>` elements
in the karaf-maven-plugin configuration. The feature repo uri is listed in the features service configuration file.

===== Plugin configuration

Control how features are installed using these elements referring to features from installed feature repositories:

* `<startupFeature>foo</startupFeature>` - This will result in the feature bundles being listed in startup.properties at
the appropriate start level and the bundles being copied into the "system" internal repository.
You can use feature_name or feature_name/feature_version formats.
* `<bootFeature>bar</bootFeature>` -  This will result in the feature name added to boot-features in the features
service configuration file and all the bundles in the feature copied into the "system" internal repository.
You can use feature_name or feature_name/feature_version formats.
* `<installedFeature>baz</installedFeature>` - This will result in all the bundles in the feature being installed in
the "system" internal repository.  Therefore at runtime the feature may be installed without access to external repositories.
You can use feature_name or feature_name/feature_version formats.

You can also define the libraries shipped in your custom distribution. For instance, it could be interesting if you
want to extend your distribution with some JDBC drivers.

The plugin accepts the `<libraries/>` element where you can add `<library/>` containing the URL of the library.
For instance:

----
<libraries>
    <library>mvn:org.postgresql/postgresql/9.3-1102-jdbc41;type:=endorsed</library>
</libraries>
----

===== Minimal Distribution Example

This is the minimal assembly pom changed to use the packaging and annotated

----
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <parent>
...
    </parent>

    <groupId>org.apache.karaf</groupId>
    <artifactId>apache-karaf-minimal</artifactId>
    <version>${project.version}</version>
    <packaging>karaf-assembly</packaging>
    <name>Apache Karaf :: Assemblies :: Minimal Distribution</name>

    <dependencies>
        <dependency>
        <!-- scope is compile so all features (there is only one) are installed into startup.properties and the feature repo itself is not added in etc/org.apache.karaf.features.cfg file -->
            <groupId>org.apache.karaf.features</groupId>
            <artifactId>framework</artifactId>
            <version>${project.version}</version>
            <type>kar</type>
        </dependency>
        <dependency>
        <!-- scope is runtime so the feature repo is listed in etc/org.apache.karaf.features.cfg file, and features will installed into the system directory -->
            <groupId>org.apache.karaf.features</groupId>
            <artifactId>standard</artifactId>
            <classifier>features</classifier>
            <type>xml</type>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <!-- if you want to include resources in the distribution -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/filtered-resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
        </resources>

        <plugins>
            <!-- karaf-maven-plugin will call both assembly and archive goals -->
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>karaf-maven-plugin</artifactId>
                <extensions>true</extensions>
                <configuration>
                    <!-- no startupFeatures -->
                    <bootFeatures>
                        <feature>standard</feature>
                    </bootFeatures>
                    <!-- no installedFeatures -->
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
----

===== Custom Distribution Example

It's possible to specify feature versions using the name/version format.

For instance, to pre-install Spring 4.0.7.RELEASE_1 feature in your custom distribution, you can use the following pom.xml:

----
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

    <modelVersion>4.0.0</modelVersion>

    <groupId>my.custom</groupId>
    <artifactId>my.distribution</artifactId>
    <version>1.0</version>
    <packaging>karaf-assembly</packaging>

    <dependencies>
        <dependency>
        <!-- scope is compile so all features (there is only one) are installed into startup.properties and the feature repo itself is not added in etc/org.apache.karaf.features.cfg file -->
            <groupId>org.apache.karaf.features</groupId>
            <artifactId>framework</artifactId>
            <version>4.0.0</version>
            <type>kar</type>
        </dependency>
        <dependency>
        <!-- scope is runtime so the feature repo is listed in etc/org.apache.karaf.features.cfg file, and features will installed into the system directory if specify in the plugin configuration -->
            <groupId>org.apache.karaf.features</groupId>
            <artifactId>standard</artifactId>
            <classifier>features</classifier>
            <type>xml</type>
            <scope>runtime</scope>
        </dependency>
        <dependency>
        <!-- scope is runtime so the feature repo is listed in etc/org.apache.karaf.features.cfg file, and features will installed into the system directory if specify in the plugin configuration -->
            <groupId>org.apache.karaf.features</groupId>
            <artifactId>spring</artifactId>
            <classifier>features</classifier>
            <type>xml</type>
            <scope>runtime</scope>
        </dependency>
    </dependencies>

    <build>
        <!-- if you want to include resources in the distribution -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <filtering>false</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/filtered-resources</directory>
                <filtering>true</filtering>
                <includes>
                    <include>**/*</include>
                </includes>
            </resource>
        </resources>

        <plugins>
            <plugin>
                <groupId>org.apache.karaf.tooling</groupId>
                <artifactId>karaf-maven-plugin</artifactId>
                <version>4.0.0</version>
                <extensions>true</extensions>
                <configuration>
                    <!-- no startupFeatures -->
                    <bootFeatures>
                      <feature>minimal</feature>
                    </bootFeatures>
                    <installedFeatures>
                        <feature>wrapper</feature>
                        <feature>spring/4.0.7.RELEASE_1</feature>
                    </installedFeatures>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
----

==== (deprecated old style) Maven assembly

Basically a Karaf custom distribution involves:

. Uncompressing a standard Karaf distribution in a given directory.
. Populating the system repo with your features.
. Populating the lib directory with your branding or other system bundle jar files.
. Overriding the configuration files in the etc folder.

These tasks could be performed using scripting, or more easily and portable, using Apache Maven and a set of Maven plugins.

For instance, the Maven POM could look like:

----
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <groupId>my.company</groupId>
  <artifactId>mycustom-karaf</artifactId>
  <version>1.0</version>
  <packaging>pom</packaging>
  <name>My Unix Custom Karaf Distribution</name>

  <properties>
    <karaf.version>${project.version}</karaf.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.apache.karaf</groupId>
      <artifactId>apache-karaf</artifactId>
      <version>${karaf.version}</version>
      <type>tar.gz</type>
    </dependency>
    <dependency>
      <groupId>org.apache.karaf</groupId>
      <artifactId>apache-karaf</artifactId>
      <version>${karaf.version}</version>
      <type>xml</type>
      <classifier>features</classifier>
    </dependency>
  </dependencies>

  <build>
    <resources>
      <resource>
        <directory>${project.basedir}/src/main/filtered-resources</directory>
        <filtering>true</filtering>
        <includes>
          <include>**/*</include>
        </includes>
      </resource>
    </resources>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-resources-plugin</artifactId>
        <executions>
          <execution>
            <id>filter</id>
            <phase>generate-resources</phase>
            <goals>
              <goal>resources</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.karaf.tooling</groupId>
        <artifactId>karaf-maven-plugin</artifactId>
        <version>${karaf.version}</version>
        <executions>
          <execution>
           <id>add-features-to-repo</id>
           <phase>generate-resources</phase>
           <goals>
             <goal>features-add-to-repo</goal>
           </goals>
           <configuration>
              <descriptors>
                <descriptor>mvn:org.apache.karaf/apache-karaf/${karaf.version}/xml/features</descriptor>
                <descriptor>file:${project.basedir}/target/classes/my-features.xml</descriptor>
              </descriptors>
              <features>
                <feature>my-feature</feature>
              </features>
           </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-dependency-plugin</artifactId>
        <executions>
          <execution>
            <id>copy</id>
            <phase>generate-resources</phase>
            <goals>
              <goal>copy</goal>
            </goals>
            <configuration>
               <!-- Define here the artifacts which should be considered in the assembly -->
               <!-- For instance, the branding jar -->
               <artifactItems>
                 <artifactItem>
                    <groupId>my.groupId</groupId>
                    <artifactId>my.branding.id</artifactId>
                    <version>1.0</version>
                    <outputDirectory>target/dependencies</outputDirectory>
                    <destFileName>mybranding.jar</destFileName>
                 </artifactItem>
               </artifactItems>
            </configuration>
          </execution>
          <execution>
            <!-- Uncompress the standard Karaf distribution -->
            <id>unpack</id>
            <phase>generate-resources</phase>
            <goals>
              <goal>unpack</goal>
            </goals>
            <configuration>
              <artifactItems>
                <artifactItem>
                  <groupId>org.apache.karaf</groupId>
                  <artifactId>apache-karaf</artifactId>
                  <type>tar.gz</type>
                  <outputDirectory>target/dependencies</outputDirectory>
                </artifactItem>
              </artifactItems>
            </configuration>
          </execution>
        </executions>
      </plugin>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-assembly-plugin</artifactId>
        <executions>
          <execution>
            <id>bin</id>
            <phase>package</phase>
            <goals>
              <goal>single</goal>
            </goals>
            <configuration>
              <descriptors>
                <descriptor>src/main/descriptors/bin.xml</descriptor>
              </descriptors>
              <appendAssemblyId>false</appendAssemblyId>
              <tarLongFileMode>gnu</tarLongFileMode>
            </configuration>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>
----

The Maven POM will download the Karaf standard distribution and prepare resources to be processed by the Maven assembly plugin.

Your Maven project structure should look like:

* pom.xml: the previous POM file
* src/main/descriptors/bin.xml: the assembly Maven plugin descriptor (see below)
* src/main/filtered-resources: contains all resource files that have Maven property values to be filtered/replaced. Typically, this will include features descriptor and configuration files.
* src/main/distribution: contains all raw files which will be copied as-is into your custom distribution.

For instance, `src/main/filtered-resources` could contain:

* `my-features.xml` where Maven properties will be replaced
* `etc/org.apache.karaf.features.cfg` file containing your my-features descriptor:

----
#
# Comma separated list of features repositories to register by default
#
featuresRepositories=mvn:org.apache.karaf/apache-karaf/${karaf.version}/xml/features,mvn:my.groupId/my-features/${project.version}/xml/features

#
# Comma separated list of features to install at startup
#
featuresBoot=config,ssh,management,my-feature
----

The `src/main/distribution` contains all your custom Karaf configuration files and script, as, for examples:

* etc/org.ops4j.pax.logging.cfg

----
# Root logger
log4j.rootLogger=INFO, out, osgi:VmLogAppender
log4j.throwableRenderer=org.apache.log4j.OsgiThrowableRenderer

# CONSOLE appender not used by default
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} | %-5.5p | %-16.16t | %-32.32C %4L | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n

# File appender
log4j.appender.out=org.apache.log4j.RollingFileAppender
log4j.appender.out.layout=org.apache.log4j.PatternLayout
log4j.appender.out.layout.ConversionPattern=%d{ABSOLUTE} | %-5.5p | %-16.16t | %-32.32C %4L | %X{bundle.id} - %X{bundle.name} - %X{bundle.version} | %m%n
log4j.appender.out.file=${karaf.home}/log/my-customer-distribution.log
log4j.appender.out.append=true
log4j.appender.out.maxFileSize=1MB
log4j.appender.out.maxBackupIndex=10

# Sift appender
log4j.appender.sift=org.apache.log4j.sift.MDCSiftingAppender
log4j.appender.sift.key=bundle.name
log4j.appender.sift.default=my-custom
log4j.appender.sift.appender=org.apache.log4j.FileAppender
log4j.appender.sift.appender.layout=org.apache.log4j.PatternLayout
log4j.appender.sift.appender.layout.ConversionPattern=%d{ABSOLUTE} | %-5.5p | %-16.16t | %-32.32c{1} | %-32.32C %4L | %m%n
log4j.appender.sift.appender.file=${karaf.log}/$\\{bundle.name\\}.log
log4j.appender.sift.appender.append=true
----

* etc/system.properties

----
#
# The properties defined in this file will be made available through system
# properties at the very beginning of the FAS boot process.
#

# Log level when the pax-logging service is not available
# This level will only be used while the pax-logging service bundle
# is not fully available.
# To change log levels, please refer to the org.ops4j.pax.logging.cfg file
# instead.
org.ops4j.pax.logging.DefaultServiceLog.level=ERROR

#
# Name of this custom instance.
#
karaf.name=my-custom

#
# Default repository where bundles will be loaded from before using
# other Maven repositories. For the full Maven configuration, see the
# org.ops4j.pax.url.mvn.cfg file.
#
karaf.default.repository=system

#
# Location of a shell script that will be run when starting a shell
# session. This script can be used to create aliases and define
# additional commands.
#
karaf.shell.init.script=${karaf.home}/etc/shell.init.script

#
# Set this empty property to avoid errors when validating xml documents.
#
xml.catalog.files=

#
# Suppress the bell in the console when hitting backspace to many times
# for example
#
jline.nobell=true

#
# Default port for the OSGi HTTP Service
#
org.osgi.service.http.port=8181

#
# Allow usage of ${custom.home} as an alias for ${karaf.home}
#
custom.home=${karaf.home}
----
* etc/users.properties
----
admin=admin,admin
----
* You can add a `etc/custom.properties`, it's a placeholder for any override you may need. For instance:
----
karaf.systemBundlesStartLevel=50
obr.repository.url=http://svn.apache.org/repos/asf/servicemix/smx4/obr-repo/repository.xml
org.osgi.framework.system.packages.extra = \
  org.apache.karaf.branding; \
  com.sun.org.apache.xalan.internal.xsltc.trax; \
  com.sun.org.apache.xerces.internal.dom; \
  com.sun.org.apache.xerces.internal.jaxp; \
  com.sun.org.apache.xerces.internal.xni; \
  com.sun.jndi.ldap
----

Now, we can "assemble" our custom distribution using the Maven assembly plugin. The Maven assembly plugin uses an
assembly descriptor, configured in POM above to be `src/main/descriptors/bin.xml`:

----
<assembly>

    <id>bin</id>

    <formats>
        <format>tar.gz</format>
    </formats>

    <fileSets>

        <!-- Expanded Karaf Standard Distribution -->
        <fileSet>
            <directory>target/dependencies/apache-karaf-${karaf.version}</directory>
            <outputDirectory>/</outputDirectory>
            <excludes>
                <exclude>bin/**</exclude>
                <exclude>etc/system.properties</exclude>
                <exclude>etc/users.properties</exclude>
                <exclude>etc/org.apache.karaf.features.cfg</exclude>
                <exclude>etc/org.ops4j.pax.logging.cfg</exclude>
                <exclude>LICENSE</exclude>
                <exclude>NOTICE</exclude>
                <exclude>README.md</exclude>
                <exclude>RELEASE-NOTES.md</exclude>
                <exclude>karaf-manual*.html</exclude>
                <exclude>karaf-manual*.pdf</exclude>
            </excludes>
        </fileSet>

        <!-- Copy over bin/* separately to get the correct file mode -->
        <fileSet>
            <directory>target/dependencies/apache-karaf-${karaf.version}</directory>
            <outputDirectory>/</outputDirectory>
            <includes>
                <include>bin/admin</include>
                <include>bin/karaf</include>
                <include>bin/start</include>
                <include>bin/stop</include>
            </includes>
            <fileMode>0755</fileMode>
        </fileSet>

        <!-- Copy over jar files -->
        <fileSet>
            <directory>target/dependencies</directory>
            <includes>
                <include>my-custom.jar</include>
            </includes>
            <outputDirectory>/lib/</outputDirectory>
        </fileSet>

        <fileSet>
            <directory>src/main/distribution</directory>
            <outputDirectory>/</outputDirectory>
            <fileMode>0644</fileMode>
        </fileSet>
        <fileSet>
            <directory>target/classes/etc</directory>
            <outputDirectory>/etc/</outputDirectory>
            <lineEnding>unix</lineEnding>
            <fileMode>0644</fileMode>
        </fileSet>

        <fileSet>
            <directory>target/features-repo</directory>
            <outputDirectory>/system</outputDirectory>
        </fileSet>

    </fileSets>

    <files>
        <file>
            <source>${basedir}/target/dependencies/apache-karaf-${karaf.version}/bin/karaf</source>
            <outputDirectory>/bin/</outputDirectory>
            <destName>my-custom</destName>
            <fileMode>0755</fileMode>
            <lineEnding>unix</lineEnding>
        </file>
        <file>
            <source>${basedir}/target/classes/features.xml</source>
            <outputDirectory>/system/my.groupid/my-features/${project.version}</outputDirectory>
            <destName>my-features-${project.version}-features.xml</destName>
            <fileMode>0644</fileMode>
            <lineEnding>unix</lineEnding>
        </file>
    </files>

</assembly>
----

To build your custom Karaf distribution, just run:

----
mvn install
----

You will find your Karaf custom distribution tar.gz in the target directory.
