/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.kie.workbench.common.screens.datamodeller.backend.server;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.event.Event;
import javax.enterprise.inject.Any;
import javax.enterprise.inject.Instance;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.Entity;
import org.drools.core.base.ClassTypeResolver;
import org.drools.workbench.models.datamodel.oracle.ProjectDataModelOracle;
import org.guvnor.common.services.backend.exceptions.ExceptionUtilities;
import org.guvnor.common.services.backend.validation.GenericValidator;
import org.guvnor.common.services.project.model.Package;
import org.guvnor.common.services.project.model.Project;
import org.guvnor.common.services.project.utils.ProjectResourcePaths;
import org.guvnor.common.services.shared.message.Level;
import org.guvnor.common.services.shared.metadata.model.Metadata;
import org.guvnor.common.services.shared.metadata.model.Overview;
import org.guvnor.common.services.shared.validation.model.ValidationMessage;
import org.guvnor.messageconsole.events.PublishBatchMessagesEvent;
import org.guvnor.messageconsole.events.SystemMessage;
import org.jboss.errai.bus.server.annotations.Service;
import org.jboss.errai.security.shared.api.identity.User;
import org.jboss.forge.roaster.Roaster;
import org.jboss.forge.roaster.model.JavaType;
import org.jboss.forge.roaster.model.source.AnnotationSource;
import org.jboss.forge.roaster.model.source.JavaClassSource;
import org.kie.workbench.common.screens.datamodeller.backend.server.file.DataModelerCopyHelper;
import org.kie.workbench.common.screens.datamodeller.backend.server.file.DataModelerRenameHelper;
import org.kie.workbench.common.screens.datamodeller.backend.server.handler.DomainHandler;
import org.kie.workbench.common.screens.datamodeller.backend.server.helper.DataModelerRenameWorkaroundHelper;
import org.kie.workbench.common.screens.datamodeller.backend.server.helper.DataModelerSaveHelper;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectCreatedEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectDeletedEvent;
import org.kie.workbench.common.screens.datamodeller.events.DataObjectRenamedEvent;
import org.kie.workbench.common.screens.datamodeller.model.DataModelerError;
import org.kie.workbench.common.screens.datamodeller.model.EditorModelContent;
import org.kie.workbench.common.screens.datamodeller.model.GenerationResult;
import org.kie.workbench.common.screens.datamodeller.model.TypeInfoResult;
import org.kie.workbench.common.screens.datamodeller.service.DataModelerService;
import org.kie.workbench.common.screens.datamodeller.service.ServiceException;
import org.kie.workbench.common.services.backend.project.ProjectClassLoaderHelper;
import org.kie.workbench.common.services.backend.service.KieService;
import org.kie.workbench.common.services.datamodel.backend.server.service.DataModelService;
import org.kie.workbench.common.services.datamodeller.codegen.GenerationContext;
import org.kie.workbench.common.services.datamodeller.codegen.GenerationEngine;
import org.kie.workbench.common.services.datamodeller.core.Annotation;
import org.kie.workbench.common.services.datamodeller.core.AnnotationDefinition;
import org.kie.workbench.common.services.datamodeller.core.DataModel;
import org.kie.workbench.common.services.datamodeller.core.DataObject;
import org.kie.workbench.common.services.datamodeller.core.ElementType;
import org.kie.workbench.common.services.datamodeller.core.PropertyType;
import org.kie.workbench.common.services.datamodeller.core.impl.DataObjectImpl;
import org.kie.workbench.common.services.datamodeller.core.impl.PropertyTypeFactoryImpl;
import org.kie.workbench.common.services.datamodeller.driver.FilterHolder;
import org.kie.workbench.common.services.datamodeller.driver.ModelDriver;
import org.kie.workbench.common.services.datamodeller.driver.ModelDriverException;
import org.kie.workbench.common.services.datamodeller.driver.impl.JavaRoasterModelDriver;
import org.kie.workbench.common.services.datamodeller.driver.impl.ProjectDataModelOracleUtils;
import org.kie.workbench.common.services.datamodeller.driver.impl.UpdateInfo;
import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationDefinitionRequest;
import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationDefinitionResponse;
import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationParseRequest;
import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationParseResponse;
import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationSourceRequest;
import org.kie.workbench.common.services.datamodeller.driver.model.AnnotationSourceResponse;
import org.kie.workbench.common.services.datamodeller.driver.model.DriverError;
import org.kie.workbench.common.services.datamodeller.driver.model.ModelDriverResult;
import org.kie.workbench.common.services.datamodeller.util.DriverUtils;
import org.kie.workbench.common.services.datamodeller.util.NamingUtils;
import org.kie.workbench.common.services.refactoring.model.query.RefactoringPageRow;
import org.kie.workbench.common.services.refactoring.service.PartType;
import org.kie.workbench.common.services.refactoring.service.RefactoringQueryService;
import org.kie.workbench.common.services.refactoring.service.ResourceType;
import org.kie.workbench.common.services.refactoring.service.impact.QueryOperationRequest;
import org.kie.workbench.common.services.shared.project.KieProject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.uberfire.backend.server.util.Paths;
import org.uberfire.backend.vfs.Path;
import org.uberfire.commons.data.Pair;
import org.uberfire.ext.editor.commons.service.CopyService;
import org.uberfire.ext.editor.commons.service.DeleteService;
import org.uberfire.ext.editor.commons.service.RenameService;
import org.uberfire.io.IOService;
import org.uberfire.java.nio.base.SegmentedPath;
import org.uberfire.java.nio.base.options.CommentedOption;
import org.uberfire.java.nio.file.FileAlreadyExistsException;
import org.uberfire.java.nio.file.FileSystem;
@Service
@ApplicationScoped
public class DataModelerServiceImpl
extends KieService<EditorModelContent>
implements DataModelerService {
private static final Logger logger = LoggerFactory.getLogger( DataModelerServiceImpl.class );
@Inject
@Named("ioStrategy")
IOService ioService;
@Inject
private User identity;
@Inject
private DataModelService dataModelService;
@Inject
private DataModelerServiceHelper serviceHelper;
@Inject
private ProjectClassLoaderHelper classLoaderHelper;
@Inject
private Event<DataObjectCreatedEvent> dataObjectCreatedEvent;
@Inject
private Event<DataObjectDeletedEvent> dataObjectDeletedEvent;
@Inject
private Event<DataObjectRenamedEvent> dataObjectRenamedEvent;
@Inject
private RefactoringQueryService queryService;
@Inject
private Event<PublishBatchMessagesEvent> publishBatchMessagesEvent;
@Inject
private DeleteService deleteService;
@Inject
private CopyService copyService;
@Inject
private RenameService renameService;
@Inject
private DataModelerCopyHelper copyHelper;
@Inject
private DataModelerRenameHelper renameHelper;
@Inject
private Instance<DataModelerSaveHelper> saveHelperInstance;
@Inject
private Instance<DataModelerRenameWorkaroundHelper> renameHelperInstance;
@Inject
private GenericValidator genericValidator;
@Inject
@Any
private Instance<DomainHandler> domainHandlers;
@Inject
private FilterHolder filterHolder;
private static final String DEFAULT_COMMIT_MESSAGE = "Data modeller generated action.";
public DataModelerServiceImpl() {
}
@Override
public EditorModelContent loadContent( Path path ) {
return loadContent( path, true );
}
@Override
public EditorModelContent loadContent( final Path path,
boolean includeTypesInfo ) {
EditorModelContent editorModelContent = super.loadContent( path );
if ( includeTypesInfo ) {
editorModelContent.setPropertyTypes( getBasePropertyTypes() );
editorModelContent.setAnnotationDefinitions( getAnnotationDefinitions() );
}
return editorModelContent;
}
@Override
public DataModel loadModel( final KieProject project ) {
Pair<DataModel, ModelDriverResult> resultPair = loadModel( project, true );
return resultPair != null ? resultPair.getK1() : null;
}
@Override
public Path createJavaFile( final Path context,
final String fileName,
final String comment ) {
return createJavaFile( context, fileName, comment, null );
}
@Override
public Path createJavaFile( final Path context,
final String fileName,
final String comment,
final Map<String, Object> options ) {
final org.uberfire.java.nio.file.Path nioPath = Paths.convert( context ).resolve( fileName );
final Path newPath = Paths.convert( nioPath );
if ( ioService.exists( nioPath ) ) {
throw new FileAlreadyExistsException( nioPath.toString() );
}
try {
final Package currentPackage = projectService.resolvePackage( context );
String packageName = currentPackage.getPackageName();
String className = fileName.substring( 0, fileName.indexOf( ".java" ) );
final KieProject currentProject = projectService.resolveProject( context );
DataObject dataObject = new DataObjectImpl( packageName, className );
Iterator<DomainHandler> it = domainHandlers != null ? domainHandlers.iterator() : null;
while ( it != null && it.hasNext() ) {
it.next().setDefaultValues( dataObject, options );
}
String source = createJavaSource( dataObject );
ioService.write( nioPath, source, serviceHelper.makeCommentedOption( comment ) );
dataObjectCreatedEvent.fire( new DataObjectCreatedEvent( currentProject, dataObject ) );
return newPath;
} catch ( Exception e ) {
//uncommon error.
logger.error( "It was not possible to create Java file, for path: " + context.toURI() + ", fileName: " + fileName, e );
throw new ServiceException( "It was not possible to create Java file, for path: " + context.toURI() + ", fileName: " + fileName, e );
}
}
@Override
protected EditorModelContent constructContent( Path path,
Overview overview ) {
if ( logger.isDebugEnabled() ) {
logger.debug( "Loading editor model from path: " + path.toURI() );
}
Long startTime = System.currentTimeMillis();
EditorModelContent editorModelContent = new EditorModelContent();
try {
KieProject project = projectService.resolveProject( path );
if ( project == null ) {
logger.warn( "File : " + path.toURI() + " do not belong to a valid project" );
return editorModelContent;
}
Pair<DataModel, ModelDriverResult> resultPair = loadModel( project, false );
String className = calculateClassName( project, path );
editorModelContent.setCurrentProject( project );
editorModelContent.setPath( path );
editorModelContent.setCurrentProjectPackages( serviceHelper.resolvePackages( project ) );
editorModelContent.setDataModel( resultPair.getK1() );
editorModelContent.setDataObject( resultPair.getK1().getDataObject( className ) );
editorModelContent.setDataObjectPaths( resultPair.getK2().getClassPaths() );
editorModelContent.setOriginalClassName( className );
editorModelContent.setOriginalPackageName( NamingUtils.extractPackageName( className ) );
//Read the sources for the file being edited.
if ( ioService.exists( Paths.convert( path ) ) ) {
String source = ioService.readAllString( Paths.convert( path ) );
editorModelContent.setSource( source );
}
if ( resultPair.getK2().hasErrors() ) {
editorModelContent.setErrors( serviceHelper.toDataModelerError( resultPair.getK2().getErrors() ) );
}
editorModelContent.setOverview( overview );
editorModelContent.setElapsedTime( System.currentTimeMillis() - startTime );
if ( logger.isDebugEnabled() ) {
logger.debug( "Time elapsed when loading editor model from:" + path + " : " + editorModelContent.getElapsedTime() + " ms" );
}
return editorModelContent;
} catch ( Exception e ) {
logger.error( "Editor model couldn't be loaded from path: " + ( path != null ? path.toURI() : path ) + ".", e );
throw new ServiceException( "Editor model couldn't be loaded from path: " + ( path != null ? path.toURI() : path ) + ".", e );
}
}
private Pair<DataModel, ModelDriverResult> loadModel( final KieProject project,
boolean processErrors ) {
if ( logger.isDebugEnabled() ) {
logger.debug( "Loading data model from path: " + project.getRootPath() );
}
Long startTime = System.currentTimeMillis();
DataModel dataModel = null;
Path projectPath = null;
Package defaultPackage = null;
try {
projectPath = project.getRootPath();
defaultPackage = projectService.resolveDefaultPackage( project );
if ( logger.isDebugEnabled() ) {
logger.debug( "Current project path is: " + projectPath );
}
ClassLoader classLoader = classLoaderHelper.getProjectClassLoader( project );
ModelDriver modelDriver = new JavaRoasterModelDriver( ioService,
Paths.convert( defaultPackage.getPackageMainSrcPath() ),
classLoader,
filterHolder );
ModelDriverResult result = modelDriver.loadModel();
dataModel = result.getDataModel();
if ( processErrors && result.hasErrors() ) {
processErrors( project, result );
}
//by now we still use the DMO to calculate project external dependencies.
ProjectDataModelOracle projectDataModelOracle = dataModelService.getProjectDataModel( projectPath );
ProjectDataModelOracleUtils.loadExternalDependencies( dataModel, projectDataModelOracle, classLoader );
Long endTime = System.currentTimeMillis();
if ( logger.isDebugEnabled() ) {
logger.debug( "Time elapsed when loading " + projectPath.getFileName() + ": " + ( endTime - startTime ) + " ms" );
}
return new Pair<DataModel, ModelDriverResult>( dataModel, result );
} catch ( Exception e ) {
logger.error( "Data model couldn't be loaded, path: " + projectPath + ", projectPath: " + projectPath + ".", e );
throw new ServiceException( "Data model couldn't be loaded, path: " + projectPath + ", projectPath: " + projectPath + ".", e );
}
}
public TypeInfoResult loadJavaTypeInfo( final String source ) {
try {
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver();
TypeInfoResult result = new TypeInfoResult();
org.kie.workbench.common.services.datamodeller.driver.TypeInfoResult driverResult = modelDriver.loadJavaTypeInfo( source );
result.setJavaTypeInfo( driverResult.getTypeInfo() );
if ( driverResult.hasErrors() ) {
result.setErrors( serviceHelper.toDataModelerError( driverResult.getErrors() ) );
}
return result;
} catch ( Exception e ) {
logger.error( "JavaTypeInfo object couldn't be loaded for source: " + source, e );
throw new ServiceException( "JavaTypeInfo object couldn't be loaded for source.", e );
}
}
public GenerationResult loadDataObject( final Path projectPath,
final String source,
final Path sourcePath ) {
if ( logger.isDebugEnabled() ) {
logger.debug( "Loading data object from projectPath: " + projectPath.toURI() );
}
KieProject project;
DataObject dataObject = null;
try {
project = projectService.resolveProject( projectPath );
if ( project == null ) {
return new GenerationResult( null, null, new ArrayList<DataModelerError>() );
}
ClassLoader classLoader = classLoaderHelper.getProjectClassLoader( project );
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver( ioService, null, classLoader, filterHolder );
ModelDriverResult driverResult = modelDriver.loadDataObject( source, Paths.convert( sourcePath ) );
if ( !driverResult.hasErrors() ) {
if ( driverResult.getDataModel().getDataObjects().size() > 0 ) {
dataObject = driverResult.getDataModel().getDataObjects().iterator().next();
}
return new GenerationResult( source, dataObject, new ArrayList<DataModelerError>() );
} else {
return new GenerationResult( source, null, serviceHelper.toDataModelerError( driverResult.getErrors() ) );
}
} catch ( Exception e ) {
logger.error( "Data object couldn't be loaded, path: " + projectPath + ", projectPath: " + projectPath + ".", e );
throw new ServiceException( "Data object couldn't be loaded, path: " + projectPath + ", projectPath: " + projectPath + ".", e );
}
}
/**
* Updates Java code provided in the source parameter with the data object values provided in the dataObject
* parameter. This method does not write any changes in the file system.
* @param source Java code to be updated.
* @param path Path to the java file. (used for error messages adf and project )
* @param dataObject Data object definition.
* @return returns a GenerationResult object with the updated Java code and the dataObject parameter as is.
*/
@Override
public GenerationResult updateSource( final String source,
final Path path,
final DataObject dataObject ) {
GenerationResult result = new GenerationResult();
KieProject project;
try {
project = projectService.resolveProject( path );
if ( project == null ) {
logger.warn( "File : " + path.toURI() + " do not belong to a valid project" );
result.setSource( source );
return result;
}
ClassLoader classLoader = classLoaderHelper.getProjectClassLoader( project );
Pair<String, List<DataModelerError>> updateResult = updateJavaSource( source, dataObject, new HashMap<String, String>(), new ArrayList<String>(), classLoader );
result.setSource( updateResult.getK1() );
result.setDataObject( dataObject );
result.setErrors( updateResult.getK2() );
return result;
} catch ( Exception e ) {
logger.error( "Source file for data object: " + dataObject.getClassName() + ", couldn't be updated", e );
throw new ServiceException( "Source file for data object: " + dataObject.getClassName() + ", couldn't be updated", e );
}
}
/**
* Updates data object provided in the dataObject parameter with the Java code provided in the source parameter.
* This method does not write changes in the file system.
* @param dataObject Data object definition to be updated.
* @param source Java code to use for the update.
* @param path Path to the java file. (used for error messages adf)
* @return returns a GenerationResult object with the updated data object and the source and path parameter as is.
*/
@Override
public GenerationResult updateDataObject( final DataObject dataObject,
final String source,
final Path path ) {
//Resolve the dataobject update in memory
GenerationResult result = new GenerationResult();
KieProject project;
try {
result.setSource( source );
project = projectService.resolveProject( path );
if ( project == null ) {
logger.warn( "File : " + path.toURI() + " do not belong to a valid project" );
result.setSource( source );
return result;
}
ClassLoader classLoader = classLoaderHelper.getProjectClassLoader( project );
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver( ioService, Paths.convert( path ), classLoader, filterHolder );
ModelDriverResult driverResult = modelDriver.loadDataObject( source, Paths.convert( path ) );
if ( driverResult.hasErrors() ) {
result.setErrors( serviceHelper.toDataModelerError( driverResult.getErrors() ) );
} else {
if ( driverResult.getDataModel().getDataObjects().size() > 0 ) {
result.setDataObject( driverResult.getDataModel().getDataObjects().iterator().next() );
}
}
return result;
} catch ( Exception e ) {
logger.error( "Source file for data object: " + dataObject.getClassName() + ", couldn't be parsed", e );
throw new ServiceException( "Source file for data object: " + dataObject.getClassName() + ", couldn't be parsed", e );
}
}
@Override
public GenerationResult saveSource( final String source,
final Path path,
final DataObject dataObject,
final Metadata metadata,
final String commitMessage ) {
return saveSource( source, path, dataObject, metadata, commitMessage, null, null );
}
@Override
public GenerationResult saveSource( final String source,
final Path path,
final DataObject dataObject,
final Metadata metadata,
final String commitMessage,
final String newPackageName,
final String newFileName ) {
boolean onBatch = false;
try {
GenerationResult result = resolveSaveSource( source, path, dataObject );
Package currentPackage = projectService.resolvePackage( path );
Package targetPackage = currentPackage;
String targetName = path.getFileName();
org.uberfire.java.nio.file.Path targetPath = Paths.convert( path );
boolean packageChanged = false;
boolean nameChanged = false;
if ( newPackageName != null && ( currentPackage == null || !newPackageName.equals( currentPackage.getPackageName() ) ) ) {
//make sure destination package exists.
targetPackage = serviceHelper.ensurePackageStructure( projectService.resolveProject( path ), newPackageName );
packageChanged = true;
}
if ( newFileName != null && !( newFileName + ".java" ).equals( path.getFileName() ) ) {
targetName = newFileName + ".java";
nameChanged = true;
}
fireMetadataSocialEvents( path, metadataService.getMetadata( path ), metadata );
ioService.startBatch( targetPath.getFileSystem() );
onBatch = true;
if ( packageChanged ) {
targetPath = Paths.convert( targetPackage.getPackageMainSrcPath() ).resolve( targetName );
ioService.write( Paths.convert( path ),
result.getSource(),
metadataService.setUpAttributes( path, metadata ),
serviceHelper.makeCommentedOption( commitMessage ) );
//deleteService.delete( path, commitMessage );
ioService.move( Paths.convert( path ), targetPath, serviceHelper.makeCommentedOption( commitMessage ) );
result.setPath( Paths.convert( targetPath ) );
} else if ( nameChanged ) {
ioService.write( Paths.convert( path ),
result.getSource(),
metadataService.setUpAttributes( path, metadata ),
serviceHelper.makeCommentedOption( commitMessage ) );
Path newPath = renameService.rename( path, newFileName, commitMessage );
result.setPath( newPath );
} else {
ioService.write( Paths.convert( path ),
result.getSource(),
metadataService.setUpAttributes( path, metadata ),
serviceHelper.makeCommentedOption( commitMessage ) );
result.setPath( path );
}
if ( saveHelperInstance != null ) {
for ( DataModelerSaveHelper saveHelper : saveHelperInstance ) {
saveHelper.postProcess( path, result.getPath() );
}
}
return result;
} catch ( Exception e ) {
logger.error( "Source file couldn't be updated, path: " + path.toURI() + ", dataObject: " + ( dataObject != null ? dataObject.getClassName() : null ) + ".", e );
throw new ServiceException( "Source file couldn't be updated, path: " + path.toURI() + ", dataObject: " + ( dataObject != null ? dataObject.getClassName() : null ) + ".", e );
} finally {
if ( onBatch ) {
ioService.endBatch();
}
}
}
private GenerationResult resolveSaveSource( final String source,
final Path path,
final DataObject dataObject ) {
GenerationResult result = new GenerationResult();
KieProject project;
String updatedSource;
try {
project = projectService.resolveProject( path );
if ( project == null ) {
logger.warn( "File : " + path.toURI() + " do not belong to a valid project" );
result.setSource( source );
return result;
}
if ( dataObject != null ) {
//the source needs to be updated with the DataObject definition prior to save
result = updateSource( source, path, dataObject );
updatedSource = result.getSource();
} else {
//if the dataObject wasn't provided the source is already prepared to be saved and likely
//it's not parsed at the ui. So we will save the provided source and try to parse the data object
updatedSource = source;
}
if ( dataObject == null ) {
ClassLoader classLoader = classLoaderHelper.getProjectClassLoader( project );
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver( ioService, Paths.convert( path ), classLoader, filterHolder );
ModelDriverResult driverResult = modelDriver.loadDataObject( source, Paths.convert( path ) );
if ( driverResult.hasErrors() ) {
result.setErrors( serviceHelper.toDataModelerError( driverResult.getErrors() ) );
} else {
if ( driverResult.getDataModel().getDataObjects().size() > 0 ) {
result.setDataObject( driverResult.getDataModel().getDataObjects().iterator().next() );
}
}
}
result.setSource( updatedSource );
return result;
} catch ( Exception e ) {
logger.error( "Source file couldn't be updated, path: " + path.toURI() + ", dataObject: " + ( dataObject != null ? dataObject.getClassName() : null ) + ".", e );
throw new ServiceException( "Source file couldn't be updated, path: " + path.toURI() + ", dataObject: " + ( dataObject != null ? dataObject.getClassName() : null ) + ".", e );
}
}
public Path copy( final Path path,
final String newName,
final String newPackageName,
final Path targetDirectory,
final String comment,
final boolean refactor ) {
Path targetPath = null;
if ( refactor ) {
try {
GenerationResult refactoringResult = refactorClass( path, newPackageName, newName );
if ( !refactoringResult.hasErrors() ) {
targetPath = Paths.convert( Paths.convert( targetDirectory ).resolve( newName + ".java" ) );
copyHelper.addRefactoredPath( targetPath, refactoringResult.getSource(), comment );
KieProject project = projectService.resolveProject( targetPath );
if ( project != null ) {
dataObjectCreatedEvent.fire( new DataObjectCreatedEvent( project, refactoringResult.getDataObject() ) );
}
}
} catch ( Exception e ) {
//if the refactoring fails for whatever reason the file still needs to be copied.
logger.error( "An error was produced during class refactoring at file copying for file: " + path + ". The file copying will continue without class refactoring", e );
}
}
try {
return copyService.copy( path, newName, targetDirectory, comment );
} finally {
if ( targetPath != null ) {
copyHelper.removeRefactoredPath( targetPath );
}
}
}
public Path rename( final Path path,
final String newName,
String comment,
final boolean refactor,
final boolean saveCurrentChanges,
final String source,
final DataObject dataObject,
final Metadata metadata ) {
GenerationResult saveResult = null;
if ( saveCurrentChanges ) {
saveResult = resolveSaveSource( source, path, dataObject );
ioService.write( Paths.convert( path ),
saveResult.getSource(),
metadataService.setUpAttributes( path, metadata ),
serviceHelper.makeCommentedOption( comment ) );
}
Path targetPath = null;
String newContent = null;
if ( refactor ) {
String sourceToRefactor;
if ( saveCurrentChanges ) {
sourceToRefactor = ( saveResult != null && !saveResult.hasErrors() ) ? saveResult.getSource() : null;
} else {
sourceToRefactor = source;
}
if ( sourceToRefactor != null ) {
try {
GenerationResult refactoringResult = refactorClass( sourceToRefactor, path, null, newName );
if ( !refactoringResult.hasErrors() ) {
targetPath = Paths.convert( Paths.convert( path ).resolveSibling( newName + ".java" ) );
renameHelper.addRefactoredPath( targetPath, refactoringResult.getSource(), comment );
//TODO send data object renamed event.
newContent = refactoringResult.getSource();
}
} catch ( Exception e ) {
//if the refactoring fails for whatever reason the file still needs to be renamed.
logger.error( "An error was produced during class refactoring at file renaming for file: " + path + ". The file renaming will continue without class refactoring", e );
}
}
}
try {
//TODO we need to investigate why we have a DeleteEvent, and a CreateEvent for the case of .java files.
boolean workaround = true;
if ( !workaround ) {
return renameService.rename( path, newName, comment );
} else {
//I will implement the rename here as a workaround
//remove this workaround when we can find the error.
Path updatedPath = renameWorkaround( path, newName, newContent, comment );
dataObjectRenamedEvent.fire( (DataObjectRenamedEvent) new DataObjectRenamedEvent().withPath( updatedPath ) );
return updatedPath;
}
} finally {
if ( targetPath != null ) {
renameHelper.removeRefactoredPath( targetPath );
}
}
}
public Path renameWorkaround( final Path path,
final String newName,
final String newContent,
final String comment ) {
try {
final org.uberfire.java.nio.file.Path _path = Paths.convert( path );
String originalFileName = _path.getFileName().toString();
final String extension = originalFileName.substring( originalFileName.lastIndexOf( "." ) );
final org.uberfire.java.nio.file.Path _target = _path.resolveSibling( newName + extension );
final Path targetPath = Paths.convert( _target );
try {
if ( newContent != null ) {
//first overwrite the content with the new content, then we can rename the file.
//this is the workaround.
ioService.write( _path, newContent, serviceHelper.makeCommentedOption( comment ) );
}
ioService.startBatch( new FileSystem[]{ _target.getFileSystem() } );
ioService.move( _path,
_target,
serviceHelper.makeCommentedOption( "File [" + path.toURI() + "] renamed to [" + targetPath.toURI() + "]." )
);
if ( renameHelperInstance != null ) {
for ( DataModelerRenameWorkaroundHelper renameHelper : renameHelperInstance ) {
renameHelper.postProcess( path, targetPath );
}
}
} catch ( final Exception e ) {
throw e;
} finally {
ioService.endBatch();
}
return Paths.convert( _target );
} catch ( Exception e ) {
throw ExceptionUtilities.handleException( e );
}
}
@Override
public String getSource( Path path ) {
org.uberfire.java.nio.file.Path convertedPath = Paths.convert( path );
return ioService.readAllString( convertedPath );
}
private void processErrors( KieProject project,
ModelDriverResult result ) {
PublishBatchMessagesEvent publishEvent = new PublishBatchMessagesEvent();
publishEvent.setCleanExisting( true );
publishEvent.setUserId( identity != null ? identity.getIdentifier() : null );
publishEvent.setMessageType( "DataModeler" );
SystemMessage systemMessage;
for ( DriverError error : result.getErrors() ) {
systemMessage = new SystemMessage();
systemMessage.setMessageType( "DataModeler" );
systemMessage.setLevel( Level.ERROR );
systemMessage.setId( error.getId() );
systemMessage.setText( error.getMessage() );
systemMessage.setColumn( error.getColumn() );
systemMessage.setLine( error.getLine() );
systemMessage.setPath( error.getFile() );
publishEvent.getMessagesToPublish().add( systemMessage );
}
publishBatchMessagesEvent.fire( publishEvent );
}
@Override
public GenerationResult saveModel( final DataModel dataModel,
final KieProject project,
final boolean overwrite,
final String commitMessage ) {
Long startTime = System.currentTimeMillis();
boolean onBatch = false;
try {
//Start IOService bath processing. IOService batch processing causes a blocking operation on the file system
//to it must be treated carefully.
CommentedOption option = serviceHelper.makeCommentedOption( commitMessage );
ioService.startBatch( Paths.convert( project.getRootPath() ).getFileSystem() );
onBatch = true;
generateModel( dataModel, project, option );
onBatch = false;
ioService.endBatch();
Long endTime = System.currentTimeMillis();
if ( logger.isDebugEnabled() ) {
logger.debug( "Time elapsed when saving " + project.getProjectName() + ": " + ( endTime - startTime ) + " ms" );
}
GenerationResult result = new GenerationResult();
result.setGenerationTime( endTime - startTime );
return result;
} catch ( Exception e ) {
logger.error( "An error was produced during data model adf, dataModel: " + dataModel + ", path: " + project.getRootPath(), e );
if ( onBatch ) {
try {
logger.warn( "IOService batch method is still on, trying to end batch processing." );
ioService.endBatch();
logger.warn( "IOService batch method is was successfully finished. The user will still get the exception, but the batch processing was finished." );
} catch ( Exception ex ) {
logger.error( "An error was produced when the IOService.endBatch processing was executed.", ex );
}
}
throw new ServiceException( "Data model couldn't be generated due to the following error. " + e );
}
}
@Override
public GenerationResult saveModel( DataModel dataModel,
final KieProject project ) {
return saveModel( dataModel, project, false, DEFAULT_COMMIT_MESSAGE );
}
@Override
public void delete( final Path path,
final String comment ) {
try {
KieProject project = projectService.resolveProject( path );
if ( project == null ) {
logger.warn( "File : " + path.toURI() + " do not belong to a valid project" );
return;
}
deleteService.delete( path, comment );
String className = calculateClassName( project, path );
DataObject dataObject = new DataObjectImpl(
NamingUtils.extractPackageName( className ),
NamingUtils.extractClassName( className ) );
dataObjectDeletedEvent.fire( new DataObjectDeletedEvent( project, dataObject ) );
} catch ( final Exception e ) {
logger.error( "File: " + path.toURI() + " couldn't be deleted due to the following error. ", e );
throw new ServiceException( "File: " + path.toURI() + " couldn't be deleted due to the following error. " + e.getMessage() );
}
}
@Override
public GenerationResult refactorClass( final Path path,
final String newPackageName,
final String newClassName ) {
final String source = ioService.readAllString( Paths.convert( path ) );
return refactorClass( source, path, newPackageName, newClassName );
}
private GenerationResult refactorClass( final String source,
final Path path,
final String newPackageName,
final String newClassName ) {
GenerationResult result = loadDataObject( path, source, path );
if ( ( result.getErrors() == null || result.getErrors().isEmpty() ) && result.getDataObject() != null ) {
final DataObject dataObject = result.getDataObject();
if ( newPackageName != null ) {
dataObject.setPackageName( newPackageName );
}
if ( newClassName != null ) {
dataObject.setName( newClassName );
}
return updateSource( source, path, dataObject );
} else {
return result;
}
}
@SuppressWarnings( "unchecked" )
@Override
public List<ValidationMessage> validate( final String source,
final Path path,
final DataObject dataObject ) {
try {
String validationSource = null;
List<ValidationMessage> validations = new ArrayList<ValidationMessage>();
KieProject project = projectService.resolveProject( path );
if ( project == null ) {
logger.warn( "File : " + path.toURI() + " do not belong to a valid project" );
ValidationMessage validationMessage = new ValidationMessage();
validationMessage.setPath( path );
validationMessage.setText( "File do no belong to a valid project" );
validationMessage.setLevel( Level.ERROR );
validations.add( new ValidationMessage() );
return validations;
}
if ( dataObject != null ) {
//the source needs to be updated with the DataObject definition prior to validation calculation.
//we must to the same processing as if the file was about to be saved.
GenerationResult result = updateSource( source, path, dataObject );
if ( !result.hasErrors() ) {
validationSource = result.getSource();
} else {
//it was not possible to update the source with the data object definition.
return serviceHelper.toValidationMessage( result.getErrors() );
}
} else {
validationSource = source;
}
return genericValidator.validate( path, validationSource != null ? validationSource : "" );
} catch ( Exception e ) {
logger.error( "An error was produced during validation", e );
throw new ServiceException( "An error was produced during validation", e );
}
}
private void generateModel( DataModel dataModel,
KieProject project,
CommentedOption option ) throws Exception {
org.uberfire.java.nio.file.Path targetFile;
org.uberfire.java.nio.file.Path javaRootPath;
String newSource;
//ensure java sources directory exists.
Path projectPath = project.getRootPath();
javaRootPath = ensureProjectJavaPath( Paths.convert( projectPath ) );
for ( DataObject dataObject : dataModel.getDataObjects() ) {
targetFile = calculateFilePath( dataObject.getClassName(), javaRootPath );
if ( logger.isDebugEnabled() ) {
logger.debug( "Data object: " + dataObject.getClassName() + " java source code will be generated from scratch and written into file: " + targetFile );
}
newSource = createJavaSource( dataObject );
ioService.write( targetFile, newSource, option );
}
}
private Pair<String, List<DataModelerError>> updateJavaSource( String originalSource,
DataObject dataObject,
Map<String, String> renames,
List<String> deletions,
ClassLoader classLoader ) throws Exception {
String newSource;
ClassTypeResolver classTypeResolver;
List<DataModelerError> errors = new ArrayList<DataModelerError>();
if ( logger.isDebugEnabled() ) {
logger.debug( "Starting java source update for class: " + dataObject.getClassName() );
}
if ( logger.isDebugEnabled() ) {
logger.debug( "original source is: " + originalSource );
}
JavaType<?> javaType = Roaster.parse( originalSource );
if ( javaType.isClass() ) {
if ( javaType.getSyntaxErrors() != null && !javaType.getSyntaxErrors().isEmpty() ) {
//if a file has parsing errors it will be skipped.
errors.addAll( serviceHelper.toDataModelerError( javaType.getSyntaxErrors(), null ) );
newSource = originalSource;
} else {
JavaClassSource javaClassSource = (JavaClassSource) javaType;
classTypeResolver = DriverUtils.createClassTypeResolver( javaClassSource, classLoader );
updateJavaClassSource( dataObject, javaClassSource, renames, deletions, classTypeResolver );
newSource = javaClassSource.toString();
}
} else {
logger.debug( "No Class definition was found for source: " + originalSource + ", original source won't be modified." );
newSource = originalSource;
}
if ( logger.isDebugEnabled() ) {
logger.debug( "updated source is: " + newSource );
}
return new Pair<String, List<DataModelerError>>( newSource, errors );
}
private void updateJavaClassSource( DataObject dataObject,
JavaClassSource javaClassSource,
Map<String, String> renames,
List<String> deletions,
ClassTypeResolver classTypeResolver ) throws Exception {
if ( javaClassSource == null || !javaClassSource.isClass() ) {
logger.warn( "A null javaClassSource or javaClassSouce is not a Class, no processing will be done. javaClassSource: " + javaClassSource + " className: " + ( javaClassSource != null ? javaClassSource.getName() : null ) );
return;
}
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver( filterHolder );
UpdateInfo updateInfo = new UpdateInfo();
//prepare additional update info prior update
if ( renames != null ) {
for ( Map.Entry<String, String> entry : renames.entrySet() ) {
updateInfo.addClassRename( entry.getKey(), entry.getValue() );
}
}
if ( deletions != null ) {
for ( String deletion : deletions ) {
updateInfo.addDeletedClass( deletion );
}
}
modelDriver.updateSource( javaClassSource, dataObject, updateInfo, classTypeResolver );
}
private String createJavaSource( DataObject dataObject ) throws Exception {
GenerationContext generationContext = new GenerationContext( null );
String source;
GenerationEngine engine;
try {
engine = GenerationEngine.getInstance();
source = engine.generateJavaClassString( generationContext, dataObject );
} catch ( Exception e ) {
logger.error( "Java source for dataObject: " + dataObject.getClassName() + " couldn't be created.", e );
throw e;
}
return source;
}
@Override
public List<Path> findClassUsages( Path currentPath,
String className ) {
KieProject project = projectService.resolveProject(currentPath);
String branch = "master";
if( currentPath instanceof SegmentedPath ) {
branch = ((SegmentedPath) currentPath).getSegmentId();
}
QueryOperationRequest request = QueryOperationRequest
.references(className, ResourceType.JAVA)
.inProjectRootPathURI( project.getRootPath().toURI() )
.onBranch(branch);
return executeReferencesQuery( request );
}
@Override
public List<Path> findFieldUsages( Path currentPath,
String className,
String fieldName ) {
KieProject project = projectService.resolveProject(currentPath);
String branch = "master";
if( currentPath instanceof SegmentedPath ) {
branch = ((SegmentedPath) currentPath).getSegmentId();
}
QueryOperationRequest request = QueryOperationRequest
.referencesPart(className, fieldName, PartType.FIELD)
.inProjectRootPathURI( project.getRootPath().toURI() )
.onBranch(branch);
return executeReferencesQuery( request );
}
@Override
public List<String> findPersistableClasses( final Path path ) {
List<String> classes = new ArrayList<String>();
KieProject project = projectService.resolveProject( path );
if ( project != null ) {
DataModel dataModel = loadModel( project );
if ( dataModel != null ) {
for ( DataObject dataObject : dataModel.getDataObjects() ) {
if ( dataObject.getAnnotation( Entity.class.getName() ) != null ) {
classes.add( dataObject.getClassName() );
}
}
}
}
return classes;
}
@Override
public Boolean isPersistableClass( String className, Path path ) {
//check the project class path to see if the class is defined likely in a project dependency or in curren project.
KieProject project = projectService.resolveProject( path );
if ( project != null ) {
ClassLoader classLoader = classLoaderHelper.getProjectClassLoader( project );
try {
classLoader.loadClass( className );
return true;
} catch ( Exception e ) {
return false;
}
}
return false;
}
private List<Path> executeReferencesQuery(QueryOperationRequest request) {
List<Path> results = new ArrayList<Path>();
try {
final List<RefactoringPageRow> queryResults = queryService.queryToList(request);
if ( queryResults != null ) {
for ( RefactoringPageRow row : queryResults ) {
results.add( (org.uberfire.backend.vfs.Path) row.getValue() );
}
}
return results;
} catch ( Exception e ) {
String msg = "Unable to query lucene index for resource references: " + e.getMessage();
logger.error( msg );
throw new ServiceException( msg, e );
}
}
@Override
public List<PropertyType> getBasePropertyTypes() {
List<PropertyType> types = new ArrayList<PropertyType>();
types.addAll( PropertyTypeFactoryImpl.getInstance().getBasePropertyTypes() );
return types;
}
@Override
public Map<String, AnnotationDefinition> getAnnotationDefinitions() {
Map<String, AnnotationDefinition> annotations = new HashMap<String, AnnotationDefinition>();
//add additional annotations configured by external domains
Iterator<DomainHandler> it = domainHandlers != null ? domainHandlers.iterator() : null;
DomainHandler domainHandler;
List<List<AnnotationDefinition>> allDomainsAnnotations = new ArrayList<List<AnnotationDefinition>>();
while ( it != null && it.hasNext() ) {
domainHandler = it.next();
allDomainsAnnotations.add( domainHandler.getManagedAnnotations() );
}
List<AnnotationDefinition> coreAnnotationDefinitions = ( new JavaRoasterModelDriver() ).getConfiguredAnnotations();
allDomainsAnnotations.add( coreAnnotationDefinitions );
for ( List<AnnotationDefinition> annotationDefinitionList : allDomainsAnnotations ) {
if ( annotationDefinitionList != null ) {
for ( AnnotationDefinition annotationDefinition : annotationDefinitionList ) {
annotations.put( annotationDefinition.getClassName(), annotationDefinition );
}
}
}
return annotations;
}
@Override
public Boolean exists( Path path ) {
return ioService.exists( Paths.convert( path ) );
}
@Override
public AnnotationSourceResponse resolveSourceRequest( AnnotationSourceRequest sourceRequest ) {
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver();
return modelDriver.resolveSourceRequest( sourceRequest );
}
@Override
public List<ValidationMessage> validateValuePair( String annotationClassName,
ElementType target,
String valuePairName,
String literalValue ) {
//Currently we only validate the syntax but additional checks may be added.
List<ValidationMessage> validationMessages = new ArrayList<ValidationMessage>();
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver();
Pair<AnnotationSource<JavaClassSource>, List<DriverError>> parseResult =
modelDriver.parseAnnotationWithValuePair( annotationClassName,
target, valuePairName, literalValue );
if ( parseResult.getK2() != null && parseResult.getK2().size() > 0 ) {
ValidationMessage validationMessage;
for ( DriverError driverError : parseResult.getK2() ) {
validationMessage = new ValidationMessage();
validationMessage.setText( driverError.getMessage() );
validationMessage.setColumn( driverError.getColumn() );
validationMessage.setLine( driverError.getLine() );
validationMessage.setLevel( Level.ERROR );
validationMessages.add( validationMessage );
}
}
return validationMessages;
}
@Override
public AnnotationParseResponse resolveParseRequest( AnnotationParseRequest parseRequest,
KieProject kieProject ) {
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver();
Pair<Annotation, List<DriverError>> driverResult = modelDriver.parseAnnotationWithValuePair(
parseRequest.getAnnotationClassName(),
parseRequest.getTarget(),
parseRequest.getValuePairName(),
parseRequest.getValuePairLiteralValue(),
classLoaderHelper.getProjectClassLoader( kieProject ) );
AnnotationParseResponse response = new AnnotationParseResponse( driverResult.getK1() );
response.withErrors( driverResult.getK2() );
return response;
}
@Override
public AnnotationDefinitionResponse resolveDefinitionRequest( AnnotationDefinitionRequest definitionRequest,
KieProject kieProject ) {
JavaRoasterModelDriver modelDriver = new JavaRoasterModelDriver();
ClassLoader classLoader = classLoaderHelper.getProjectClassLoader( kieProject );
ClassTypeResolver classTypeResolver = DriverUtils.createClassTypeResolver( classLoader );
AnnotationDefinitionResponse definitionResponse = new AnnotationDefinitionResponse();
try {
AnnotationDefinition annotationDefinition = modelDriver.buildAnnotationDefinition( definitionRequest.getClassName(), classTypeResolver );
definitionResponse.withAnnotationDefinition( annotationDefinition );
} catch ( ModelDriverException e ) {
DriverError driverError = new DriverError( e.getMessage() );
definitionResponse.addError( driverError );
}
return definitionResponse;
}
private org.uberfire.java.nio.file.Path ensureProjectJavaPath( org.uberfire.java.nio.file.Path projectPath ) {
org.uberfire.java.nio.file.Path javaPath = projectPath.resolve( "src" );
if ( !ioService.exists( javaPath ) ) {
javaPath = ioService.createDirectory( javaPath );
}
javaPath = javaPath.resolve( "main" );
if ( !ioService.exists( javaPath ) ) {
javaPath = ioService.createDirectory( javaPath );
}
javaPath = javaPath.resolve( "java" );
if ( !ioService.exists( javaPath ) ) {
javaPath = ioService.createDirectory( javaPath );
}
return javaPath;
}
/**
* Given a path within a project calculates the expected class name for the given class.
*/
private String calculateClassName( Project project,
Path path ) {
String rootPathURI = project.getRootPath().toURI();
String pathURI = path.toURI();
String strPath = null;
if ( !pathURI.startsWith( rootPathURI ) ) {
return null;
}
pathURI = pathURI.substring( rootPathURI.length() + 1, pathURI.length() );
if ( pathURI.startsWith( ProjectResourcePaths.MAIN_SRC_PATH ) ) {
strPath = pathURI.substring( ProjectResourcePaths.MAIN_SRC_PATH.length() + 1, pathURI.length() );
} else if ( pathURI.startsWith( ProjectResourcePaths.TEST_SRC_PATH ) ) {
strPath = pathURI.substring( ProjectResourcePaths.TEST_SRC_PATH.length() + 1, pathURI.length() );
}
if ( strPath == null ) {
return null;
}
strPath = strPath.replace( "/", "." );
strPath = strPath.substring( 0, strPath.indexOf( ".java" ) );
return strPath;
}
/**
* Given a className calculates the path to the java file allocating the corresponding pojo.
*/
private org.uberfire.java.nio.file.Path calculateFilePath( String className,
org.uberfire.java.nio.file.Path javaPath ) {
String name = NamingUtils.extractClassName( className );
String packageName = NamingUtils.extractPackageName( className );
org.uberfire.java.nio.file.Path filePath = javaPath;
if ( packageName != null ) {
List<String> packageNameTokens = tokenizePackageName( packageName );
for ( String token : packageNameTokens ) {
filePath = filePath.resolve( token );
}
}
filePath = filePath.resolve( name + ".java" );
return filePath;
}
public List<String> tokenizePackageName( final String packageName ) {
List<String> tokens = new ArrayList<String>();
if ( packageName != null ) {
StringTokenizer st = new StringTokenizer( packageName, "." );
while ( st.hasMoreTokens() ) {
tokens.add( st.nextToken() );
}
}
return tokens;
}
}