Extract license common structures (#36)

diff --git a/pom.xml b/pom.xml
index 5710de7..2e8d352 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
 
     <groupId>com.baidu.hugegraph</groupId>
     <artifactId>hugegraph-common</artifactId>
-    <version>1.6.13</version>
+    <version>1.6.14</version>
 
     <name>hugegraph-common</name>
     <url>https://github.com/hugegraph/hugegraph-common</url>
@@ -175,6 +175,12 @@
             <artifactId>jersey-hk2</artifactId>
             <version>${jersey.hk2.version}</version>
         </dependency>
+
+        <dependency>
+            <groupId>de.schlichtherle.truelicense</groupId>
+            <artifactId>truelicense-core</artifactId>
+            <version>1.33</version>
+        </dependency>
     </dependencies>
 
     <build>
@@ -212,7 +218,7 @@
                         <manifestEntries>
                             <!-- Must be on one line, otherwise the automatic
                                  upgrade script cannot replace the version number -->
-                            <Implementation-Version>1.6.13.0</Implementation-Version>
+                            <Implementation-Version>1.6.14.0</Implementation-Version>
                         </manifestEntries>
                     </archive>
                 </configuration>
diff --git a/src/main/java/com/baidu/hugegraph/license/CommonLicenseManager.java b/src/main/java/com/baidu/hugegraph/license/CommonLicenseManager.java
new file mode 100644
index 0000000..ede39d0
--- /dev/null
+++ b/src/main/java/com/baidu/hugegraph/license/CommonLicenseManager.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.license;
+
+import java.beans.XMLDecoder;
+import java.io.BufferedInputStream;
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.io.UnsupportedEncodingException;
+
+import org.slf4j.Logger;
+
+import com.baidu.hugegraph.util.Log;
+
+import de.schlichtherle.license.LicenseContent;
+import de.schlichtherle.license.LicenseContentException;
+import de.schlichtherle.license.LicenseManager;
+import de.schlichtherle.license.LicenseNotary;
+import de.schlichtherle.license.LicenseParam;
+import de.schlichtherle.license.NoLicenseInstalledException;
+import de.schlichtherle.xml.GenericCertificate;
+
+public class CommonLicenseManager extends LicenseManager {
+
+    private static final Logger LOG = Log.logger(CommonLicenseManager.class);
+
+    private static final String CHARSET = "UTF-8";
+    private static final int BUF_SIZE = 8 * 1024;
+
+    public CommonLicenseManager(LicenseParam param) {
+        super(param);
+    }
+
+    @Override
+    protected synchronized byte[] create(LicenseContent content,
+                                         LicenseNotary notary)
+                                         throws Exception {
+        super.initialize(content);
+        this.validateCreate(content);
+        GenericCertificate certificate = notary.sign(content);
+        return super.getPrivacyGuard().cert2key(certificate);
+    }
+
+    @Override
+    protected synchronized LicenseContent install(byte[] key,
+                                                  LicenseNotary notary)
+                                                  throws Exception {
+        GenericCertificate certificate = super.getPrivacyGuard().key2cert(key);
+        notary.verify(certificate);
+        String encodedText = certificate.getEncoded();
+        LicenseContent content = (LicenseContent) this.load(encodedText);
+        this.validate(content);
+        super.setLicenseKey(key);
+        super.setCertificate(certificate);
+        return content;
+    }
+
+    @Override
+    protected synchronized LicenseContent verify(LicenseNotary notary)
+                                                 throws Exception {
+        // Load license key from preferences
+        byte[] key = super.getLicenseKey();
+        if (key == null) {
+            String subject = super.getLicenseParam().getSubject();
+            throw new NoLicenseInstalledException(subject);
+        }
+
+        GenericCertificate certificate = super.getPrivacyGuard().key2cert(key);
+        notary.verify(certificate);
+        String encodedText = certificate.getEncoded();
+        LicenseContent content = (LicenseContent) this.load(encodedText);
+        this.validate(content);
+        super.setCertificate(certificate);
+        return content;
+    }
+
+    @Override
+    protected synchronized void validate(LicenseContent content)
+                                         throws LicenseContentException {
+        // Call super validate, expected to be overwritten
+        super.validate(content);
+    }
+
+    protected synchronized void validateCreate(LicenseContent content)
+                                               throws LicenseContentException {
+        // Just call super validate is ok
+        super.validate(content);
+    }
+
+    private Object load(String text) throws Exception {
+        InputStream bis = null;
+        XMLDecoder decoder = null;
+        try {
+            bis = new ByteArrayInputStream(text.getBytes(CHARSET));
+            decoder = new XMLDecoder(new BufferedInputStream(bis, BUF_SIZE));
+            return decoder.readObject();
+        } catch (UnsupportedEncodingException e) {
+            throw new LicenseContentException(String.format(
+                      "Unsupported charset: %s", CHARSET));
+        } finally {
+            if (decoder != null) {
+                decoder.close();
+            }
+            try {
+                if (bis != null) {
+                    bis.close();
+                }
+            } catch (Exception e) {
+                LOG.warn("Failed to close stream", e);
+            }
+        }
+    }
+}
diff --git a/src/main/java/com/baidu/hugegraph/license/ExtraParam.java b/src/main/java/com/baidu/hugegraph/license/ExtraParam.java
new file mode 100644
index 0000000..43f6d05
--- /dev/null
+++ b/src/main/java/com/baidu/hugegraph/license/ExtraParam.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.license;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class ExtraParam {
+
+    @JsonProperty("id")
+    private String id;
+
+    @JsonProperty("version")
+    private String version;
+
+    @JsonProperty("graphs")
+    private int graphs;
+
+    @JsonProperty("ip")
+    private String ip;
+
+    @JsonProperty("mac")
+    private String mac;
+
+    @JsonProperty("cpus")
+    private int cpus;
+
+    @JsonProperty("ram")
+    private int ram;
+
+    @JsonProperty("threads")
+    private int threads;
+
+    @JsonProperty("memory")
+    private int memory;
+
+    public String id() {
+        return this.id;
+    }
+
+    public String version() {
+        return this.version;
+    }
+
+    public int graphs() {
+        return this.graphs;
+    }
+
+    public String ip() {
+        return this.ip;
+    }
+
+    public String mac() {
+        return this.mac;
+    }
+
+    public int cpus() {
+        return this.cpus;
+    }
+
+    public int ram() {
+        return this.ram;
+    }
+
+    public int threads() {
+        return this.threads;
+    }
+
+    public int memory() {
+        return this.memory;
+    }
+}
diff --git a/src/main/java/com/baidu/hugegraph/license/FileKeyStoreParam.java b/src/main/java/com/baidu/hugegraph/license/FileKeyStoreParam.java
new file mode 100755
index 0000000..919cc23
--- /dev/null
+++ b/src/main/java/com/baidu/hugegraph/license/FileKeyStoreParam.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.license;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import de.schlichtherle.license.AbstractKeyStoreParam;
+
+/**
+ * Custom KeyStoreParam to store public and private key storage files to
+ * other disk locations instead of projects
+ */
+public class FileKeyStoreParam extends AbstractKeyStoreParam {
+
+    private String storePath;
+    private String alias;
+    private String keyPwd;
+    private String storePwd;
+
+    public FileKeyStoreParam(Class clazz, String resource, String alias,
+                             String storePwd, String keyPwd) {
+        super(clazz, resource);
+        this.storePath = resource;
+        this.alias = alias;
+        this.storePwd = storePwd;
+        this.keyPwd = keyPwd;
+    }
+
+    @Override
+    public String getAlias() {
+        return this.alias;
+    }
+
+    @Override
+    public String getStorePwd() {
+        return this.storePwd;
+    }
+
+    @Override
+    public String getKeyPwd() {
+        return this.keyPwd;
+    }
+
+    @Override
+    public InputStream getStream() throws IOException {
+        return new FileInputStream(new File(this.storePath));
+    }
+}
diff --git a/src/main/java/com/baidu/hugegraph/license/LicenseCreateParam.java b/src/main/java/com/baidu/hugegraph/license/LicenseCreateParam.java
new file mode 100644
index 0000000..00f0bdc
--- /dev/null
+++ b/src/main/java/com/baidu/hugegraph/license/LicenseCreateParam.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.license;
+
+import java.util.Date;
+import java.util.List;
+
+import org.apache.commons.lang3.time.DateUtils;
+
+import com.fasterxml.jackson.annotation.JsonFormat;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class LicenseCreateParam {
+
+    @JsonProperty("subject")
+    private String subject;
+
+    @JsonProperty("private_alias")
+    private String privateAlias;
+
+    @JsonProperty("key_password")
+    private String keyPassword;
+
+    @JsonProperty("store_password")
+    private String storePassword;
+
+    @JsonProperty("privatekey_path")
+    private String privateKeyPath;
+
+    @JsonProperty("license_path")
+    private String licensePath;
+
+    @JsonProperty("issued_time")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date issuedTime = new Date();
+
+    @JsonProperty("not_before")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date notBefore = this.issuedTime;
+
+    @JsonProperty("not_after")
+    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
+    private Date notAfter = DateUtils.addDays(this.notBefore, 30);
+
+    @JsonProperty("consumer_type")
+    private String consumerType = "user";
+
+    @JsonProperty("consumer_amount")
+    private Integer consumerAmount = 1;
+
+    @JsonProperty("description")
+    private String description = "";
+
+    @JsonProperty("extra_params")
+    private List<ExtraParam> extraParams;
+
+    public String subject() {
+        return this.subject;
+    }
+
+    public String privateAlias() {
+        return this.privateAlias;
+    }
+
+    public String keyPassword() {
+        return this.keyPassword;
+    }
+
+    public String storePassword() {
+        return this.storePassword;
+    }
+
+    public String privateKeyPath() {
+        return this.privateKeyPath;
+    }
+
+    public String licensePath() {
+        return this.licensePath;
+    }
+
+    public Date issuedTime() {
+        return this.issuedTime;
+    }
+
+    public Date notBefore() {
+        return this.notBefore;
+    }
+
+    public Date notAfter() {
+        return this.notAfter;
+    }
+
+    public String consumerType() {
+        return this.consumerType;
+    }
+
+    public Integer consumerAmount() {
+        return this.consumerAmount;
+    }
+
+    public String description() {
+        return this.description;
+    }
+
+    public List<ExtraParam> extraParams() {
+        return this.extraParams;
+    }
+}
diff --git a/src/main/java/com/baidu/hugegraph/license/LicenseVerifyParam.java b/src/main/java/com/baidu/hugegraph/license/LicenseVerifyParam.java
new file mode 100644
index 0000000..f2359bf
--- /dev/null
+++ b/src/main/java/com/baidu/hugegraph/license/LicenseVerifyParam.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.license;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public class LicenseVerifyParam {
+
+    @JsonProperty("subject")
+    private String subject;
+
+    @JsonProperty("public_alias")
+    private String publicAlias;
+
+    @JsonProperty("store_password")
+    private String storePassword;
+
+    @JsonProperty("publickey_path")
+    private String publicKeyPath;
+
+    @JsonProperty("license_path")
+    private String licensePath;
+
+    public String subject() {
+        return this.subject;
+    }
+
+    public String publicAlias() {
+        return this.publicAlias;
+    }
+
+    public String storePassword() {
+        return this.storePassword;
+    }
+
+    public String licensePath() {
+        return this.licensePath;
+    }
+
+    public String publicKeyPath() {
+        return this.publicKeyPath;
+    }
+}
diff --git a/src/main/java/com/baidu/hugegraph/license/MachineInfo.java b/src/main/java/com/baidu/hugegraph/license/MachineInfo.java
new file mode 100755
index 0000000..eccc7bb
--- /dev/null
+++ b/src/main/java/com/baidu/hugegraph/license/MachineInfo.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.license;
+
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public class MachineInfo {
+
+    private List<String> ipAddressList;
+    private List<String> macAddressList;
+
+    public MachineInfo() {
+        this.ipAddressList = null;
+        this.macAddressList = null;
+    }
+
+    public List<String> getIpAddress() {
+        if (this.ipAddressList != null) {
+            return this.ipAddressList;
+        }
+        this.ipAddressList = new ArrayList<>();
+        List<InetAddress> inetAddresses = this.getLocalAllInetAddress();
+        if (inetAddresses != null && !inetAddresses.isEmpty()) {
+            this.ipAddressList = inetAddresses.stream()
+                                              .map(InetAddress::getHostAddress)
+                                              .distinct()
+                                              .map(String::toLowerCase)
+                                              .collect(Collectors.toList());
+        }
+        return this.ipAddressList;
+    }
+
+    public List<String> getMacAddress() {
+        if (this.macAddressList != null) {
+            return this.macAddressList;
+        }
+        this.macAddressList = new ArrayList<>();
+        List<InetAddress> inetAddresses = this.getLocalAllInetAddress();
+        if (inetAddresses != null && !inetAddresses.isEmpty()) {
+            // Get the Mac address of all network interfaces
+            List<String> list = new ArrayList<>();
+            Set<String> uniqueValues = new HashSet<>();
+            for (InetAddress inetAddress : inetAddresses) {
+                String macByInetAddress = this.getMacByInetAddress(inetAddress);
+                if (uniqueValues.add(macByInetAddress)) {
+                    list.add(macByInetAddress);
+                }
+            }
+            this.macAddressList = list;
+        }
+        return this.macAddressList;
+    }
+
+    public List<InetAddress> getLocalAllInetAddress() {
+        Enumeration interfaces;
+        try {
+            interfaces = NetworkInterface.getNetworkInterfaces();
+        } catch (SocketException e) {
+            throw new RuntimeException("Failed to get network interfaces");
+        }
+
+        List<InetAddress> result = new ArrayList<>();
+        while (interfaces.hasMoreElements()) {
+            NetworkInterface iface = (NetworkInterface) interfaces.nextElement();
+            for (Enumeration inetAddresses = iface.getInetAddresses();
+                 inetAddresses.hasMoreElements(); ) {
+                InetAddress inetAddr = (InetAddress) inetAddresses.nextElement();
+                if (!inetAddr.isLoopbackAddress() &&
+                    !inetAddr.isLinkLocalAddress() &&
+                    !inetAddr.isMulticastAddress()) {
+                    result.add(inetAddr);
+                }
+            }
+        }
+        return result;
+    }
+
+    public String getMacByInetAddress(InetAddress inetAddr) {
+        byte[] mac;
+        try {
+            mac = NetworkInterface.getByInetAddress(inetAddr)
+                                  .getHardwareAddress();
+        } catch (Exception e) {
+            throw new RuntimeException(String.format(
+                      "Failed to get mac address for inet address '%s'",
+                      inetAddr));
+        }
+
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < mac.length; i++) {
+            if (i != 0) {
+                sb.append("-");
+            }
+            String temp = Integer.toHexString(mac[i] & 0xff);
+            if (temp.length() == 1) {
+                sb.append("0").append(temp);
+            } else {
+                sb.append(temp);
+            }
+        }
+        return sb.toString().toUpperCase();
+    }
+}
diff --git a/src/main/java/com/baidu/hugegraph/version/CommonVersion.java b/src/main/java/com/baidu/hugegraph/version/CommonVersion.java
index d38af9c..92c5f27 100644
--- a/src/main/java/com/baidu/hugegraph/version/CommonVersion.java
+++ b/src/main/java/com/baidu/hugegraph/version/CommonVersion.java
@@ -27,5 +27,5 @@
 
     // The second parameter of Version.of() is for all-in-one JAR
     public static final Version VERSION = Version.of(CommonVersion.class,
-                                                     "1.6.13");
+                                                     "1.6.14");
 }
diff --git a/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java b/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java
index e5f65d7..6131b33 100644
--- a/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java
+++ b/src/test/java/com/baidu/hugegraph/unit/UnitTestSuite.java
@@ -32,6 +32,11 @@
 import com.baidu.hugegraph.unit.iterator.FlatMapperFilterIteratorTest;
 import com.baidu.hugegraph.unit.iterator.FlatMapperIteratorTest;
 import com.baidu.hugegraph.unit.iterator.MapperIteratorTest;
+import com.baidu.hugegraph.unit.license.ExtraParamTest;
+import com.baidu.hugegraph.unit.license.LicenseCreateParamTest;
+import com.baidu.hugegraph.unit.license.LicenseManagerTest;
+import com.baidu.hugegraph.unit.license.LicenseVerifyParamTest;
+import com.baidu.hugegraph.unit.license.MachineInfoTest;
 import com.baidu.hugegraph.unit.perf.PerfUtilTest;
 import com.baidu.hugegraph.unit.rest.RestClientTest;
 import com.baidu.hugegraph.unit.rest.RestResultTest;
@@ -76,6 +81,12 @@
     LongEncodingTest.class,
     OrderLimitMapTest.class,
 
+    ExtraParamTest.class,
+    LicenseCreateParamTest.class,
+    LicenseVerifyParamTest.class,
+    MachineInfoTest.class,
+    LicenseManagerTest.class,
+
     AssertTest.class,
     WhiteboxTest.class
 })
diff --git a/src/test/java/com/baidu/hugegraph/unit/license/ExtraParamTest.java b/src/test/java/com/baidu/hugegraph/unit/license/ExtraParamTest.java
new file mode 100644
index 0000000..0e0cc22
--- /dev/null
+++ b/src/test/java/com/baidu/hugegraph/unit/license/ExtraParamTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.unit.license;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.license.ExtraParam;
+import com.baidu.hugegraph.testutil.Assert;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class ExtraParamTest {
+
+    @Test
+    public void testDeserializeExtraParam() throws IOException {
+        String json = "{"
+                + "\"id\":\"server-1\","
+                + "\"version\":\"0.10.2\","
+                + "\"graphs\":3,"
+                + "\"ip\":\"127.0.0.1\","
+                + "\"mac\":\"00-01-6C-06-A6-29\","
+                + "\"cpus\":32,"
+                + "\"ram\":65536,"
+                + "\"threads\":96,"
+                + "\"memory\":32768"
+                + "}";
+        ObjectMapper mapper = new ObjectMapper();
+        ExtraParam param = mapper.readValue(json, ExtraParam.class);
+        Assert.assertEquals("server-1", param.id());
+        Assert.assertEquals("0.10.2", param.version());
+        Assert.assertEquals(3, param.graphs());
+        Assert.assertEquals("127.0.0.1", param.ip());
+        Assert.assertEquals("00-01-6C-06-A6-29", param.mac());
+        Assert.assertEquals(32, param.cpus());
+        Assert.assertEquals(65536, param.ram());
+        Assert.assertEquals(96, param.threads());
+        Assert.assertEquals(32768, param.memory());
+    }
+}
diff --git a/src/test/java/com/baidu/hugegraph/unit/license/LicenseCreateParamTest.java b/src/test/java/com/baidu/hugegraph/unit/license/LicenseCreateParamTest.java
new file mode 100644
index 0000000..cc9ddaa
--- /dev/null
+++ b/src/test/java/com/baidu/hugegraph/unit/license/LicenseCreateParamTest.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.unit.license;
+
+import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.util.TimeZone;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.date.SafeDateFormat;
+import com.baidu.hugegraph.license.LicenseCreateParam;
+import com.baidu.hugegraph.testutil.Assert;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class LicenseCreateParamTest {
+
+    @Test
+    public void testDeserializeLicenseCreateParam()
+           throws IOException, ParseException {
+        String json = "{"
+                + "\"subject\":\"hugegraph-evaluation\","
+                + "\"private_alias\":\"privatekey\","
+                + "\"key_password\":\"123456\","
+                + "\"store_password\":\"123456\","
+                + "\"privatekey_path\":\"./privateKeys.store\","
+                + "\"license_path\":\"./hugegraph-evaluation.license\","
+                + "\"issued_time\":\"2019-08-10 00:00:00\","
+                + "\"not_before\":\"2019-08-10 00:00:00\","
+                + "\"not_after\":\"2020-08-10 00:00:00\","
+                + "\"consumer_type\":\"user\","
+                + "\"consumer_amount\":1,"
+                + "\"description\":\"hugegraph license\","
+                + "\"extra_params\":["
+                + "{"
+                + "\"id\":\"server-1\","
+                + "\"version\":\"0.9.2\","
+                + "\"graphs\":3,"
+                + "\"ip\":\"127.0.0.1\","
+                + "\"mac\":\"00-01-6C-06-A6-29\","
+                + "\"cpus\":32,"
+                + "\"ram\":65536,"
+                + "\"threads\":96,"
+                + "\"memory\":32768"
+                + "},"
+                + "{"
+                + "\"id\":\"server-2\","
+                + "\"version\":\"0.10.2\","
+                + "\"graphs\":3,"
+                + "\"ip\":\"127.0.0.1\","
+                + "\"mac\":\"00-02-6C-06-A6-29\","
+                + "\"cpus\":64,"
+                + "\"ram\":65536,"
+                + "\"threads\":96,"
+                + "\"memory\":65536"
+                + "}"
+                + "]"
+                + "}";
+        ObjectMapper mapper = new ObjectMapper();
+        LicenseCreateParam param = mapper.readValue(json,
+                                                    LicenseCreateParam.class);
+        Assert.assertEquals("hugegraph-evaluation", param.subject());
+        Assert.assertEquals("privatekey", param.privateAlias());
+        Assert.assertEquals("123456", param.keyPassword());
+        Assert.assertEquals("123456", param.storePassword());
+        Assert.assertEquals("./privateKeys.store", param.privateKeyPath());
+        Assert.assertEquals("./hugegraph-evaluation.license",
+                            param.licensePath());
+
+        DateFormat df = new SafeDateFormat("yyyy-MM-dd HH:mm:ss");
+        df.setTimeZone(TimeZone.getTimeZone("GMT+08:00"));
+        Assert.assertEquals(df.parse("2019-08-10 00:00:00"),
+                            param.issuedTime());
+        Assert.assertEquals(df.parse("2019-08-10 00:00:00"),
+                            param.notBefore());
+        Assert.assertEquals(df.parse("2020-08-10 00:00:00"),
+                            param.notAfter());
+        Assert.assertEquals("user", param.consumerType());
+        Assert.assertEquals(1, param.consumerAmount());
+        Assert.assertEquals("hugegraph license", param.description());
+        Assert.assertEquals(2, param.extraParams().size());
+    }
+}
diff --git a/src/test/java/com/baidu/hugegraph/unit/license/LicenseManagerTest.java b/src/test/java/com/baidu/hugegraph/unit/license/LicenseManagerTest.java
new file mode 100644
index 0000000..65f6e03
--- /dev/null
+++ b/src/test/java/com/baidu/hugegraph/unit/license/LicenseManagerTest.java
@@ -0,0 +1,297 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.unit.license;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+import java.util.prefs.Preferences;
+
+import javax.security.auth.x500.X500Principal;
+
+import org.apache.commons.codec.Charsets;
+import org.apache.commons.io.FileUtils;
+import org.junit.Test;
+
+import com.baidu.hugegraph.license.CommonLicenseManager;
+import com.baidu.hugegraph.license.ExtraParam;
+import com.baidu.hugegraph.license.FileKeyStoreParam;
+import com.baidu.hugegraph.license.LicenseCreateParam;
+import com.baidu.hugegraph.license.LicenseVerifyParam;
+import com.baidu.hugegraph.testutil.Assert;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+import de.schlichtherle.license.CipherParam;
+import de.schlichtherle.license.DefaultCipherParam;
+import de.schlichtherle.license.DefaultLicenseParam;
+import de.schlichtherle.license.KeyStoreParam;
+import de.schlichtherle.license.LicenseContent;
+import de.schlichtherle.license.LicenseContentException;
+import de.schlichtherle.license.LicenseManager;
+import de.schlichtherle.license.LicenseParam;
+import de.schlichtherle.license.NoLicenseInstalledException;
+
+public class LicenseManagerTest {
+
+    private static final Charset CHARSET = Charsets.UTF_8;
+    private static final ObjectMapper MAPPER = new ObjectMapper();
+
+    @Test
+    public void testCreateInstallVerifyLicense() throws IOException {
+        String createConfigPath = "src/test/resources/create-license.json";
+        LicenseCreator creator = LicenseCreator.build(createConfigPath);
+        creator.create();
+
+        String verifyConfigPath = "src/test/resources/verify-license.json";
+        LicenseVerifier verifier = LicenseVerifier.build(verifyConfigPath);
+        verifier.install();
+        verifier.verify();
+    }
+
+    @Test
+    public void testCreateVerifyLicenseWithoutInstall() throws IOException {
+        String createConfigPath = "src/test/resources/create-license.json";
+        LicenseCreator creator = LicenseCreator.build(createConfigPath);
+        creator.create();
+
+        String verifyConfigPath = "src/test/resources/verify-license.json";
+        LicenseVerifier verifier = LicenseVerifier.build(verifyConfigPath);
+        verifier.uninstall();
+        Assert.assertThrows(RuntimeException.class, () -> {
+            verifier.verify();
+        }, e -> {
+            Assert.assertEquals(NoLicenseInstalledException.class,
+                                e.getCause().getClass());
+        });
+    }
+
+    private static class LicenseCreator {
+
+        private static final X500Principal DEFAULT_ISSUER = new X500Principal(
+                "CN=localhost, OU=localhost, O=localhost, L=SH, ST=SH, C=CN");
+
+        private final LicenseCreateParam param;
+
+        public LicenseCreator(LicenseCreateParam param) {
+            this.param = param;
+        }
+
+        public static LicenseCreator build(String path) throws IOException {
+            File file = FileUtils.getFile(path);
+            String json;
+            try {
+                json = FileUtils.readFileToString(file, CHARSET);
+            } catch (IOException e) {
+                throw new RuntimeException(String.format(
+                          "Failed to read file '%s'", path));
+            }
+            LicenseCreateParam param = MAPPER.readValue(
+                                       json, LicenseCreateParam.class);
+            return new LicenseCreator(param);
+        }
+
+        public void create(){
+            try {
+                LicenseParam licenseParam = this.initLicenseParam();
+                LicenseManager manager = new LicenseCreateManager(licenseParam);
+                LicenseContent licenseContent = this.initLicenseContent();
+                File licenseFile = new File(this.param.licensePath());
+                manager.store(licenseContent, licenseFile);
+            } catch (Exception e){
+                throw new RuntimeException("Generate license failed", e);
+            }
+        }
+
+        private LicenseParam initLicenseParam(){
+            Preferences preferences = Preferences.userNodeForPackage(
+                                      LicenseCreator.class);
+            CipherParam cipherParam = new DefaultCipherParam(
+                                      this.param.storePassword());
+            KeyStoreParam keyStoreParam;
+            keyStoreParam = new FileKeyStoreParam(LicenseCreator.class,
+                                                  this.param.privateKeyPath(),
+                                                  this.param.privateAlias(),
+                                                  this.param.storePassword(),
+                                                  this.param.keyPassword());
+            return new DefaultLicenseParam(this.param.subject(), preferences,
+                                           keyStoreParam, cipherParam);
+        }
+
+        private LicenseContent initLicenseContent(){
+            LicenseContent content = new LicenseContent();
+            content.setHolder(DEFAULT_ISSUER);
+            content.setIssuer(DEFAULT_ISSUER);
+            content.setSubject(this.param.subject());
+            content.setIssued(this.param.issuedTime());
+            content.setNotBefore(this.param.notBefore());
+            content.setNotAfter(this.param.notAfter());
+            content.setConsumerType(this.param.consumerType());
+            content.setConsumerAmount(this.param.consumerAmount());
+            content.setInfo(this.param.description());
+            // Customized verification params
+            String json;
+            try {
+                json = MAPPER.writeValueAsString(this.param.extraParams());
+            } catch (JsonProcessingException e) {
+                throw new RuntimeException("Failed to write extra params", e);
+            }
+            content.setExtra(json);
+            return content;
+        }
+    }
+
+    private static class LicenseCreateManager extends CommonLicenseManager {
+
+        public LicenseCreateManager(LicenseParam param) {
+            super(param);
+        }
+
+        @Override
+        protected synchronized void validateCreate(LicenseContent content)
+                  throws LicenseContentException {
+            super.validateCreate(content);
+        }
+    }
+
+    private static class LicenseVerifier {
+
+        private final LicenseVerifyParam param;
+        private final LicenseVerifyManager manager;
+
+        public LicenseVerifier(LicenseVerifyParam param) {
+            this.param = param;
+            LicenseParam licenseParam = this.initLicenseParam(param);
+            this.manager = new LicenseVerifyManager(licenseParam);
+        }
+
+        public static LicenseVerifier build(String path) throws IOException {
+            File file = FileUtils.getFile(path);
+            String json;
+            try {
+                json = FileUtils.readFileToString(file, CHARSET);
+            } catch (IOException e) {
+                throw new RuntimeException(String.format(
+                          "Failed to read file '%s'", path));
+            }
+            LicenseVerifyParam param = MAPPER.readValue(
+                                       json, LicenseVerifyParam.class);
+            return new LicenseVerifier(param);
+        }
+
+        public synchronized void install() {
+            try {
+                this.manager.uninstall();
+                File licenseFile = new File(this.param.licensePath());
+                this.manager.install(licenseFile);
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to install license", e);
+            }
+        }
+
+        public synchronized void uninstall() {
+            try {
+                this.manager.uninstall();
+            } catch (Exception e) {
+                throw new RuntimeException("Failed to uninstall license", e);
+            }
+        }
+
+        public void verify() {
+            try {
+                this.manager.verify();
+            } catch (Exception e) {
+                throw new RuntimeException("The license verify failed", e);
+            }
+        }
+
+        private LicenseParam initLicenseParam(LicenseVerifyParam param) {
+            Preferences preferences = Preferences.userNodeForPackage(
+                                      LicenseVerifier.class);
+            CipherParam cipherParam = new DefaultCipherParam(
+                                      param.storePassword());
+            KeyStoreParam keyStoreParam = new FileKeyStoreParam(
+                                          LicenseVerifier.class,
+                                          param.publicKeyPath(),
+                                          param.publicAlias(),
+                                          param.storePassword(),
+                                          null);
+            return new DefaultLicenseParam(param.subject(), preferences,
+                                           keyStoreParam, cipherParam);
+        }
+    }
+
+    private static class LicenseVerifyManager extends CommonLicenseManager {
+
+        private final String serverId = "server-1";
+
+        public LicenseVerifyManager(LicenseParam param) {
+            super(param);
+        }
+
+        @Override
+        protected synchronized void validate(LicenseContent content)
+                  throws LicenseContentException {
+            // Call super validate firstly, will verify expired
+            super.validate(content);
+
+            // Verify the customized license parameters
+            String extra = (String) content.getExtra();
+            List<ExtraParam> extraParams;
+            try {
+                TypeReference type = new TypeReference<List<ExtraParam>>() {};
+                extraParams = MAPPER.readValue(extra, type);
+            } catch (IOException e) {
+                throw new RuntimeException("Failed to read extra params", e);
+            }
+            ExtraParam param = this.matchParam(this.serverId, extraParams);
+            if (param == null) {
+                throw newLicenseException("The current server's id '%s' " +
+                                          "is not authorized");
+            }
+            if (this.usingGraphs() > param.graphs()) {
+                throw newLicenseException("The using graphs exceeded '%s'" +
+                                          "authorized limit '%s'",
+                                          this.usingGraphs(), param.graphs());
+            }
+        }
+
+        private int usingGraphs() {
+            // Assume
+            return 2;
+        }
+
+        private ExtraParam matchParam(String id, List<ExtraParam> extraParams) {
+            for (ExtraParam param : extraParams) {
+                if (param.id().equals(id)) {
+                    return param;
+                }
+            }
+            return null;
+        }
+
+        private LicenseContentException newLicenseException(String message,
+                                                            Object... args) {
+            return new LicenseContentException(String.format(message, args));
+        }
+    }
+}
diff --git a/src/test/java/com/baidu/hugegraph/unit/license/LicenseVerifyParamTest.java b/src/test/java/com/baidu/hugegraph/unit/license/LicenseVerifyParamTest.java
new file mode 100644
index 0000000..f1126ce
--- /dev/null
+++ b/src/test/java/com/baidu/hugegraph/unit/license/LicenseVerifyParamTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.unit.license;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.license.LicenseVerifyParam;
+import com.baidu.hugegraph.testutil.Assert;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
+public class LicenseVerifyParamTest {
+
+    @Test
+    public void testDeserializeLicenseVerifyParam() throws IOException {
+        String json = "{"
+                + "\"subject\":\"hugegraph-evaluation\","
+                + "\"public_alias\":\"publiccert\","
+                + "\"store_password\":\"123456\","
+                + "\"publickey_path\":\"./publicCerts.store\","
+                + "\"license_path\":\"./hugegraph-evaluation.license\""
+                + "}";
+        ObjectMapper mapper = new ObjectMapper();
+        LicenseVerifyParam param = mapper.readValue(json,
+                                                    LicenseVerifyParam.class);
+        Assert.assertEquals("hugegraph-evaluation", param.subject());
+        Assert.assertEquals("publiccert", param.publicAlias());
+        Assert.assertEquals("123456", param.storePassword());
+        Assert.assertEquals("./publicCerts.store", param.publicKeyPath());
+        Assert.assertEquals("./hugegraph-evaluation.license",
+                            param.licensePath());
+    }
+}
diff --git a/src/test/java/com/baidu/hugegraph/unit/license/MachineInfoTest.java b/src/test/java/com/baidu/hugegraph/unit/license/MachineInfoTest.java
new file mode 100644
index 0000000..c436943
--- /dev/null
+++ b/src/test/java/com/baidu/hugegraph/unit/license/MachineInfoTest.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 HugeGraph Authors
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+
+package com.baidu.hugegraph.unit.license;
+
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.junit.Test;
+
+import com.baidu.hugegraph.license.MachineInfo;
+import com.baidu.hugegraph.testutil.Assert;
+
+public class MachineInfoTest {
+
+    private static final Pattern IPV4_PATTERN = Pattern.compile(
+            "^(([01]?\\d\\d?|2[0-4]\\d|25[0-5])\\.){3}" +
+            "([01]?\\d\\d?|2[0-4]\\d|25[0-5])$"
+    );
+    private static final Pattern IPV6_PATTERN = Pattern.compile(
+            "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"
+    );
+
+    private static final Pattern MAC_PATTERN = Pattern.compile(
+            "^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"
+    );
+
+    private static final MachineInfo machineInfo = new MachineInfo();
+
+    @Test
+    public void testGetIpAddressList() {
+        List<String> ipAddressList = machineInfo.getIpAddress();
+        for (String ip : ipAddressList) {
+            Assert.assertTrue(IPV4_PATTERN.matcher(ip).matches() ||
+                              IPV6_PATTERN.matcher(ip).matches());
+        }
+        Assert.assertEquals(ipAddressList, machineInfo.getIpAddress());
+    }
+
+    @Test
+    public void testGetMacAddressList() {
+        List<String> macAddressList = machineInfo.getMacAddress();
+        for (String mac : macAddressList) {
+            Assert.assertTrue(MAC_PATTERN.matcher(mac).matches());
+        }
+        Assert.assertEquals(macAddressList, machineInfo.getMacAddress());
+    }
+
+    @Test
+    public void testGetLocalAllInetAddress() {
+        List<InetAddress> addressList = machineInfo.getLocalAllInetAddress();
+        for (InetAddress address : addressList) {
+            String ip = address.getHostAddress();
+            Assert.assertTrue(IPV4_PATTERN.matcher(ip).matches() ||
+                              IPV6_PATTERN.matcher(ip).matches());
+        }
+    }
+
+    @Test
+    public void testGetMacByInetAddress() throws UnknownHostException {
+        List<InetAddress> addressList = machineInfo.getLocalAllInetAddress();
+        for (InetAddress address : addressList) {
+            String mac = machineInfo.getMacByInetAddress(address);
+            Assert.assertTrue(MAC_PATTERN.matcher(mac).matches());
+        }
+        InetAddress address = InetAddress.getByAddress(new byte[]{0, 0, 0, 0});
+        Assert.assertThrows(RuntimeException.class, () -> {
+            machineInfo.getMacByInetAddress(address);
+        });
+    }
+}
diff --git a/src/test/resources/create-license.json b/src/test/resources/create-license.json
new file mode 100644
index 0000000..26b3bd5
--- /dev/null
+++ b/src/test/resources/create-license.json
@@ -0,0 +1,19 @@
+{
+  "subject": "hugegraph-evaluation",
+  "private_alias": "privatekey",
+  "key_password": "private_password123456",
+  "store_password": "public_password123456",
+  "privatekey_path": "src/test/resources/privateKeys.store",
+  "license_path": "src/test/resources/hugegraph-evaluation.license",
+  "issued_time": "2019-08-01 00:00:00",
+  "not_before": "2019-08-01 00:00:00",
+  "not_after": "2029-08-01 00:00:00",
+  "consumer_type": "user",
+  "consumer_amount": 1,
+  "extra_params": [
+    {
+      "id": "server-1",
+      "graphs": 3
+    }
+  ]
+}
diff --git a/src/test/resources/hugegraph-evaluation.license b/src/test/resources/hugegraph-evaluation.license
new file mode 100644
index 0000000..d09b0b9
--- /dev/null
+++ b/src/test/resources/hugegraph-evaluation.license
Binary files differ
diff --git a/src/test/resources/log4j2.xml b/src/test/resources/log4j2.xml
new file mode 100644
index 0000000..7ef111d
--- /dev/null
+++ b/src/test/resources/log4j2.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<configuration status="error">
+    <appenders>
+        <Console name="console" target="SYSTEM_OUT">
+            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
+            <PatternLayout pattern="%-d{yyyy-MM-dd HH:mm:ss} %-5r [%t] [%-5p] %c %x - %m%n"/>
+        </Console>
+
+        <RollingFile name="file" fileName="logs/hugegraph-common.log"
+                     filePattern="logs/$${date:yyyy-MM}/hugegraph-common-%d{yyyy-MM-dd}-%i.log">
+            <ThresholdFilter level="INFO" onMatch="ACCEPT" onMismatch="DENY"/>
+            <PatternLayout pattern="%-d{yyyy-MM-dd HH:mm:ss} %-5r [%t] [%-5p] %c %x - %m%n"/>
+            <SizeBasedTriggeringPolicy size="100MB"/>
+        </RollingFile>
+    </appenders>
+    <loggers>
+        <root level="INFO">
+            <appender-ref ref="console"/>
+            <appender-ref ref="file"/>
+        </root>
+        <logger name="com.baidu.hugegraph" level="INFO" additivity="false">
+            <appender-ref ref="console"/>
+            <appender-ref ref="file"/>
+        </logger>
+    </loggers>
+</configuration>
diff --git a/src/test/resources/privateKeys.store b/src/test/resources/privateKeys.store
new file mode 100644
index 0000000..836e413
--- /dev/null
+++ b/src/test/resources/privateKeys.store
Binary files differ
diff --git a/src/test/resources/publicCerts.store b/src/test/resources/publicCerts.store
new file mode 100644
index 0000000..7eee045
--- /dev/null
+++ b/src/test/resources/publicCerts.store
Binary files differ
diff --git a/src/test/resources/verify-license.json b/src/test/resources/verify-license.json
new file mode 100644
index 0000000..277e25d
--- /dev/null
+++ b/src/test/resources/verify-license.json
@@ -0,0 +1,7 @@
+{
+  "subject": "hugegraph-evaluation",
+  "public_alias": "publiccert",
+  "store_password": "public_password123456",
+  "publickey_path": "src/test/resources/publicCerts.store",
+  "license_path": "src/test/resources/hugegraph-evaluation.license"
+}