text:
title: How to Publish a Grails Plugin to the Maven Central Repository
date:  April 07, 2021
description: In this post, we will outline how to publish Grails plugins to Maven Central.
author: Puneet Behl
image: grails-blog-index-6.png
CSS: [%url]/stylesheets/prism.css
JAVASCRIPT: [%url]/javascripts/prism.js
---

# [%title]

[%author]

[%date]

Tags: #bintray #plugins

Beginning May 1, 2021, you will [no longer be able to publish your Grails plugins/artifacts to JFrog Bintray](https://grails.org/blog/2021-02-12-alerts-regarding-bintray-sunset.html). We have already copied most of the Grails plugins from JFrog Bintray to the [Grails Artifactory](https://repo.grails.org), which allows us to resolve those artifacts. Going forward, we recommend that you start publishing artifacts to Maven Central.

In this post, we outline how to publish Grails plugins to Maven Central. For detailed information, we also suggest checking [Sonatype OSSRH Guide](https://central.sonatype.org/publish/publish-guide/).

The Sonatype's OSSRH service is the primary place to publish plugins to the Central repository. In order to use OSSRH, you need to take care of a few prerequisites and follow some basic steps.

## Prerequisites

1. ### Choose your coordinates

    When publishing Grails plugins to Bintray, it was possible to publish under the same groupId (org.grails.plugins).

    To publish to Sonatype, the groupID needs to be changed.

    1. If your plugins are hosted on GitHub, you could use "io.github.yourusername", "io.github.yourorganization", etc. For example: "io.github.puneetbehl", where "puneetbehl" is my GitHub username.
    2. If you own or manage a domain then you could use groupId starting with the reverse domain name.

   Please note that there is a Sonatype validation process to verify that you own the GitHub username or control the domain. See [Individual Projects - Open Source Software Repository Hosting (OSSRH)](https://central.sonatype.org/publish/#individual-projects-open-source-software-repository-hosting-ossrh) for more information.

2. ### Sign up and provide project details

   Once you identify a groupId, claim your namespace on the central repository.

   1. Sign up on [Sonatype JIRA](https://issues.sonatype.org/secure/Signup!default.jspa).
   2. Provide project details by creating a new project ticket.
      1. Create a new Issue in the "Community Support - Open Source Project Repository Hosting" project.
      2. The issue type should be "New Project".
      3. On the Project Details page, enter basic project details. See the screenshot below for an example:
         ![](2021-04-07-img01.png)
         **NOTE:** Once the ticket is resolved, you should be good to deploy your artifacts.

3. ### Make sure your project meets the minimum requirements
   When publishing to the central repository, your project must meet a basic set of metadata and content requirements. Even though metadata will be generated with Gradle plugins, it is useful to understand it.

   1. #### Javadoc and Sources
      Verify that your plugin supplies javadoc and sources. For example, if your plugin name is myplugin then it should also package "myplugin-1.0.0-sources.jar" and "myplugin-1.0.0-javadoc.jar".

   2. #### Sign Files with GnuPG/PGP
      All files must be signed with GnuPG/PGP, and an ".asc" file containing the signature must be included for each file. For example, when you deploy, the following files must be included:
       - myplugin-1.0.0.pom
       - myplugin-1.0.0.jar
       - myplugin-1.0.0-sources.jar
       - myplugin-1.0.0-javadoc.jar
       - myplugin-1.0.0.pom.asc
       - myplugin-1.0.0.jar.asc
       - myplugin-1.0.0-sources.jar.asc
       - myplugin-1.0.0-javadoc.jar.asc

   3. #### Metadata
      Ensure that the POM file contains the following metadata:
      - Project coordinates - groupId, artifactId, version
      - Project name, description, and URL
      - Declare license
      - Developer information
      - SCM information

      You can refer to the [Sonatype Requirements](https://central.sonatype.org/publish/requirements/) page for more information.

   4. #### Install GnuPG, and Distribute Your Public Key
      One of the requirements for publishing to the central repository is to sign your artifacts using GnuPG. To install it, please read the instructions [here](https://central.sonatype.org/publish/requirements/gpg/).

      After generating the GnuPG key, distribute your public key to the key server so others can use it to verify your files. See [Distributing Your Public Key Section](https://central.sonatype.org/publish/requirements/gpg/#distributing-your-public-key).

      You may receive an error from Sonatype stating that the signature could not be verified.  The error message will list the key servers that it is checking. You may need to distribute your key to another server (for example: http://keyserver.ubuntu.com:11371) in order for Sonatype to validate the artifacts.

## Publishing

In order to deploy your plugin artifacts to OSSRH, you must meet the requirements for metadata in the POM and sign the artifacts. But first, let's remove the previous Grails plugin, which was used to upload artifacts to JFrog Bintray.

The previous Grails plugin is configured with "grails-plugin-publish", which uses a Gradle Bintray plugin to upload artifacts.

Delete the following lines from your project build.gradle file to remove the plugin:

```groovy
apply plugin:"org.grails.grails-plugin-publish"

grailsPublish {
   user = 'user'
   key = 'key'
   githubSlug = 'foo/bar'
   license {
       name = 'Apache-2.0'
   }
   title = "My Plugin"
   desc = "Full plugin description"
   developers = [johndoe:"John Doe"]
}
```

1. ### Generating Metadata
   Use the Gradle [maven-publish](https://docs.gradle.org/current/userguide/publishing_maven.html) plugin to generate the necessary metadata, and deploy artifacts to the repository.

   1. Apply the Maven Publish plugin

      ```groovy
      apply plugin: 'maven-publish'
      ```

   2. Customize Metadata - The following sample shows how to customize the metadata for a Grails plugin:

      ```groovy
      group 'io.github.puneetbehl'
      Version '1.0.0'

      publishing {
         publications {
             maven(MavenPublication) {
                 groupId = project.group
                 artifactId = 'my-plugin'
                 version = project.version

                 from components.java
                 artifact sourcesJar
                 artifact javadocJar

                 pom {
                     name = 'My Grails Plugin'
                     description = 'A concise description of my library'
                     url = 'https://github.com/puneetbehl/myplugin'
                     licenses {
                         license {
                             name = 'The Apache License, Version 2.0'
                             url = 'https://www.apache.org/licenses/LICENSE-2.0.txt'
                         }
                     }
                     developers {
                         developer {
                             id = 'puneetbehl'
                             name = 'Puneet Behl'
                             email = 'behlp@objectcomputing.com'
                         }
                     }
                     scm {
                         connection = 'scm:git:git://github.com/puneetbehl/myplugin.git'
                         developerConnection = 'scm:git:ssh://github.com:puneetbehl/myplugin.git'
                         url = 'https://github.com/puneetbehl/myplugin/tree/main'
                     }
                 }
             }
         }
      }
      ```

2. ### Signing Artifacts
   Use the Gradle [Signing](https://docs.gradle.org/5.0/userguide/signing_plugin.html) plugin to sign the artifacts, including metadata and pom.

   1. Apply the Signing Plugin

      ```groovy
      apply plugin: 'signing'
      ```

   2. Provide GnuPG key Information in your build.gradle file.

      ```groovy
      ext."signing.keyId" = project.hasProperty("signing.keyId") ? project.getProperty('signing.keyId') : System.getenv('SIGNING_KEY_ID')
      ext."signing.password" = project.hasProperty("signing.password") ? project.getProperty('signing.password') : System.getenv('SIGNING_PASSPHRASE')
      ext."signing.secretKeyRingFile" = project.hasProperty("signing.secretKeyRingFile") ? project.getProperty('signing.secretKeyRingFile') : (System.getenv('SIGNING_PASSPHRASE')?:"${System.properties['user.home']}${File.separator}.gnupg${File.separator}secring.gpg")
      ```

   3. Get Signatory Credentials
      1. Get the public key ID (The last 8 symbols using `gpg -K` command).
      2. Export the keys with command `gpg --keyring secring.gpg --export-secret-keys > ~/.gnupg/secring.gpg`.
      3. Know the Passphrase used to protect private keys.

   4. Export the following environment variables
      1. `SIGNING_KEY_ID` - The public key ID.
      2. `SIGNING_PASSPHRASE` - The passphrase used while generating GnuPG key.
      3. `SECRING_FILE` - The path to secring.gpg file.

   5. Sign the artifacts and also prevent signing snapshots by adding required check as follows:

      ```groovy
      ext.isReleaseVersion = !version.endsWith("SNAPSHOT")

      afterEvaluate {
         signing {
             required { isReleaseVersion && gradle.taskGraph.hasTask("publish") }
             sign publishing.publications.maven
         }
      }

      tasks.withType(Sign) {
         onlyIf { isReleaseVersion }
      }
      ```

3. ### Configuring A Multi-Project Build
   In case of multi-project builds, you need to configure plugins to generate metadata and signing for each of the sub-projects. The easiest way to achieve this is:

      ```groovy
      subprojects { project ->
          // your code here
      }

      ```

4. ### Publish Your Artifacts to OSSRH, followed by release to Maven Central
   The [Gradle Nexus Publish Plugin](https://plugins.gradle.org/plugin/io.github.gradle-nexus.publish-plugin) is used to publish your artifacts to Sonatype and then to Maven Central in a fully automated way. Check the plugin [README](https://github.com/gradle-nexus/publish-plugin) for more information.

    1. Applying the plugin

         ```groovy
         buildscript {
            repositories {
                maven { url "https://repo.grails.org/grails/core" }
                maven { url "https://plugins.gradle.org/m2/" }
            }
            dependencies {
                classpath "org.grails:grails-gradle-plugin:$grailsVersion"
                classpath "com.bertramlabs.plugins:asset-pipeline-gradle:3.0.10"
                classpath "io.github.gradle-nexus:publish-plugin:1.0.0"
            }
         }

         apply plugin: 'maven-publish'
         apply plugin: 'signing'
         apply plugin: "io.github.gradle-nexus.publish-plugin"
         ```

    2. To publish to Maven Central via Sonatype OSSRH, add the Sonatype repository as follows:

         ```groovy
         nexusPublishing {
            repositories {
                sonatype {
                    def ossUser = System.getenv("SONATYPE_USERNAME") ?: project.hasProperty("sonatypeOssUsername") ? project.sonatypeOssUsername : ''
                    def ossPass = System.getenv("SONATYPE_PASSWORD") ?: project.hasProperty("sonatypeOssPassword") ? project.sonatypeOssPassword : ''
                    def ossStagingProfileId = System.getenv("SONATYPE_STAGING_PROFILE_ID") ?: project.hasProperty("sonatypeOssStagingProfileId") ? project.sonatypeOssStagingProfileId : ''
                    nexusUrl = uri("https://s01.oss.sonatype.org/service/local/")
                    snapshotRepositoryUrl = uri("https://s01.oss.sonatype.org/content/repositories/snapshots/")
                    username = ossUser
                    password = ossPass
                    stagingProfileId = ossStagingProfileId
                }
            }
         }
         ```

         Please note that in case of a multi project build, you must add the configuration from steps 1 and 2 under the root project.

    3. Retrieve the Sonatype Staging Profile ID
         1. Log in to [https://s01.oss.sonatype.org/](https://s01.oss.sonatype.org/) using your Sonatype credentials.
         2. Select "Staging Profiles" under the "Build Promotions" section in the left navigation panel.
         3. Select the Staging Profile with name as the groupId value.
         4. Copy the last part in your browser URL which is the Staging Profile ID. For example, when the browser URL is https://s01.oss.sonatype.org/#stagingProfiles;ce7c8eca12345 then profile id is "ce7c8eca12345" (the portion after semicolon).

   4. Export the following environment variables:
         1. SONATYPE_USERNAME - Your sonatype username
         2. SONATYPE_PASSWORD - Your sonatype password
         3. SONATYPE_STAGING_PROFILE_ID - The Staging Profile ID copied in the previous step

   5. Publish artifacts to the new staging repository and verify them manually.

         ```groovy
         ./gradlew publishToSonatype closeSonatypeStagingRepository
         ```

   6. After verifying, manually drop the staging repository.
         1. Log in to [https://s01.oss.sonatype.org/](https://s01.oss.sonatype.org/)
         2. Click on "Staging Repositories" which is under the Build Promotion section in the left navigation.
         3. Click the checkbox against the repository and select drop.

   7. Publish artifacts to the new staging repository and verify them manually.

        ```groovy
        ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository
        ```

## Automate Publishing Using GitHub Action

To automatically release using GitHub Workflow, add the following [.github/workflows/release.yml](https://raw.githubusercontent.com/puneetbehl/myplugin/main/.github/workflows/release.yml) workflow configuration file to your project.

When using this file, you must configure the following GitHub Secrets:

1. SONATYPE_USERNAME
2. SONATYPE_PASSWORD
3. SONATYPE_STAGING_PROFILE_ID
4. SIGNING_KEY
5. SIGNING_PASSPHRASE
6. SECRING_FILE - This is a BASE64 encoded value. Run the command `gpg --keyring ~/.gnupg/pubring.kbx --export-secret-keys > ~/.gnupg/secring.gpg && base64 ~/.gnupg/secring.gpg | pbcopy` to copy BASE64 encoded value to the clipboard.

To trigger a release, create a new release from the GitHub releases page. Here is a sample [Grails Plugin](https://github.com/puneetbehl/myplugin) that's configured to publish to Maven Central.

## Update Information on Grails Plugin Portal
The [Grails Plugin Portal](https://plugins.grails.org) uses the [grails-plugins.json](https://raw.githubusercontent.com/grails/grails-plugins-metadata/main/grails-plugins.json) metadata file to display plugin information.

The metadata is hosted at [https://github.com/grails/grails-plugins-metadata/blob/main/grails-plugins.json](https://github.com/grails/grails-plugins-metadata/blob/main/grails-plugins.json). When you publish a new plugin or release an existing plugin, you must send a pull request to this file to update the information on the portal.

1. When you release a new plugin, add the following information at the end of the JSON file:
    ```groovy
    {
        "bintrayPackage": {
            "name": "My Plugin",
            "repo": "plugins",
            "owner": "puneetbehl",
            "desc": "A concise description",
            "labels": [
                    "grails3",
                    "spring-boot",
                    "test-label"
            ],
            "licenses": [
                    "Apache-2.0"
            ],
            "issueTrackerUrl": "https://github.com/puneetbehl/myplugin/issues",
            "latestVersion": "1.1",
            "updated": "2021-03-25T04:00:40.855Z",
            "systemIds": [
                    "io.github.puneetbehl:myplugin"
            ],
            "vcsUrl": "https://github.com/puneetbehl/myplugin"
        }
        "documentationUrl": "https://puneetbehl.github.io/myplugin/",
        "mavenMetadataUrl": "https://repo1.maven.org/maven2/com/github/puneetbehl/myplugin/maven-metadata.xml",
    }
    ```

2. If you publish a new version to Maven Central, locate the entry in the JSON file and updatelatestVersion and mavenMetadataUrl. This will reflect the latest version in the plugin portal. In the future, we will use the mavenMetadataUrl field to automatically update the latest version.


text:
### [146\. LRU Cache](https://leetcode.com/problems/lru-cache/)

Difficulty: **Medium**

Design and implement a data structure for . It should support the following operations: `get` and `put`.

`get(key)` - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
`put(key, value)` - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.

The cache is initialized with a **positive** capacity.

**Follow up:**
Could you do both operations in **O(1)** time complexity?

**Example:**

```
LRUCache cache = new LRUCache( 2 /* capacity */ );

cache.put(1, 1);
cache.put(2, 2);
cache.get(1);       // returns 1
cache.put(3, 3);    // evicts key 2
cache.get(2);       // returns -1 (not found)
cache.put(4, 4);    // evicts key 1
cache.get(1);       // returns -1 (not found)
cache.get(3);       // returns 3
cache.get(4);       // returns 4
```

#### Solution

Language: **JavaScript**

```javascript
/**
 * @param {number} capacity
 */
var LRUCache = function(capacity) {
    this.capacity = capacity
    this.map = new Map()
    this.list = new LinkedList()
};

/**
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function(key) {
    if (!this.map.has(key)) return -1
    const node = this.map.get(key)
    // console.log('head', this.list.head.val)
    // console.log('move', node.val)
    this.list.moveToHead(node)
    // console.log('head', this.list.head.val)
    return node.val[1]
};

/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function(key, value) {
    if (this.map.has(key)) {
        const node = this.map.get(key)
        node.val = [key, value]
        this.list.moveToHead(node)
        return
    }

    if (this.list.length === this.capacity) {
        const node = this.list.pop()
        // console.log(node.val)
        this.map.delete(node.val[0])
    }
    const node = this.list.insert([key, value])
    this.map.set(key, node)
    // console.log(this.list.length, this.map.size)
};

class LinkedList {
    constructor() {
        this.length = 0
        this.head = null
        this.tail = null
    }

    insert(val) {
        const node = new LinkedListNode(val)
        if (this.head) {
            node.next = this.head
            this.head.prev = node
        } else {
            this.tail = node
        }
        this.head = node
        this.length += 1
        return node
    }

    moveToHead(node) {
        if (this.head === node) return
        if (this.tail === node) this.tail = node.prev
        if (node.prev) node.prev.next = node.next
        if (node.next) node.next.prev = node.prev
        node.next = this.head
        this.head.prev = node
        this.head = node
    }

    pop() {
        if (!this.tail) return
        const node = this.tail
        this.tail = this.tail.prev
        if (this.tail) this.tail.next = null
        this.length -= 1
        return node
    }
}

class LinkedListNode {
    constructor(val) {
        this.val = val
        this.prev = null
        this.next = null
    }
}

/**
 * Your LRUCache object will be instantiated and called as such:
 * var obj = new LRUCache(capacity)
 * var param_1 = obj.get(key)
 * obj.put(key,value)
 */
```

```javascript
/**
 * @param {number} capacity
 */
var LRUCache = function(capacity) {
    this.capacity = capacity
    this.map = new Map()
};

/**
 * @param {number} key
 * @return {number}
 */
LRUCache.prototype.get = function(key) {
    if (!this.map.has(key)) return -1
    const value = this.map.get(key)
    this.map.delete(key)
    this.map.set(key, value)
    return value
};

/**
 * @param {number} key
 * @param {number} value
 * @return {void}
 */
LRUCache.prototype.put = function(key, value) {
    this.map.delete(key)
    this.map.set(key, value)

    if (this.map.size > this.capacity) {
        this.map.delete(this.map.keys().next().value)
    }
};

/**
 * Your LRUCache object will be instantiated and called as such:
 * var obj = new LRUCache(capacity)
 * var param_1 = obj.get(key)
 * obj.put(key,value)
 */
```

redpajama_set_name: RedPajamaGithub


text:
SYNONYM

#### According to
The Catalogue of Life, 3rd January 2011

#### Published in
Ill. Gard. Dict. 2:201. 1885

#### Original name
null

### Remarks
null


text:
<?php

namespace App\Transformers;

use App\Category;
use Hashids;
use League\Fractal;
use League\Fractal\TransformerAbstract;
use League\Fractal\Resource\Collection;
use League\Fractal\Resource\Item;

class CategoryTransformer extends TransformerAbstract
{
    /**
     * List of resources possible to include
     *
     * @var  array
     */
    protected $availableIncludes = ['posts'];

    /**
     * List of resources to automatically include
     *
     * @var  array
     */
    protected $defaultIncludes = [];

    /**
     * Transform object into a generic array
     *
     * @var  object
     * @return array
     */
    public function transform(Category $category)
    {
        return [
            'hashid' => Hashids::encode($category->id),
            'name' => $category->name,
            'slug' => $category->slug,
            'icon' => $category->icon
        ];
    }

    public function includePosts(Category $category){
        return $this->collection($category->posts, new PostTransformer);
    }

}


text:
pelican-theme-responsive-neon
=============================

A responsive theme for the pelican static site generator lovingly rendered in neon


text:
<?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/maven-v4_0_0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <name>JSR-269 to Java DOM Bridge</name>
  <artifactId>jsr269-bridge</artifactId>
  <parent>
    <artifactId>asakusa-utils-project</artifactId>
    <groupId>com.asakusafw</groupId>
    <version>0.9.1-SNAPSHOT</version>
  </parent>

  <packaging>jar</packaging>

  <dependencies>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>java-dom</artifactId>
      <version>${project.version}</version>
    </dependency>
    <dependency>
      <groupId>${project.groupId}</groupId>
      <artifactId>jsr199-testing</artifactId>
      <version>${project.version}</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.hamcrest</groupId>
      <artifactId>hamcrest-library</artifactId>
      <scope>test</scope>
    </dependency>
  </dependencies>

</project>


text:
var Apps = require("q-io/http-apps");
var methods = require("./methods");

exports.Setup = function (chain, prefix, setup) {
    if (!setup) {
        setup = prefix;
        prefix = "/";
    }
    var choices = [];
    var named = {};
    var positional = [];
    [null].concat(methods).forEach(function (method, i) {
        var create = function () {
            var _prefix = prefix;
            var choice = {
                predicate: function (request, response) {
                    return method === null || request.method === method;
                },
                chain: chain.constructor()
            };
            Array.prototype.forEach.call(arguments, function (arg) {
                if (typeof arg === "string") {
                    choice.predicate = union(
                        choice.predicate,
                        pathPredicate(_prefix, arg)
                    );
                    _prefix = "/"
                } else if (typeof arg === "function") {
                    choice.predicate = union(choice.predicate, arg);
                }
            });
            choices.push(choice);
            return choice.chain;
        };
        named[method] = create;
        if (i < 5)
            positional[i] = create;
    });
    // Allow referring to this.any
    named.any = named[null];
    setup.apply(named, positional);
    return function (next) {
        choices.forEach(function (choice) {
            choice.app = choice.chain.end(next);
        });
        return exports.Route(choices, next);
    };
};

var union = function (a, b) {
    if (!a)
        return b;
    return function () {
        return a.apply(this, arguments) && b.apply(this, arguments);
    };
};

var pathPredicate = function (prefix, path) {
    var keys = [];
    var regex = exports.makeRegExp(prefix + path, keys);
    return function (request, response) {
        var match = regex.exec(request.pathInfo)
        if (match) {
            request.scriptName += request.pathInfo;
            request.pathInfo = "";
            var params = request.params = request.params || {};
            keys.forEach(function (key, i) {
                if (key.prefix === "...") {
                    request.pathInfo = match[i + 1];
                } else {
                    if (key.name) {
                        params[key.name] = match[i + 1] || "";
                    }
                    params[key.i] = match[i + 1] || "";
                }
            });
            return true;
        }
    };
};

exports.Route = function (choices, notFound) {
    return choices.reduceRight(function (next, choice) {
        return function (request, response) {
            if (choice.predicate(request)) {
                return choice.app(request, response);
            } else {
                return next(request, response);
            }
        };
    }, notFound || Apps.notFound);
};

var expression = /(?:(::)|(\/?)(:|\*|\.\.\.)(\w*)(\??)|([-[\]{}()*+?.\\^$|,#\s]))/g;
exports.makeRegExp = function (path, keys, insensitive) {
    var i = 0;
    var re = path.replace(expression, function ($0, colon, slash, prefix, name, optional, esc) {
        if (prefix) { // name, slash, optional
            keys.push({ prefix: prefix, name: name, i: i++, optional: !!optional})
            if (prefix === "...") {
                return (
                    "(" +
                        (slash ? "/?" : "") +
                        ".*" +
                    ")"
                );
            } else {
                return (
                    "(?:" +
                        (slash || "") + // "/" or ""
                        (prefix === "*" ? "(.*)" : "([^/]*)") +
                    ")" +
                    (optional || "") // "?" or ""
                );
            }
        } else if (colon) {
            return ":";
        } else {
            return "\\" + $0;
        }
    })
    return new RegExp('^' + re + '$', !insensitive ? '' : 'i');
};


text:
SYNONYM

#### According to
The Catalogue of Life, 3rd January 2011

#### Published in
Geneac. Lich. 185 (1872)

#### Original name
Lecidea pulchella Schaer.

### Remarks
null


text:
package liquibase;

import liquibase.changelog.*;
import liquibase.changelog.filter.*;
import liquibase.changelog.visitor.*;
import liquibase.database.Database;
import liquibase.database.DatabaseConnection;
import liquibase.database.DatabaseFactory;
import liquibase.diff.Diff;
import liquibase.exception.DatabaseException;
import liquibase.exception.LiquibaseException;
import liquibase.exception.LockException;
import liquibase.executor.Executor;
import liquibase.executor.ExecutorService;
import liquibase.executor.LoggingExecutor;
import liquibase.lockservice.DatabaseChangeLogLock;
import liquibase.lockservice.LockService;
import liquibase.logging.LogFactory;
import liquibase.logging.Logger;
import liquibase.parser.ChangeLogParserFactory;
import liquibase.resource.ResourceAccessor;
import liquibase.statement.core.UpdateStatement;
import liquibase.util.LiquibaseUtil;
import liquibase.util.StreamUtil;
import liquibase.util.StringUtils;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.Writer;
import java.text.DateFormat;
import java.util.*;

/**
 * Core Liquibase facade.
 * Although there are several ways of executing Liquibase (Ant, command line, etc.) they are all wrappers around this class.
 */
public class Liquibase {

    public static final String SHOULD_RUN_SYSTEM_PROPERTY = "liquibase.should.run";

    private String changeLogFile;
    private ResourceAccessor resourceAccessor;

    protected Database database;
    private Logger log;

    private ChangeLogParameters changeLogParameters;

    public Liquibase(String changeLogFile, ResourceAccessor resourceAccessor, DatabaseConnection conn) throws LiquibaseException {
        this(changeLogFile, resourceAccessor, DatabaseFactory.getInstance().findCorrectDatabaseImplementation(conn));
    }

    public Liquibase(String changeLogFile, ResourceAccessor resourceAccessor, Database database) throws LiquibaseException {
        log = LogFactory.getLogger();

        if (changeLogFile != null) {
            this.changeLogFile = changeLogFile.replace('\\', '/');  //convert to standard / if usign absolute path on windows
        }
        this.resourceAccessor = resourceAccessor;

        changeLogParameters = new ChangeLogParameters(database);
        setDatabase(database);

    }

    public ChangeLogParameters getChangeLogParameters() {
        return changeLogParameters;
    }

    public Database getDatabase() {
        return database;
    }

    private void setDatabase(Database database) throws DatabaseException {
        this.database=database;
        if(database!=null) //Some tests use a null database
            setDatabasePropertiesAsChangelogParameters(database);
    }

    /**
     * FileOpener to use for accessing changelog files.
     */
    public ResourceAccessor getFileOpener() {
        return resourceAccessor;
    }

    /**
     * Use this function to override the current date/time function used to insert dates into the database.
     * Especially useful when using an unsupported database.
     */
    public void setCurrentDateTimeFunction(String currentDateTimeFunction) {
        if (currentDateTimeFunction != null) {
            this.database.setCurrentDateTimeFunction(currentDateTimeFunction);
        }
    }

    public void update(String contexts) throws LiquibaseException {

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        try {
            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);

            checkDatabaseChangeLogTable(true, changeLog, contexts);

            changeLog.validate(database, contexts);
            ChangeLogIterator changeLogIterator = getStandardChangelogIterator(contexts, changeLog);

            changeLogIterator.run(new UpdateVisitor(database), database);
        } finally {
            try {
                lockService.releaseLock();
            } catch (LockException e) {
                log.severe("Could not release lock", e);
            }
        }
    }

    private ChangeLogIterator getStandardChangelogIterator(String contexts, DatabaseChangeLog changeLog) throws DatabaseException {
        return new ChangeLogIterator(changeLog,
                new ShouldRunChangeSetFilter(database),
                new ContextChangeSetFilter(contexts),
                new DbmsChangeSetFilter(database));
    }

    public void update(String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        LoggingExecutor loggingExecutor = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database);
        ExecutorService.getInstance().setExecutor(database, loggingExecutor);

        outputHeader("Update Database Script");

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {

            update(contexts);

            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        } finally {
            lockService.releaseLock();
        }

        ExecutorService.getInstance().setExecutor(database, oldTemplate);
    }

    public void update(int changesToApply, String contexts) throws LiquibaseException {

        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {

            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);

            checkDatabaseChangeLogTable(true, changeLog, contexts);
            changeLog.validate(database, contexts);

            ChangeLogIterator logIterator = new ChangeLogIterator(changeLog,
                    new ShouldRunChangeSetFilter(database),
                    new ContextChangeSetFilter(contexts),
                    new DbmsChangeSetFilter(database),
                    new CountChangeSetFilter(changesToApply));

            logIterator.run(new UpdateVisitor(database), database);
        } finally {
            lockService.releaseLock();
        }
    }

    public void update(int changesToApply, String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        LoggingExecutor loggingExecutor = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database);
        ExecutorService.getInstance().setExecutor(database, loggingExecutor);

        outputHeader("Update " + changesToApply + " Change Sets Database Script");

        update(changesToApply, contexts);

        try {
            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }

        ExecutorService.getInstance().setExecutor(database, oldTemplate);
    }

    private void outputHeader(String message) throws DatabaseException {
        Executor executor = ExecutorService.getInstance().getExecutor(database);
        executor.comment("*********************************************************************");
        executor.comment(message);
        executor.comment("*********************************************************************");
        executor.comment("Change Log: " + changeLogFile);
        executor.comment("Ran at: " + DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT).format(new Date()));
        executor.comment("Against: " + getDatabase().getConnection().getConnectionUserName() + "@" + getDatabase().getConnection().getURL());
        executor.comment("Liquibase version: " + LiquibaseUtil.getBuildVersion());
        executor.comment("*********************************************************************" + StreamUtil.getLineSeparator());
    }

    public void rollback(int changesToRollback, String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        ExecutorService.getInstance().setExecutor(database, new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database));

        outputHeader("Rollback " + changesToRollback + " Change(s) Script");

        rollback(changesToRollback, contexts);

        try {
            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }
        ExecutorService.getInstance().setExecutor(database, oldTemplate);
    }

    public void rollback(int changesToRollback, String contexts) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
            checkDatabaseChangeLogTable(false, changeLog, contexts);

            changeLog.validate(database, contexts);

            ChangeLogIterator logIterator = new ChangeLogIterator(database.getRanChangeSetList(), changeLog,
                    new AlreadyRanChangeSetFilter(database.getRanChangeSetList()),
                    new ContextChangeSetFilter(contexts),
                    new DbmsChangeSetFilter(database),
                    new CountChangeSetFilter(changesToRollback));

            logIterator.run(new RollbackVisitor(database), database);
        } finally {
            try {
                lockService.releaseLock();
            } catch (LockException e) {
                log.severe("Error releasing lock", e);
            }
        }
    }

    public void rollback(String tagToRollBackTo, String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        ExecutorService.getInstance().setExecutor(database, new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database));

        outputHeader("Rollback to '" + tagToRollBackTo + "' Script");

        rollback(tagToRollBackTo, contexts);

        try {
            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }
        ExecutorService.getInstance().setExecutor(database, oldTemplate);
    }

    public void rollback(String tagToRollBackTo, String contexts) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {

            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
            checkDatabaseChangeLogTable(false, changeLog, contexts);

            changeLog.validate(database, contexts);

            List<RanChangeSet> ranChangeSetList = database.getRanChangeSetList();
            ChangeLogIterator logIterator = new ChangeLogIterator(ranChangeSetList, changeLog,
                    new AfterTagChangeSetFilter(tagToRollBackTo, ranChangeSetList),
                    new AlreadyRanChangeSetFilter(ranChangeSetList),
                    new ContextChangeSetFilter(contexts),
                    new DbmsChangeSetFilter(database));

            logIterator.run(new RollbackVisitor(database), database);
        } finally {
            lockService.releaseLock();
        }
    }

    public void rollback(Date dateToRollBackTo, String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        ExecutorService.getInstance().setExecutor(database, new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database));

        outputHeader("Rollback to " + dateToRollBackTo + " Script");

        rollback(dateToRollBackTo, contexts);

        try {
            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }
        ExecutorService.getInstance().setExecutor(database, oldTemplate);
    }

    public void rollback(Date dateToRollBackTo, String contexts) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
            checkDatabaseChangeLogTable(false, changeLog, contexts);
            changeLog.validate(database, contexts);

            List<RanChangeSet> ranChangeSetList = database.getRanChangeSetList();
            ChangeLogIterator logIterator = new ChangeLogIterator(ranChangeSetList, changeLog,
                    new ExecutedAfterChangeSetFilter(dateToRollBackTo, ranChangeSetList),
                    new AlreadyRanChangeSetFilter(ranChangeSetList),
                    new ContextChangeSetFilter(contexts),
                    new DbmsChangeSetFilter(database));

            logIterator.run(new RollbackVisitor(database), database);
        } finally {
            lockService.releaseLock();
        }
    }

    public void changeLogSync(String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LoggingExecutor outputTemplate = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database);
        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        ExecutorService.getInstance().setExecutor(database, outputTemplate);

        outputHeader("SQL to add all changesets to database history table");

        changeLogSync(contexts);

        try {
            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }

        ExecutorService.getInstance().setExecutor(database, oldTemplate);
    }

    public void changeLogSync(String contexts) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
            checkDatabaseChangeLogTable(true, changeLog, contexts);
            changeLog.validate(database, contexts);

            ChangeLogIterator logIterator = new ChangeLogIterator(changeLog,
                    new NotRanChangeSetFilter(database.getRanChangeSetList()),
                    new ContextChangeSetFilter(contexts),
                    new DbmsChangeSetFilter(database));

            logIterator.run(new ChangeLogSyncVisitor(database), database);
        } finally {
            lockService.releaseLock();
        }
    }

    public void markNextChangeSetRan(String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LoggingExecutor outputTemplate = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database);
        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        ExecutorService.getInstance().setExecutor(database, outputTemplate);

        outputHeader("SQL to add all changesets to database history table");

        markNextChangeSetRan(contexts);

        try {
            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }

        ExecutorService.getInstance().setExecutor(database, oldTemplate);
    }

    public void markNextChangeSetRan(String contexts) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
            checkDatabaseChangeLogTable(false, changeLog, contexts);
            changeLog.validate(database, contexts);

            ChangeLogIterator logIterator = new ChangeLogIterator(changeLog,
                    new NotRanChangeSetFilter(database.getRanChangeSetList()),
                    new ContextChangeSetFilter(contexts),
                    new DbmsChangeSetFilter(database),
                    new CountChangeSetFilter(1));

            logIterator.run(new ChangeLogSyncVisitor(database), database);
        } finally {
            lockService.releaseLock();
        }
    }

    public void futureRollbackSQL(String contexts, Writer output) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        LoggingExecutor outputTemplate = new LoggingExecutor(ExecutorService.getInstance().getExecutor(database), output, database);
        Executor oldTemplate = ExecutorService.getInstance().getExecutor(database);
        ExecutorService.getInstance().setExecutor(database, outputTemplate);

        outputHeader("SQL to roll back currently unexecuted changes");

        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
            checkDatabaseChangeLogTable(false, changeLog, contexts);
            changeLog.validate(database, contexts);

            ChangeLogIterator logIterator = new ChangeLogIterator(changeLog,
                    new NotRanChangeSetFilter(database.getRanChangeSetList()),
                    new ContextChangeSetFilter(contexts),
                    new DbmsChangeSetFilter(database));

            logIterator.run(new RollbackVisitor(database), database);
        } finally {
            lockService.releaseLock();
            ExecutorService.getInstance().setExecutor(database, oldTemplate);
        }

        try {
            output.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }

    }

    /**
     * Drops all database objects owned by the current user.
     */
    public final void dropAll() throws DatabaseException, LockException {
        dropAll(getDatabase().getDefaultSchemaName());
    }

    /**
     * Drops all database objects owned by the current user.
     */
    public final void dropAll(String... schemas) throws DatabaseException {
        try {
            LockService.getInstance(database).waitForLock();

            for (String schema : schemas) {
                log.info("Dropping Database Objects in schema: " + database.convertRequestedSchemaToSchema(schema));
                checkDatabaseChangeLogTable(false, null, null);
                getDatabase().dropDatabaseObjects(schema);
                checkDatabaseChangeLogTable(false, null, null);
                log.debug("Objects dropped successfully");
            }
        } catch (DatabaseException e) {
            throw e;
        } catch (Exception e) {
            throw new DatabaseException(e);
        } finally {
            try {
                LockService.getInstance(database).releaseLock();
            } catch (LockException e) {
                log.severe("Unable to release lock: " + e.getMessage());
            }
        }
    }

    /**
     * 'Tags' the database for future rollback
     */
    public void tag(String tagString) throws LiquibaseException {
        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            checkDatabaseChangeLogTable(false, null, null);
            getDatabase().tag(tagString);
        } finally {
            lockService.releaseLock();
        }
    }

    public void updateTestingRollback(String contexts) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        Date baseDate = new Date();
        update(contexts);
        rollback(baseDate, contexts);
        update(contexts);
    }

    public void checkDatabaseChangeLogTable(boolean updateExistingNullChecksums, DatabaseChangeLog databaseChangeLog, String contexts) throws LiquibaseException {
        if (updateExistingNullChecksums && databaseChangeLog == null) {
            throw new LiquibaseException("changeLog parameter is required if updating existing checksums");
        }
        String[] splitContexts = null;
        if (StringUtils.trimToNull(contexts) != null) {
            splitContexts = contexts.split(",");
        }
        getDatabase().checkDatabaseChangeLogTable(updateExistingNullChecksums, databaseChangeLog, splitContexts);
        if (!LockService.getInstance(database).hasChangeLogLock()) {
            getDatabase().checkDatabaseChangeLogLockTable();
        }
    }

    /**
     * Returns true if it is "save" to migrate the database.
     * Currently, "safe" is defined as running in an output-sql mode or against a database on localhost.
     * It is fine to run Liquibase against a "non-safe" database, the method is mainly used to determine if the user
     * should be prompted before continuing.
     */
    public boolean isSafeToRunMigration() throws DatabaseException {
        return getDatabase().isLocalDatabase();
    }

    /**
     * Display change log lock information.
     */
    public DatabaseChangeLogLock[] listLocks() throws LiquibaseException {
        checkDatabaseChangeLogTable(false, null, null);

        return LockService.getInstance(getDatabase()).listLocks();
    }

    public void reportLocks(PrintStream out) throws LiquibaseException {
        DatabaseChangeLogLock[] locks = listLocks();
        out.println("Database change log locks for " + getDatabase().getConnection().getConnectionUserName() + "@" + getDatabase().getConnection().getURL());
        if (locks.length == 0) {
            out.println(" - No locks");
        }
        for (DatabaseChangeLogLock lock : locks) {
            out.println(" - " + lock.getLockedBy() + " at " + DateFormat.getDateTimeInstance().format(lock.getLockGranted()));
        }

    }

    public void forceReleaseLocks() throws LiquibaseException {
        checkDatabaseChangeLogTable(false, null, null);

        LockService.getInstance(getDatabase()).forceReleaseLock();
    }

    public List<ChangeSet> listUnrunChangeSets(String contexts) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);

        changeLog.validate(database, contexts);

        ChangeLogIterator logIterator = getStandardChangelogIterator(contexts, changeLog);

        ListVisitor visitor = new ListVisitor();
        logIterator.run(visitor, database);
        return visitor.getSeenChangeSets();
    }

    public void reportStatus(boolean verbose, String contexts, Writer out) throws LiquibaseException {
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));

        try {
            List<ChangeSet> unrunChangeSets = listUnrunChangeSets(contexts);
            if (unrunChangeSets.size() == 0) {
                out.append(getDatabase().getConnection().getConnectionUserName());
                out.append("@");
                out.append(getDatabase().getConnection().getURL());
                out.append(" is up to date");
                out.append(StreamUtil.getLineSeparator());
            } else {
                out.append(String.valueOf(unrunChangeSets.size()));
                out.append(" change sets have not been applied to ");
                out.append(getDatabase().getConnection().getConnectionUserName());
                out.append("@");
                out.append(getDatabase().getConnection().getURL());
                out.append(StreamUtil.getLineSeparator());
                if (verbose) {
                    for (ChangeSet changeSet : unrunChangeSets) {
                        out.append("     ").append(changeSet.toString(false)).append(StreamUtil.getLineSeparator());
                    }
                }
            }

            out.flush();
        } catch (IOException e) {
            throw new LiquibaseException(e);
        }

    }

    /**
     * Sets checksums to null so they will be repopulated next run
     */
    public void clearCheckSums() throws LiquibaseException {
        log.info("Clearing database change log checksums");
        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            checkDatabaseChangeLogTable(false, null, null);

            UpdateStatement updateStatement = new UpdateStatement(getDatabase().getLiquibaseSchemaName(), getDatabase().getDatabaseChangeLogTableName());
            updateStatement.addNewColumnValue("MD5SUM", null);
            ExecutorService.getInstance().getExecutor(database).execute(updateStatement);
            getDatabase().commit();
        } finally {
            lockService.releaseLock();
        }
    }

    public void generateDocumentation(String outputDirectory) throws LiquibaseException {
    	// call without context
    	generateDocumentation(outputDirectory, null);
    }

    public void generateDocumentation(String outputDirectory, String contexts) throws LiquibaseException {
        log.info("Generating Database Documentation");
        changeLogParameters.setContexts(StringUtils.splitAndTrim(contexts, ","));
        LockService lockService = LockService.getInstance(database);
        lockService.waitForLock();

        try {
            DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
            checkDatabaseChangeLogTable(false, changeLog, null);

            String[] splitContexts = null;
            if (StringUtils.trimToNull(contexts) != null) {
                splitContexts = contexts.split(",");
            }
            changeLog.validate(database, splitContexts);

            ChangeLogIterator logIterator = new ChangeLogIterator(changeLog,
                    new DbmsChangeSetFilter(database));

            DBDocVisitor visitor = new DBDocVisitor(database);
            logIterator.run(visitor, database);

            visitor.writeHTML(new File(outputDirectory), resourceAccessor);
        } catch (IOException e) {
            throw new LiquibaseException(e);
        } finally {
            lockService.releaseLock();
        }

//        try {
//            if (!LockService.getExecutor(database).waitForLock()) {
//                return;
//            }
//
//            DBDocChangeLogHandler changeLogHandler = new DBDocChangeLogHandler(outputDirectory, this, changeLogFile,resourceAccessor);
//            runChangeLogs(changeLogHandler);
//
//            changeLogHandler.writeHTML(this);
//        } finally {
//            releaseLock();
//        }
    }

    public Diff diff(Database referenceDatabase, Database targetDatabase) {
        return new Diff(referenceDatabase, targetDatabase);
    }

    /**
     * Checks changelogs for bad MD5Sums and preconditions before attempting a migration
     */
    public void validate() throws LiquibaseException {

        DatabaseChangeLog changeLog = ChangeLogParserFactory.getInstance().getParser(changeLogFile, resourceAccessor).parse(changeLogFile, changeLogParameters, resourceAccessor);
        changeLog.validate(database);
    }

    public void setChangeLogParameter(String key, Object value) {
        this.changeLogParameters.set(key, value);
    }

    /**
     * Add safe database properties as changelog parameters.<br/>
     * Safe properties are the ones that doesn't have side effects in liquibase state and also don't change in during the liquibase execution
     * @param database Database which propeties are put in the changelog
     * @throws DatabaseException
     */
    private void setDatabasePropertiesAsChangelogParameters(Database database) throws DatabaseException {
            setChangeLogParameter("database.autoIncrementClause", database.getAutoIncrementClause(null, null));
            setChangeLogParameter("database.currentDateTimeFunction", database.getCurrentDateTimeFunction());
            setChangeLogParameter("database.databaseChangeLogLockTableName", database.getDatabaseChangeLogLockTableName());
            setChangeLogParameter("database.databaseChangeLogTableName", database.getDatabaseChangeLogTableName());
            setChangeLogParameter("database.databaseMajorVersion", database.getDatabaseMajorVersion());
            setChangeLogParameter("database.databaseMinorVersion", database.getDatabaseMinorVersion());
            setChangeLogParameter("database.databaseProductName", database.getDatabaseProductName());
            setChangeLogParameter("database.databaseProductVersion", database.getDatabaseProductVersion());
            setChangeLogParameter("database.defaultCatalogName", database.getDefaultCatalogName());
            setChangeLogParameter("database.defaultSchemaName", database.getDefaultSchemaName());
            setChangeLogParameter("database.lineComment", database.getLineComment());
            setChangeLogParameter("database.liquibaseSchemaName", database.getLiquibaseSchemaName());
            setChangeLogParameter("database.typeName", database.getTypeName());
            setChangeLogParameter("database.isLocalDatabase", database.isLocalDatabase());
            setChangeLogParameter("database.requiresPassword", database.requiresPassword());
            setChangeLogParameter("database.requiresUsername", database.requiresUsername());
            setChangeLogParameter("database.supportsForeignKeyDisable", database.supportsForeignKeyDisable());
            setChangeLogParameter("database.supportsInitiallyDeferrableColumns", database.supportsInitiallyDeferrableColumns());
            setChangeLogParameter("database.supportsRestrictForeignKeys", database.supportsRestrictForeignKeys());
            setChangeLogParameter("database.supportsSchemas", database.supportsSchemas());
            setChangeLogParameter("database.supportsSequences", database.supportsSequences());
            setChangeLogParameter("database.supportsTablespaces", database.supportsTablespaces());
    }
}


text:
package me.manage.interfaces.bianwu.manuscript;

import me.manage.domain.model.bianwu.manuscript.Manuscript;
import me.manage.domain.service.bianwu.manuscript.IManuscriptService;
import me.manage.infrastructure.persistence.hibernate.generic.Pagination;
import me.manage.interfaces.bianwu.manuscript.command.CreateManuscriptCommand;
import me.manage.interfaces.bianwu.manuscript.command.EditManuscriptCommand;
import me.manage.interfaces.bianwu.manuscript.command.ListCommand;
import me.manage.interfaces.shared.web.AlertMessage;
import me.manage.interfaces.shared.web.BaseController;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;

import java.util.Locale;

/**
 * Created by savion on 2015/4/28.
 */
@Controller
@RequestMapping("manuscript")
public class ManuscriptController extends BaseController {

    @Autowired
    private IManuscriptService manuscriptService;

    @RequestMapping("/list")
    public ModelAndView list(@ModelAttribute("manuscript")ListCommand command) throws Exception {
        Pagination<Manuscript> pagination = manuscriptService.pagination(command);
        return new ModelAndView("/bianwu/manuscript/list", "pagination", pagination)
                .addObject("manuscript", command);
    }

    @RequestMapping(value = "/create", method = RequestMethod.GET)
    public ModelAndView create(@ModelAttribute("manuscript")CreateManuscriptCommand command) throws Exception {
        return new ModelAndView("/bianwu/manuscript/create", "manuscript", command);
    }

    @RequestMapping(value = "/create", method = RequestMethod.POST)
    public ModelAndView create(@ModelAttribute("manuscript")CreateManuscriptCommand command,
                               RedirectAttributes redirectAttributes,
                               Locale locale) throws Exception {

        manuscriptService.create(command);

        AlertMessage alertMessage = new AlertMessage(AlertMessage.MessageType.SUCCESS,
                this.getMessage("default.create.success.message", new Object[]{command.getName()}, locale));
        redirectAttributes.addFlashAttribute(AlertMessage.MODEL_ATTRIBUTE_KEY, alertMessage);

        return new ModelAndView("redirect:/manuscript/list");
    }

    @RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
    public ModelAndView edit(@PathVariable String id) throws Exception {
        Manuscript manuscript = manuscriptService.findById(id);
        return new ModelAndView("/bianwu/manuscript/edit", "manuscript", manuscript);
    }

    @RequestMapping(value = "/edit/{id}", method = RequestMethod.POST)
    public ModelAndView edit(@ModelAttribute("manuscript")EditManuscriptCommand command,
                             RedirectAttributes redirectAttributes,
                             Locale locale) throws Exception {

        manuscriptService.edit(command);

        AlertMessage alertMessage = new AlertMessage(AlertMessage.MessageType.SUCCESS,
                this.getMessage("default.create.success.message", new Object[]{command.getName()}, locale));
        redirectAttributes.addFlashAttribute(AlertMessage.MODEL_ATTRIBUTE_KEY, alertMessage);

        return new ModelAndView("redirect:/manuscript/list");
    }

    @RequestMapping(value = "/view/{id}")
    public ModelAndView view(@PathVariable String id) throws Exception {
        Manuscript manuscript = manuscriptService.findById(id);
        return new ModelAndView("/bianwu/manuscript/view", "manuscript", manuscript);
    }

    @RequestMapping(value = "/delete/{id}")
    public ModelAndView delete(@PathVariable String id, RedirectAttributes redirectAttributes, Locale locale) throws Exception {

        manuscriptService.delete(id);

        AlertMessage alertMessage = new AlertMessage(AlertMessage.MessageType.SUCCESS, this.getMessage("default.delete.success.message",
                new Object[]{id}, locale));
        redirectAttributes.addFlashAttribute(AlertMessage.MODEL_ATTRIBUTE_KEY, alertMessage);

        return new ModelAndView("redirect:/manuscript/list");
    }

}