Skip to content

Commit

Permalink
General cleanup, docs, and better error handling on failed XML mapping
Browse files Browse the repository at this point in the history
generation
  • Loading branch information
michaelborn committed Aug 13, 2024
1 parent d29456e commit bd1071c
Show file tree
Hide file tree
Showing 2 changed files with 116 additions and 85 deletions.
91 changes: 31 additions & 60 deletions src/main/java/ortus/boxlang/modules/orm/SessionFactoryBuilder.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package ortus.boxlang.modules.orm;

import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

Expand All @@ -28,7 +25,6 @@
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.IStruct;
import ortus.boxlang.runtime.types.exceptions.BoxRuntimeException;
import ortus.boxlang.runtime.util.FileSystemUtil;

public class SessionFactoryBuilder {

Expand Down Expand Up @@ -93,7 +89,6 @@ public SessionFactoryBuilder( IJDBCCapableContext context, Key appName, IStruct

public SessionFactory build() {
Configuration configuration = buildConfiguration();
// getORMMappingFiles().forEach( configuration::addFile );

SessionFactory factory = configuration.buildSessionFactory();

Expand Down Expand Up @@ -125,56 +120,11 @@ private DataSource getORMDataSource() {
// return currentContext.getConnectionManager().getDefaultDatasourceOrThrow();
}

private List<File> getORMMappingFiles() {
// @TODO: Should we use the application name, or the ORM configuration hash?
String xmlMappingLocation = Path.of( FileSystemUtil.getTempDirectory(), "orm_mappings", getAppName().getName() ).toString();
if ( !ormConfig.autoGenMap ) {
// Skip mapping generation and load the pre-generated mappings from this
// location.
// xmlMappingLocation = ormConfig.cfcLocation;
throw new BoxRuntimeException( "ORMConfiguration setting `autoGenMap=false` is currently unsupported." );
} else {
// @TODO: Here we generate entity mappings and populate the temp directory (aka
// xmlMappingLocation) with the generated files.
// If `ormConfig.getAsBoolean(ORMKeys.savemapping)` is true, we should save the
// generated files to `ormConfig.getAsString(ORMKeys.cfclocation)`. Else, we
// should save them to the temp directory.
// @TODO: Also remember to use the
// `ormConfig.getAsBoolean(ORMKeys.skipCFCWithError)` setting to determine
// whether to throw exceptions on compile-time errors.
}
// Regardless of the autoGenMap configuration value, we now have a directory
// populated with JPA orm.xml mapping files.
// We now need to load these files into the Hibernate configuration.
// return Arrays.stream(xmlMappingLocation.split(","))
// .flatMap(path -> {
// try {
// return Files.walk(Paths.get(path), 1);
// } catch (IOException e) {
// throw new BoxRuntimeException("Error walking cfclcation path: " +
// path.toString(), e);
// }
// })
// // filter to valid orm.xml files
// .filter(filePath -> FilePath.endsWith(".orm.xml"))
// // @TODO: I'm unsure, but we may need to convert the string path to a File
// // object to work around JPA's classpath limitations.
// // We should first try this without the conversion and see if it works.
// .map(filePath -> filePath.toFile())
// .toList();

Map<String, EntityRecord> entities = new MappingGenerator( ( IBoxContext ) context, ormConfig.cfcLocation, xmlMappingLocation )
.generateMappings()
.getEntityMap();

// Alternative test implementation
List<File> files = new java.util.ArrayList<>();
// Dummy file for testing
files.add( Paths.get( "src/test/resources/app/models/Event.hbm.xml" ).toFile() );
files.add( Paths.get( "src/test/resources/app/models/Developer.hbm.xml" ).toFile() );
return files;
}

/**
* Configure the Hibernate session factory with the ORM configuration, entity mappings, etc.
*
* @return a populated Hibernate configuration object
*/
private Configuration buildConfiguration() {
Configuration configuration = ormConfig.toHibernateConfig();

Expand All @@ -200,23 +150,44 @@ private Configuration buildConfiguration() {
configuration.getEntityTuplizerFactory().registerDefaultTuplizerClass( EntityMode.MAP, EntityTuplizer.class );
configuration.getEntityTuplizerFactory().registerDefaultTuplizerClass( EntityMode.POJO, EntityTuplizer.class );

String xmlMappingLocation = Path.of( FileSystemUtil.getTempDirectory(), "orm_mappings", getAppName().getName() ).toString();
Map<String, EntityRecord> entities = new MappingGenerator( ( IBoxContext ) context, ormConfig.cfcLocation, xmlMappingLocation )
.generateMappings()
.getEntityMap();
Map<String, EntityRecord> entities = getEntityMap();
properties.put( BOXLANG_ENTITY_MAP, entities );

entities.values()
.stream()
.map( EntityRecord::mappingFile )
.map( Path::toString )
.forEach( configuration::addFile );
.forEach( ( path ) -> {
logger.error( "Adding entity mapping file: {}", path );
configuration.addFile( path );
} );

configuration.addProperties( properties );

return configuration;
}

/**
* Read or generate entity mappings and return a map of entity names to entity file paths
*
* @return
*/
private Map<String, EntityRecord> getEntityMap() {
if ( !ormConfig.autoGenMap ) {
// Skip mapping generation and load the pre-generated mappings from `ormConfig.cfcLocation`
throw new BoxRuntimeException( "ORMConfiguration setting `autoGenMap=false` is currently unsupported." );
} else {
// @TODO: Here we generate entity mappings and populate the temp directory (aka
// xmlMappingLocation) with the generated files.
// If `ormConfig.getAsBoolean(ORMKeys.savemapping)` is true, we should save the
// generated files to `ormConfig.getAsString(ORMKeys.cfcLocation)`. Else, we
// should save them to the temp directory.
return new MappingGenerator( ( IBoxContext ) context, ormConfig )
.generateMappings()
.getEntityMap();
}
}

/**
* Get the application name for this session factory.
* /**
Expand Down
110 changes: 85 additions & 25 deletions src/main/java/ortus/boxlang/modules/orm/mapping/MappingGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@

import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
Expand All @@ -24,6 +23,7 @@
import ortus.boxlang.compiler.ast.visitor.ClassMetadataVisitor;
import ortus.boxlang.compiler.parser.BoxScriptParser;
import ortus.boxlang.compiler.parser.ParsingResult;
import ortus.boxlang.modules.orm.config.ORMConfig;
import ortus.boxlang.modules.orm.config.ORMKeys;
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.dynamic.casters.BooleanCaster;
Expand All @@ -40,28 +40,56 @@ public class MappingGenerator {
* <p>
* This could be a temporary directory, or (when `savemapping` is enabled) the same as the entity directory.
*/
private String xmlMappingLocation;
private String saveDirectory;

/**
* Whether to save the mapping files alongside the entity files. (Default: false)
* <p>
* Alias for {@link ORMConfig#saveMapping}.
*
* <p>
* If true, the mapping files will be saved in the same directory as the entity files and {@link #xmlMappingLocation} will be ignored.
*/
private boolean saveMappingAlongsideEntity;

/**
* Map of discovered entities.
*/
private Map<String, EntityRecord> entityMap;

/**
*
* List of paths to search for entities.
*/
private List<Path> entityPaths;

/**
* ALL ORM configuration.
*/
private ORMConfig config;

/**
* IBoxContext - we shouldn't need this, but it's here for now.
* <p>
*
* @TODO: Drop once development stabilizes.
*/
private IBoxContext context;

private static final Logger logger = LoggerFactory.getLogger( MappingGenerator.class );

public MappingGenerator( IBoxContext context, String[] entityPaths, String saveDirectory ) {
this.entityMap = new java.util.HashMap<>();
this.context = context;
this.xmlMappingLocation = saveDirectory;
this.entityPaths = new java.util.ArrayList<>();
for ( String entityPath : entityPaths ) {
public MappingGenerator( IBoxContext context, ORMConfig config ) {
this.entityMap = new java.util.HashMap<>();
this.context = context;
this.config = config;
this.saveDirectory = Path.of( FileSystemUtil.getTempDirectory(), "orm_mappings", String.valueOf( config.hashCode() ) ).toString();
this.saveMappingAlongsideEntity = config.saveMapping;
this.entityPaths = new java.util.ArrayList<>();

if ( !this.saveMappingAlongsideEntity ) {
new File( this.saveDirectory ).mkdirs();
}

for ( String entityPath : config.cfcLocation ) {
this.entityPaths.add( FileSystemUtil.expandPath( context, entityPath ).absolutePath() );
}
}
Expand All @@ -74,6 +102,7 @@ public MappingGenerator generateMappings() {
.forEach( ( path ) -> {
logger.warn( "Checking path for entities: [{}]", path );
try {
// @TODO: Switch to .reduce() to build a list of .bx/.cfc files so we can process metadata in parallel
Files
.walk( path )
// TODO: Once this is all working, switch to parallel streams IF > 20ish entities
Expand All @@ -100,7 +129,13 @@ public MappingGenerator generateMappings() {
} )
.forEach( ( IStruct meta ) -> {
logger.warn( "Working with persistent entity: {}", meta.getAsString( Key.path ) );
writeXMLFile( meta );
entityMap.put( meta.getAsString( Key._name ),
new EntityRecord(
meta.getAsString( Key._name ),
"foo.foo",
writeXMLFile( meta )
)
);
} );
} catch ( IOException e ) {
// TODO Auto-generated catch block
Expand Down Expand Up @@ -135,26 +170,51 @@ public Map<String, EntityRecord> getEntityMap() {
return entityMap;
}

/**
* Write the XML mapping file for the given entity metadata.
*
* @param meta The entity metadata.
*
* @return The path to the generated XML mapping file If `saveMappingAlongsideEntity` is true, the path will be the same as the entity file, but with
* a `.hbm.xml` extension.
*/
private Path writeXMLFile( IStruct meta ) {
String name = meta.getAsString( Key._name );
Path xmlPath = Path.of( xmlMappingLocation, name + ".hbm.xml" );
Path xmlPath = this.saveMappingAlongsideEntity
? Path.of( meta.getAsString( Key.path ).replace( ".bx", ".hbm.xml" ) )
: Path.of( this.saveDirectory, name + ".hbm.xml" );
try {
new File( xmlMappingLocation ).mkdirs();

logger.warn( "Writing Hibernate XML mapping file for entity [{}] to [{}]", name, xmlPath );
Files.write( xmlPath, generateXML( meta ).getBytes() );
String finalXML = generateXML( meta );
if ( finalXML.isEmpty() ) {
logger.warn( "No XML mapping generated for entity [{}]; skipping file write", name );
} else {
Files.write( xmlPath, finalXML.getBytes() );
}

} catch ( IOException e ) {
// TODO Auto-generated catch block
// @TODO: Check `skipCFCWithError` setting before throwing exception
e.printStackTrace();
throw new BoxRuntimeException( String.format( "Failed to generate Hibernate XML for class: [{}]", name ), e );
String message = String.format( "Failed to save XML mapping for class: [%s]", name );
if ( config.skipCFCWithError ) {
logger.error( message );
return null;
}
throw new BoxRuntimeException( message, e );
}

return xmlPath;
}

public String generateXML( IStruct meta ) {
/**
* Generate the XML mapping for the given entity metadata.
* <p>
* Calls the HibernateXMLWriter to generate the XML mapping, then wraps it with a bit of pre and post XML to close out the file.
*
* @param meta The entity metadata.
*
* @return The full XML mapping for the entity as a string. If an exception was encountered and {@link ORMConfig#skipCFCWithError} is true, an empty
* string will be returned.
*/
private String generateXML( IStruct meta ) {
try {
ORMAnnotationInspector inspector = new ORMAnnotationInspector( meta );
Document doc = new HibernateXMLWriter().generateXML( inspector );
Expand All @@ -174,14 +234,14 @@ public String generateXML( IStruct meta ) {
transformer.transform( new DOMSource( doc ), new StreamResult( writer ) );

return writer.getBuffer().toString();
} catch ( TransformerConfigurationException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch ( TransformerException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
String entityName = meta.getAsString( Key._name );
logger.warn( "Failed to transform XML to string for entity [{}]", entityName, e );
if ( !config.skipCFCWithError ) {
throw new BoxRuntimeException( String.format( "Failed to transform XML to string for entity [%s]", entityName ), e );
}
}

return "test";
return "";
}
}

0 comments on commit bd1071c

Please sign in to comment.