dk.grinn.keycloak.migration.boundary.RealmHistoryLockController Maven / Gradle / Ivy
package dk.grinn.keycloak.migration.boundary;
/*-
* #%L
* Keycloak : Migrate : Spi
* %%
* Copyright (C) 2019 Jonas Grann & Kim Jersin
* %%
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
* #L%
*/
import dk.grinn.keycloak.migration.services.RealmHistoryLockService;
import dk.grinn.keycloak.migration.entities.RealmHistory;
import dk.grinn.keycloak.migration.entities.RealmHistoryLock;
import dk.grinn.keycloak.migration.entities.Session;
import java.nio.ByteBuffer;
import static java.util.Arrays.asList;
import java.util.List;
import java.util.UUID;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import static javax.persistence.LockModeType.PESSIMISTIC_WRITE;
import javax.persistence.NoResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PessimisticLockException;
import javax.persistence.Query;
import javax.persistence.TypedQuery;
/**
*
* @author @author Kim Jersin <[email protected]>
*/
public class RealmHistoryLockController implements RealmHistoryLockService {
private static final Integer HISTORY_LOCK_MASTER_ID = 1;
protected EntityManager em;
@Inject
public RealmHistoryLockController(EntityManager em) {
this.em = em;
}
/**
* Get all history locks.
*
* @return
*/
public List getAll() {
TypedQuery q = em.createNamedQuery("RealmHistoryLock.findAll", RealmHistoryLock.class);
return q.getResultList();
}
/**
* Get all history locks as sessions.
*
* @param onlyActive
* @return
*/
public List getSessions(boolean onlyActive) {
TypedQuery q = em.createNamedQuery("RealmHistoryLock.findSessions", Session.class);
q.setParameter("onlyActive", onlyActive);
List result = q.getResultList();
return result;
}
/**
*
* @param session
* @return
* @throws NoResultException
*/
public RealmHistoryLock get(UUID session) {
TypedQuery q = em.createNamedQuery("RealmHistoryLock.findBySession", RealmHistoryLock.class);
q.setParameter("session", getBinary(session));
return q.getSingleResult();
}
/**
*
* @param realm
* @param lockUser
* @return
* @throws OptimisticLockException Realm all ready in session use.
* @throws PessimisticLockException Could not lock the master entry (30
* seconds timeout).
*/
@Override
public UUID createSession(String realm, String lockUser) {
// Need exclusive access to the whole history while creating the session
lockMaster(lockUser);
// Create or update session
TypedQuery q = em.createNamedQuery(
"RealmHistoryLock.findByRealm", RealmHistoryLock.class
);
q.setParameter("realm", realm);
List realmLocks = q.getResultList();
RealmHistoryLock realmLock;
if (realmLocks.size() == 1) {
realmLock = realmLocks.get(0);
if (realmLock.getSession() != null) {
throw new OptimisticLockException("Realm allready locked by: " + realmLock.getLockedBy());
}
realmLock.setSession(lockUser);
} else {
// The lock entry
realmLock = new RealmHistoryLock(realm, lockUser);
em.persist(realmLock);
// The first migration -
realmLock.getHistory().add(
new RealmHistory(
realmLock, asList(realm), "<>", lockUser
)
);
}
em.merge(realmLock);
em.flush();
return realmLock.getSession();
}
/**
* Release the session.
*
* @param session
* @return
*/
@Override
public int releaseSession(UUID session) {
Query stmt = em.createNamedQuery("RealmHistoryLock.releaseSession");
stmt.setParameter("session", getBinary(session));
int result = stmt.executeUpdate();
em.clear();
return result;
}
/**
* Abort any session on the realm.
*
* Only to be used in an abnormal situation where the session id has been
* lost (caused by an error on a previous run, etc.).
*
* @param realm
* @param abortedBy
* @return
*/
@Override
public int abortSession(String realm, String abortedBy) {
Query stmt = em.createNamedQuery("RealmHistoryLock.releaseSessionByRealm");
stmt.setParameter("realm", realm);
int result = stmt.executeUpdate();
em.clear();
return result;
}
/**
* {@inheritDoc}
*/
@Override
public void removeSession(UUID sessionId) {
em.remove(get(sessionId));
}
/**
* Get a write lock on the master history lock element.
*
* @param lockUser
* @return
* @throws NoResultException If no master entry is found.
* @throws PessimisticLockException Didn't get the lock (30 seconds
* timeout).
*/
public RealmHistoryLock lockMaster(String lockUser) {
RealmHistoryLock result = em.find(RealmHistoryLock.class, HISTORY_LOCK_MASTER_ID, PESSIMISTIC_WRITE);
if (result == null) {
throw new NoResultException("Unable to find the master history lock element");
}
return result;
}
public static byte[] getBinary(UUID id) {
ByteBuffer bb = ByteBuffer.allocate(16);
bb.putLong(id.getMostSignificantBits());
bb.putLong(id.getLeastSignificantBits());
return bb.array();
}
}