package org.gcube.application.geoportal.service.engine.providers.ucd; import com.mongodb.MongoServerException; import lombok.extern.slf4j.Slf4j; import org.gcube.application.cms.caches.AbstractScopedMap; import org.gcube.application.cms.caches.Engine; import org.gcube.application.cms.caches.ObjectManager; import org.gcube.application.cms.implementations.ImplementationProvider; import org.gcube.application.cms.implementations.utils.UserUtils; import org.gcube.application.cms.serialization.Serialization; import org.gcube.application.geoportal.common.model.rest.ConfigurationException; import org.gcube.application.geoportal.common.model.rest.QueryRequest; import org.gcube.application.geoportal.common.model.useCaseDescriptor.UseCaseDescriptor; import org.gcube.application.geoportal.common.utils.ContextUtils; import org.gcube.application.geoportal.service.engine.mongo.UCDManagerI; import org.gcube.application.geoportal.service.engine.mongo.UCDMongoManager; import org.gcube.application.cms.implementations.faults.RegistrationException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Response; import java.time.Duration; import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.Map; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import static java.lang.Thread.sleep; /** * Provider of UCDManagerI (this) * * - nb on get should check / trigger cache refresh TTL * - uses UCDMongoManager for storing * - profile live map engine is discovered * * * */ @Slf4j public class UCDManager extends AbstractScopedMap implements UCDManagerI { private Map memCache = new HashMap<>(); private final ReadWriteLock cacheLock = new ReentrantReadWriteLock(); public UCDManager() { super("UCD MANAGER"); setTTL(Duration.of(2, ChronoUnit.MINUTES)); } @Override public Iterable query(QueryRequest queryRequest) throws ConfigurationException { cacheLock.readLock().lock(); try{ return getMongoManager().query(queryRequest); }finally { cacheLock.readLock().unlock(); } } @Override public void deleteById(String id, boolean force) throws RegistrationException, ConfigurationException { // NB Check for existing ID UseCaseDescriptor found = getByUCID(id,true); cacheLock.writeLock().lock(); try{ if(found!=null) { // TODO validate DELETE REQUEST // TODO DELETE UCD // forceUpdateCache(); throw new WebApplicationException("TO IMPLEMENT ", Response.Status.INTERNAL_SERVER_ERROR); } else throw new WebApplicationException("No Matching UCD with ID "+id, Response.Status.NOT_FOUND); }finally{ cacheLock.writeLock().unlock(); } } @Override public UseCaseDescriptor put(UseCaseDescriptor desc) throws ConfigurationException, RegistrationException { log.info("Update UCD {} ",desc.getId()); log.debug("Mongo id is {} ",desc.getMongoId()); // NB Check for existing ID UseCaseDescriptor found = getByUCID(desc.getId(),true); if(found!=null) { // TODO validate UPDATE // TODO STORE UCD // forceUpdateCache(); throw new WebApplicationException("Update Feature is yet TO IMPLEMENT ", Response.Status.INTERNAL_SERVER_ERROR); } else { // create new registerNew(desc); forceUpdateCache(); int attempt=0; do{ found =getByUCID(desc.getId(),true); log.info("Waiting for backend to update.. "); try{sleep(4000);}catch (Throwable t){} attempt++; } while(found == null && attempt<=4); return found; } } @Override protected UCDManagerI retrieveObject(String context) throws ConfigurationException { // Called when TTL ends forceUpdateCache(); return this; } @Override public UseCaseDescriptor getById(String id) throws ConfigurationException, RegistrationException { // GET from mongo cache // UCDMongoManager mongo=getMongoManager(); // UseCaseDescriptor toReturn=mongo.getById(id); // log.debug("UCD ID : {} from mongo is {} ",id,toReturn); // if(toReturn == null) { // // IF void try from ProfileEngine // toReturn =getLiveMap().get(id); // if(toReturn != null ){ // log.debug("Force update of live map {} from live map ",id); // toReturn = mongo.put(toReturn); // } // } // return toReturn; return getByUCID(id,true); } private UseCaseDescriptor getByUCID(String ucid,boolean refreshOnMissing) throws ConfigurationException { // Wait / check for cache loaded log.debug("Trying to getById from memcache {} [refresh on missing : {}]",ucid,refreshOnMissing); cacheLock.readLock().lock(); boolean releaseLock = true; UseCaseDescriptor toReturn =null; try{ if(memCache.containsKey(ucid)) toReturn = memCache.get(ucid); else { if(refreshOnMissing){ cacheLock.readLock().unlock(); releaseLock=false; forceUpdateCache(); // pass false in order to trigger cache update only on first NOT FOUND event toReturn = getByUCID(ucid,false); } } }finally { if(releaseLock) cacheLock.readLock().unlock(); } return toReturn; } private void registerNew(UseCaseDescriptor ucd) throws ConfigurationException { cacheLock.writeLock().lock(); try{ Engine engine=ImplementationProvider.get().getEngineByManagedClass(ProfileMap.class); if(engine instanceof ObjectManager){ ((ObjectManager)engine).insert(ucd); } else throw new ConfigurationException("Profile Map Engine is not Object Manager. Actual implementation is "+engine.getClass()); }finally { cacheLock.writeLock().unlock(); } } private UCDMongoManager getMongoManager() throws ConfigurationException { return new UCDMongoManager(); } private void forceUpdateCache() throws ConfigurationException { log.info("UPDATING PROFILE CACHE.."); cacheLock.writeLock().lock(); final UCDMongoManager manager = getMongoManager(); manager.deleteAll(); memCache.clear(); Engine liveMapProvider =ImplementationProvider.get().getEngineByManagedClass(ProfileMap.class); log.trace("LiveMap Provider class is {} ",liveMapProvider.getClass()); ProfileMap liveMap= liveMapProvider.getObject(); log.debug("LiveMap size is {} ",liveMap.size()); for (Map.Entry entry : liveMap.entrySet()) { // Copying object so we don't alter live Map UseCaseDescriptor useCaseDescriptor = Serialization.convert(entry.getValue(),UseCaseDescriptor.class); try { log.debug("Updating cache with {}, mongo id is {}", useCaseDescriptor.getId(),useCaseDescriptor.getMongoId()); if(useCaseDescriptor.getMongoId()!=null){ log.warn("Retrieved UCD {} from Live Map has a Mongo id [{}]. Removing it..",useCaseDescriptor.getId(),useCaseDescriptor.getMongoId()); useCaseDescriptor.setMongoId(null); } // insert/update into DB manager.insert(useCaseDescriptor); memCache.put(useCaseDescriptor.getId(), useCaseDescriptor); } catch (RegistrationException | MongoServerException e) { log.warn("Unable to cache UCD {}",entry.getKey(),e); } } String context = UserUtils.getCurrent().getContext(); log.info("Cached {} UCDs in {} ",memCache.size(),context); cacheLock.writeLock().unlock(); } }