Skip to content

Commit 0a6b73d

Browse files
committed
mongodb session store fix #63
```java { use(new Mongodb()); session(MongoSessionStore.class); get("/", req -> { req.session().set("name", "jooby"); }); } ``` The ```name``` attribute and value will be stored in a {{mongodb}}. By default, a mongodb session will expire after ```30 minutes```. Changing the default timeout is as simple as: ```properties session.timeout = 8h session.timeout = 15 session.timeout = 120m session.timeout = -1 ``` It uses [MongoDB's TTL](docs.mongodb.org/manual/core/index-ttl) collection feature (2.2+) to have ```mongod``` automatically remove expired sessions. Default {{mongodb}} collection is ```sessions```. It's possible to change the default key setting the ```mongodb.sesssion.collection``` properties.
1 parent 1a7748b commit 0a6b73d

9 files changed

Lines changed: 648 additions & 4 deletions

File tree

jooby-mongodb/README.md

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
[MongoDB](http://mongodb.github.io/mongo-java-driver/) driver for Jooby.
44

5-
Exposes a [MongoClient](http://api.mongodb.org/java/2.13/com/mongodb/MongoClient.html) and a [DB](http://api.mongodb.org/java/2.13/com/mongodb/DB.html)
5+
Exposes a [MongoClient](http://api.mongodb.org/java/2.13/com/mongodb/MongoClient.html), a [DB](http://api.mongodb.org/java/2.13/com/mongodb/DB.html) and a [Session Store](/apidocs/org/jooby/mongodb/MongoSessionStore.html)
66

77
## dependency
88

@@ -101,4 +101,82 @@ Use [named](/apidocs/org/jooby/mongodb/Mongodb.html#-named) when you need two or
101101
}
102102
```
103103

104+
# mongodb session store
105+
106+
## usage
107+
108+
```java
109+
{
110+
use(new Mongodb());
111+
112+
session(MongoSessionStore.class);
113+
114+
get("/", req -> {
115+
req.session().set("name", "jooby");
116+
});
117+
}
118+
```
119+
120+
The ```name``` attribute and value will be stored in a [MongoDB](http://mongodb.github.io/mongo-java-driver/).
121+
122+
## options
123+
124+
### timeout
125+
126+
By default, a mongodb session will expire after ```30 minutes```. Changing the default timeout is as simple as:
127+
128+
```properties
129+
# 8 hours
130+
session.timeout = 8h
131+
132+
# 15 seconds
133+
session.timeout = 15
134+
135+
# 120 minutes
136+
session.timeout = 120m
137+
138+
# no timeout
139+
session.timeout = -1
140+
```
141+
142+
It uses [MongoDB's TTL](docs.mongodb.org/manual/core/index-ttl) collection feature (2.2+) to have ```mongod``` automatically remove expired sessions.
143+
144+
### session collection
145+
146+
Default [MongoDB](http://mongodb.github.io/mongo-java-driver/) collection is ```sessions```.
147+
148+
It's possible to change the default key setting the ```mongodb.sesssion.collection``` properties.
149+
150+
104151
That's all folks! Enjoy it!!
152+
153+
154+
# appendix: mongodb.conf
155+
```properties
156+
###################################################################################################
157+
# mongodb
158+
###################################################################################################
159+
mongodb.connectionsPerHost = 100
160+
mongodb.threadsAllowedToBlockForConnectionMultiplier = 5
161+
mongodb.maxWaitTime = 120s
162+
mongodb.connectTimeout = 10s
163+
mongodb.socketTimeout = 0
164+
mongodb.socketKeepAlive = false
165+
mongodb.cursorFinalizerEnabled = true
166+
mongodb.alwaysUseMBeans = false
167+
mongodb.heartbeatFrequency = 5000
168+
mongodb.minHeartbeatFrequency = 500
169+
mongodb.heartbeatConnectTimeout = 20s
170+
mongodb.heartbeatSocketTimeout = 20s
171+
172+
###################################################################################################
173+
# session datastore
174+
# collection: sessions
175+
# timeout: 30m
176+
###################################################################################################
177+
mongodb.session.collection = sessions
178+
179+
```
180+
181+
182+
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
package org.jooby.mongodb;
2+
3+
import static java.util.Objects.requireNonNull;
4+
5+
import java.util.Date;
6+
import java.util.Map;
7+
import java.util.Optional;
8+
import java.util.concurrent.TimeUnit;
9+
import java.util.concurrent.atomic.AtomicBoolean;
10+
11+
import javax.inject.Inject;
12+
import javax.inject.Named;
13+
14+
import org.jooby.Session;
15+
import org.jooby.Session.Builder;
16+
import org.slf4j.Logger;
17+
import org.slf4j.LoggerFactory;
18+
19+
import com.mongodb.BasicDBObject;
20+
import com.mongodb.BasicDBObjectBuilder;
21+
import com.mongodb.DB;
22+
import com.mongodb.DBCollection;
23+
import com.mongodb.MongoException;
24+
import com.typesafe.config.Config;
25+
import com.typesafe.config.ConfigFactory;
26+
import com.typesafe.config.ConfigValueFactory;
27+
28+
/**
29+
* A {@link Session.Store} powered by
30+
* <a href="http://mongodb.github.io/mongo-java-driver/">Mongodb</a>.
31+
*
32+
* <h2>usage</h2>
33+
*
34+
* <pre>
35+
* {
36+
* use(new Mongodb());
37+
*
38+
* session(MongoSessionStore.class);
39+
*
40+
* get("/", req {@literal ->} {
41+
* req.session().set("name", "jooby");
42+
* });
43+
* }
44+
* </pre>
45+
*
46+
* The <code>name</code> attribute and value will be stored in a
47+
* <a href="http://mongodb.github.io/mongo-java-driver/">Mongodb</a>.
48+
*
49+
* <h2>options</h2>
50+
*
51+
* <h3>timeout</h3>
52+
* <p>
53+
* By default, a mongodb session will expire after <code>30 minutes</code>. Changing the default
54+
* timeout is as simple as:
55+
* </p>
56+
*
57+
* <pre>
58+
* # 8 hours
59+
* session.timeout = 8h
60+
*
61+
* # 15 seconds
62+
* session.timeout = 15
63+
*
64+
* # 120 minutes
65+
* session.timeout = 120m
66+
* </pre>
67+
*
68+
* <p>
69+
* It uses MongoDB's TTL collection feature (2.2+) to have <code>mongod</code> automatically remove
70+
* expired sessions.
71+
* </p>
72+
*
73+
* If no timeout is required, use <code>-1</code>.
74+
*
75+
* <h3>session collection</h3>
76+
* <p>
77+
* Default mongodb collection is <code>sessions</code>.
78+
*
79+
* <p>
80+
* It's possible to change the default key setting the <code>mongodb.sesssion.collection</code>
81+
* properties.
82+
* </p>
83+
*
84+
* @author edgar
85+
* @since 0.5.0
86+
*/
87+
public class MongoSessionStore implements Session.Store {
88+
89+
/** The logging system. */
90+
private final Logger log = LoggerFactory.getLogger(getClass());
91+
92+
protected final DBCollection sessions;
93+
94+
protected final int timeout;
95+
96+
protected final String collection;
97+
98+
private final AtomicBoolean ttlSync = new AtomicBoolean(false);
99+
100+
protected final DB db;
101+
102+
public MongoSessionStore(final DB db, final String collection, final int timeout) {
103+
this.db = requireNonNull(db, "Mongo db is required.");
104+
this.collection = requireNonNull(collection, "Collection is required.");
105+
this.sessions = db.getCollection(collection);
106+
this.timeout = timeout;
107+
}
108+
109+
@Inject
110+
public MongoSessionStore(final DB db,
111+
final @Named("mongodb.session.collection") String collection,
112+
final @Named("session.timeout") String timeout) {
113+
this(db, collection, seconds(timeout));
114+
}
115+
116+
@SuppressWarnings({"unchecked", "rawtypes" })
117+
@Override
118+
public Session get(final Builder builder) {
119+
return Optional.ofNullable(sessions.findOne(builder.sessionId())).map(dbobj -> {
120+
Map session = dbobj.toMap();
121+
122+
Date accessedAt = (Date) session.remove("_accessedAt");
123+
Date createdAt = (Date) session.remove("_createdAt");
124+
Date savedAt = (Date) session.remove("_savedAt");
125+
session.remove("_id");
126+
127+
return builder
128+
.accessedAt(accessedAt.getTime())
129+
.createdAt(createdAt.getTime())
130+
.savedAt(savedAt.getTime())
131+
.set(session)
132+
.build();
133+
}).orElse(null);
134+
}
135+
136+
@Override
137+
public void save(final Session session) {
138+
syncTtl();
139+
140+
BasicDBObjectBuilder ob = BasicDBObjectBuilder.start()
141+
.add("_id", session.id())
142+
.add("_accessedAt", new Date(session.accessedAt()))
143+
.add("_createdAt", new Date(session.createdAt()))
144+
.add("_savedAt", new Date(session.savedAt()));
145+
// dump attributes
146+
session.attributes().forEach((k, v) -> ob.add(k, v));
147+
148+
sessions.save(ob.get());
149+
}
150+
151+
@Override
152+
public void create(final Session session) {
153+
save(session);
154+
}
155+
156+
private void syncTtl() {
157+
if (!ttlSync.get()) {
158+
ttlSync.set(true);
159+
160+
if (timeout <= 0) {
161+
return;
162+
}
163+
164+
try {
165+
log.debug("creating session timeout index");
166+
sessions.createIndex(
167+
new BasicDBObject("_accessedAt", 1),
168+
new BasicDBObject("expireAfterSeconds", timeout)
169+
);
170+
} catch (MongoException ex) {
171+
log.debug("Couldn't update session timeout, we are going to update session timeout", ex);
172+
// TODO: allow to customize? ... not sure
173+
db.command(BasicDBObjectBuilder.start()
174+
.add("collMod", collection)
175+
.add("index", BasicDBObjectBuilder.start()
176+
.add("keyPattern", new BasicDBObject("_accessedAt", 1))
177+
.add("expireAfterSeconds", timeout)
178+
.get()
179+
)
180+
.get()
181+
);
182+
}
183+
}
184+
}
185+
186+
@Override
187+
public void delete(final String id) {
188+
sessions.remove(new BasicDBObject("_id", id));
189+
}
190+
191+
private static int seconds(final String value) {
192+
try {
193+
return Integer.parseInt(value);
194+
} catch (NumberFormatException ex) {
195+
Config config = ConfigFactory.empty()
196+
.withValue("timeout", ConfigValueFactory.fromAnyRef(value));
197+
return (int) config.getDuration("timeout", TimeUnit.SECONDS);
198+
}
199+
}
200+
201+
}

jooby-mongodb/src/main/java/org/jooby/mongodb/Mongodb.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,7 @@ protected void configure(final Env env, final Config config, final Binder binder
190190
MongoClientURI uri = new MongoClientURI(config.getString(db), options);
191191
MongodbManaged mongodb = new MongodbManaged(uri);
192192
String database = uri.getDatabase();
193-
checkArgument(database != null, "No database was set: " + uri);
193+
checkArgument(database != null, "Database not found: " + uri);
194194

195195
binder.bind(key(MongoClient.class, database))
196196
.toProvider(mongodb)

jooby-mongodb/src/main/resources/org/jooby/mongodb/mongodb.conf

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# mongodb default options
1+
###################################################################################################
2+
# mongodb
3+
###################################################################################################
24
mongodb.connectionsPerHost = 100
35
mongodb.threadsAllowedToBlockForConnectionMultiplier = 5
46
mongodb.maxWaitTime = 120s
@@ -11,3 +13,10 @@ mongodb.heartbeatFrequency = 5000
1113
mongodb.minHeartbeatFrequency = 500
1214
mongodb.heartbeatConnectTimeout = 20s
1315
mongodb.heartbeatSocketTimeout = 20s
16+
17+
###################################################################################################
18+
# session datastore
19+
# collection: sessions
20+
# timeout: 30m
21+
###################################################################################################
22+
mongodb.session.collection = sessions

0 commit comments

Comments
 (0)