
rapture.kernel.SysApiImpl Maven / Gradle / Ivy
/**
* The MIT License (MIT)
*
* Copyright (c) 2011-2016 Incapture Technologies LLC
*
* 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.
*/
package rapture.kernel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import rapture.common.CallingContext;
import rapture.common.ChildrenTransferObject;
import rapture.common.ConnectionInfo;
import rapture.common.NodeEnum;
import rapture.common.RaptureFolderInfo;
import rapture.common.RaptureIdGenConfig;
import rapture.common.RaptureURI;
import rapture.common.Scheme;
import rapture.common.api.SysApi;
import rapture.common.connection.ConnectionInfoConfigurer;
import rapture.common.connection.ESConnectionInfoConfigurer;
import rapture.common.connection.MongoConnectionInfoConfigurer;
import rapture.common.connection.PostgresConnectionInfoConfigurer;
import rapture.common.connection.ConnectionType;
import rapture.common.dp.Workflow;
import rapture.common.exception.RaptureException;
import rapture.common.exception.RaptureExceptionFactory;
import rapture.common.impl.jackson.JacksonUtil;
import rapture.common.model.RaptureEntitlement;
import rapture.common.model.RaptureEntitlementGroup;
import rapture.config.MultiValueConfigLoader;
import rapture.kernel.sys.SysArea;
import rapture.repo.Repository;
import rapture.util.IDGenerator;
import com.fasterxml.jackson.core.type.TypeReference;
/**
* Low level config settings manipulation
*
* @author amkimian
*/
public class SysApiImpl extends KernelBase implements SysApi {
private static Map configurers =
ImmutableMap.of(
ConnectionType.MONGODB, new MongoConnectionInfoConfigurer(),
ConnectionType.POSTGRES, new PostgresConnectionInfoConfigurer(),
ConnectionType.ES, new ESConnectionInfoConfigurer()
);
Repository ephemeralRepo;
// Exposed For Testing
Thread cacheThread = null;
boolean allowExpireThread = true;
Set caches = new HashSet<>();
Long timeToLive = 60 * 60 * 1000L;
Long cacheRefresh = 5 * 60 * 1000L;
public SysApiImpl(Kernel raptureKernel) {
super(raptureKernel);
ephemeralRepo = getEphemeralRepo();
String ttlStr = StringUtils.trimToNull(MultiValueConfigLoader.getConfig("Cache-TimeToLive"));
if (ttlStr != null) {
try {
timeToLive = Long.parseLong(ttlStr);
} catch (NumberFormatException e) {
log.debug("Cannot parse "+ttlStr+" as long");
}
}
String cacheStr = StringUtils.trimToNull(MultiValueConfigLoader.getConfig("Cache-Refresh"));
if (cacheStr != null) {
try {
cacheRefresh = Long.parseLong(cacheStr);
} catch (NumberFormatException e) {
log.debug("Cannot parse "+cacheStr+" as long");
}
}
cacheThread = new Thread(new CacheExpirationThread(ContextFactory.getKernelUser()));
cacheThread.start();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
allowExpireThread = false;
cacheThread.interrupt();
}
static Logger log = Logger.getLogger(SysApiImpl.class);
private Repository getRepoFromArea(SysArea area) {
switch (area) {
case CONFIG:
return getConfigRepo();
default:
log.error("This statement should not be reachable. All cases have been specified");
case SETTINGS:
case PRIVATE:
return getSettingsRepo();
case EPHEMERAL:
return getEphemeralRepo();
case BOOTSTRAP:
return getBootstrapRepo();
}
}
private SysArea sysAreaFromArea(String area) {
return SysArea.valueOf(area.toUpperCase());
}
private String transformPathForArea(SysArea sysArea, String path) {
switch (sysArea) {
case PRIVATE:
return "private/" + path;
default:
return path;
}
}
@Override
public String retrieveSystemConfig(CallingContext context, String area, String path) {
// All we do here is access the config repo and return the
// information requested
log.info("Retrieving system config document - " + path);
SysArea sysArea = sysAreaFromArea(area);
return getRepoFromArea(sysArea).getDocument(transformPathForArea(sysArea, path));
}
@Override
public String writeSystemConfig(CallingContext context, String area, String path, String content) {
log.info("Writing system config document - " + path);
SysArea sysArea = sysAreaFromArea(area);
getRepoFromArea(sysArea).addDocument(transformPathForArea(sysArea, path), content, context.getUser(), "Saving system config", false);
return content;
}
@Override
public void removeSystemConfig(CallingContext context, String area, String path) {
log.info("Removing system config document - " + path);
SysArea sysArea = sysAreaFromArea(area);
getRepoFromArea(sysArea).removeDocument(transformPathForArea(sysArea, path), context.getUser(), "Removed system document");
}
@Override
public List getSystemFolders(CallingContext context, String area, String path) {
SysArea sysArea = sysAreaFromArea(area);
LowLevelRepoHelper helper = new LowLevelRepoHelper(getRepoFromArea(sysArea));
return helper.getChildren(path);
}
private static final String root = "//";
private static final String blobParent = Scheme.BLOB + ":" + root;
private static final String docParent = Scheme.DOCUMENT + ":" + root;
private static final String scriptParent = Scheme.SCRIPT + ":" + root;
private static final String seriesParent = Scheme.SERIES + ":" + root;
private static final String flowParent = Scheme.WORKFLOW + ":" + root;
private static final String entParent = Scheme.ENTITLEMENT + ":" + root;
private static final String entGrpParent = Scheme.ENTITLEMENTGROUP + ":" + root;
// Currently can't list users
// private static final String userParent = Scheme.USER + ":" + root;
private static final String jobParent = Scheme.JOB + ":" + root;
private static final String fountainParent = Scheme.IDGEN + ":" + root;
@Override
public List getAllTopLevelRepos(CallingContext context) {
List ret = new ArrayList<>();
// What about users? They show up under entitlements. Should we allow browsing of them?
// Anything else?
ret.add(blobParent);
ret.add(docParent);
ret.add(entParent);
ret.add(entGrpParent);
ret.add(flowParent);
ret.add(fountainParent);
ret.add(jobParent);
ret.add(scriptParent);
ret.add(seriesParent);
// Currently can't list users
// ret.add(userParent);
return ret;
}
@Override
public NodeEnum getFolderInfo(CallingContext context, String uri) {
RaptureURI ruri = new RaptureURI(uri);
if (!ruri.hasScheme()) return NodeEnum.NOT_VALID;
Map map = findByUri(context, ruri.getParent(), 2);
RaptureFolderInfo node = map.get(uri);
RaptureFolderInfo folder = map.get(uri + "/");
if (node == null) return (folder == null) ? NodeEnum.NOT_PRESENT : NodeEnum.FOLDER_ONLY;
else return (folder == null) ? NodeEnum.OBJECT_ONLY : NodeEnum.OBJECT_AND_FOLDER;
}
boolean depthCheck(String str, int depth) {
if (StringUtils.isEmpty(str)) return false;
String[] split = str.split("/+"); // ignore empty path elements
int i = split.length;
if ((i > 0) && StringUtils.isEmpty(split[0])) i--;
return ((i > 0) && (i <= depth));
}
private Map findByUri(CallingContext context, String uri, int depth) {
Map children = new HashMap<>();
if (uri.startsWith(Scheme.ENTITLEMENTGROUP.toString())) {
List entGrps = Kernel.getEntitlement().getEntitlementGroups(context);
if ((entGrps != null) && !entGrps.isEmpty()) {
for (RaptureEntitlementGroup ent : entGrps) {
int urilen = uri.length();
String path = ent.getAddressURI().toString();
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
if (!path.startsWith(uri) || path.equals(uri)) continue;
int i = path.lastIndexOf('/');
String name = path.substring(i + 1);
if (depthCheck(path.substring(urilen), depth)) children.put(path, new RaptureFolderInfo(name, true));
do {
path = path.substring(0, i + 1);
i = path.lastIndexOf('/', i - 1);
if ((i + 1) < urilen) break;
name = path.substring(i + 1, path.length() - 1);
if (depthCheck(path.substring(urilen), depth)) children.put(path, new RaptureFolderInfo(name, false));
} while (true);
}
}
} else if (uri.startsWith(Scheme.ENTITLEMENT.toString())) {
List ents = Kernel.getEntitlement().getEntitlements(context);
if ((ents != null) && !ents.isEmpty()) {
for (RaptureEntitlement ent : ents) {
int urilen = uri.length();
String path = ent.getAddressURI().toString();
if (path.endsWith("/")) path = path.substring(0, path.length() - 1);
if (!path.startsWith(uri) || path.equals(uri)) continue;
int i = path.lastIndexOf('/');
String name = path.substring(i + 1);
if (depthCheck(path.substring(urilen), depth)) children.put(path, new RaptureFolderInfo(name, true));
do {
path = path.substring(0, i + 1);
i = path.lastIndexOf('/', i - 1);
if ((i + 1) < urilen) break;
name = path.substring(i + 1, path.length() - 1);
if (depthCheck(path.substring(urilen), depth)) children.put(path, new RaptureFolderInfo(name, false));
} while (true);
}
}
} else if (uri.startsWith(Scheme.BLOB.toString())) {
children = Kernel.getBlob().listBlobsByUriPrefix(context, uri, depth);
} else if (uri.startsWith(Scheme.DOCUMENT.toString())) {
children = Kernel.getDoc().listDocsByUriPrefix(context, uri, depth);
} else if (uri.startsWith(Scheme.SCRIPT.toString())) {
children = Kernel.getScript().listScriptsByUriPrefix(context, uri, depth);
} else if (uri.startsWith(Scheme.SERIES.toString())) {
children = Kernel.getSeries().listSeriesByUriPrefix(context, uri, depth);
} else if (uri.startsWith(Scheme.WORKFLOW.toString())) {
List flows = Kernel.getDecision().getAllWorkflows(context);
for (Workflow flow : flows) {
RaptureURI furi = flow.getAddressURI();
String str = furi.toString();
children.put(str, new RaptureFolderInfo(str.substring(str.indexOf(':') + 3), false));
}
} else if (uri.startsWith(Scheme.JOB.toString())) {
List jobs = Kernel.getSchedule().getJobs(context);
for (String job : jobs) {
children.put(job, new RaptureFolderInfo(job.substring(job.indexOf(':') + 3), false));
}
} else if (uri.startsWith(Scheme.IDGEN.toString())) {
List ids = Kernel.getIdGen().getIdGenConfigs(context, uri);
for (RaptureIdGenConfig id : ids) {
RaptureURI furi = id.getAddressURI();
String str = furi.toString();
children.put(str, new RaptureFolderInfo(str.substring(str.indexOf(':') + 3), false));
}
}
return children;
}
// Exposed For Testing
private static final char SEPARATOR = '_';
private static final String PENDING = "_pending";
private static final String WRITTEN = "_written";
/**
* Get children from the current point.
*
* @param context
* @param uri
* @param marker
* @param recursive
* @param maximum
* @param timeToLive
* @return
*/
@Override
public ChildrenTransferObject listByUriPrefix(CallingContext context, String uri, String marker, int depth, Long maximum, Long timeToLive) {
ChildrenTransferObject cto = new ChildrenTransferObject();
cto.setRemainder(new Long(0));
Long count = 0L;
Map children;
Map pending = new HashMap<>();
Map written = new HashMap<>();
Map deleted = new HashMap<>();
String locMarker = marker;
Long locMax = maximum;
Long locTTL = (timeToLive > 0) ? timeToLive : this.timeToLive;
Boolean refresh = (marker == null); // if marker is null we have to
/**
* If there are more results than the defined maximum then store the remainder in the cache for quick access next time
*/
// Do we already have a partial read? Ensure that the marker is still valid
if (marker != null) {
TypeReference> typeRef = new TypeReference>() {
};
String pendingContent = ephemeralRepo.getDocument(marker+PENDING);
if (pendingContent != null)
System.out.println("Found pending content for "+marker+PENDING);
String writtenContent = ephemeralRepo.getDocument(marker+WRITTEN);
if ((pendingContent != null) && (writtenContent != null)) {
pending = JacksonUtil.objectFromJson(pendingContent, typeRef);
written = JacksonUtil.objectFromJson(writtenContent, typeRef);
}
ephemeralRepo.removeDocument(marker+WRITTEN, ContextFactory.getKernelUser().getUser(), "Expired");
ephemeralRepo.removeDocument(marker+PENDING, ContextFactory.getKernelUser().getUser(), "Expired");
synchronized(caches) {
caches.remove(locMarker+PENDING);
caches.remove(locMarker+WRITTEN);
}
}
if (pending.size() == 0) refresh = true;
// If the refresh flag is set then rescan the children
if (refresh) {
locMarker = IDGenerator.getUUID(20) + SEPARATOR + (System.currentTimeMillis() + locTTL);
children = findByUri(context, uri, depth);
if (!written.isEmpty()) {
for (String child : ImmutableSet.copyOf(written.keySet())) {
if (!children.containsKey(child)) deleted.put(child, written.remove(child));
else children.remove(child);
}
}
} else {
children = pending;
pending = new HashMap<>();
}
// We may have too many. Only want the first N results
for (String s : ImmutableSet.copyOf(children.keySet())) {
if (locMax-- > 0) written.put(s, children.get(s));
else {
pending.put(s, children.remove(s));
count++;
}
}
if ((marker != null) || (count > 0)) {
ephemeralRepo.addDocument(locMarker+PENDING, JacksonUtil.jsonFromObject(pending), ContextFactory.getKernelUser().getUser(), "Unread Data", false);
ephemeralRepo.addDocument(locMarker+WRITTEN, JacksonUtil.jsonFromObject(written), ContextFactory.getKernelUser().getUser(), "Sent Data", false);
synchronized(caches) {
caches.add(locMarker+PENDING);
caches.add(locMarker+WRITTEN);
}
} else {
locMarker = null;
}
cto.setRemainder(count);
cto.setParentURI(uri);
cto.setChildren(children);
cto.setDeleted(deleted);
cto.setIndexMark(locMarker);
return cto;
}
class CacheExpirationThread implements Runnable {
CallingContext context = null;
CacheExpirationThread(CallingContext context) {
super();
this.context = (context != null) ? context : ContextFactory.getKernelUser();
}
@Override
public void run() {
boolean terminate = false;
while (allowExpireThread && !terminate) {
long now = System.currentTimeMillis();
List toRemove = null;
synchronized(caches) {
for (String uri : caches) {
try {
int start = uri.indexOf(SEPARATOR);
int end = uri.lastIndexOf(SEPARATOR);
if (end > start) {
long time = Long.parseLong(uri.substring(start + 1, end));
if (time < now) {
ephemeralRepo.removeDocument(uri, ContextFactory.getKernelUser().getUser(), "Cache expired");
if (toRemove == null) toRemove = new ArrayList<>();
toRemove.add(uri);
}
}
} catch (RaptureException re) {
log.warn("Cannot delete cache entry "+uri+": "+re.getMessage());
} catch (NumberFormatException nfe) {
log.warn("Bad URI - cannot parse " + uri);
}
}
if (toRemove != null) {
caches.removeAll(toRemove);
}
}
try {
Thread.sleep(cacheRefresh);
} catch (InterruptedException e) {
// Used for testing to fake a timeout
log.debug("Interrupted\n");
}
}
}
}
public Long getTimeToLive() {
return timeToLive;
}
public void setTimeToLive(Long timeToLive) {
this.timeToLive = timeToLive;
}
public Long getCacheRefresh() {
return cacheRefresh;
}
public void setCacheRefresh(Long cacheRefresh) {
this.cacheRefresh = cacheRefresh;
}
@Override
public ChildrenTransferObject getChildren(CallingContext context, String raptureURI) {
return listByUriPrefix(context, raptureURI, null, 1, 0L, 0L);
}
@Override
public ChildrenTransferObject getAllChildren(CallingContext context, String raptureURI, String marker, Long maximum) {
return listByUriPrefix(context, raptureURI, marker, Integer.MAX_VALUE, maximum, 0L);
}
@Override
public Map getConnectionInfo(CallingContext context, String connectionType) {
return getConfigurer(connectionType).getConnectionInfo(context);
}
@Override
public void putConnectionInfo(CallingContext context, String connectionType, ConnectionInfo connectionInfo) {
getConfigurer(connectionType).putConnectionInfo(context, connectionInfo);
}
@Override
public void setConnectionInfo(CallingContext context, String connectionType, ConnectionInfo connectionInfo) {
getConfigurer(connectionType).setConnectionInfo(context, connectionInfo);
}
private ConnectionInfoConfigurer getConfigurer(String storeType) {
try {
ConnectionType type = ConnectionType.valueOf(storeType.toUpperCase());
return configurers.get(type);
} catch (IllegalArgumentException e) {
throw RaptureExceptionFactory.create("Unsupported store type " + storeType);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy