Skip to content
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

JSON & XML Sources #507

Open
wants to merge 71 commits into
base: master
Choose a base branch
from
Open
Changes from 1 commit
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
c11a877
Start implementation (not working yet)
Apr 24, 2024
5202005
Start implementation (not working yet)
Apr 24, 2024
1fa81f6
Fix gradle compile issue
Apr 24, 2024
f294e89
Add json to document conversion
Apr 24, 2024
c248533
Add JSON enumerator
Apr 25, 2024
54746cb
Add namespace and meta functions
Apr 25, 2024
001248f
Fix gradle
Apr 25, 2024
1a4f8af
Add example data
Apr 25, 2024
12aa4e8
Add json conversion
Apr 25, 2024
e56271d
Improve meta retrieval
Apr 30, 2024
f986ca6
Add delegate
May 2, 2024
d1292e8
Start document source refactoring
May 2, 2024
8196df9
Introduce data model handling
May 3, 2024
71977e1
Implement ScanRule, Scan and costs
May 6, 2024
1b88ae4
Fix query execution
Jun 19, 2024
1f9064c
Fix merge conflict
Jun 19, 2024
5ee9966
Fix json queries
Jun 19, 2024
78fdfde
Fix json file path
Jun 19, 2024
f8f01a8
Add directory import
Jun 19, 2024
4de81b6
Add error on modification of collections provided by a source
Jun 27, 2024
794e4d2
Add simple XML source
Jun 27, 2024
01a09c9
Add tests for value conversion
Jun 27, 2024
d0e7cae
Xml enumerator fix
Jul 5, 2024
e53caae
Add error on duplicate files
Jul 17, 2024
0180e22
Add upload and link modes to json source
Jul 17, 2024
a1e3191
Add upload and link modes to xml source
Jul 17, 2024
b96d772
Add useful error message on duplicate file names for JSON source
Jul 17, 2024
d996505
Fix error on dropping document source
Jul 17, 2024
e09e8b7
Fix broken linking on data sources
Jul 19, 2024
32c743e
Add url option to json source
Aug 5, 2024
749c06f
Add url import to xml source
Aug 5, 2024
464fa8b
Add xml file name from url to xml source
Aug 5, 2024
8ae810d
Set useful default value for url
Aug 5, 2024
4ad101b
Fix JSON bugs
Aug 5, 2024
d2c2f43
Fix heap issues on XML source creation
Aug 5, 2024
cb05187
Fix heap issues on XML source enumeration
Aug 5, 2024
4878f08
Merge with master
gartens Aug 6, 2024
445368d
Remove comment
gartens Aug 6, 2024
563356d
Reformat & optimize imports
gartens Aug 6, 2024
bcb7f44
Allow MIT
gartens Aug 6, 2024
25c55fb
Fix XmlToPolyConverterTest
gartens Aug 6, 2024
898960d
Use GenericRuntimeException instead of RuntimeException
gartens Aug 6, 2024
013a474
Add missing dependency on :dbms:shadowJar
gartens Aug 6, 2024
23f98e1
Allow MIT
gartens Aug 6, 2024
b84513c
Add missing dependency on :dbms:shadowJar
gartens Aug 6, 2024
9552b0b
Remove broken upload feature
Aug 7, 2024
84af7ed
Fix leftovers from rebase
gartens Aug 7, 2024
684d340
Fix unit tests
Aug 7, 2024
2606d1a
Fix parser and add more complex example
Aug 8, 2024
c6c1f8a
Various changes according to code review on PR
Aug 11, 2024
4f8d21f
Format
gartens Aug 12, 2024
9501699
Convert ExportedDocument to record class
gartens Aug 19, 2024
ce91406
Remove unused method
gartens Aug 19, 2024
d956739
Convert ExportedColumn into a record
gartens Aug 19, 2024
af8f049
Move implements a class up to AbstractJdbcSource
gartens Aug 19, 2024
a3398de
Use exhaustive switch expressions
gartens Oct 21, 2024
fb7ba52
Use GenericRuntimeException instead of RuntimeException
gartens Oct 21, 2024
80033f2
Use .snapshot instead of .getInstance().getSnapshot()
gartens Oct 21, 2024
d41e57b
Use exhaustive switch expression instead of if
gartens Oct 21, 2024
85f63cc
Fix typos in log messages
gartens Oct 21, 2024
8d833d5
Small improvements
gartens Oct 21, 2024
3ad762e
Remove unused class
gartens Oct 21, 2024
bdb4d90
Remove unused variable
gartens Oct 21, 2024
6ef698c
Annotate parameter as not null
gartens Oct 21, 2024
34eadc0
Replace builder with passing parameters directly
gartens Oct 21, 2024
19fc8f1
Inline simple conversions
gartens Oct 21, 2024
f1e2869
Remove superfluous overrides
gartens Oct 21, 2024
16dce89
Fix typo in JsonToPolyConversion
gartens Oct 21, 2024
e0af943
Annotate parameter as not null
gartens Oct 21, 2024
8d5bc93
Use stricter visibilities for classes & methods
gartens Oct 21, 2024
8a654b2
Remove unused field "fields" from {Json,Xml}Scan
gartens Oct 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Fix broken linking on data sources
Tobias Hafner committed Jul 19, 2024
commit e09e8b7720eefc07662df5e1a60e2f3ce2ded147
25 changes: 19 additions & 6 deletions core/src/main/java/org/polypheny/db/security/SecurityManager.java
Original file line number Diff line number Diff line change
@@ -16,7 +16,9 @@

package org.polypheny.db.security;

import java.net.URL;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
@@ -25,8 +27,9 @@
import javax.annotation.Nullable;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;


@Slf4j
public class SecurityManager {

private static SecurityManager INSTANCE = null;
@@ -74,20 +77,30 @@ public boolean checkPathAccess( Path path ) {
dir = dir.getParent();
}
AuthStatus status = this.status.get( dir );

if (dir.startsWith("classpath:")) {
status.setStep( AuthStep.SUCCESSFUL );
return true;
}
if (status == null) {
log.debug( "No auth status available for directory {}", dir );
return false;
}
if ( status.step == AuthStep.SUCCESSFUL ) {
return true;
}
if ( Arrays.stream( Objects.requireNonNull( status.path.toFile().listFiles() ) ).noneMatch( f -> f.getName().equals( "polypheny.access" ) && f.isFile() ) ) {
// TODO: if more fine-grained access control is required, add as content of file
try {
if ( Arrays.stream( Objects.requireNonNull( status.path.toFile().listFiles() ) ).noneMatch( f -> f.getName().equals( "polypheny.access" ) && f.isFile() ) ) {
// TODO: if more fine-grained access control is required, add as content of file
return false;
}
} catch (Exception e) {
log.debug( "Filed to check for polypheny.access as the specified path is not a directory {}", dir );
return false;
}

status.setStep( AuthStep.SUCCESSFUL );
return true;
}


private enum AuthStep {
INITIAL,
SUCCESSFUL
13 changes: 6 additions & 7 deletions dbms/src/main/java/org/polypheny/db/ddl/DdlManagerImpl.java
Original file line number Diff line number Diff line change
@@ -334,16 +334,15 @@ public void dropAdapter( String name, Statement statement ) {
LogicalAdapter adapter = catalog.getSnapshot().getAdapter( name ).orElseThrow();
if ( adapter.type == AdapterType.SOURCE ) {
for ( AllocationEntity allocation : catalog.getSnapshot().alloc().getEntitiesOnAdapter( adapter.id ).orElse( List.of() ) ) {
// Make sure that there is only one adapter
if ( catalog.getSnapshot().alloc().getFromLogical( allocation.logicalId ).size() != 1 ) {
throw new GenericRuntimeException( "The data source contains entities with more than one placement. This should not happen!" );
if ( catalog.getSnapshot().alloc().getFromLogical( allocation.logicalId ).isEmpty() ) {
continue;
}

if ( allocation.unwrap( AllocationCollection.class ).isPresent() ) {
//TODO: currently each document source creates its own namespace which can be disposed of together with the source
//dropNamespace( catalog.getSnapshot().doc().getCollection( allocation.logicalId ).orElseThrow().getNamespaceName(), true, statement);
dropCollection( catalog.getSnapshot().doc().getCollection( allocation.logicalId ).orElseThrow(), statement );
} else if ( allocation.unwrap( AllocationTable.class ).isPresent() ) {
dropNamespace( catalog.getSnapshot().doc().getCollection( allocation.logicalId ).orElseThrow().getNamespaceName(), true, statement);
continue;
}
if ( allocation.unwrap( AllocationTable.class ).isPresent() ) {

for ( LogicalForeignKey fk : catalog.getSnapshot().rel().getForeignKeys( allocation.logicalId ) ) {
catalog.getLogicalRel( allocation.namespaceId ).deleteForeignKey( fk.id );
Original file line number Diff line number Diff line change
@@ -56,12 +56,14 @@ public static URL findDocumentUrl(URL jsonFiles, String name) throws MalformedUR
String[] extensions = { ".json", ".json.gz" };
String path = jsonFiles.getPath();

// handle single file
for ( String ext : extensions ) {
if ( path.endsWith( name + ext + "/" ) ) {
if ( path.endsWith( name + ext) ) {
return jsonFiles;
}
}

// handle directory
Set<String> fileNames = getFileNames( jsonFiles );
for ( String file : fileNames ) {
for ( String ext : extensions ) {
@@ -71,7 +73,7 @@ public static URL findDocumentUrl(URL jsonFiles, String name) throws MalformedUR
}
}

throw new NoSuchFileException( "No XML file(s) found under the URL '" + jsonFiles + "'" );
throw new NoSuchFileException( "No JSON file(s) found under the URL '" + jsonFiles + "'" );
}


Original file line number Diff line number Diff line change
@@ -65,7 +65,7 @@ public static URL findDocumentUrl( URL xmlFiles, String name ) throws MalformedU
String path = xmlFiles.getPath();

for ( String ext : extensions ) {
if ( path.endsWith( name + ext + "/" ) ) {
if ( path.endsWith( name + ext) ) {
return xmlFiles;
}
}
73 changes: 38 additions & 35 deletions webui/src/main/java/org/polypheny/db/webui/Crud.java
Original file line number Diff line number Diff line change
@@ -78,14 +78,14 @@
import org.polypheny.db.ResultIterator;
import org.polypheny.db.adapter.AbstractAdapterSetting;
import org.polypheny.db.adapter.AbstractAdapterSettingDirectory;
import org.polypheny.db.adapter.AbstractAdapterSettingString;
import org.polypheny.db.adapter.Adapter;
import org.polypheny.db.adapter.AdapterManager;
import org.polypheny.db.adapter.AdapterManager.AdapterInformation;
import org.polypheny.db.adapter.ConnectionMethod;
import org.polypheny.db.adapter.DataSource;
import org.polypheny.db.adapter.DataStore;
import org.polypheny.db.adapter.DataStore.FunctionalIndexInfo;
import org.polypheny.db.adapter.RelationalDataSource;
import org.polypheny.db.adapter.RelationalDataSource.ExportedColumn;
import org.polypheny.db.adapter.index.IndexManager;
import org.polypheny.db.adapter.java.AdapterTemplate;
@@ -2089,7 +2089,7 @@ void createAdapter( final Context ctx ) throws ServletException, IOException {
String body = "";
Map<String, InputStream> inputStreams = new HashMap<>();

final AdapterModel a;
final AdapterModel adapterModel;
if ( ctx.isMultipartFormData() ) {
// collect all files e.g. csv files
for ( Part part : ctx.req.getParts() ) {
@@ -2099,50 +2099,56 @@ void createAdapter( final Context ctx ) throws ServletException, IOException {
inputStreams.put( part.getName(), part.getInputStream() );
}
}
a = HttpServer.mapper.readValue( body, AdapterModel.class );
adapterModel = HttpServer.mapper.readValue( body, AdapterModel.class );
} else if ( "application/json".equals( ctx.contentType() ) ) {
a = ctx.bodyAsClass( AdapterModel.class );
adapterModel = ctx.bodyAsClass( AdapterModel.class );
} else {
ctx.status( HttpCode.BAD_REQUEST );
return;
}

Map<String, String> settings = new HashMap<>();
AdapterTemplate adapterTemplate = AdapterManager.getAdapterTemplate( adapterModel.adapterName, adapterModel.type );

// This is only used to be able to get the type of property based on the key
Map<String, AbstractAdapterSetting> defaultSettings = adapterTemplate.settings.stream().collect( Collectors.toMap( e -> e.name, e -> e ) );
ConnectionMethod method = ConnectionMethod.UPLOAD;
if ( a.settings.containsKey( "method" ) ) {
method = ConnectionMethod.valueOf( a.settings.get( "method" ).toUpperCase() );
if ( adapterModel.settings.containsKey( "method" ) ) {
method = ConnectionMethod.valueOf( adapterModel.settings.get( "method" ).toUpperCase() );
}
AdapterTemplate adapter = AdapterManager.getAdapterTemplate( a.adapterName, a.type );
Map<String, AbstractAdapterSetting> allSettings = adapter.settings.stream().collect( Collectors.toMap( e -> e.name, e -> e ) );

for ( Map.Entry<String, String> entry : a.settings.entrySet() ) {
AbstractAdapterSetting set = allSettings.get( entry.getKey() );
if ( set == null ) {
Map<String, String> adapterSettings = new HashMap<>();

for ( Map.Entry<String, String> entry : adapterModel.settings.entrySet() ) {
if ( !defaultSettings.containsKey( entry.getKey() ) ) {
// specified property is not available for this adapter
continue;
}
if ( set instanceof AbstractAdapterSettingDirectory setting ) {
if ( method == ConnectionMethod.LINK ) {
Exception e = handleLinkFiles( ctx, a, setting, allSettings );
if ( e != null ) {
ctx.json( RelationalResult.builder().exception( e ).build() );
return;
}
settings.put( set.name, entry.getValue() );
} else {
List<String> fileNames = HttpServer.mapper.readValue( entry.getValue(), new TypeReference<>() {
} );
String directory = handleUploadFiles( inputStreams, fileNames, setting, a );
settings.put( set.name, directory );
adapterSettings.put( entry.getKey(), entry.getValue() );
AbstractAdapterSetting set = defaultSettings.get( entry.getKey() );
// handle upload
if ( (set instanceof AbstractAdapterSettingDirectory settingDirectory) && method == ConnectionMethod.UPLOAD ) {
List<String> fileNames = HttpServer.mapper.readValue( entry.getValue(), new TypeReference<>() {
} );
String directory = handleUploadFiles( inputStreams, fileNames, settingDirectory, adapterModel );
adapterSettings.put( entry.getKey(), directory );
continue;
}
// handle linking
if ( (set instanceof AbstractAdapterSettingString settingString) && method == ConnectionMethod.LINK ) {
if ( !settingString.name.equals( "directoryName" ) ) {
continue;
}
Exception e = handleLinkFiles( settingString );
if ( e != null ) {
ctx.json( RelationalResult.builder().exception( e ).build() );
return;
}
} else {
settings.put( set.name, entry.getValue() );
}
}

settings.put( "mode", a.mode.toString() );
adapterSettings.put( "mode", adapterModel.mode.toString() );

String query = String.format( "ALTER ADAPTERS ADD \"%s\" USING '%s' AS '%s' WITH '%s'", a.name, a.adapterName, a.type, Crud.gson.toJson( settings ) );
String query = String.format( "ALTER ADAPTERS ADD \"%s\" USING '%s' AS '%s' WITH '%s'", adapterModel.name, adapterModel.adapterName, adapterModel.type, Crud.gson.toJson( adapterSettings ) );
QueryLanguage language = QueryLanguage.from( "sql" );
Result<?, ?> res = LanguageCrud.anyQueryResult(
QueryContext.builder()
@@ -2166,15 +2172,12 @@ public void startAccessRequest( Context ctx ) {
}


private Exception handleLinkFiles( Context ctx, AdapterModel a, AbstractAdapterSettingDirectory setting, Map<String, AbstractAdapterSetting> settings ) {
if ( !settings.containsKey( "directoryName" ) ) {
return new GenericRuntimeException( "Security check for access was not performed; id missing." );
}
Path path = Path.of( settings.get( "directoryName" ).defaultValue );
private Exception handleLinkFiles( AbstractAdapterSettingString setting ) {
Path path = Path.of( setting.getValue() );
SecurityManager.getInstance().requestPathAccess( "webui", "webui", path );
if ( !SecurityManager.getInstance().checkPathAccess( path ) ) {
return new GenericRuntimeException( "Security check for access was not successful; not enough permissions." );
}

return null;
}