All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.cayenne.gen.ClassGenerationAction Maven / Gradle / Ivy

/*****************************************************************
 *   Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you 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
 *
 *    https://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.apache.cayenne.gen;

import org.apache.cayenne.CayenneRuntimeException;
import org.apache.cayenne.map.DataMap;
import org.apache.cayenne.map.Embeddable;
import org.apache.cayenne.map.ObjEntity;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.context.Context;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.StringResourceLoader;
import org.apache.velocity.runtime.resource.util.StringResourceRepository;
import org.apache.velocity.runtime.resource.util.StringResourceRepositoryImpl;
import org.apache.velocity.tools.ToolManager;
import org.apache.velocity.tools.config.ConfigurationUtils;
import org.apache.velocity.tools.config.FactoryConfiguration;
import org.slf4j.Logger;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;

public class ClassGenerationAction {

    public static final String SUPERCLASS_PREFIX = "_";
    private static final String WILDCARD = "*";
    private static final String CUSTOM_TEMPLATE_REPO = "customTemplateRepo";

    /**
     * @since 4.1
     */
    protected CgenConfiguration cgenConfiguration;
    protected Logger logger;

    // runtime ivars
    protected Context context;
    protected Map templateCache;

    private ToolsUtilsFactory utilsFactory;
    private MetadataUtils metadataUtils;

    /**
     * Optionally allows user-defined tools besides {@link ImportUtils} for working with velocity templates.
* To use this feature, either set the java system property {@code -Dorg.apache.velocity.tools=tools.properties} * or set the {@code externalToolConfig} property to "tools.properties" in {@code CgenConfiguration}. Then * create the file "tools.properties" in the working directory or in the root of the classpath with content * like this: *
     * tools.toolbox = application
     * tools.application.myTool = com.mycompany.MyTool
* Then the methods in the MyTool class will be available for use in the template like ${myTool.myMethod(arg)} */ public ClassGenerationAction(CgenConfiguration cgenConfig) { this.cgenConfiguration = cgenConfig; String toolConfigFile = cgenConfig.getExternalToolConfig(); if (System.getProperty("org.apache.velocity.tools") != null || toolConfigFile != null) { ToolManager manager = new ToolManager(true, true); if (toolConfigFile != null) { FactoryConfiguration config = ConfigurationUtils.find(toolConfigFile); manager.getToolboxFactory().configure(config); } this.context = manager.createContext(); } else { this.context = new VelocityContext(); } this.templateCache = new HashMap<>(5); } /** * VelocityContext initialization method called once per artifact. */ public void resetContextForArtifact(Artifact artifact) { StringUtils stringUtils = StringUtils.getInstance(); String qualifiedClassName = artifact.getQualifiedClassName(); String packageName = stringUtils.stripClass(qualifiedClassName); String className = stringUtils.stripPackageName(qualifiedClassName); String qualifiedBaseClassName = artifact.getQualifiedBaseClassName(); String basePackageName = stringUtils.stripClass(qualifiedBaseClassName); String baseClassName = stringUtils.stripPackageName(qualifiedBaseClassName); String superClassName = SUPERCLASS_PREFIX + stringUtils.stripPackageName(qualifiedClassName); String superPackageName = cgenConfiguration.getSuperPkg(); if (superPackageName == null || superPackageName.isEmpty()) { superPackageName = packageName + ".auto"; } context.put(Artifact.BASE_CLASS_KEY, baseClassName); context.put(Artifact.BASE_PACKAGE_KEY, basePackageName); context.put(Artifact.SUB_CLASS_KEY, className); context.put(Artifact.SUB_PACKAGE_KEY, packageName); context.put(Artifact.SUPER_CLASS_KEY, superClassName); context.put(Artifact.SUPER_PACKAGE_KEY, superPackageName); context.put(Artifact.OBJECT_KEY, artifact.getObject()); context.put(Artifact.STRING_UTILS_KEY, stringUtils); context.put(Artifact.CREATE_PROPERTY_NAMES, cgenConfiguration.isCreatePropertyNames()); context.put(Artifact.CREATE_PK_PROPERTIES, cgenConfiguration.isCreatePKProperties()); } /** * VelocityContext initialization method called once per each artifact and * template type combination. */ void resetContextForArtifactTemplate(Artifact artifact) { ImportUtils importUtils = utilsFactory.createImportUtils(); context.put(Artifact.IMPORT_UTILS_KEY, importUtils); context.put(Artifact.PROPERTY_UTILS_KEY, utilsFactory.createPropertyUtils(logger, importUtils)); context.put(Artifact.METADATA_UTILS_KEY, metadataUtils); artifact.postInitContext(context); } /** * Adds entities to the internal entity list. * * @param entities collection * @since 4.0 throws exception */ public void addEntities(Collection entities) { if (entities != null) { for (ObjEntity entity : entities) { cgenConfiguration.addArtifact(new EntityArtifact(entity)); } } } public void addEmbeddables(Collection embeddables) { if (embeddables != null) { for (Embeddable embeddable : embeddables) { cgenConfiguration.addArtifact(new EmbeddableArtifact(embeddable)); } } } /** * @param dataMap to add to the list of artifacts to generate * @since 5.0 replaces removed {@code addQueries()} method */ public void addDataMap(DataMap dataMap) { // data map should be used only in ArtifactsGenerationMode.ALL if (!cgenConfiguration.getArtifactsGenerationMode().equals(ArtifactsGenerationMode.ALL.getLabel())) { return; } Artifact artifact = new DataMapArtifact(cgenConfiguration.getDataMap(), dataMap.getQueryDescriptors()); if (!cgenConfiguration.getArtifacts().contains(artifact)) { cgenConfiguration.addArtifact(artifact); } } /** * @since 4.1 */ public void prepareArtifacts() { cgenConfiguration.getArtifacts().clear(); addEntities(cgenConfiguration.getEntities().stream() .map(entity -> cgenConfiguration.getDataMap().getObjEntity(entity)) .collect(Collectors.toList())); addEmbeddables(cgenConfiguration.getEmbeddables().stream() .map(embeddable -> cgenConfiguration.getDataMap().getEmbeddable(embeddable)) .collect(Collectors.toList())); addDataMap(cgenConfiguration.getDataMap()); } /** * Executes class generation once per each artifact. */ public void execute() throws Exception { validateAttributes(); try { for (Artifact artifact : cgenConfiguration.getArtifacts()) { execute(artifact); } } finally { // must reset engine at the end of class generator run to avoid memory leaks and stale templates templateCache.clear(); } } /** * Executes class generation for a single artifact. */ protected void execute(Artifact artifact) throws Exception { resetContextForArtifact(artifact); ArtifactGenerationMode artifactMode = cgenConfiguration.isMakePairs() ? ArtifactGenerationMode.GENERATION_GAP : ArtifactGenerationMode.SINGLE_CLASS; TemplateType[] templateTypes = artifact.getTemplateTypes(artifactMode); for (TemplateType type : templateTypes) { try (Writer out = openWriter(type)) { if (out != null) { resetContextForArtifactTemplate(artifact); getTemplate(type).merge(context, out); } } } } protected Template getTemplate(TemplateType type) { Properties props = new Properties(); initVelocityProperties(props, type); VelocityEngine velocityEngine = new VelocityEngine(); velocityEngine.init(props); return velocityEngine.getTemplate(cgenConfiguration.getTemplateByType(type).getName()); } protected void initVelocityProperties(Properties props, TemplateType type) { CgenTemplate template = cgenConfiguration.getTemplateByType(type); if (template.isFile()) { props.put(RuntimeConstants.RESOURCE_LOADERS, "cayenne"); props.put("resource.loader.cayenne.class", ClassGeneratorResourceLoader.class.getName()); props.put("resource.loader.cayenne.cache", "false"); if(cgenConfiguration.getRootPath() != null) { props.put("resource.loader.cayenne.root", cgenConfiguration.getRootPath()); } } else { props.put(RuntimeConstants.RESOURCE_LOADERS, "string"); props.put("resource.loader.string.class", StringResourceLoader.class.getName()); props.put("resource.loader.string.repository.name", CUSTOM_TEMPLATE_REPO); putTemplateTextInRepository(template); } } private void putTemplateTextInRepository(CgenTemplate template) { StringResourceRepository repo = new StringResourceRepositoryImpl(); repo.putStringResource(template.getName(), template.getData()); StringResourceLoader.setRepository(CUSTOM_TEMPLATE_REPO, repo); } /** * Validates the state of this class generator. * Throws CayenneRuntimeException if it is in an inconsistent state. * Called internally from "execute". */ protected void validateAttributes() { Path dir = cgenConfiguration.buildOutputPath(); if (dir == null) { throw new CayenneRuntimeException("Output directory is not set."); } if (Files.notExists(dir)) { try { Files.createDirectories(dir); } catch (IOException e) { throw new CayenneRuntimeException("Can't create output directory '%s'", dir); } } if (!Files.isDirectory(dir)) { throw new CayenneRuntimeException("'%s' is not a directory.", dir); } if (!Files.isWritable(dir)) { throw new CayenneRuntimeException("No write permission for the output directory '%s'", dir); } } /** * Opens a Writer to write generated output. Returned Writer is mapped to a * filesystem file (although subclasses may override that). File location is * determined from the current state of VelocityContext and the TemplateType * passed as a parameter. Writer encoding is determined from the value of * the "encoding" property. */ protected Writer openWriter(TemplateType templateType) throws Exception { File outFile = (templateType.isSuperclass()) ? fileForSuperclass() : fileForClass(); if (outFile == null) { return null; } if (logger != null) { String label = templateType.isSuperclass() ? "superclass" : "class"; logger.info("Generating " + label + " file: " + outFile.getCanonicalPath()); } // return writer with specified encoding FileOutputStream out = new FileOutputStream(outFile); return (cgenConfiguration.getEncoding() != null) ? new OutputStreamWriter(out, cgenConfiguration.getEncoding()) : new OutputStreamWriter(out); } /** * Returns a target file where a generated superclass must be saved. If null * is returned, class shouldn't be generated. */ private File fileForSuperclass() throws Exception { String packageName = (String) context.get(Artifact.SUPER_PACKAGE_KEY); String className = (String) context.get(Artifact.SUPER_CLASS_KEY); String filename = StringUtils.getInstance().replaceWildcardInStringWithString(WILDCARD, cgenConfiguration.getOutputPattern(), className); File dest = new File(mkpath(cgenConfiguration.buildOutputPath().toFile(), packageName), filename); if (dest.exists() && !fileNeedUpdate(dest, cgenConfiguration.getSuperTemplate().getData())) { return null; } return dest; } /** * Returns a target file where a generated class must be saved. If null is * returned, class shouldn't be generated. */ private File fileForClass() throws Exception { String packageName = (String) context.get(Artifact.SUB_PACKAGE_KEY); String className = (String) context.get(Artifact.SUB_CLASS_KEY); String filename = StringUtils.getInstance().replaceWildcardInStringWithString(WILDCARD, cgenConfiguration.getOutputPattern(), className); File dest = new File(mkpath(cgenConfiguration.buildOutputPath().toFile(), packageName), filename); if (dest.exists()) { // no overwrite of subclasses if (cgenConfiguration.isMakePairs()) { return null; } // skip if said so if (!cgenConfiguration.isOverwrite()) { return null; } if (!fileNeedUpdate(dest, cgenConfiguration.getTemplate().getData())) { return null; } } return dest; } /** * Ignore if the destination is newer than the map * (internal timestamp), i.e. has been generated after the map was * last saved AND the template is older than the destination file */ protected boolean fileNeedUpdate(File dest, String templateFileName) { if (cgenConfiguration.isForce()) { return true; } if (isOld(dest)) { if (templateFileName == null) { return false; } File templateFile = new File(templateFileName); return templateFile.lastModified() >= dest.lastModified(); } return true; } /** * Is file modified after internal timestamp (usually equal to mtime of datamap file) */ protected boolean isOld(File file) { return file.lastModified() > cgenConfiguration.getTimestamp(); } /** * Returns a File object corresponding to a directory where files that * belong to pkgName package should reside. Creates any missing * diectories below dest. */ private File mkpath(File dest, String pkgName) throws Exception { if (!cgenConfiguration.isUsePkgPath() || pkgName == null) { return dest; } String path = pkgName.replace('.', File.separatorChar); File fullPath = new File(dest, path); if (!fullPath.isDirectory() && !fullPath.mkdirs()) { throw new Exception("Error making path: " + fullPath); } return fullPath; } /** * Injects an optional logger that will be used to trace generated files at * the info level. */ public void setLogger(Logger logger) { this.logger = logger; } /** * @since 4.1 */ public CgenConfiguration getCgenConfiguration() { return cgenConfiguration; } /** * Sets an optional shared VelocityContext. Useful with tools like VPP that * can set custom values in the context, not known to Cayenne. */ public void setContext(Context context) { this.context = context; } /** * @since 4.1 */ public void setCgenConfiguration(CgenConfiguration cgenConfiguration) { this.cgenConfiguration = cgenConfiguration; } public ToolsUtilsFactory getUtilsFactory() { return utilsFactory; } public void setUtilsFactory(ToolsUtilsFactory utilsFactory) { this.utilsFactory = utilsFactory; } public void setMetadataUtils(MetadataUtils metadataUtils) { this.metadataUtils = metadataUtils; } public MetadataUtils getMetadataUtils() { return metadataUtils; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy