package org.concord.otrunk.overlay;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.concord.framework.otrunk.OTID;
import org.concord.framework.otrunk.OTObject;
import org.concord.framework.otrunk.OTObjectService;
import org.concord.framework.otrunk.wrapper.OTObjectSet;
import org.concord.otrunk.OTObjectServiceImpl;
import org.concord.otrunk.OTrunkImpl;
import org.concord.otrunk.datamodel.OTDatabase;
import org.concord.otrunk.net.HTTPRequestException;
import org.concord.otrunk.user.OTUserObject;
import org.concord.otrunk.xml.XMLDatabase;
public class OTUserMappedOverlayManager
extends OTUserOverlayManager
{
private static final Logger logger = Logger.getLogger(OTUserMappedOverlayManager.class.getName());
public OTUserMappedOverlayManager(OTrunkImpl otrunk)
{
super(otrunk);
OTDatabase db;
try {
OTOverlay overlay = otrunk.createObject(OTOverlay.class);
db = new CompositeDatabase(otrunk.getDataObjectFinder(), new OverlayImpl(overlay));
} catch (Exception e) {
db = new XMLDatabase();
}
tempObjService = otrunk.createObjectService(db);
}
private HashMap<OTUserObject, OTObjectToOverlayReferenceMap> userToOverlayReferenceMaps = new HashMap<OTUserObject, OTObjectToOverlayReferenceMap>();
private OTObjectService tempObjService;
/**
*
* @param user
* @param original
* @param reference - if null, gets the most recent entry
* @return
*/
@SuppressWarnings("unchecked")
public <T extends OTObject> T getOTObject(OTUserObject user, T original, OTOverlayReference reference) {
writeLock();
try {
OTID authoredId = getAuthoredId(original);
if (reference == null) {
reference = findLastReference(user, original);
}
if (reference != null) {
URL overlayURL = reference.getOverlayURL();
if (reference.getCanReload() && overlayToObjectServiceMap.containsKey(overlayURL)) {
try {
if (doesUrlNeedReloaded(overlayURL)) {
removeReference(overlayURL);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
removeReference(overlayURL);
}
}
if (! overlayToObjectServiceMap.containsKey(overlayURL)) {
loadOverlay(overlayURL, false);
}
OTObjectService objService = overlayToObjectServiceMap.get(overlayURL);
try {
return (T) objService.getOTObject(authoredId);
} catch (Exception e) {
logger.log(Level.SEVERE, "Can't load object (" + authoredId + ") from object service with url: " + overlayURL, e);
}
}
try {
return (T) tempObjService.getOTObject(authoredId);
} catch (Exception e) {
logger.log(Level.SEVERE, "Can't load object (" + authoredId + ") from temp object service", e);
}
return null;
} finally {
writeUnlock();
}
}
private OTOverlayReference findLastReference(OTUserObject user, OTObject object) {
readLock();
try {
if (userToOverlayReferenceMaps.containsKey(user)) {
OTID authoredId = getAuthoredId(object);
OTObjectToOverlayReferenceMap referenceMap = userToOverlayReferenceMaps.get(user);
OTObjectSet set = (OTObjectSet) referenceMap.getObjectToOverlayMap().getObject(authoredId.toExternalForm());
if (set == null || set.getObjects().size() < 1) {
return null;
}
return (OTOverlayReference) set.getObjects().get(set.getObjects().size()-1);
}
return null;
} finally {
readUnlock();
}
}
@Override
public void addReadOnly(URL referenceMapURL, OTUserObject userObject, boolean isGlobal)
throws Exception
{
writeLock();
try {
OTObjectToOverlayReferenceMap referenceMap = loadRemoteObject(referenceMapURL, OTObjectToOverlayReferenceMap.class);
userToOverlayReferenceMaps.put(userObject, referenceMap);
userToOverlayMap.put(userObject, referenceMapURL);
readOnlyUsers.add(userObject);
} finally {
writeUnlock();
}
}
@Override
public void addWriteable(URL referenceMapURL, OTUserObject userObject, boolean isGlobal)
throws Exception
{
writeLock();
try {
OTObjectToOverlayReferenceMap referenceMap = loadRemoteObject(referenceMapURL, OTObjectToOverlayReferenceMap.class);
userToOverlayReferenceMaps.put(userObject, referenceMap);
userToOverlayMap.put(userObject, referenceMapURL);
writeableUsers.add(userObject);
} finally {
writeUnlock();
}
}
@Override
public <T extends OTObject> T getOTObject(OTUserObject userObject, T object) throws Exception
{
return getOTObject(userObject, object, null);
}
@Override
protected OTObjectService getObjectService(OTUserObject userObject, OTObject object)
{
OTObject authoredObject = getAuthoredObject(object);
return getOTObject(userObject, authoredObject, null).getOTObjectService();
}
@Override
protected Set<OTUserObject> getAllUsers()
{
readLock();
try {
return userToOverlayReferenceMaps.keySet();
} finally {
readUnlock();
}
}
@Override
public void remove(OTUserObject userObject)
{
writeLock();
try {
userToOverlayReferenceMaps.remove(userObject);
super.remove(userObject);
} finally {
writeUnlock();
}
}
private void removeReference(URL referenceURL) {
writeLock();
try {
OTObjectService objService = overlayToObjectServiceMap.get(referenceURL);
otrunk.removeObjectService((OTObjectServiceImpl) objService);
overlayToObjectServiceMap.remove(referenceURL);
} finally {
writeUnlock();
}
}
@Override
public void reload(OTUserObject userObject) throws Exception
{
writeLock();
try {
if (! readOnlyUsers.contains(userObject)) {
return;
}
OTObjectToOverlayReferenceMap otObjectToOverlayReferenceMap = userToOverlayReferenceMaps.get(userObject);
if (otObjectToOverlayReferenceMap == null) {
return;
}
XMLDatabase xmlDb = getXMLDatabase(otObjectToOverlayReferenceMap.getOTObjectService());
if (doesDbNeedReloaded(xmlDb)) {
logger.info("Reloading database: " + xmlDb.getSourceURL());
remove(userObject);
addReadOnly(xmlDb.getSourceURL(), userObject, false);
// TODO we could potentially also figure out which object changed and include that in our notification
notifyListeners(userObject);
}
} finally {
writeUnlock();
}
}
@Override
public void remoteSave(OTUserObject user, OTObject object) throws Exception
{
writeLock();
try {
if (! writeableUsers.contains(user)) {
return;
}
incrementSubmitCount(object);
OTOverlayReference ref = spawnOverlay(user, object);
addReferenceToMap(user, object, ref);
} finally {
writeUnlock();
}
}
private void addReferenceToMap(OTUserObject user, OTObject object, OTOverlayReference ref) throws HTTPRequestException, Exception
{
writeLock();
try {
// add overlay reference to the user's overlay reference map
OTID authoredId = getAuthoredId(object);
OTObjectToOverlayReferenceMap map = userToOverlayReferenceMaps.get(user);
OTObjectSet otObjectSet = (OTObjectSet)map.getObjectToOverlayMap().getObject(authoredId.toExternalForm());
if (otObjectSet == null) {
otObjectSet = map.getOTObjectService().createObject(OTObjectSet.class);
map.getObjectToOverlayMap().putObject(authoredId.toExternalForm(), otObjectSet);
}
if (! otObjectSet.getObjects().contains(ref)) {
otObjectSet.getObjects().add(ref);
// save overlay reference database
actualRemoteSave(map.getOTObjectService());
}
} finally {
writeUnlock();
}
}
private OTOverlayReference spawnOverlay(OTUserObject user, OTObject object) throws Exception {
writeLock();
try {
// generate next overlay url
OTOverlayReference overlayRef = generateNextOverlayReference(user, object);
// create overlay for object
OTObject obj = getOTObject(user, object, overlayRef);
OTObjectService objService = obj.getOTObjectService();
// copy object into overlay
if (copyObjectIntoOverlay(user, object, obj)) {
// save overlay
actualRemoteSave(objService);
}
return overlayRef;
} finally {
writeUnlock();
}
}
private OTOverlayReference generateNextOverlayReference(OTUserObject user, OTObject object)
{
readLock();
try {
OTID authoredId = getAuthoredId(object);
OTOverlayReference lastRef = findLastReference(user, object);
int number = 1;
if (lastRef != null) {
number = lastRef.getNumber()+1;
}
String suffix = "-" + authoredId.toExternalForm() + "-" + number;
suffix = suffix.replaceAll("[^a-zA-Z0-9\\-]+", "-") + ".otml";
OTOverlayReference newRef;
try {
newRef = userToOverlayReferenceMaps.get(user).getOTObjectService().createObject(OTOverlayReference.class);
} catch (Exception e1) {
e1.printStackTrace();
return null;
}
newRef.setNumber(number);
URL url = null;
try {
// create as URI first so that it can do any escaping of invalid url chars when it converts to a URL
URL mapUrl = userToOverlayMap.get(user);
URI referenceUri = new URI(mapUrl.toExternalForm().replaceFirst("\\.otml", suffix));
url = referenceUri.toURL();
} catch (Exception e) {
e.printStackTrace();
return null;
}
newRef.setOverlayURL(url);
return newRef;
} finally {
readUnlock();
}
}
}