org.integratedmodelling.kserver.controller.KServerController Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of klab-server Show documentation
Show all versions of klab-server Show documentation
Spring controllers and common components for all k.LAB REST servers
/*******************************************************************************
* Copyright (C) 2007, 2015:
*
* - Ferdinando Villa - integratedmodelling.org - any
* other authors listed in @author annotations
*
* All rights reserved. This file is part of the k.LAB software suite, meant to enable
* modular, collaborative, integrated development of interoperable data and model
* components. For details, see http://integratedmodelling.org.
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the Affero General Public License Version 3 or any later version.
*
* This program is distributed in the hope that it will be useful, but without any
* warranty; without even the implied warranty of merchantability or fitness for a
* particular purpose. See the Affero General Public License for more details.
*
* You should have received a copy of the Affero General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite
* 330, Boston, MA 02111-1307, USA. The license is also available at:
* https://www.gnu.org/licenses/agpl.html
*******************************************************************************/
package org.integratedmodelling.kserver.controller;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Date;
import javax.imageio.ImageIO;
import javax.imageio.spi.ImageReaderSpi;
import javax.imageio.spi.ImageWriterSpi;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.geotools.coverage.CoverageFactoryFinder;
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.GeoTools;
import org.geotools.factory.Hints;
import org.geotools.image.io.ImageIOExt;
import org.integratedmodelling.api.auth.IUser;
import org.integratedmodelling.api.configuration.IConfiguration;
import org.integratedmodelling.api.engine.IModelingEngine;
import org.integratedmodelling.api.network.API;
import org.integratedmodelling.api.network.IComponent;
import org.integratedmodelling.api.project.IProject;
import org.integratedmodelling.api.services.IPrototype;
import org.integratedmodelling.common.auth.User;
import org.integratedmodelling.common.beans.Service;
import org.integratedmodelling.common.beans.responses.Capabilities;
import org.integratedmodelling.common.beans.responses.Directory;
import org.integratedmodelling.common.beans.responses.FileResource;
import org.integratedmodelling.common.configuration.KLAB;
import org.integratedmodelling.common.configuration.KLAB.BootMode;
import org.integratedmodelling.common.resources.ResourceFactory;
import org.integratedmodelling.common.utils.FileUtils;
import org.integratedmodelling.common.utils.IPUtils;
import org.integratedmodelling.common.utils.URLUtils;
import org.integratedmodelling.engine.ModelingEngine;
import org.integratedmodelling.engine.NodeEngine;
import org.integratedmodelling.engine.modelling.kbox.ModelKbox;
import org.integratedmodelling.engine.modelling.kbox.ObservationKbox;
import org.integratedmodelling.exceptions.KlabAuthorizationException;
import org.integratedmodelling.exceptions.KlabIOException;
import org.integratedmodelling.kserver.Configuration;
import org.integratedmodelling.kserver.controller.components.UserManager;
import org.integratedmodelling.kserver.resources.services.DirectoryService;
import org.integratedmodelling.kserver.resources.services.FileService;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.http.HttpHeaders;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
/**
* Controller that allows to boot and access the k.LAB engine in both node and
* modeling engine modes. Provides the public capabilities endpoint.
*
* @author ferdinando.villa
*/
@RestController
public class KServerController {
private final static double DEFAULT_COMPARISON_TOLERANCE = 1e-9;
@Autowired
RequestMappingHandlerMapping handlerMapping;
@Autowired
ApplicationContext appContext;
@Autowired
UserManager userManager;
private static Log logger = LogFactory.getLog(KServerController.class);
@Value("${server.port}")
private int port;
/**
* True when the client and the engine share the filesystem, so there is no need
* for web transfer of assets.
*/
private String launchingClientSignature = null;
/**
* Extract a set of resources from the classpath to the filesystem,
* recursively.
*
* @param rootPath
* @param destination
* @throws Exception
*/
public static void extract(String rootPath, File destination) throws Exception {
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources(rootPath + "/**");
for (Resource resource : resources) {
String path = null;
if (resource instanceof FileSystemResource) {
path = ((FileSystemResource) resource).getPath();
} else if (resource instanceof ClassPathResource) {
path = ((ClassPathResource) resource).getPath();
}
if (path == null) {
throw new KlabIOException("internal: cannot establish path for resource " + resource);
}
String filePath = path.substring(path.indexOf(rootPath + "/") + rootPath.length() + 1);
int pind = filePath.lastIndexOf('/');
if (pind >= 0) {
String fileDir = filePath.substring(0, pind);
File destDir = new File(destination + File.separator + fileDir);
destDir.mkdirs();
}
File dest = new File(destination + File.separator + filePath);
try (InputStream is = resource.getInputStream()) {
FileUtils.copyInputStreamToFile(is, dest);
} catch (Exception e) {
/*
* FIXME I can't find a way to determine if a resource
* corresponds to a directory, other than trying to extract as
* an inputstream and ignoring the exception (which can be both
* IOException and FileNotFoundException, thrown in different
* circumstances and even depending on shell substitution of
* path separators in same OS). This will silently ignore errors
* with other causes.
*/
}
}
}
/*
* extract core knowledge to a directory. Should obviously be generalized,
* but will become unnecessary as we can use
* /get/directory/im:ks:core.knowledge for the same purposes.
* The code below will also insert beans in the correspondent catalogs, so
* we can just remove the file extraction when it's time.
*/
@SuppressWarnings("javadoc")
public static void extractKnowledge() throws Exception {
File kdir = KLAB.CONFIG.getDataPath("knowledge");
File sdir = KLAB.CONFIG.getDataPath("ssh");
File rcon = new File(KLAB.CONFIG.getDataPath() + File.separator + "access.properties");
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
Resource[] resources = resolver.getResources("/knowledge/**");
Resource[] keyring = resolver.getResources("/ssh/pubring.gpg");
if (!rcon.exists()) {
URL prototype = null;
try {
prototype = resolver.getResource("classpath:access.properties.prototype").getURL();
URLUtils.copy(prototype, rcon);
} catch (IOException e) {
// just don't
}
}
if (keyring.length > 0) {
try (InputStream in = keyring[0].getURL().openStream()) {
FileUtils.copyInputStreamToFile(in, new File(sdir + File.separator + "pubring.gpg"));
} catch (Exception e) {
throw new KlabIOException(e);
}
}
Directory directory = new Directory();
for (Resource resource : resources) {
String path = null;
if (resource instanceof FileSystemResource) {
path = ((FileSystemResource) resource).getPath();
} else if (resource instanceof ClassPathResource) {
path = ((ClassPathResource) resource).getPath();
}
if (path == null) {
throw new KlabIOException("internal: cannot establish path for resource " + resource);
}
if (!path.endsWith("owl")) {
continue;
}
String filePath = path.substring(path.indexOf("knowledge/") + "knowledge/".length());
directory.getResourceUrns().add(API.CORE_KNOWLEDGE_URN + "#" + filePath);
int pind = filePath.lastIndexOf('/');
if (pind >= 0) {
String fileDir = filePath.substring(0, pind);
File destDir = new File(kdir + File.separator + fileDir);
destDir.mkdirs();
}
File dest = new File(kdir + File.separator + filePath);
InputStream is = resource.getInputStream();
FileUtils.copyInputStreamToFile(is, dest);
is.close();
}
DirectoryService.predefinedDirectories.put(API.CORE_KNOWLEDGE_URN, directory);
DirectoryService.publishedDirectories.put(API.CORE_KNOWLEDGE_URN, kdir);
FileService.publishedResources.put(API.CORE_PUBKEY_URN, FileResource
.newFromClasspath("ssh/pubring.gpg", API.CORE_PUBKEY_URN, new Date().getTime()));
}
/**
* Perform all initial setup tasks and calls boot() on the desired kind of
* engine.
*
* @param mode
* public node or modeling engine.
* @return true if setup and boot succeeded.
*/
public static boolean setup(BootMode mode) {
/*
* I'd rather not do this, but 100M of debug output when nobody has ever
* asked for it and no property files are around anywhere are a bit much
* to take.
*/
Logger root = (Logger) LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME);
if (root != null) {
root.setLevel(Level.INFO);
}
setupExtensions();
KLAB.setLogger(logger);
if (System.getProperty(IConfiguration.KLAB_DATA_DIRECTORY_PROPERTY) == null) {
System.setProperty(IConfiguration.KLAB_DATA_DIRECTORY_PROPERTY, Configuration
.getApplicationPath(mode));
}
KLAB.CONFIG = new org.integratedmodelling.common.configuration.Configuration(false);
/**
* If we have the test.assets=xxx property, extract the named test
* configuration to the data path.
*/
String testAssets = System.getProperty(Configuration.TEST_ASSETS_PROPERTY);
if (testAssets != null) {
KLAB.info("test configuration " + testAssets + " requested: extracting assets to "
+ KLAB.CONFIG.getDataPath());
/*
* substitute data directory with contents of asset dir
*/
try {
File datadir = KLAB.CONFIG.getDataPath();
FileUtils.deleteDirectory(datadir);
datadir.mkdirs();
extract("test.assets/" + testAssets, datadir);
} catch (Exception e) {
KLAB.error(e);
return false;
}
}
try {
extractKnowledge();
if (mode == BootMode.MODELER) {
ModelingEngine.boot();
} else if (mode == BootMode.NODE) {
NodeEngine.boot();
}
} catch (Exception e) {
KLAB.error(e);
return false;
}
return true;
}
/**
* If this is not null, a client that passes the same signature can use
* {@link API#DEPLOY}, {@link API#DELETE_NAMESPACE} and
* {@link API#UPDATE_NAMESPACE} to work with local files instead of using the web.
*
* @return
*/
public String getLocalClientSignature() {
if (this.launchingClientSignature == null) {
this.launchingClientSignature = "no, sorry";
File f = new File(KLAB.CONFIG.getDataPath() + File.separator + ".clientsig");
if (f.exists()) {
try {
this.launchingClientSignature = FileUtils.readFileToString(f);
} catch (IOException e) {
// just use the web
}
}
}
return this.launchingClientSignature;
}
/*
* Get extensions (ImageIO, Geotools) optimally configured. Most copied from
* Geoserver's GeoserverInitStartupListener
*/
private static void setupExtensions() {
// if the server admin did not set it up otherwise, force X/Y axis
// ordering
// This one is a good place because we need to initialize this property
// before any other opeation can trigger the initialization of the CRS
// subsystem
if (System.getProperty("org.geotools.referencing.forceXY") == null) {
System.setProperty("org.geotools.referencing.forceXY", "true");
}
if (Boolean.TRUE.equals(Hints.getSystemDefault(Hints.FORCE_LONGITUDE_FIRST_AXIS_ORDER))) {
Hints.putSystemDefault(Hints.FORCE_AXIS_ORDER_HONORING, "http");
}
Hints.putSystemDefault(Hints.LENIENT_DATUM_SHIFT, true);
// setup the referencing tolerance to make it more tolerant to tiny
// differences
// between projections (increases the chance of matching a random prj
// file content
// to an actual EPSG code
double comparisonTolerance = DEFAULT_COMPARISON_TOLERANCE;
// Register logging, and bridge to JAI logging
GeoTools.init((Hints) null);
/*
* TODO make this a property and implement if it ever becomes necessary
*/
// if (comparisonToleranceProperty != null) {
// try {
// comparisonTolerance =
// Double.parseDouble(comparisonToleranceProperty);
// } catch (NumberFormatException nfe) {
// KLAB.warn("Unable to parse the specified COMPARISON_TOLERANCE "
// + "system property: " + comparisonToleranceProperty +
// " which should be a number. Using Default: " +
// DEFAULT_COMPARISON_TOLERANCE);
// }
// }
Hints.putSystemDefault(Hints.COMPARISON_TOLERANCE, comparisonTolerance);
/*
* avoid expiration of EPSG data. FIXME: does not seem to avoid
* anything.
*/
System.setProperty("org.geotools.epsg.factory.timeout", "-1");
/*
* Prevents leak ()
*/
ImageIO.scanForPlugins();
// in any case, the native png reader is worse than the pure java ones,
// so
// let's disable it (the native png writer is on the other side
// faster)...
ImageIOExt.allowNativeCodec("png", ImageReaderSpi.class, false);
ImageIOExt.allowNativeCodec("png", ImageWriterSpi.class, true);
// initialize GeoTools factories so that we don't make a SPI lookup
// every time a
// factory is needed
Hints.putSystemDefault(Hints.FILTER_FACTORY, CommonFactoryFinder.getFilterFactory2(null));
Hints.putSystemDefault(Hints.STYLE_FACTORY, CommonFactoryFinder.getStyleFactory(null));
Hints.putSystemDefault(Hints.FEATURE_FACTORY, CommonFactoryFinder.getFeatureFactory(null));
final Hints defHints = GeoTools.getDefaultHints();
// Initialize GridCoverageFactory so that we don't make a lookup every
// time a
// factory is needed
Hints.putSystemDefault(Hints.GRID_COVERAGE_FACTORY, CoverageFactoryFinder
.getGridCoverageFactory(defHints));
}
/**
* @return the port
*/
public int getPort() {
return port;
}
/**
* Get a capabilities bean. List of capabilities is unrestricted even if the
* services may be.
*
* @param headers
* @param request
* @return capabilities
*/
@RequestMapping(value = API.CAPABILITIES, method = RequestMethod.GET)
public Capabilities capabilities(@RequestHeader HttpHeaders headers, HttpServletRequest request) {
IUser user = userManager.getUser(headers.get(API.AUTHENTICATION_HEADER));
Capabilities ret = new Capabilities();
ret.setName(KLAB.NAME);
/**
* TODO publish services (from components etc). We could check for the
* namespace of the method providers and attribute REST service
* prototypes to components.
*/
for (RequestMappingInfo hm : this.handlerMapping.getHandlerMethods().keySet()) {
// System.out.println("" + hm);
}
/*
* publish allowed components
*/
for (IComponent c : KLAB.PMANAGER.getComponents()) {
if (KLAB.ENGINE.getResourceConfiguration().isAuthorized(c, user, request.getRemoteAddr())) {
ret.getComponentUrns().add(ResourceFactory.getComponentUrn(c));
}
}
/**
* publish function prototypes from all components; if modeling engine,
* also publish all other prototypes.
*/
for (IPrototype p : KLAB.ENGINE.getFunctionPrototypes()) {
if (KLAB.ENGINE instanceof IModelingEngine
|| (p.getComponentId() != null
&& KLAB.PMANAGER.getComponent(p.getComponentId()) != null)) {
ret.getFunctions().add(KLAB.MFACTORY.adapt(p, Service.class));
}
}
for (String s : KLAB.ENGINE.getResourceConfiguration().getSynchronizedProjectIds()) {
IProject project = KLAB.PMANAGER.getProject(s);
if (project != null
&& KLAB.ENGINE.getResourceConfiguration()
.isAuthorized(project, user, request.getRemoteAddr())) {
ret.getSynchronizedProjectUrns().add(ResourceFactory.getProjectUrn(project));
}
}
if (KLAB.ENGINE instanceof IModelingEngine) {
ret.setEngineUser(((User) ((IModelingEngine) KLAB.ENGINE).getUser()).getProfile());
ret.setCommunicationChannel(((IModelingEngine) KLAB.ENGINE).getCommunicationChannel());
}
ret.setModelCount(ModelKbox.get().count());
ret.setObservationCount(ObservationKbox.get().count());
ret.setBootMode(KLAB.ENGINE instanceof IModelingEngine ? BootMode.MODELER.name()
: BootMode.NODE.name());
return ret;
}
/**
* Simply check if the passed request comes from the same host we run on.
*
* @param request
* @return true if request is from local address
*/
public static boolean isLocalIp(HttpServletRequest request) {
try {
return request.getRemoteAddr().startsWith("127.")
|| request.getRemoteAddr().startsWith("localhost")
|| IPUtils.isLocal(request.getRemoteAddr());
} catch (Exception e) {
return false;
}
}
/**
* @param headers
* @param request
* @return 0 if OK
* @throws KlabAuthorizationException
*/
@RequestMapping(value = API.SHUTDOWN, method = RequestMethod.GET)
public int shutdown(@RequestHeader HttpHeaders headers, HttpServletRequest request)
throws KlabAuthorizationException {
if (!(isLocalIp(request) || isAdmin(headers))) {
throw new KlabAuthorizationException("no authorization for shutdown");
}
KLAB.ENGINE.shutdown(2);
return 0;
}
/**
* Check a request's headers against the configured administrative key for
* session-less access to administrative functions.
*
* @param headers
* @return true if the request contains the installed administrative key in
* the authentication header.
*/
public static boolean isAdmin(HttpHeaders headers) {
if (KLAB.ENGINE.getResourceConfiguration().getAdministrationKey() == null) {
return false;
}
if (headers.get(API.AUTHENTICATION_HEADER) != null) {
for (String h : headers.get(API.AUTHENTICATION_HEADER)) {
if (h.equals(KLAB.ENGINE.getResourceConfiguration().getAdministrationKey())) {
return true;
}
}
}
return false;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy