/**
* 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 org.jooby.couchbase;
import static java.util.Objects.requireNonNull;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import javax.inject.Named;
import org.jooby.Session;
import org.jooby.Session.Builder;
import com.couchbase.client.java.Bucket;
import com.couchbase.client.java.document.JsonDocument;
import com.couchbase.client.java.document.json.JsonObject;
import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;
import com.typesafe.config.ConfigValueFactory;
/**
* <h1>couchbase session store</h1>
* <p>
* A {@link Session.Store} powered by <a href="http://www.couchbase.com">Couchbase</a>.
* </p>
*
* <h2>usage</h2>
*
* <pre>{@code
* {
* use(new Couchbase("couchbase://localhost/bucket"));
*
* session(CouchbaseSessionStore.class);
*
* get("/", req -> {
* Session session = req.session();
* session.put("foo", "bar");
*
* ..
* });
* }
* }</pre>
*
* <p>
* Session data is persisted in Couchbase and document looks like:
* </p>
*
* <pre>{@code
* {
* "session::{SESSION_ID}": {
* "foo": "bar"
* }
* }
* }</pre>
*
* <h2>options</h2>
*
* <h3>timeout</h3>
* <p>
* By default, a session will expire after <code>30 minutes</code>. Changing the default timeout is
* as simple as:
* </p>
*
* <pre>
* # 8 hours
* session.timeout = 8h
*
* # 15 seconds
* session.timeout = 15
*
* # 120 minutes
* session.timeout = 120m
* </pre>
*
* <p>
* Expiration is done via Couchbase expiry/ttl option.
* </p>
*
* <p>
* If no timeout is required, use <code>-1</code>.
* </p>
*
* <h3>custom bucket</h3>
* <p>
* The session document are persisted in the application/default bucket, if you need/want a
* different bucket then use {@link Couchbase#sessionBucket(String)}, like:
* </p>
*
* <pre>{@code
* {
* use(
* new Couchbase("couchbase://localhost/myapp")
* .sessionBucket("session")
* );
* }
* }</pre>
*
* @author edgar
* @since 1.0.0.CR7
*/
@SuppressWarnings({"unchecked", "rawtypes" })
public class CouchbaseSessionStore implements Session.Store {
private static final String SESSION = "session";
private Bucket bucket;
private int expiry;
public CouchbaseSessionStore(final Bucket bucket, final int timeout) {
this.bucket = requireNonNull(bucket, "Bucket is required.");
this.expiry = timeout > 0 ? timeout : 0;
}
@Inject
public CouchbaseSessionStore(final @Named(SESSION) Bucket bucket,
final @Named("session.timeout") String timeout) {
this(bucket, seconds(timeout));
}
@Override
public Session get(final Builder builder) {
return Optional
.ofNullable(bucket.getAndTouch(N1Q.qualifyId(SESSION, builder.sessionId()), expiry))
.map(doc -> {
Map session = doc.content().toMap();
Long accessedAt = (Long) session.remove("_accessedAt");
Long createdAt = (Long) session.remove("_createdAt");
Long savedAt = (Long) session.remove("_savedAt");
return builder
.accessedAt(accessedAt)
.createdAt(createdAt)
.savedAt(savedAt)
.set(session)
.build();
}).orElse(null);
}
@Override
public void save(final Session session) {
JsonObject json = JsonObject.from(session.attributes());
// session metadata
json.put("_accessedAt", session.accessedAt());
json.put("_createdAt", session.createdAt());
json.put("_savedAt", session.savedAt());
JsonDocument doc = JsonDocument.create(N1Q.qualifyId(SESSION, session.id()), expiry, json);
bucket.upsert(doc);
}
@Override
public void create(final Session session) {
save(session);
}
@Override
public void delete(final String id) {
bucket.remove(N1Q.qualifyId(SESSION, id));
}
private static int seconds(final String value) {
try {
return Integer.parseInt(value);
} catch (NumberFormatException ex) {
Config config = ConfigFactory.empty()
.withValue("timeout", ConfigValueFactory.fromAnyRef(value));
return (int) config.getDuration("timeout", TimeUnit.SECONDS);
}
}
}