Java tutorial
/* * Copyright (c) Open Source Strategies, Inc. * * Opentaps is free software: you can redistribute it and/or modify it * under the terms of the GNU Affero General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * Opentaps is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Opentaps. If not, see <http://www.gnu.org/licenses/>. */ /******************************************************************************* * 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 * * 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. *******************************************************************************/ /* This file has been modified by Open Source Strategies, Inc. */ package org.opentaps.domain.container; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.io.Writer; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; import org.apache.commons.io.FileUtils; import org.jvnet.inflector.Noun; import org.ofbiz.base.container.Container; import org.ofbiz.base.container.ContainerConfig; import org.ofbiz.base.container.ContainerException; import org.ofbiz.base.util.Debug; import org.ofbiz.base.util.ObjectType; import org.ofbiz.base.util.UtilProperties; import org.ofbiz.base.util.UtilURL; import org.ofbiz.base.util.UtilValidate; import org.ofbiz.base.util.template.FreeMarkerWorker; import org.ofbiz.entity.Delegator; import org.ofbiz.entity.DelegatorFactory; import org.ofbiz.entity.GenericEntityException; import org.ofbiz.entity.model.ModelEntity; import org.ofbiz.entity.model.ModelField; import org.ofbiz.entity.model.ModelFieldType; import org.ofbiz.entity.model.ModelKeyMap; import org.ofbiz.entity.model.ModelReader; import org.ofbiz.entity.model.ModelRelation; import org.ofbiz.entity.model.ModelUtil; import org.ofbiz.entity.model.ModelViewEntity; import org.ofbiz.entity.model.ModelViewEntity.ModelAlias; import org.ofbiz.entity.model.ModelViewEntity.ModelMemberEntity; import org.ofbiz.entity.model.ModelViewEntity.ModelViewLink; import org.ofbiz.service.DispatchContext; import org.ofbiz.service.GenericDispatcher; import org.ofbiz.service.GenericServiceException; import org.ofbiz.service.LocalDispatcher; import org.ofbiz.service.ModelParam; import org.ofbiz.service.ModelService; import org.ofbiz.service.ServiceDispatcher; import freemarker.template.TemplateException; /** * Some utility routines for loading seed data. */ public class PojoGeneratorContainer implements Container { private static final String MODULE = PojoGeneratorContainer.class.getName(); private static final String containerName = "pojo-generator-container"; private static final String fileExtension = ".java"; private static final List<String> extendClassNames = new ArrayList<String>(); static { extendClassNames.add("Person"); extendClassNames.add("PartyGroup"); } /** Config file. */ private String configFile; /** The path of output. */ private String entityOutputPath; private String baseEntityOutputPath; private String serviceOutputPath; private Set<String> baseEntities; /** Java service class FTL Template. */ private String serviceTemplate; /** Java entity class FTL Template. */ private String entityTemplate; /** Hibernate IdClass FTL Template. */ private String pkTemplate; /** Hibernate Search PkBridge FTL Template. */ private String pkBridgeTemplate; /** Hibernate configuration FTL Template. */ private String hibernateCfgTemplate; private String hibernateCfgPath; private Properties entitySearchProperties; private String delegatorNameToUse; private Delegator delegator; /** * Creates a new <code>PojoGeneratorContainer</code> instance. */ public PojoGeneratorContainer() { super(); } /** {@inheritDoc} */ public void init(String[] args, String configFile) throws ContainerException { this.configFile = configFile; // disable job scheduler, JMS listener and startup services ServiceDispatcher.enableJM(false); ServiceDispatcher.enableJMS(false); ServiceDispatcher.enableSvcs(false); // parse arguments here if needed } /** {@inheritDoc} */ public boolean start() throws ContainerException { ContainerConfig.Container cfg = ContainerConfig.getContainer(containerName, configFile); ContainerConfig.Container.Property delegatorNameProp = cfg.getProperty("delegator-name"); ContainerConfig.Container.Property baseEntitiesProp = cfg.getProperty("baseEntities"); ContainerConfig.Container.Property entityOutputPathProp = cfg.getProperty("entityOutputPath"); ContainerConfig.Container.Property baseEntityOutputPathProp = cfg.getProperty("baseEntityOutputPath"); ContainerConfig.Container.Property serviceOutputPathProp = cfg.getProperty("serviceOutputPath"); ContainerConfig.Container.Property entityTemplateProp = cfg.getProperty("entityTemplate"); ContainerConfig.Container.Property serviceTemplateProp = cfg.getProperty("serviceTemplate"); ContainerConfig.Container.Property pkTemplateProp = cfg.getProperty("pkTemplate"); ContainerConfig.Container.Property pkBridgeTemplateProp = cfg.getProperty("pkBridgeTemplate"); ContainerConfig.Container.Property hibernateCfgTemplateProp = cfg.getProperty("hibernateCfgTemplate"); ContainerConfig.Container.Property hibernateCfgProp = cfg.getProperty("hibernateCfgPath"); entitySearchProperties = UtilProperties.getProperties("entitysearch.properties"); // get delegator to use from the container configuration if (delegatorNameProp == null || delegatorNameProp.value == null || delegatorNameProp.value.length() == 0) { throw new ContainerException("Invalid delegator-name defined in container configuration"); } else { delegatorNameToUse = delegatorNameProp.value; } // get the delegator delegator = DelegatorFactory.getDelegator(delegatorNameToUse); if (delegator == null) { throw new ContainerException("Invalid delegator name: " + delegatorNameToUse); } // get the list of entities to use as base entities if (baseEntitiesProp != null && baseEntitiesProp.value != null && baseEntitiesProp.value.length() > 0) { String[] fes = baseEntitiesProp.value.split(","); baseEntities = new TreeSet<String>(); for (int i = 0; i < fes.length; i++) { baseEntities.add(fes[i]); } Debug.logInfo("Entities configured to be generated as base entities: " + baseEntities, MODULE); } // get output path to use from the container configuration if (baseEntityOutputPathProp == null || baseEntityOutputPathProp.value == null || baseEntityOutputPathProp.value.length() == 0) { throw new ContainerException("Invalid baseEntityOutputPath defined in container configuration"); } else { baseEntityOutputPath = baseEntityOutputPathProp.value; } // check the output path File outputDir = new File(baseEntityOutputPath); if (!outputDir.canWrite()) { throw new ContainerException( "Unable to use base entity output path: [" + baseEntityOutputPath + "], it is not writable"); } // get output path to use from the container configuration if (entityOutputPathProp == null || entityOutputPathProp.value == null || entityOutputPathProp.value.length() == 0) { throw new ContainerException("Invalid entityOutputPath defined in container configuration"); } else { entityOutputPath = entityOutputPathProp.value; } // check the output path outputDir = new File(entityOutputPath); if (!outputDir.canWrite()) { throw new ContainerException( "Unable to use entity output path: [" + entityOutputPath + "], it is not writable"); } // get output path to use from the container configuration if (serviceOutputPathProp == null || serviceOutputPathProp.value == null || serviceOutputPathProp.value.length() == 0) { throw new ContainerException("Invalid serviceOutputPath defined in container configuration"); } else { serviceOutputPath = serviceOutputPathProp.value; } // check the output path outputDir = new File(serviceOutputPath); if (!outputDir.canWrite()) { throw new ContainerException( "Unable to use service output path: [" + serviceOutputPath + "], it is not writable"); } // get the template file to use from the container configuration if (entityTemplateProp == null || entityTemplateProp.value == null || entityTemplateProp.value.length() == 0) { throw new ContainerException("Invalid entity template defined in container configuration"); } else { entityTemplate = entityTemplateProp.value; } if (serviceTemplateProp == null || serviceTemplateProp.value == null || serviceTemplateProp.value.length() == 0) { throw new ContainerException("Invalid service template defined in container configuration"); } else { serviceTemplate = serviceTemplateProp.value; } // get the primay key template file to use from the container configuration if (pkTemplateProp == null || pkTemplateProp.value == null || pkTemplateProp.value.length() == 0) { throw new ContainerException("Invalid pk template defined in container configuration"); } else { pkTemplate = pkTemplateProp.value; } // get the primay key template file to use from the container configuration if (hibernateCfgTemplateProp == null || hibernateCfgTemplateProp.value == null || hibernateCfgTemplateProp.value.length() == 0) { throw new ContainerException("Invalid hibernate cfg template defined in container configuration"); } else { hibernateCfgTemplate = hibernateCfgTemplateProp.value; } // get the primay key bridge template file to use from the container configuration if (pkBridgeTemplateProp == null || pkBridgeTemplateProp.value == null || pkBridgeTemplateProp.value.length() == 0) { throw new ContainerException("Invalid pk bridge template defined in container configuration"); } else { pkBridgeTemplate = pkBridgeTemplateProp.value; } // get hibernate.cfg.xml output path to use from the container configuration if (hibernateCfgProp == null || hibernateCfgProp.value == null || hibernateCfgProp.value.length() == 0) { throw new ContainerException("Invalid hibernate cfg path defined in container configuration"); } else { hibernateCfgPath = hibernateCfgProp.value; } return generateEntities() && generateServices(); } private boolean generateServices() throws ContainerException { // check the service template file File serviceTemplateFile = new File(serviceTemplate); if (!serviceTemplateFile.canRead()) { throw new ContainerException("Unable to read service template file: [" + serviceTemplate + "]"); } // get services list LocalDispatcher localDispatcher = GenericDispatcher.getLocalDispatcher("webtools", delegator); DispatchContext dispatchContext = localDispatcher.getDispatchContext(); Set<String> serviceNames = dispatchContext.getAllServiceNames(); // record errors for summary List<String> errorEntities = new LinkedList<String>(); int totalGeneratedClasses = 0; if (serviceNames != null && serviceNames.size() > 0) { Debug.logImportant("=-=-=-=-=-=-= Generating the POJO services ...", MODULE); Map<String, String> generatedServices = new HashMap<String, String>(); for (String serviceName : serviceNames) { ModelService modelService = null; try { modelService = dispatchContext.getModelService(serviceName); } catch (GenericServiceException e) { Debug.logError(e, MODULE); } if (modelService == null) { errorEntities.add(serviceName); Debug.logError("Error getting the service model for [" + serviceName + "]", MODULE); continue; } // service names can include characters which cannot be used in class names // and we also need to upper the first char to conform to the Java standard String serviceClassName = makeClassName(serviceName) + "Service"; // check if we have a conflict after the name change if (generatedServices.containsKey(serviceClassName)) { Debug.logError("Service [" + serviceName + "] has conflicting class name with service [" + generatedServices.get(serviceClassName) + "], both resolved to [" + serviceClassName + "]", MODULE); errorEntities.add(serviceName); continue; } Map<String, Object> entityInfo = new HashMap<String, Object>(); // distinct types for the import section Set<String> types = new TreeSet<String>(); types.add("java.util.Map"); types.add("java.util.Set"); types.add("javolution.util.FastMap"); types.add("javolution.util.FastSet"); types.add("org.opentaps.foundation.infrastructure.User"); // a type for each field during declarations Map<String, String> fieldTypes = new TreeMap<String, String>(); Map<String, String> fieldRealTypes = new TreeMap<String, String>(); // temporary, those are stored at the end of each param so we test duplicates Map<String, ModelParam> fieldModels = new TreeMap<String, ModelParam>(); // map parameter names to valid java field names Map<String, String> inFieldNames = new TreeMap<String, String>(); Map<String, String> outFieldNames = new TreeMap<String, String>(); // read the service parameters Map<String, String> paramNames = new TreeMap<String, String>(); // for the enums Map<String, ModelParam> inParams = new TreeMap<String, ModelParam>(); Map<String, ModelParam> outParams = new TreeMap<String, ModelParam>(); // names of the get and set methods, we need two sets for In and Out parameters Map<String, String> inGetMethodNames = new TreeMap<String, String>(); Map<String, String> inSetMethodNames = new TreeMap<String, String>(); Map<String, String> outGetMethodNames = new TreeMap<String, String>(); Map<String, String> outSetMethodNames = new TreeMap<String, String>(); boolean hasError = false; for (ModelParam p : modelService.getModelParamList()) { // use the class name transform, we will append "in" or "out" to get the field name String fieldName = makeClassName(p.name); String shortFieldName = Character.toLowerCase(fieldName.charAt(0)) + fieldName.substring(1); // check that two different parameter names did not translate into the same java field // eg: some.param and someParam if ((!paramNames.containsKey(p.name) && paramNames.containsValue(shortFieldName))) { errorEntities.add(serviceName); Debug.logError("Service [" + serviceName + "] has parameter [" + p.name + "] which conflicts with another of its parameters that was translated to the same java field [" + fieldName + "]", MODULE); hasError = true; break; } // for the enumeration paramNames.put(p.name, shortFieldName); try { if (p.isIn()) { inParams.put(p.name, p); inGetMethodNames.put(p.name, accessorMethodName("getIn", fieldName)); inSetMethodNames.put(p.name, accessorMethodName("setIn", fieldName)); if (inFieldNames.containsKey(p.name)) { Debug.logWarning("Service [" + serviceName + "] redefines IN parameter [" + p.name + "] (used attribute instead of override, or redefined an auto attribute)", MODULE); } inFieldNames.put(p.name, "in" + fieldName); } // no else, can be INOUT if (p.isOut()) { outParams.put(p.name, p); outGetMethodNames.put(p.name, accessorMethodName("getOut", fieldName)); outSetMethodNames.put(p.name, accessorMethodName("setOut", fieldName)); if (outFieldNames.containsKey(p.name)) { Debug.logWarning("Service [" + serviceName + "] redefines OUT parameter [" + p.name + "] (used attribute instead of override, or redefined an auto attribute)", MODULE); } outFieldNames.put(p.name, "out" + fieldName); } } catch (IllegalArgumentException e) { errorEntities.add(serviceName); Debug.logError(e, MODULE); hasError = true; break; } // this is the short type: ie, java.lang.String -> String; java.util.HashMap -> HashMap, etc. String type = p.type; // replace $ with dot in some types that points to inner classes type = type.replaceAll("\\$", "."); String shortType = type; int idx = shortType.lastIndexOf("."); if (idx > 0) { // need to work around some compilation issues for service that are using a type defined in their own // application, fallback to the generic Object if (shortType.startsWith("org.opentaps.") && !shortType.startsWith("org.opentaps.common")) { Debug.logWarning("Service [" + serviceName + "] field [" + p.name + "] has type [" + type + "] which is not available yet, falling back to 'Object'.", MODULE); shortType = "Object"; fieldRealTypes.put(p.name, type); } else { shortType = shortType.substring(idx + 1); types.add(type); } } else { // some short types also need to be imported if (Arrays.asList("List", "Map", "Set", "Date").contains(type)) { types.add("java.util." + type); } else if (Arrays.asList("GenericValue", "GenericEntity", "GenericPK").contains(type)) { types.add("org.ofbiz.entity." + type); } else if (Arrays.asList("Timestamp").contains(type)) { types.add("java.sql." + type); } else if ("BigDecimal".equals(type)) { types.add("java.math." + type); } } // check that if a parameter is defined IN and OUT it has the same type if (fieldTypes.containsKey(p.name) && !shortType.equals(fieldTypes.get(p.name))) { // this can happen if the service redefined an internal parameter ModelParam p2 = fieldModels.get(p.name); if (p2 != null && (p2.internal || p.internal)) { Debug.logWarning("Service [" + serviceName + "] has parameter [" + p.name + "] with type [" + shortType + "] which conflicts with an internal service parameter of the same name and with type [" + fieldTypes.get(p.name) + "]", MODULE); } else { errorEntities.add(serviceName); Debug.logError("Service [" + serviceName + "] has parameter [" + p.name + "] with type [" + shortType + "] which conflicts with another definition of this parameter that has type [" + fieldTypes.get(p.name) + "]", MODULE); hasError = true; break; } } fieldTypes.put(p.name, shortType); fieldModels.put(p.name, p); } if (hasError) { continue; } entityInfo.put("name", serviceName); entityInfo.put("className", serviceClassName); entityInfo.put("types", types); entityInfo.put("fieldTypes", fieldTypes); entityInfo.put("fieldRealTypes", fieldRealTypes); entityInfo.put("inFieldNames", inFieldNames); entityInfo.put("outFieldNames", outFieldNames); entityInfo.put("inGetMethodNames", inGetMethodNames); entityInfo.put("inSetMethodNames", inSetMethodNames); entityInfo.put("outGetMethodNames", outGetMethodNames); entityInfo.put("outSetMethodNames", outSetMethodNames); entityInfo.put("paramNames", paramNames); entityInfo.put("inParams", inParams); entityInfo.put("outParams", outParams); entityInfo.put("requiresAuth", modelService.auth); entityInfo.put("requiresNewTransaction", modelService.requireNewTransaction); entityInfo.put("usesTransaction", modelService.useTransaction); entityInfo.put("serviceDescription", modelService.description); entityInfo.put("serviceEngine", modelService.engineName); entityInfo.put("serviceLocation", modelService.location); entityInfo.put("serviceInvoke", modelService.invoke); String serviceDefPath = null; try { serviceDefPath = UtilURL.getOfbizHomeRelativeLocation(new URL(modelService.definitionLocation)); } catch (MalformedURLException e) { Debug.logError(e.getMessage(), MODULE); } entityInfo.put("serviceDefinition", serviceDefPath); // render it as FTL Writer writer = new StringWriter(); try { FreeMarkerWorker.renderTemplateAtLocation(serviceTemplate, entityInfo, writer); } catch (MalformedURLException e) { Debug.logError(e, MODULE); errorEntities.add(serviceName); break; } catch (TemplateException e) { Debug.logError(e, MODULE); errorEntities.add(serviceName); break; } catch (IOException e) { Debug.logError(e, MODULE); errorEntities.add(serviceName); break; } catch (IllegalArgumentException e) { Debug.logError(e, MODULE); errorEntities.add(serviceName); break; } // write it as a Java file File file = new File(serviceOutputPath + serviceClassName + fileExtension); try { FileUtils.writeStringToFile(file, writer.toString(), "UTF-8"); } catch (IOException e) { Debug.logError(e, "Aborting, error writing file " + serviceOutputPath + serviceClassName + fileExtension, MODULE); errorEntities.add(serviceName); break; } // increment counter generatedServices.put(serviceClassName, serviceName); totalGeneratedClasses++; } } else { Debug.logImportant("=-=-=-=-=-=-= No service found.", MODULE); } // error summary if (errorEntities.size() > 0) { Debug.logImportant("The following services could not be generated:", MODULE); for (String name : errorEntities) { Debug.logImportant(name, MODULE); } } Debug.logImportant("=-=-=-=-=-=-= Finished the POJO services generation with " + totalGeneratedClasses + " classes generated.", MODULE); if (errorEntities.size() > 0) { return false; } return true; } private boolean generateEntities() throws ContainerException { // check the entity template file File entityTemplateFile = new File(entityTemplate); if (!entityTemplateFile.canRead()) { throw new ContainerException("Unable to read entity template file: [" + entityTemplate + "]"); } // check the pk template file File pkTemplateFile = new File(pkTemplate); if (!pkTemplateFile.canRead()) { throw new ContainerException("Unable to read pk template file: [" + pkTemplate + "]"); } // check the pk template file File cfgTemplateFile = new File(hibernateCfgTemplate); if (!cfgTemplateFile.canRead()) { throw new ContainerException( "Unable to read hibernate cfg template file: [" + hibernateCfgTemplate + "]"); } // check the pk template file File pkBridgeTemplateFile = new File(pkBridgeTemplate); if (!pkBridgeTemplateFile.canRead()) { throw new ContainerException("Unable to read pk bridge template file: [" + pkBridgeTemplate + "]"); } // get entities list ModelReader modelReader = delegator.getModelReader(); Collection<String> entities = null; try { entities = modelReader.getEntityNames(); } catch (GenericEntityException e) { Debug.logError(e, "Error getting the entities list from delegator " + delegatorNameToUse, MODULE); } // record errors for summary List<String> errorEntities = new LinkedList<String>(); // record view entities List<String> viewEntities = new LinkedList<String>(); int totalGeneratedClasses = 0; if (entities != null && entities.size() > 0) { Debug.logImportant("=-=-=-=-=-=-= Generating the POJO entities ...", MODULE); for (String entityName : entities) { ModelEntity modelEntity = null; try { modelEntity = modelReader.getModelEntity(entityName); if (modelEntity != null && modelEntity instanceof ModelViewEntity) { viewEntities.add(entityName); } } catch (GenericEntityException e) { Debug.logError(e, MODULE); } } List<String> needIndexEntities = new LinkedList<String>(); for (String entityName : entities) { //retrieve entityName if (entitySearchProperties != null && entitySearchProperties.containsKey(entityName)) { if (entitySearchProperties.getProperty(entityName).equals("index")) { needIndexEntities.add(entityName); } } } for (String entityName : entities) { ModelEntity modelEntity = null; try { modelEntity = modelReader.getModelEntity(entityName); } catch (GenericEntityException e) { Debug.logError(e, MODULE); } if (modelEntity == null) { errorEntities.add(entityName); Debug.logError("Error getting the entity model from delegator " + delegatorNameToUse + " and entity " + entityName, MODULE); continue; } // could be an object, but is just a Map for simplicity Map<String, Object> entityInfo = new HashMap<String, Object>(); // entity columns what used in entity field List<String> entityColumns = new ArrayList<String>(); boolean isView = false; // justify the entity whether need index or not boolean isNeedIndex = needIndexEntities.contains(entityName); if ((modelEntity instanceof ModelViewEntity)) { isView = true; // the view entity cannot index isNeedIndex = false; } // get name of the entity entityName = modelEntity.getEntityName(); entityInfo.put("name", entityName); entityInfo.put("tableName", modelEntity.getPlainTableName()); entityInfo.put("isView", isView); entityInfo.put("isNeedIndex", isNeedIndex); entityInfo.put("needIndexEntities", needIndexEntities); entityInfo.put("resourceName", modelEntity.getDefaultResourceName()); entityInfo.put("primaryKeys", modelEntity.getPkFieldNames()); entityInfo.put("viewEntities", viewEntities); // get all the fields of the entity which become members of the class List<String> fieldNames = modelEntity.getAllFieldNames(); entityInfo.put("fields", fieldNames); //get all the columns of the entity Map<String, String> columnNames = new TreeMap<String, String>(); for (String fieldName : fieldNames) { String columnName = modelEntity.getColNameOrAlias(fieldName); columnNames.put(fieldName, columnName); entityColumns.add(columnName); } entityInfo.put("columnNames", columnNames); // distinct types for the import section Set<String> types = new TreeSet<String>(); // a type for each field during declarations Map<String, String> fieldTypes = new TreeMap<String, String>(); // indicate declarations Map<String, String> indexWeights = new TreeMap<String, String>(); Map<String, String> tokenTypes = new TreeMap<String, String>(); // names of the get and set methods Map<String, String> getMethodNames = new TreeMap<String, String>(); Map<String, String> setMethodNames = new TreeMap<String, String>(); Map<String, List<String>> validatorMaps = new TreeMap<String, List<String>>(); // now go through all the fields boolean hasError = false; for (String fieldName : fieldNames) { // use the model field to figure out the Java type ModelField modelField = modelEntity.getField(fieldName); String type = null; try { // this converts String to java.lang.String, Timestamp to java.sql.Timestamp, etc. ModelFieldType fieldType = delegator.getEntityFieldType(modelEntity, modelField.getType()); if (fieldType == null) { throw new GenericEntityException("No field type defined for field " + fieldName); } type = ObjectType.loadClass(fieldType.getJavaType()).getName(); } catch (Exception e) { Debug.logError(e, MODULE); } if (type == null) { errorEntities.add(entityName); Debug.logError("Error getting the type of the field " + fieldName + " of entity " + entityName + " for delegator " + delegatorNameToUse, MODULE); hasError = true; break; } // make all Doubles BigDecimals -- in the entity model fieldtype XML files, all floating points are defined as Doubles // and there is a GenericEntity.getBigDecimal method which converts them to BigDecimal. We will make them all BigDecimals // and then use the Entity.convertToBigDecimal method if ("java.lang.Double".equals(type)) { type = "java.math.BigDecimal"; } // make all Object field to byte[] if ("java.lang.Object".equals(type) || "java.sql.Blob".equals(type)) { type = "byte[]"; } else { types.add(type); } // this is the short type: ie, java.lang.String -> String; java.util.HashMap -> HashMap, etc. String shortType = type; int idx = type.lastIndexOf("."); if (idx > 0) { shortType = type.substring(idx + 1); } fieldTypes.put(fieldName, shortType); // if entitysearch.properties contain the field, then add boost annotation to set index weight String indexFieldKey = entityName + "." + fieldName; if (entitySearchProperties != null && entitySearchProperties.containsKey(indexFieldKey)) { String[] indexValue = entitySearchProperties.getProperty(indexFieldKey).split(","); String weight = indexValue[0]; String tokenType = indexValue.length > 1 ? indexValue[1] : "TOKENIZED"; indexWeights.put(fieldName, weight); tokenTypes.put(fieldName, tokenType); } // accessor method names try { getMethodNames.put(fieldName, getterName(fieldName)); setMethodNames.put(fieldName, setterName(fieldName)); } catch (IllegalArgumentException e) { errorEntities.add(entityName); Debug.logError(e, MODULE); hasError = true; break; } if (modelField.getValidatorsSize() > 0) { List<String> validators = new ArrayList<String>(); for (int i = 0; i < modelField.getValidatorsSize(); i++) { String validator = modelField.getValidator(i); validators.add(validator); } validatorMaps.put(fieldName, validators); } } if (hasError) { continue; } // get the relations List<Map<String, String>> relations = new ArrayList<Map<String, String>>(); Iterator<ModelRelation> relationsIter = modelEntity.getRelationsIterator(); while (relationsIter.hasNext()) { ModelRelation modelRelation = relationsIter.next(); Map<String, String> relationInfo = new TreeMap<String, String>(); relationInfo.put("entityName", modelRelation.getRelEntityName()); // the string to put in the getRelated() method, title is "" if null relationInfo.put("relationName", modelRelation.getTitle() + modelRelation.getRelEntityName()); // the names are pluralized for relation of type many String accessorName = modelRelation.getTitle() + modelRelation.getRelEntityName(); // title may have white spaces, remove them to use as an attribute in the java class accessorName = accessorName.replaceAll(" ", ""); // justify if need create hibernate mapping annotation String isNeedMapping = "Y"; if ("many".equals(modelRelation.getType())) { relationInfo.put("type", "many"); try { accessorName = Noun.pluralOf(accessorName, Locale.ENGLISH); } catch (Exception e) { Debug.logWarning("For entity " + entityName + ", could not get the plural of " + accessorName + ", falling back to " + accessorName + "s.", MODULE); accessorName = accessorName + "s"; } // if the relation class is extendClass, such as PartyGroup and not oneToOne relation, then don't mapping this relation for hibernate if (extendClassNames.contains(entityName)) { isNeedMapping = "N"; } } else { // we do not care about the difference between one and one-nofk types, because we can use one-to-one to cascade relationInfo.put("type", "one"); // mark isOneToOne field for Entity ftl, this is useful for hibernate search feature try { String joinField = modelRelation.getKeyMap(0).getFieldName(); ModelEntity refEntity = modelReader.getModelEntity(modelRelation.getRelEntityName()); if (modelRelation.isAutoRelation() && refEntity.getPkFieldNames().size() == 1 && modelEntity.getPkFieldNames().size() == 1 && (refEntity.getPkFieldNames().contains(modelEntity.getPkFieldNames()) || modelEntity.getPkFieldNames().contains(joinField))) { relationInfo.put("isOneToOne", "Y"); } else { relationInfo.put("isOneToOne", "N"); // if the relation class is extendClass, such as PartyGroup and not oneToOne relation, then don't mapping this relation for hibernate if (extendClassNames.contains(modelRelation.getRelEntityName())) { isNeedMapping = "N"; } } } catch (Exception e) { Debug.logWarning("For entity " + entityName + ", could not get the relative Entity " + modelRelation.getRelEntityName() + ".", MODULE); accessorName = accessorName + "s"; } } // check if the accessor conflicts with an already defined field String fieldName = accessorName.substring(0, 1).toLowerCase() + accessorName.substring(1); if (fieldNames.contains(fieldName)) { String oldFieldName = fieldName; accessorName = "Related" + accessorName; fieldName = accessorName.substring(0, 1).toLowerCase() + accessorName.substring(1); Debug.logWarning("For entity " + entityName + ", field " + oldFieldName + " already defined, using " + fieldName + " instead.", MODULE); } String fkName = modelRelation.getFkName(); relationInfo.put("accessorName", accessorName); relationInfo.put("fieldName", fieldName); relationInfo.put("fkName", fkName); relationInfo.put("isNeedMapping", isNeedMapping); if (modelRelation.getKeyMapsSize() == 1) { //relation child Entity field String joinField = ""; //relation parent Entity field String mappedByFieldId = ""; String columnName = ""; if ("many".equals(modelRelation.getType())) { joinField = modelRelation.getKeyMap(0).getRelFieldName(); mappedByFieldId = modelRelation.getKeyMap(0).getFieldName(); try { // get relative column name ModelEntity refEntity = modelReader .getModelEntity(modelRelation.getRelEntityName()); columnName = refEntity.getColNameOrAlias(joinField); } catch (GenericEntityException e) { Debug.logError(e, MODULE); } } else { joinField = modelRelation.getKeyMap(0).getFieldName(); mappedByFieldId = modelRelation.getKeyMap(0).getRelFieldName(); // get joinField column columnName = modelEntity.getColNameOrAlias(joinField); } //adjust this column if use for other relation, if used we should mapped with insert="false" update="false" if (!entityColumns.contains(columnName)) { entityColumns.add(columnName); relationInfo.put("isRepeated", "N"); } else { relationInfo.put("isRepeated", "Y"); } relationInfo.put("joinColumn", columnName); //if this property is collection, and have only one primary key, and find it in child property // this is useful for hibernate's cascading feature if ("many".equals(modelRelation.getType()) && modelEntity.getPkFieldNames().size() == 1) { try { ModelEntity refEntity = modelReader .getModelEntity(modelRelation.getRelEntityName()); // manyToOne field String refField = ""; Iterator<ModelRelation> it = refEntity.getRelationsIterator(); while (it.hasNext()) { ModelRelation relation = it.next(); //if relation map modelRelation if (relation.getRelEntityName().equals(entityName) && relation.getKeyMapsSize() == 1 && relation.getKeyMap(0).getFieldName().equals(joinField)) { //get access name String aName = relation.getTitle() + relation.getRelEntityName(); //get ref field name String relateField = aName.substring(0, 1).toLowerCase() + aName.substring(1); // replace all space characters relateField = relateField.replaceAll("\\s*", ""); // if relate entity contain relate field if (refEntity.getAllFieldNames().contains(relateField + "Id") || refEntity .getAllFieldNames().containsAll(modelEntity.getPkFieldNames())) { refField = relateField; break; } } } if (!refField.equals("") && (refEntity.getPkFieldNames().size() == 1 || refEntity.getPkFieldNames().contains(joinField))) { relationInfo.put("itemName", itemName(fieldName)); relationInfo.put("refField", refField); // if this collection contains current pk, it should be a cascade collection property if (refEntity.getPkFieldNames().contains(joinField)) { //put add item method of collection relationInfo.put("addMethodName", addName(fieldName)); //put remove item method of collection relationInfo.put("removeMethodName", removeName(fieldName)); //put clear item method of collection relationInfo.put("clearMethodName", clearName(fieldName)); } } } catch (GenericEntityException e) { Debug.logError(e, MODULE); } } } relations.add(relationInfo); } entityInfo.put("relations", relations); entityInfo.put("types", types); entityInfo.put("fieldTypes", fieldTypes); entityInfo.put("getMethodNames", getMethodNames); entityInfo.put("setMethodNames", setMethodNames); entityInfo.put("validatorMaps", validatorMaps); entityInfo.put("indexWeights", indexWeights); entityInfo.put("tokenTypes", tokenTypes); // map view-entity to @NamedNativeQuery if (isView) { //pk fields of the first entity as view-entity pk List<String> viewEntityPks = new LinkedList<String>(); StringBuffer query = new StringBuffer(); query.append("SELECT "); // field <-> column alias mapping Map<String, String> fieldMapAlias = new TreeMap<String, String>(); // field <-> column name mapping Map<String, String> fieldMapColumns = new TreeMap<String, String>(); List<String> relationAlias = new LinkedList<String>(); ModelViewEntity modelViewEntity = (ModelViewEntity) modelEntity; Iterator<ModelAlias> aliasIter = modelViewEntity.getAliasesIterator(); // iterate all fields, construct select sentence while (aliasIter.hasNext()) { ModelAlias alias = aliasIter.next(); String columnName = ModelUtil.javaNameToDbName(alias.getField()); if (fieldMapAlias.size() > 0) { query.append(","); } //iterator field, such as "P.PARTY_ID \"partyId\" query.append( alias.getEntityAlias() + "." + columnName + " AS \\\"" + alias.getField() + "\\\""); String colAlias = alias.getColAlias(); String field = ModelUtil.dbNameToVarName(alias.getColAlias()); // put field-colAlias mapping fieldMapAlias.put(field, colAlias); // put field-column mapping fieldMapColumns.put(field, alias.getEntityAlias() + "." + columnName); } // iterate all entities, construct from sentence for (int i = 0; i < modelViewEntity.getAllModelMemberEntities().size(); i++) { ModelMemberEntity modelMemberEntity = (ModelMemberEntity) modelViewEntity .getAllModelMemberEntities().get(i); String tableName = ModelUtil.javaNameToDbName(modelMemberEntity.getEntityName()); String tableAlias = modelMemberEntity.getEntityAlias(); relationAlias.add(tableAlias); if (i == 0) { // main table query.append(" FROM " + tableName + " " + tableAlias); // get all the pk fields of the first entity which become members of the class try { ModelEntity firstEntity = modelReader .getModelEntity(modelMemberEntity.getEntityName()); Iterator<ModelField> it = firstEntity.getPksIterator(); while (it.hasNext()) { ModelField field = it.next(); //just need one pk, else secondary pk would be null if (fieldMapAlias.containsKey(field.getName()) && viewEntityPks.size() == 0) { viewEntityPks.add(field.getName()); } } } catch (GenericEntityException e) { Debug.logError(e, MODULE); } } else { // join table Iterator<ModelViewLink> viewLinkIter = modelViewEntity.getViewLinksIterator(); while (viewLinkIter.hasNext()) { ModelViewLink modelViewLink = viewLinkIter.next(); if (relationAlias.contains(modelViewLink.getEntityAlias()) && relationAlias.contains(modelViewLink.getRelEntityAlias()) && (tableAlias.equals(modelViewLink.getEntityAlias()) || tableAlias.equals(modelViewLink.getRelEntityAlias()))) { // adjust if left join or inner join String joinType = modelViewLink.isRelOptional() ? " LEFT JOIN " : " INNER JOIN "; query.append(joinType + tableName + " " + tableAlias); for (int k = 0; k < modelViewLink.getKeyMapsSize(); k++) { ModelKeyMap modelKeyMap = modelViewLink.getKeyMap(k); //get join conditions String joinCondition = modelViewLink.getEntityAlias() + "." + ModelUtil.javaNameToDbName(modelKeyMap.getFieldName()) + " = " + modelViewLink.getRelEntityAlias() + "." + ModelUtil.javaNameToDbName(modelKeyMap.getRelFieldName()); if (k == 0) { // if it is first join condition query.append(" ON " + joinCondition); } else { // if it isn't first join condition query.append(" AND " + joinCondition); } } } } } } // if no pk for this viewEntity, then give a random field, because hibernate annotation need at least one pk filed. if (viewEntityPks.size() == 0) { viewEntityPks.add(fieldNames.get(0)); } entityInfo.put("query", query.toString()); entityInfo.put("fieldMapAlias", fieldMapAlias); entityInfo.put("fieldMapColumns", fieldMapColumns); entityInfo.put("viewEntityPks", viewEntityPks); } // render it as FTL Writer writer = new StringWriter(); try { FreeMarkerWorker.renderTemplateAtLocation(entityTemplate, entityInfo, writer); } catch (MalformedURLException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (TemplateException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (IOException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (IllegalArgumentException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } // write it as a Java file String path = entityOutputPath; if (baseEntities.contains(entityName)) { Debug.logInfo("Entity [" + entityName + "] is configured as a base entity.", MODULE); path = baseEntityOutputPath; } String fileName = path + entityName + fileExtension; File file = new File(fileName); try { FileUtils.writeStringToFile(file, writer.toString(), "UTF-8"); } catch (IOException e) { Debug.logError(e, "Aborting, error writing file " + fileName, MODULE); errorEntities.add(entityName); break; } // pk class info Map<String, Object> pkInfo = null; if (!isView && modelEntity.getPkFieldNames().size() > 1) { //if entity has more than one pk pkInfo = new TreeMap<String, Object>(); // get all the pk fields of the entity which become members of the class List<String> primaryKeys = modelEntity.getPkFieldNames(); // a type for each pk during declarations Map<String, String> pkTypes = new TreeMap<String, String>(); // names of the get and set methods Map<String, String> getPkMethodNames = new TreeMap<String, String>(); Map<String, String> setPkMethodNames = new TreeMap<String, String>(); // distinct types for the import section Set<String> pkFieldTypes = new TreeSet<String>(); Iterator<ModelField> pksIter = modelEntity.getPksIterator(); while (pksIter.hasNext()) { String type = null; ModelField modelField = pksIter.next(); getPkMethodNames.put(modelField.getName(), getterName(modelField.getName())); setPkMethodNames.put(modelField.getName(), setterName(modelField.getName())); try { // this converts String to java.lang.String, Timestamp to java.sql.Timestamp, etc. type = ObjectType.loadClass( delegator.getEntityFieldType(modelEntity, modelField.getType()).getJavaType()) .getName(); } catch (Exception e) { Debug.logError(e, MODULE); } if (type == null) { errorEntities.add(entityName); Debug.logError("Error getting the type of the field " + modelField.getName() + " of entity " + entityName + " for delegator " + delegatorNameToUse, MODULE); hasError = true; break; } // make all Doubles BigDecimals -- in the entity model fieldtype XML files, all floating points are defined as Doubles // and there is a GenericEntity.getBigDecimal method which converts them to BigDecimal. We will make them all BigDecimals // and then use the Entity.convertToBigDecimal method if ("java.lang.Double".equals(type)) { type = "java.math.BigDecimal"; } pkFieldTypes.add(type); // this is the short type: ie, java.lang.String -> String; java.util.HashMap -> HashMap, etc. String shortType = type; int idx = type.lastIndexOf("."); if (idx > 0) { shortType = type.substring(idx + 1); } pkTypes.put(modelField.getName(), shortType); } pkInfo.put("pkName", modelEntity.getEntityName() + "Pk"); pkInfo.put("entityName", modelEntity.getEntityName()); pkInfo.put("primaryKeys", primaryKeys); pkInfo.put("pkTypes", pkTypes); pkInfo.put("getPkMethodNames", getPkMethodNames); pkInfo.put("setPkMethodNames", setPkMethodNames); pkInfo.put("pkFieldTypes", pkFieldTypes); pkInfo.put("columnNames", columnNames); } if (pkInfo != null) { // render pk class as FTL Writer pkWriter = new StringWriter(); try { FreeMarkerWorker.renderTemplateAtLocation(pkTemplate, pkInfo, pkWriter); } catch (MalformedURLException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (TemplateException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (IOException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (IllegalArgumentException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } // write it as a Java file (PK) File pkFile = new File(entityOutputPath + entityName + "Pk" + fileExtension); try { FileUtils.writeStringToFile(pkFile, pkWriter.toString(), "UTF-8"); } catch (IOException e) { Debug.logError(e, "Aborting, error writing file " + entityOutputPath + entityName + "Pk" + fileExtension, MODULE); errorEntities.add(entityName); break; } // render pk bridge class as FTL Writer pkBridgeWriter = new StringWriter(); try { FreeMarkerWorker.renderTemplateAtLocation(pkBridgeTemplate, pkInfo, pkBridgeWriter); } catch (MalformedURLException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (TemplateException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (IOException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } catch (IllegalArgumentException e) { Debug.logError(e, MODULE); errorEntities.add(entityName); break; } // write it as a Java file (PK) File pkBridgeFile = new File( entityOutputPath + "/bridge/" + entityName + "PkBridge" + fileExtension); try { FileUtils.writeStringToFile(pkBridgeFile, pkBridgeWriter.toString(), "UTF-8"); } catch (IOException e) { Debug.logError(e, "Aborting, error writing file " + entityOutputPath + "/bridge/" + entityName + "PkBridge" + fileExtension, MODULE); errorEntities.add(entityName); break; } } // increment counter totalGeneratedClasses++; } // render hibernate.cfg.xml by FTL Writer cfgWriter = new StringWriter(); Map<String, Object> cfgInfo = new HashMap<String, Object>(); cfgInfo.put("entities", entities); try { FreeMarkerWorker.renderTemplateAtLocation(hibernateCfgTemplate, cfgInfo, cfgWriter); } catch (MalformedURLException e1) { Debug.logError(e1, MODULE); } catch (TemplateException e1) { Debug.logError(e1, MODULE); } catch (IOException e1) { Debug.logError(e1, MODULE); } // write it as a hibernate.cfg.xml File hibernateFile = new File(hibernateCfgPath); try { FileUtils.writeStringToFile(hibernateFile, cfgWriter.toString(), "UTF-8"); } catch (IOException e) { Debug.logError(e, "Aborting, error writing file " + hibernateCfgPath, MODULE); } } else { Debug.logImportant("=-=-=-=-=-=-= No entity found.", MODULE); } // error summary if (errorEntities.size() > 0) { Debug.logImportant("The following entities could not be generated:", MODULE); for (String name : errorEntities) { Debug.logImportant(name, MODULE); } } Debug.logImportant("=-=-=-=-=-=-= Finished the POJO entities generation with " + totalGeneratedClasses + " classes generated.", MODULE); if (errorEntities.size() > 0) { return false; } return true; } /** {@inheritDoc} */ public void stop() throws ContainerException { } /** * Transform a string into a class name by capitalizing the first char and filtering invliad chars. * @param name the name to transform * @return a class name */ public static String makeClassName(String name) { if (UtilValidate.isEmpty(name)) { throw new IllegalArgumentException("className called for null or empty string"); } else { String className = Character.toUpperCase(name.charAt(0)) + name.substring(1); className = filterChar(className, "."); className = filterChar(className, "_"); className = filterChar(className, "-"); return className; } } private static String filterChar(String name, String filter) { int idx = -1; String newName = name; while ((idx = newName.indexOf(filter)) >= 0) { String temp = newName.substring(0, idx); if (newName.length() > (idx + 1)) { temp += Character.toUpperCase(newName.charAt(idx + 1)); } if (newName.length() > (idx + 2)) { temp += newName.substring(idx + 2); } newName = temp; } return newName; } /** * Standardize accessor method names. If fieldName is orderId, will return "prefix" + OrderId. * * @param prefix prefix to the method name, for example "get" or "set" * @param fieldName name of the field the accessor method access * @return the accessor method name */ public static String accessorMethodName(String prefix, String fieldName) { if (UtilValidate.isEmpty(fieldName)) { throw new IllegalArgumentException("methodName called for null or empty fieldName"); } else { return prefix + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1); } } /** * Standardize accessor collection method names. If fieldName is items, will return "prefix" + Item. * * @param prefix prefix to the method name, for example "add" or "remove" * @param fieldName name of the field the accessor method access * @return the accessor method name */ public static String accessorCollectionMethodName(String prefix, String fieldName) { if (UtilValidate.isEmpty(fieldName)) { throw new IllegalArgumentException("methodName called for null or empty fieldName"); } else { return prefix + Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1, fieldName.length() - 1); } } /** * Standardize getter method names. * * @param fieldName name of the field the accessor method access * @return the getter method name */ public static String getterName(String fieldName) { return accessorMethodName("get", fieldName); } /** * Standardize setter method names. * * @param fieldName name of the field the accessor method access * @return the setter method name */ public static String setterName(String fieldName) { return accessorMethodName("set", fieldName); } /** * Standardize add method of Collection property names. * * @param fieldName name of the field the add method access * @return the method name */ public static String addName(String fieldName) { return accessorCollectionMethodName("add", fieldName); } /** * Standardize remove method of Collection property names. * * @param fieldName name of the field the remove method access * @return the method name */ public static String removeName(String fieldName) { return accessorCollectionMethodName("remove", fieldName); } /** * Standardize clear method of Collection property names. * * @param fieldName name of the field the clear method access * @return the method name */ public static String clearName(String fieldName) { return accessorCollectionMethodName("clear", fieldName); } /** * get item of Collection property names. * * @param fieldName name of the field the clear method access * @return the item field name */ public static String itemName(String fieldName) { if (UtilValidate.isEmpty(fieldName)) { throw new IllegalArgumentException("methodName called for null or empty fieldName"); } else { return fieldName.substring(0, fieldName.length() - 1); } } /** * get Pk name of Entity. * * @param entityName name of the Entity * @return the Pk field name */ public static String getEntityPkName(String entityName) { if (entityName == null || entityName.equals("")) { return entityName; } String pkName = Character.toLowerCase(entityName.charAt(0)) + entityName.substring(1, entityName.length()) + "Id"; return pkName; } }