├── .gitignore ├── tables.cql ├── src ├── main │ └── java │ │ └── com │ │ └── dekses │ │ └── cassandra │ │ └── lock │ │ ├── LockLeaseLostException.java │ │ ├── Lock.java │ │ ├── CassandraLock.java │ │ └── LockFactory.java └── test │ └── java │ └── com │ └── dekses │ └── cassandra │ └── lock │ ├── LockFactoryTest.java │ └── CassandraLockTest.java ├── LICENSE.txt ├── README.md └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .project 2 | .settings 3 | .classpath 4 | /target/ 5 | -------------------------------------------------------------------------------- /tables.cql: -------------------------------------------------------------------------------- 1 | DROP KEYSPACE IF EXISTS casslock_test; 2 | 3 | CREATE KEYSPACE casslock_test 4 | WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 } 5 | AND DURABLE_WRITES = true; 6 | 7 | USE casslock_test; 8 | 9 | CREATE TABLE lock_leases ( 10 | name text PRIMARY KEY, 11 | owner text, 12 | value text 13 | ) WITH default_time_to_live = 60; 14 | -------------------------------------------------------------------------------- /src/main/java/com/dekses/cassandra/lock/LockLeaseLostException.java: -------------------------------------------------------------------------------- 1 | package com.dekses.cassandra.lock; 2 | 3 | /** 4 | * Thrown if lock lease was lost by the owner, this can happen 5 | * due to client inability to update lease on time. 6 | */ 7 | public class LockLeaseLostException extends IllegalMonitorStateException { 8 | 9 | /** Serial UID */ 10 | private static final long serialVersionUID = 7515630326135527763L; 11 | 12 | /** Empty constructor */ 13 | public LockLeaseLostException() { 14 | super(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/dekses/cassandra/lock/LockFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.dekses.cassandra.lock; 2 | 3 | import static org.junit.Assert.*; 4 | import org.junit.Test; 5 | import com.datastax.driver.core.exceptions.DriverException; 6 | 7 | public class LockFactoryTest { 8 | 9 | @Test 10 | public void testInitialize() { 11 | LockFactory lf1 = new LockFactory("127.0.0.1", "casslock_test"); 12 | assertNotNull(lf1); 13 | LockFactory lf2 = new LockFactory("localhost, 127.0.0.1", "casslock_test"); 14 | assertNotNull(lf2); 15 | } 16 | 17 | @Test(expected=DriverException.class) 18 | public void testInitializeFailure() { 19 | LockFactory lf1 = new LockFactory("127.0.0.1", "casslock_test__"); 20 | assertNull(lf1); // should not happen 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/dekses/cassandra/lock/Lock.java: -------------------------------------------------------------------------------- 1 | package com.dekses.cassandra.lock; 2 | 3 | /** 4 | * Generalized lock interface 5 | */ 6 | public interface Lock { 7 | 8 | /** 9 | * Try to acquire lock lease 10 | * @return True if acquired, False if not (lease taken already) 11 | */ 12 | boolean tryLock(); 13 | 14 | /** 15 | * Releases current lock lease 16 | * @throws LockLeaseLostException Thrown in case lock lease lost by the owner 17 | */ 18 | void unlock() throws LockLeaseLostException; 19 | 20 | /** 21 | * Updates current lock lease 22 | * @throws LockLeaseLostException Thrown in case lock lease lost by the owner 23 | */ 24 | void keepAlive() throws LockLeaseLostException; 25 | 26 | /** @return Lock owner */ 27 | String getOwner(); 28 | 29 | /** @return Lock resource name */ 30 | String getName(); 31 | 32 | /** @return Lock TTL */ 33 | int getTTL(); 34 | } 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Yuri Subach 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cassandra-lock 2 | 3 | Java implementation of distributed lock based on lightweight transactions in 4 | Cassandra database. 5 | 6 | Inspired by [Consensus on 7 | Cassandra](http://www.datastax.com/dev/blog/consensus-on-cassandra) 8 | 9 | ## Installation 10 | 11 | Add Maven dependency in your project: 12 | ``` 13 | 14 | com.dekses 15 | cassandra-lock 16 | 0.0.3 17 | 18 | ``` 19 | 20 | More details for other build systems and latest version available at 21 | [Maven Central 22 | repository](https://search.maven.org/artifact/com.dekses/cassandra-lock). 23 | 24 | ## Usage 25 | 26 | ### LockFactory 27 | 28 | Create `lock_leases` table in your project keyspace, it's definition can be 29 | found in `tables.cql` file. 30 | 31 | ``` 32 | CREATE TABLE lock_leases ( 33 | name text PRIMARY KEY, 34 | owner text, 35 | value text 36 | ) WITH default_time_to_live = 60; 37 | ``` 38 | 39 | In your Java code create `LockFactory` instance using existing Cassandra 40 | session object or by providing cluster contact points and keyspace: 41 | 42 | ```java 43 | // with session 44 | LockFactory lockFactory = new LockFactory(session); 45 | 46 | // with contact points and keyspace 47 | LockFactory lockFactory = new LockFactory("127.0.0.1", "casslock_test"); 48 | ``` 49 | 50 | Alternatively, if you prefer to use factory singleton object, you can 51 | initialize it and then retrieve using `getInstance()`: 52 | 53 | ```java 54 | // Initialize 55 | // with session 56 | LockFactory.initialize(session); 57 | 58 | // with contact points and keyspace 59 | LockFactory.initialize("127.0.0.1", "casslock_test"); 60 | 61 | // Retrieve factory object 62 | LockFactory lockFactory = LockFactory.getInstance(); 63 | ``` 64 | 65 | ### Lock 66 | 67 | Create new `Lock` instances using `LockFactory` object: 68 | 69 | ```java 70 | Lock lock = lockFactory.getLock("my-resource"); 71 | if (lock.tryLock()) { 72 | try { 73 | // do something related to my-resource 74 | // ... 75 | 76 | // call periodically to keep lock 77 | lock.keepAlive(); 78 | } finally { 79 | lock.unlock(); 80 | } 81 | } else { 82 | // Can not acquire lock on resource, 83 | // it's already taken by other owner/process 84 | } 85 | ``` 86 | 87 | ### TTL (time to live) 88 | 89 | Each lock lease has TTL measured in seconds. It is 60 seconds by default, if 90 | lease is not updated during this interval using keepAlive() method call it will 91 | be automatically removed. 92 | 93 | You can change default TTL for LockFactory, it affects every lock created by 94 | this factory (excluding locks that already exist). 95 | 96 | ```java 97 | // local object 98 | lockFactory.setDefaultTTL(120); 99 | 100 | // singleton 101 | LockFactory.getInstance().setDefaultTTL(120); 102 | ``` 103 | 104 | Also TTL can be specified for each individual lock object. 105 | 106 | ```java 107 | Lock lock = lockFactory.getLock("my-resource", 120); 108 | ``` 109 | -------------------------------------------------------------------------------- /src/main/java/com/dekses/cassandra/lock/CassandraLock.java: -------------------------------------------------------------------------------- 1 | package com.dekses.cassandra.lock; 2 | 3 | import com.datastax.driver.core.PreparedStatement; 4 | import com.datastax.driver.core.ResultSet; 5 | import com.datastax.driver.core.Row; 6 | import com.datastax.driver.core.Session; 7 | 8 | /** 9 | * Distributed lock implementation based on Cassandra lightweight 10 | * transactions feature (uses Paxos consensus protocol) 11 | */ 12 | public class CassandraLock implements Lock { 13 | 14 | /** Current Cassandra session */ 15 | private Session session; 16 | 17 | /** Lock owner name */ 18 | private String owner; 19 | 20 | /** Lock lease (resource) name */ 21 | private String name; 22 | 23 | /** Lock time to live in seconds */ 24 | private int ttl; 25 | 26 | // Prepared CQL statements 27 | private PreparedStatement insertPrep; 28 | private PreparedStatement selectPrep; 29 | private PreparedStatement deletePrep; 30 | private PreparedStatement updatePrep; 31 | 32 | /** 33 | * Constructor 34 | * @param session 35 | * @param owner 36 | * @param name 37 | * @param ttl 38 | * @param insertPrep 39 | * @param selectPrep 40 | * @param deletePrep 41 | * @param updatePrep 42 | */ 43 | public CassandraLock(Session session, String owner, String name, int ttl, PreparedStatement insertPrep, PreparedStatement selectPrep, PreparedStatement deletePrep, PreparedStatement updatePrep) { 44 | this.session = session; 45 | this.owner = owner; 46 | this.name = name; 47 | this.ttl = ttl; 48 | this.insertPrep = insertPrep; 49 | this.selectPrep = selectPrep; 50 | this.deletePrep = deletePrep; 51 | this.updatePrep = updatePrep; 52 | } 53 | 54 | /** @return Lock owner */ 55 | public String getOwner() { 56 | return owner; 57 | } 58 | 59 | /** @return Lock resource name */ 60 | public String getName() { 61 | return name; 62 | } 63 | 64 | /** @return Lock TTL */ 65 | public int getTTL() { 66 | return ttl; 67 | } 68 | 69 | /** 70 | * Try to acquire lock lease, uses INSERT query. 71 | */ 72 | public boolean tryLock() { 73 | ResultSet rs = session.execute(insertPrep.bind(name, owner, ttl)); 74 | if (rs.wasApplied()) { 75 | return true; 76 | } else { 77 | Row lease = rs.one(); 78 | return owner.equals(lease.getString("owner")); 79 | } 80 | } 81 | 82 | /** 83 | * Releases lock lease using DELETE query. 84 | * Throws exception if query not applied, it means lock lease was lost. 85 | */ 86 | public void unlock() throws LockLeaseLostException { 87 | ResultSet rs = session.execute(deletePrep.bind(name, owner)); 88 | if (!rs.wasApplied()) { 89 | throw new LockLeaseLostException(); 90 | } 91 | } 92 | 93 | /** 94 | * Updates current lock lease using UPDATE query. 95 | * Throws exception if query not applied, it means lock lease was lost. 96 | */ 97 | public void keepAlive() throws LockLeaseLostException { 98 | ResultSet rs = session.execute(updatePrep.bind(ttl, owner, name, owner)); 99 | if (!rs.wasApplied()) { 100 | throw new LockLeaseLostException(); 101 | } 102 | } 103 | } -------------------------------------------------------------------------------- /src/test/java/com/dekses/cassandra/lock/CassandraLockTest.java: -------------------------------------------------------------------------------- 1 | package com.dekses.cassandra.lock; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.After; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | 9 | import com.datastax.driver.core.Cluster; 10 | import com.datastax.driver.core.Session; 11 | 12 | public class CassandraLockTest { 13 | 14 | private static final String HOST = "127.0.0.1"; 15 | private static final String KEYSPACE = "casslock_test"; 16 | 17 | private Session session; 18 | 19 | @Before 20 | public void setUp() { 21 | session = Cluster.builder().addContactPoint(HOST).build().connect(); 22 | session.execute("USE " + KEYSPACE); 23 | session.execute("TRUNCATE lock_leases"); 24 | } 25 | 26 | @After 27 | public void tearDown() { 28 | session.close(); 29 | } 30 | 31 | @Test 32 | public void testLockTrueFalse() { 33 | LockFactory lf1 = new LockFactory(HOST, KEYSPACE); 34 | assertNotNull(lf1); 35 | Lock l1 = lf1.getLock("test"); 36 | assertTrue(l1.tryLock()); 37 | 38 | LockFactory lf2 = new LockFactory(HOST, KEYSPACE); 39 | assertNotNull(lf2); 40 | Lock l2 = lf2.getLock("test"); 41 | assertFalse(l2.tryLock()); 42 | } 43 | 44 | @Test 45 | public void testLockTrueTrue() { 46 | LockFactory lf1 = new LockFactory(HOST, KEYSPACE); 47 | assertNotNull(lf1); 48 | Lock l1 = lf1.getLock("test"); 49 | assertTrue(l1.tryLock()); 50 | 51 | Lock l2 = lf1.getLock("test"); 52 | assertTrue(l2.tryLock()); 53 | } 54 | 55 | @Test 56 | public void testLockAndUnlock() { 57 | LockFactory lf1 = new LockFactory(HOST, KEYSPACE); 58 | assertNotNull(lf1); 59 | Lock l1 = lf1.getLock("test"); 60 | assertTrue(l1.tryLock()); 61 | 62 | LockFactory lf2 = new LockFactory(HOST, KEYSPACE); 63 | assertNotNull(lf2); 64 | Lock l2 = lf2.getLock("test"); 65 | assertFalse(l2.tryLock()); 66 | 67 | l1.unlock(); 68 | assertTrue(l2.tryLock()); 69 | } 70 | 71 | @Test(expected=LockLeaseLostException.class) 72 | public void testLockAndUnlockException() throws InterruptedException { 73 | LockFactory lf1 = new LockFactory(HOST, KEYSPACE); 74 | assertNotNull(lf1); 75 | Lock l1 = lf1.getLock("test"); 76 | assertTrue(l1.tryLock()); 77 | 78 | // simulates lock loss due to TTL 79 | session.execute("TRUNCATE lock_leases"); 80 | Thread.sleep(1000); 81 | 82 | l1.unlock(); 83 | } 84 | 85 | @Test 86 | public void testKeepAlive() { 87 | LockFactory lf1 = new LockFactory(HOST, KEYSPACE); 88 | assertNotNull(lf1); 89 | Lock l1 = lf1.getLock("test"); 90 | assertTrue(l1.tryLock()); 91 | 92 | l1.keepAlive(); 93 | assertTrue(true); 94 | } 95 | 96 | @Test(expected=LockLeaseLostException.class) 97 | public void testKeepAliveException() throws InterruptedException { 98 | LockFactory lf1 = new LockFactory(HOST, KEYSPACE); 99 | assertNotNull(lf1); 100 | Lock l1 = lf1.getLock("test"); 101 | assertTrue(l1.tryLock()); 102 | 103 | // simulates lock loss due to TTL 104 | session.execute("TRUNCATE lock_leases"); 105 | Thread.sleep(1000); 106 | 107 | l1.keepAlive(); 108 | } 109 | 110 | @Test(expected=LockLeaseLostException.class) 111 | public void testShortTTL() throws InterruptedException { 112 | LockFactory lf1 = new LockFactory(HOST, KEYSPACE); 113 | assertNotNull(lf1); 114 | Lock l1 = lf1.getLock("test", 1); 115 | assertTrue(l1.tryLock()); 116 | 117 | // lock loss due to TTL 118 | Thread.sleep(1500); 119 | 120 | l1.keepAlive(); 121 | } 122 | } -------------------------------------------------------------------------------- /src/main/java/com/dekses/cassandra/lock/LockFactory.java: -------------------------------------------------------------------------------- 1 | package com.dekses.cassandra.lock; 2 | 3 | import java.util.UUID; 4 | 5 | import com.datastax.driver.core.Cluster; 6 | import com.datastax.driver.core.ConsistencyLevel; 7 | import com.datastax.driver.core.PreparedStatement; 8 | import com.datastax.driver.core.Cluster.Builder; 9 | import com.datastax.driver.core.Session; 10 | 11 | /** 12 | * Factory class for lock objects. Encapsulates creation of Cassandra 13 | * based locks. Also provide methods for singleton style usage of 14 | * the factory. 15 | */ 16 | public class LockFactory { 17 | 18 | /** Current Cassandra session */ 19 | private Session session; 20 | 21 | /** Default owner name */ 22 | private String defaultOwner; 23 | 24 | /** Default lock time to live in seconds */ 25 | private int defaultTTL = 60; 26 | 27 | // Prepared CQL statements 28 | private PreparedStatement insertPrep; 29 | private PreparedStatement selectPrep; 30 | private PreparedStatement deletePrep; 31 | private PreparedStatement updatePrep; 32 | 33 | 34 | /** 35 | * Constructor, uses Cassandra session 36 | * @param session External Cassandra session 37 | */ 38 | public LockFactory(Session session) { 39 | this.session = session; 40 | generalInit(); 41 | } 42 | 43 | /** 44 | * Constructor, creates Cassandra session 45 | * @param contactPoints Cassandra cluster contact points 46 | * @param keyspace Keyspace for `lock_leases` 47 | */ 48 | public LockFactory(String contactPoints, String keyspace) { 49 | Builder builder = Cluster.builder(); 50 | for (String point : contactPoints.split(",")) { 51 | builder.addContactPoint(point.trim()); 52 | } 53 | 54 | Cluster cluster = builder.build(); 55 | session = cluster.connect(); 56 | session.execute("USE " + keyspace); 57 | generalInit(); 58 | } 59 | 60 | /** 61 | * Shared initialization method. 62 | * Generates random owner name for the factory. 63 | */ 64 | private void generalInit() { 65 | defaultOwner = UUID.randomUUID().toString(); 66 | insertPrep = session.prepare("INSERT INTO lock_leases (name, owner) VALUES (?,?) IF NOT EXISTS USING TTL ?"); // 67 | insertPrep.setConsistencyLevel(ConsistencyLevel.QUORUM); 68 | selectPrep = session.prepare("SELECT * FROM lock_leases WHERE name = ?"); 69 | selectPrep.setConsistencyLevel(ConsistencyLevel.SERIAL); 70 | deletePrep = session.prepare("DELETE FROM lock_leases WHERE name = ? IF owner = ?"); 71 | deletePrep.setConsistencyLevel(ConsistencyLevel.QUORUM); 72 | updatePrep = session.prepare("UPDATE lock_leases USING TTL ? SET owner = ? WHERE name = ? IF owner = ?"); 73 | updatePrep.setConsistencyLevel(ConsistencyLevel.QUORUM); 74 | } 75 | 76 | /** 77 | * Set new default TTL for locks 78 | * @param ttl New TTL value 79 | */ 80 | public void setDefaultTTL(int ttl) { 81 | defaultTTL = ttl; 82 | } 83 | 84 | /** 85 | * Set new default owner of lock 86 | * @param owner New owner of lock 87 | */ 88 | public void setDefaultOwner(String owner) { 89 | defaultOwner = owner; 90 | } 91 | 92 | 93 | /** 94 | * Lock factory method 95 | * @param resource Unique name of resource to be locked 96 | * @return New lock object 97 | */ 98 | public Lock getLock(final String resource) { 99 | return new CassandraLock(session, defaultOwner, resource, defaultTTL, insertPrep, selectPrep, deletePrep, updatePrep); 100 | } 101 | 102 | /** 103 | * Lock factory method, supports custom owner 104 | * @param resource Unique name of resource to be locked 105 | * @param owner Custom owner of lock 106 | * @return New lock object 107 | */ 108 | public Lock getLock(final String resource, final String owner) { 109 | return new CassandraLock(session, owner, resource, defaultTTL, insertPrep, selectPrep, deletePrep, updatePrep); 110 | } 111 | 112 | /** 113 | * Lock factory method, supports custom TTL 114 | * @param resource Unique name of resource to be locked 115 | * @param ttl Custom TTL value for lock 116 | * @return New lock object 117 | */ 118 | public Lock getLock(final String resource, final int ttl) { 119 | return new CassandraLock(session, defaultOwner, resource, ttl, insertPrep, selectPrep, deletePrep, updatePrep); 120 | } 121 | 122 | /** 123 | * Lock factory method, supports custom owner and TTL 124 | * @param resource Unique name of resource to be locked 125 | * @param owner Custom owner of lock 126 | * @param ttl Custom TTL value for lock 127 | * @return New lock object 128 | */ 129 | public Lock getLock(final String resource, final String owner, final int ttl) { 130 | return new CassandraLock(session, owner, resource, ttl, insertPrep, selectPrep, deletePrep, updatePrep); 131 | } 132 | 133 | 134 | /// --- Singleton implementation below --- /// 135 | 136 | /** Single instance */ 137 | private static LockFactory instance; 138 | 139 | /** 140 | * Singleton initialization 141 | * @param session Cassandra session 142 | */ 143 | public static void initialize(Session session) { 144 | instance = new LockFactory(session); 145 | } 146 | 147 | /** 148 | * Singleton initialization 149 | * @param contactPoints Cassandra cluster contact points 150 | * @param keyspace Keyspace for `lock_leases` 151 | */ 152 | public static void initialize(String contactPoints, String keyspace) { 153 | instance = new LockFactory(contactPoints, keyspace); 154 | } 155 | 156 | /** 157 | * Factory getter, must be called only after singleton initialization. 158 | * @return Single factory instance. 159 | */ 160 | public static LockFactory getInstance() { 161 | return instance; 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 3 | 4.0.0 4 | 5 | com.dekses 6 | cassandra-lock 7 | 0.0.3 8 | jar 9 | 10 | cassandra-lock 11 | Distributed lock based on lightweight transactions in Cassandra database 12 | https://github.com/dekses/cassandra-lock 13 | 14 | 15 | 16 | MIT License 17 | https://github.com/dekses/cassandra-lock/blob/master/LICENSE.txt 18 | 19 | 20 | 21 | 22 | 23 | Yuri Subach 24 | yuri@dekses.com 25 | 26 | 27 | 28 | 29 | scm:git:git://github.com/dekses/cassandra-lock.git 30 | scm:git:ssh://github.com:dekses/cassandra-lock.git 31 | https://github.com/dekses/cassandra-lock/tree/master 32 | 33 | 34 | 35 | 36 | com.datastax.cassandra 37 | cassandra-driver-core 38 | 3.0.0 39 | 40 | 41 | junit 42 | junit 43 | 4.12 44 | test 45 | 46 | 47 | 48 | 49 | 50 | ossrh 51 | https://oss.sonatype.org/content/repositories/snapshots 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.sonatype.plugins 59 | nexus-staging-maven-plugin 60 | 1.6.7 61 | true 62 | 63 | ossrh 64 | https://oss.sonatype.org/ 65 | true 66 | 67 | 68 | 69 | org.apache.maven.plugins 70 | maven-source-plugin 71 | 2.2.1 72 | 73 | 74 | attach-sources 75 | 76 | jar-no-fork 77 | 78 | 79 | 80 | 81 | 82 | org.apache.maven.plugins 83 | maven-javadoc-plugin 84 | 2.9.1 85 | 86 | 87 | attach-javadocs 88 | 89 | jar 90 | 91 | 92 | 93 | 94 | 95 | org.apache.maven.plugins 96 | maven-gpg-plugin 97 | 1.5 98 | 99 | 100 | sign-artifacts 101 | verify 102 | 103 | sign 104 | 105 | 106 | 107 | 108 | 109 | org.apache.maven.plugins 110 | maven-install-plugin 111 | 2.5.2 112 | 113 | 114 | default-install 115 | install 116 | 117 | install 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | org.eclipse.m2e 129 | lifecycle-mapping 130 | 1.0.0 131 | 132 | 133 | 134 | 135 | 136 | 137 | org.apache.maven.plugins 138 | 139 | 140 | maven-install-plugin 141 | 142 | 143 | [2.5.2,) 144 | 145 | 146 | install 147 | 148 | 149 | 150 | 151 | true 152 | false 153 | 154 | 155 | 156 | 157 | 158 | org.apache.maven.plugins 159 | maven-jar-plugin 160 | 3.1.1 161 | 162 | jar 163 | 164 | 165 | 166 | 167 | true 168 | false 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | --------------------------------------------------------------------------------