-
Notifications
You must be signed in to change notification settings - Fork 17
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add ability to auto-register GeoMesa File System DataStores #25
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/*********************************************************************** | ||
* Copyright (c) 2013-2017 Commonwealth Computer Research, Inc. | ||
* All rights reserved. This program and the accompanying materials are | ||
* made available under the terms of the GNU GENERAL PUBLIC LICENSE, | ||
* Version 2 which accompanies this distribution and is available at | ||
* https://opensource.org/licenses/GPL-2.0. | ||
***********************************************************************/ | ||
|
||
package org.geomesa.gs.catalog | ||
|
||
import javax.servlet.http.HttpServletRequest | ||
import javax.servlet.{FilterChain, FilterConfig, ServletRequest, ServletResponse} | ||
|
||
import org.apache.hadoop.fs.Path | ||
import org.geoserver.catalog.Catalog | ||
import org.geoserver.filters.GeoServerFilter | ||
import org.geoserver.ows.Request | ||
|
||
class AutoRegisterFilter extends GeoServerFilter { | ||
var catalog: Catalog = _ | ||
def setCatalog(catalog: Catalog): Unit = this.catalog = catalog | ||
def getCatalog: Catalog = this.catalog | ||
|
||
override def init(filterConfig: FilterConfig): Unit = { } | ||
|
||
override def doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain): Unit = { | ||
val request = new Request() | ||
request.setHttpRequest(servletRequest.asInstanceOf[HttpServletRequest]) | ||
|
||
val path = servletRequest.asInstanceOf[HttpServletRequest].getServletPath.split("/") | ||
if (path.length > 1) { | ||
createWorkspace(path(1)) | ||
} | ||
filterChain.doFilter(servletRequest, servletResponse) | ||
} | ||
|
||
def createWorkspace(ws: String): Unit = { | ||
val wsi = catalog.getWorkspaceByName(ws) | ||
if (wsi == null && shouldCreate(ws)) { | ||
val newWorkspace = catalog.getFactory.createWorkspace() | ||
newWorkspace.setName(ws) | ||
|
||
val namespace = catalog.getFactory.createNamespace() | ||
namespace.setPrefix(newWorkspace.getName) | ||
namespace.setURI(s"http://geomesa.org/$ws") | ||
|
||
catalog.add(namespace) | ||
catalog.add(newWorkspace) | ||
} | ||
} | ||
|
||
def shouldCreate(workspace: String): Boolean = { | ||
val base = System.getProperty("GEOMESA_FSDS_BASE_DIRECTORY") | ||
if(base == null || Seq("styles", "web", "index.html", "openlayers3").contains(workspace)) return false | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. it might be safer to make these configurable in the spring bean, so that they can be configured. what are the typical url patterns you're matching here? |
||
|
||
val path = new Path(base, workspace) | ||
val fs = path.getFileSystem(new org.apache.hadoop.conf.Configuration) | ||
fs.exists(path) | ||
} | ||
|
||
override def destroy(): Unit = { } | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,11 +9,17 @@ | |
package org.geomesa.gs.catalog | ||
|
||
import com.typesafe.scalalogging.LazyLogging | ||
import org.apache.hadoop.conf.Configuration | ||
import org.apache.hadoop.fs.{FileContext, FileStatus, Path, RemoteIterator} | ||
import org.geoserver.catalog.event._ | ||
import org.geoserver.catalog.{Catalog, DataStoreInfo, FeatureTypeInfo, WorkspaceInfo} | ||
import org.geoserver.catalog._ | ||
import org.geoserver.security.decorators.DecoratingDataStore | ||
import org.geotools.data.DataStore | ||
import org.locationtech.geomesa.accumulo.data.AccumuloDataStore | ||
import org.geotools.geometry.jts.ReferencedEnvelope | ||
import org.locationtech.geomesa.fs.FileSystemDataStoreFactory | ||
import org.locationtech.geomesa.fs.FileSystemDataStoreFactory.FileSystemDataStoreParams | ||
import org.locationtech.geomesa.fs.storage.common.utils.StorageUtils | ||
import org.locationtech.geomesa.index.geotools.GeoMesaDataStore | ||
import org.locationtech.geomesa.web.core.GeoMesaServletCatalog | ||
import org.locationtech.geomesa.web.core.GeoMesaServletCatalog.GeoMesaLayerInfo | ||
import org.springframework.beans.factory.InitializingBean | ||
|
@@ -38,19 +44,18 @@ class GeoMesaCatalogListener extends CatalogListener with InitializingBean with | |
def crawlCatalog(): Unit = catalog.getFeatureTypes.foreach(addFeatureTypeInfo) | ||
|
||
def addFeatureTypeInfo(featureTypeInfo: FeatureTypeInfo): Unit = { | ||
// Handle ADS case | ||
getStore(featureTypeInfo) match { | ||
case ads: AccumuloDataStore => | ||
case gmds: GeoMesaDataStore[_, _, _] => | ||
|
||
val workspace = featureTypeInfo.getStore.getWorkspace.getName | ||
val layerName = featureTypeInfo.getName | ||
|
||
val nativeName = featureTypeInfo.getNativeName | ||
|
||
val sft = ads.getSchema(nativeName) | ||
val sft = gmds.getSchema(nativeName) | ||
|
||
logger.debug(s"Registering info for layer $layerName with SFT ${sft.getTypeName} with GeoMesa Stats REST API.") | ||
GeoMesaServletCatalog.putGeoMesaLayerInfo(workspace, layerName, GeoMesaLayerInfo(ads, sft)) | ||
GeoMesaServletCatalog.putGeoMesaLayerInfo(workspace, layerName, GeoMesaLayerInfo(gmds, sft)) | ||
case s => logger.debug(s"Encountered non-GeoMesa store: ${Option(s).map(_.getClass.getName).orNull}") | ||
} | ||
} | ||
|
@@ -64,9 +69,8 @@ class GeoMesaCatalogListener extends CatalogListener with InitializingBean with | |
} | ||
|
||
def removeFeatureTypeInfo(featureTypeInfo: FeatureTypeInfo): Unit = { | ||
// Handle ADS case | ||
getStore(featureTypeInfo) match { | ||
case ads: AccumuloDataStore => | ||
case gmds: GeoMesaDataStore[_, _, _] => | ||
|
||
val workspace = featureTypeInfo.getStore.getWorkspace.getName | ||
val layerName = featureTypeInfo.getName | ||
|
@@ -79,13 +83,34 @@ class GeoMesaCatalogListener extends CatalogListener with InitializingBean with | |
} | ||
|
||
override def handleAddEvent(event: CatalogAddEvent): Unit = { | ||
logger.debug(s"GeoMesa Catalog Listener received add event: $event") | ||
logger.debug(s"GeoMesa Catalog Listener received add event: $event of type ${event.getSource.getClass}") | ||
event.getSource match { | ||
case fti: FeatureTypeInfo => addFeatureTypeInfo(fti) | ||
case _ => // not a new layer; no action necessary. | ||
case dti: DataStoreInfo => registerLayers(dti) | ||
case wsi: WorkspaceInfo => registerDataStore(wsi) | ||
case _ => event.getSource.getClass // not a new layer; no action necessary. | ||
} | ||
} | ||
|
||
def registerDataStore(wsi: WorkspaceInfo): Unit = { | ||
val base = System.getProperty("GEOMESA_FSDS_BASE_DIRECTORY") | ||
val directory = wsi.getName | ||
|
||
val dsi = catalog.getFactory.createDataStore() | ||
dsi.setWorkspace(wsi) | ||
dsi.setEnabled(true) | ||
dsi.setName(directory) | ||
|
||
val factory = new FileSystemDataStoreFactory | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what happens if you're not using the FSDS? is this going to break all other datastores? |
||
dsi.setType(factory.getDisplayName()) | ||
val connParams = dsi.getConnectionParameters | ||
val path = s"$base$directory" | ||
connParams.put(FileSystemDataStoreParams.PathParam.getName, path) | ||
connParams.put("namespace", catalog.getNamespaceByPrefix(wsi.getName).getURI) | ||
|
||
catalog.save(dsi) | ||
} | ||
|
||
override def handleRemoveEvent(event: CatalogRemoveEvent): Unit = { | ||
logger.debug(s"GeoMesa Catalog Listener received remove event: $event") | ||
event.getSource match { | ||
|
@@ -109,6 +134,36 @@ class GeoMesaCatalogListener extends CatalogListener with InitializingBean with | |
} | ||
} | ||
|
||
// First pass of auto-registering layers. | ||
def registerLayers(dti: DataStoreInfo): Unit = { | ||
val ds = dti.getDataStore(null) | ||
val wsi = dti.getWorkspace | ||
|
||
ds.getNames.foreach { name => | ||
val fs = ds.getFeatureSource(name) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. seems like this should check to see if the layers are already registered? |
||
|
||
val builder: CatalogBuilder = new CatalogBuilder(catalog) | ||
builder.setStore(dti) | ||
val fti = builder.buildFeatureType(name) | ||
val li = builder.buildLayer(fti) | ||
val bbox = { | ||
val tmp = builder.getNativeBounds(fti) | ||
if (tmp.equals(ReferencedEnvelope.EVERYTHING)) { | ||
builder.getBoundsFromCRS(fti) | ||
} else { | ||
tmp | ||
} | ||
} | ||
|
||
fti.setNativeBoundingBox(bbox) | ||
fti.setLatLonBoundingBox(bbox) | ||
val namespace = catalog.getNamespaceByPrefix(wsi.getName) | ||
fti.setNamespace(namespace) | ||
catalog.add(fti) | ||
catalog.add(li) | ||
} | ||
} | ||
|
||
def handleDataStoreUpdate(event: CatalogModifyEvent): Unit = { | ||
event.getSource match { | ||
case dsi: DataStoreInfo if event.getPropertyNames.contains("workspace") => | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -445,6 +445,12 @@ | |
<version>${slf4j.version}</version> | ||
<scope>provided</scope> | ||
</dependency> | ||
<dependency> | ||
<groupId>org.specs2</groupId> | ||
<artifactId>specs2_2.11</artifactId> | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. any chance for some unit tests? |
||
<version>${specs2.version}</version> | ||
<scope>test</scope> | ||
</dependency> | ||
</dependencies> | ||
</dependencyManagement> | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are these 2 lines doing anything? seems like they could be removed