├── .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 |
--------------------------------------------------------------------------------