| /******************************************************************************* |
| * Copyright (c) 2007, 2012 Oracle. All rights reserved. |
| * This program and the accompanying materials are made available under the |
| * terms of the Eclipse Public License v1.0, which accompanies this distribution |
| * and is available at http://www.eclipse.org/legal/epl-v10.html. |
| * |
| * Contributors: |
| * Oracle - initial API and implementation |
| ******************************************************************************/ |
| |
| package org.eclipse.jpt.jpa.gen.internal; |
| |
| import java.io.ByteArrayInputStream; |
| import java.io.File; |
| import java.io.IOException; |
| import java.io.StringWriter; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Properties; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| |
| import org.apache.velocity.VelocityContext; |
| import org.apache.velocity.app.VelocityEngine; |
| import org.apache.velocity.runtime.log.JdkLogChute; |
| import org.eclipse.core.resources.IContainer; |
| import org.eclipse.core.resources.IFile; |
| import org.eclipse.core.resources.IFolder; |
| import org.eclipse.core.resources.IProject; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.FileLocator; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.jdt.core.IJavaProject; |
| import org.eclipse.jdt.core.IPackageFragment; |
| import org.eclipse.jdt.core.IPackageFragmentRoot; |
| import org.eclipse.jdt.core.JavaModelException; |
| import org.eclipse.jpt.common.core.resource.ProjectResourceLocator; |
| import org.eclipse.jpt.common.utility.internal.CollectionTools; |
| import org.eclipse.jpt.jpa.core.JpaProject; |
| import org.eclipse.jpt.jpa.core.context.persistence.Persistence; |
| import org.eclipse.jpt.jpa.core.context.persistence.PersistenceUnit; |
| import org.eclipse.jpt.jpa.core.resource.xml.JpaXmlResource; |
| import org.eclipse.jpt.jpa.gen.internal.util.CompilationUnitModifier; |
| import org.eclipse.jpt.jpa.gen.internal.util.FileUtil; |
| import org.eclipse.jpt.jpa.gen.internal.util.UrlUtil; |
| import org.eclipse.osgi.util.NLS; |
| import org.osgi.framework.Bundle; |
| |
| /** |
| * This generator will generate a package of entities for a set of tables. |
| */ |
| public class PackageGenerator { |
| |
| private static final String LOGGER_NAME = "org.eclipse.jpt.entities.gen.log"; //$NON-NLS-1$ |
| private final JpaProject jpaProject; |
| private final ORMGenCustomizer customizer; |
| private final OverwriteConfirmer overwriteConfirmer; |
| |
| public static void generate(JpaProject jpaProject, ORMGenCustomizer customizer, OverwriteConfirmer overwriteConfirmer, IProgressMonitor monitor, boolean generateXml) throws CoreException { |
| SubMonitor sm = SubMonitor.convert(monitor, 20); |
| PackageGenerator generator = new PackageGenerator(jpaProject, customizer, overwriteConfirmer); |
| sm.worked(1); |
| try { |
| if (generateXml) { |
| generator.doXmlGenerate(sm.newChild(19)); |
| } |
| else { |
| generator.doGenerate(sm.newChild(19)); |
| } |
| } catch (Exception e) { |
| throw new CoreException(new Status(IStatus.ERROR, JptJpaGenPlugin.PLUGIN_ID, JptGenMessages.Error_Generating_Entities, e)); |
| } |
| } |
| |
| private PackageGenerator(JpaProject jpaProject, ORMGenCustomizer customizer, OverwriteConfirmer confirmer) { |
| super(); |
| this.jpaProject = jpaProject; |
| this.customizer = customizer; |
| this.overwriteConfirmer = confirmer; |
| } |
| |
| private Object getCustomizer() { |
| return this.customizer; |
| } |
| |
| private IJavaProject getJavaProject(){ |
| return this.jpaProject.getJavaProject(); |
| } |
| |
| |
| protected void doGenerate(IProgressMonitor monitor) throws Exception { |
| generateInternal(monitor); |
| } |
| |
| protected void doXmlGenerate(IProgressMonitor monitor) throws Exception { |
| generateXmlInternal(monitor); |
| } |
| |
| protected void generateInternal(IProgressMonitor monitor) throws Exception { |
| File templDir = prepareTemplatesFolder("templates/entities/"); //$NON-NLS-1$ |
| |
| List<String> genClasses = new java.util.ArrayList<String>(); |
| List<String> tableNames = this.customizer.getGenTableNames(); |
| |
| /* .java per table, persistence.xml, refresh package folder */ |
| SubMonitor sm = SubMonitor.convert(monitor, tableNames.size() + 2); |
| |
| |
| for (Iterator<String> iter = tableNames.iterator(); iter.hasNext();) { |
| if (sm.isCanceled()) { |
| return; |
| } |
| String tableName = iter.next(); |
| ORMGenTable table = this.customizer.getTable(tableName); |
| |
| String className = table.getQualifiedClassName(); |
| |
| generateClass(table, templDir.getAbsolutePath(), sm.newChild(1, SubMonitor.SUPPRESS_NONE)); |
| |
| genClasses.add(className); |
| /* |
| * add the composite key class to persistence.xml because some |
| * JPA provider(e.g. Kodo) requires it. Hibernate doesn't seem to care). |
| */ |
| if (table.isCompositeKey()) { |
| genClasses.add(table.getQualifiedCompositeKeyClassName()); |
| } |
| } |
| if (sm.isCanceled()) { |
| return; |
| } |
| |
| //update persistence.xml |
| if (this.customizer.updatePersistenceXml()) { |
| updatePersistenceXml(genClasses); |
| } |
| sm.worked(2); |
| } |
| |
| private void updatePersistenceXml(final List<String> genClasses) { |
| JpaXmlResource resource = this.jpaProject.getPersistenceXmlResource(); |
| if (resource == null) { |
| //the resource would only be null if the persistence.xml file had an invalid content type, |
| //do not attempt to update |
| return; |
| } |
| |
| Persistence persistence = this.jpaProject.getRootContextNode().getPersistenceXml().getPersistence(); |
| if (persistence == null) { |
| // invalid content, do not attempt to update |
| return; |
| } |
| PersistenceUnit persistenceUnit; |
| // create a persistence unit if one doesn't already exist |
| if (persistence.getPersistenceUnitsSize() == 0) { |
| persistenceUnit = persistence.addPersistenceUnit(); |
| persistenceUnit.setName(PackageGenerator.this.jpaProject.getName()); |
| } else { |
| // we only support one persistence unit - take the first one |
| persistenceUnit = persistence.getPersistenceUnit(0); |
| } |
| for (String className : genClasses) { |
| if (CollectionTools.isEmpty(persistenceUnit.getMappingFileRefsContaining(className)) && !persistenceUnit.specifiesPersistentType(className)) { |
| persistenceUnit.addSpecifiedClassRef(className); |
| } |
| } |
| resource.save(); |
| } |
| |
| //TODO can probably axe this method |
| protected void generateXmlInternal(IProgressMonitor monitor) throws Exception { |
| File templDir = prepareTemplatesFolder("templates/xml_entities/"); //$NON-NLS-1$ |
| |
| List<String> tableNames = this.customizer.getGenTableNames(); |
| |
| //TODO Need to fix progress monitor |
| SubMonitor sm = SubMonitor.convert(monitor, tableNames.size() + 2); |
| |
| generateXmlMappingFile(tableNames, templDir.getAbsolutePath(), sm.newChild(1, SubMonitor.SUPPRESS_NONE)); |
| |
| if (sm.isCanceled()) { |
| return; |
| } |
| |
| sm.worked(2); |
| } |
| |
| private File prepareTemplatesFolder(String templatesPath) throws IOException, Exception, |
| CoreException { |
| //Prepare the Velocity template folder: |
| //If the plug-in is packaged as a JAR, we need extract the template |
| //folder into the plug-in state location. This is required by Velocity |
| //since we use included templates. |
| Bundle bundle = Platform.getBundle(JptJpaGenPlugin.PLUGIN_ID); |
| Path path = new Path( templatesPath); |
| URL url = FileLocator.find(bundle, path, null); |
| if (url == null) { |
| throw new CoreException(new Status(IStatus.ERROR, JptJpaGenPlugin.PLUGIN_ID, JptGenMessages.Templates_notFound + " "+ JptJpaGenPlugin.PLUGIN_ID + "/" + templatesPath) );//$NON-NLS-1$ |
| } |
| URL templUrl = FileLocator.resolve(url); |
| |
| //Have this check so that the code would work in both PDE and JARed plug-in at runtime |
| File templDir = null; |
| if (UrlUtil.isJarUrl(templUrl)) { |
| templDir = FileUtil.extractFilesFromBundle( templUrl, bundle, templatesPath ); |
| } else { |
| templDir = UrlUtil.getUrlFile(templUrl); |
| } |
| |
| |
| if (templDir == null || !templDir.exists()) { |
| throw new CoreException(new Status(IStatus.ERROR, JptJpaGenPlugin.PLUGIN_ID, JptGenMessages.Templates_notFound + " "+ JptJpaGenPlugin.PLUGIN_ID ) );//$NON-NLS-1$ |
| } |
| return templDir; |
| } |
| |
| /** |
| * Saves/Creates the .java file corresponding to a database table |
| * with the given content. |
| * |
| * @param templDir The velocity template file directory. It is assumed |
| * that this directory contains the 2 files <em>main.java.vm</em> |
| * and <em>pk.java.vm</em> |
| * @param progress |
| */ |
| protected void generateClass(ORMGenTable table, String templateDirPath, IProgressMonitor monitor) throws Exception { |
| |
| String subTaskName = NLS.bind(JptGenMessages.EntityGenerator_taskName, table.getName()); |
| SubMonitor sm = SubMonitor.convert(monitor, subTaskName, 10); |
| |
| try { |
| IFolder javaPackageFolder = getJavaPackageFolder(table, monitor); |
| IFile javaFile = javaPackageFolder.getFile( table.getClassName() + ".java"); //$NON-NLS-1$ |
| |
| if (javaFile.exists()) { |
| if (this.overwriteConfirmer != null && !this.overwriteConfirmer.overwrite(javaFile.getName())) { |
| return; |
| } |
| } |
| //JdkLogChute in this version of Velocity not allow to set log level |
| //Workaround by preset the log level before Velocity is initialized |
| Logger logger = Logger.getLogger( LOGGER_NAME ); |
| logger.setLevel( Level.SEVERE ); |
| |
| Properties vep = new Properties(); |
| vep.setProperty("file.resource.loader.path", templateDirPath); //$NON-NLS-1$ |
| vep.setProperty( JdkLogChute.RUNTIME_LOG_JDK_LOGGER, LOGGER_NAME ); |
| VelocityEngine ve = new VelocityEngine(); |
| ve.init(vep); |
| sm.worked(2); |
| |
| generateJavaFile(table, javaFile, ve, "main.java.vm", true/*isDomainClass*/, sm.newChild(6)); //$NON-NLS-1$ |
| |
| if (table.isCompositeKey()) { |
| IFile compositeKeyFile = javaPackageFolder.getFile( table.getCompositeKeyClassName()+".java"); //$NON-NLS-1$ |
| generateJavaFile(table, compositeKeyFile, ve, "pk.java.vm", false/*isDomainClass*/, sm.newChild(1)); //$NON-NLS-1$ |
| } |
| else { |
| sm.setWorkRemaining(1); |
| } |
| javaFile.refreshLocal(1, sm.newChild(1)); |
| |
| } catch (Throwable e) { |
| CoreException ce = new CoreException(new Status(IStatus.ERROR, JptJpaGenPlugin.PLUGIN_ID, JptGenMessages.Templates_notFound + "" + JptJpaGenPlugin.PLUGIN_ID , e) );//$NON-NLS-1$ |
| JptJpaGenPlugin.logException( ce ); |
| } |
| } |
| |
| private void generateJavaFile(ORMGenTable table, IFile javaFile, VelocityEngine ve |
| , String templateName, boolean isDomainClass, IProgressMonitor monitor) throws Exception { |
| VelocityContext context = new VelocityContext(); |
| context.put("table", table); //$NON-NLS-1$ |
| context.put("customizer", getCustomizer()); //$NON-NLS-1$ |
| |
| StringWriter w = new StringWriter(); |
| ve.mergeTemplate(templateName, context, w); |
| |
| String fileContent = w.toString(); |
| if (javaFile.exists()) { |
| if (isDomainClass) { |
| updateExistingDomainClass(table.getQualifiedClassName(), javaFile, fileContent); |
| } else { |
| byte[] content = fileContent.getBytes(javaFile.getCharset()); |
| javaFile.setContents(new ByteArrayInputStream(content), true, true, monitor); |
| } |
| } else { |
| byte[] content = fileContent.getBytes(javaFile.getCharset()); |
| createFile(javaFile, new ByteArrayInputStream(content)); |
| } |
| } |
| |
| |
| /** |
| * Updates the (existing) Java file corresponding to the given class. |
| * |
| * @param className The qualified class name. |
| * |
| * @param javaFile The existing Java file of the class to update. |
| * |
| * @param fileContent The new file content. |
| */ |
| protected void updateExistingDomainClass(String className, IFile javaFile, String fileContent) throws Exception { |
| /*use CompilationUnitModifier instead of calling WideEnv.getEnv().setFileContent |
| * so that if the unit is up to date if it is used before file change |
| * notifications are delivered (see EJB3ImportSchemaWizard.updateExistingDomainClass for example)*/ |
| CompilationUnitModifier modifier = new CompilationUnitModifier(this.getJavaProject(), className); |
| modifier.setJavaSource(fileContent); |
| modifier.save(); |
| } |
| |
| public void createFile(IFile file, java.io.InputStream contents) throws CoreException { |
| file.create(contents, false, null/*monitor*/); |
| } |
| |
| public IFolder getJavaPackageFolder(ORMGenTable table, IProgressMonitor monitor) throws CoreException { |
| IPackageFragmentRoot root = getDefaultJavaSourceLocation(this.getJavaProject(), table.getSourceFolder()); |
| String packageName = table.getPackage(); |
| if (packageName == null) packageName = ""; //$NON-NLS-1$ |
| IPackageFragment packageFragment = root.getPackageFragment(packageName); |
| if( !packageFragment.exists()){ |
| root.createPackageFragment(packageName, true, monitor); |
| } |
| return (IFolder) packageFragment.getResource(); |
| } |
| |
| private IPackageFragmentRoot getDefaultJavaSourceLocation(IJavaProject jproject, String sourceFolder){ |
| IPackageFragmentRoot defaultSrcPath = null; |
| if (jproject != null && jproject.exists()) { |
| try { |
| IPackageFragmentRoot[] roots = jproject.getPackageFragmentRoots(); |
| for (int i = 0; i < roots.length; i++) { |
| if (roots[i].getKind() == IPackageFragmentRoot.K_SOURCE ){ |
| if (defaultSrcPath == null) { |
| defaultSrcPath = roots[i]; |
| } |
| String path = roots[i].getPath().toString(); |
| if (path.equals('/' + sourceFolder)) { |
| return roots[i] ; |
| } |
| } |
| } |
| } catch (JavaModelException e) { |
| JptJpaGenPlugin.logException(e); |
| } |
| } |
| return defaultSrcPath; |
| } |
| |
| protected void generateXmlMappingFile(List<String> tableNames, String templateDirPath, IProgressMonitor monitor) throws Exception { |
| |
| try { |
| String xmlMappingFileLocation = this.customizer.getXmlMappingFile(); |
| JpaXmlResource xmlResource = this.jpaProject.getMappingFileXmlResource(new Path(xmlMappingFileLocation)); |
| IFile xmlFile; |
| if (xmlResource != null) { |
| xmlFile = xmlResource.getFile(); |
| } |
| else{ |
| //TODO This is a temp hack for M6 use default location if writing to a new mapping file |
| IProject project = jpaProject.getProject(); |
| IContainer container = ((ProjectResourceLocator) project.getAdapter(ProjectResourceLocator.class)).getDefaultResourceLocation(); |
| xmlFile = container.getFile(new Path(xmlMappingFileLocation.substring(xmlMappingFileLocation.lastIndexOf("/")))); //$NON-NLS-1$ |
| } |
| |
| if (xmlFile.exists()) { |
| if (this.overwriteConfirmer != null && !this.overwriteConfirmer.overwrite(xmlFile.getName())) { |
| return; |
| } |
| } |
| //JdkLogChute in this version of Velocity not allow to set log level |
| //Workaround by preset the log level before Velocity is initialized |
| Logger logger = Logger.getLogger( LOGGER_NAME ); |
| logger.setLevel( Level.SEVERE ); |
| |
| Properties vep = new Properties(); |
| vep.setProperty("file.resource.loader.path", templateDirPath); //$NON-NLS-1$ |
| vep.setProperty( JdkLogChute.RUNTIME_LOG_JDK_LOGGER, LOGGER_NAME ); |
| VelocityEngine ve = new VelocityEngine(); |
| ve.init(vep); |
| |
| StringBuilder xmlFileContents = new StringBuilder(); |
| xmlFileContents.append(generateXmlHeaderFooter(ve, "header.vm")); //$NON-NLS-1$ |
| List<ORMGenTable> compositeKeyTables = new ArrayList<ORMGenTable>(); |
| |
| for (Iterator<String> names = tableNames.iterator(); names.hasNext();) { |
| |
| ORMGenTable table = this.customizer.getTable(names.next()); |
| String subTaskName = NLS.bind(JptGenMessages.EntityGenerator_taskName, table.getName()); |
| SubMonitor sm = SubMonitor.convert(monitor, subTaskName, 10); |
| |
| if (sm.isCanceled()) { |
| return; |
| } |
| |
| xmlFileContents.append(generateXmlTypeMapping(table, ve, "main.xml.vm", sm.newChild(10))); //$NON-NLS-1$ |
| |
| if (table.isCompositeKey()) { |
| compositeKeyTables.add(table); |
| } |
| } |
| |
| //Embeddables need to come after entities in the XML |
| for (ORMGenTable table : compositeKeyTables) { |
| SubMonitor sm = SubMonitor.convert(monitor, NLS.bind(JptGenMessages.EntityGenerator_taskName, table.getName()), 1); |
| if (table.isCompositeKey()) { |
| xmlFileContents.append(generateXmlTypeMapping(table, ve, "embeddable.vm", sm.newChild(1))); //$NON-NLS-1$ |
| } |
| } |
| |
| xmlFileContents.append(generateXmlHeaderFooter(ve, "footer.vm")); //$NON-NLS-1$ |
| |
| if(xmlFile.exists()){ |
| byte[] content = xmlFileContents.toString().getBytes(xmlFile.getCharset()); |
| xmlFile.setContents(new ByteArrayInputStream(content), false, true, null); |
| } |
| else { |
| byte[] content = xmlFileContents.toString().getBytes(xmlFile.getCharset()); |
| createFile(xmlFile, new ByteArrayInputStream(content)); |
| } |
| |
| xmlFile.refreshLocal(1, null); |
| |
| } catch (Throwable e) { |
| CoreException ce = new CoreException(new Status(IStatus.ERROR, JptJpaGenPlugin.PLUGIN_ID, JptGenMessages.Templates_notFound + "" + JptJpaGenPlugin.PLUGIN_ID , e) );//$NON-NLS-1$ |
| JptJpaGenPlugin.logException( ce ); |
| } |
| } |
| |
| private String generateXmlHeaderFooter(VelocityEngine ve, String templateName) throws Exception{ |
| StringWriter stringWriter = new StringWriter(); |
| VelocityContext context = new VelocityContext(); |
| ve.mergeTemplate(templateName, context, stringWriter); |
| return stringWriter.toString(); |
| } |
| |
| |
| private String generateXmlTypeMapping(ORMGenTable table, VelocityEngine ve |
| , String templateName, IProgressMonitor monitor) throws Exception { |
| VelocityContext context = new VelocityContext(); |
| context.put("table", table); //$NON-NLS-1$ |
| context.put("customizer", getCustomizer()); //$NON-NLS-1$ |
| |
| StringWriter w = new StringWriter(); |
| ve.mergeTemplate(templateName, context, w); |
| |
| return w.toString(); |
| } |
| |
| } |