Java tutorial
/* * Copyright 2008-2009 the original author or authors. * * 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.guzz.builder; import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Map; import java.util.Properties; import javax.persistence.AccessType; import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverrides; import javax.persistence.Column; import javax.persistence.EnumType; import javax.persistence.FetchType; import javax.persistence.GenerationType; import javax.persistence.MappedSuperclass; import javax.persistence.PersistenceException; import javax.persistence.SequenceGenerator; import javax.persistence.TableGenerator; import javax.persistence.Transient; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.guzz.GuzzContextImpl; import org.guzz.annotations.GenericGenerator; import org.guzz.annotations.GenericGenerators; import org.guzz.annotations.Parameter; import org.guzz.annotations.Table; import org.guzz.connection.DBGroup; import org.guzz.exception.DataTypeException; import org.guzz.exception.GuzzException; import org.guzz.exception.IllegalParameterException; import org.guzz.id.Configurable; import org.guzz.id.IdentifierGenerator; import org.guzz.id.IdentifierGeneratorFactory; import org.guzz.id.SequenceIdGenerator; import org.guzz.id.TableMultiIdGenerator; import org.guzz.lang.NullValue; import org.guzz.orm.Business; import org.guzz.orm.CustomTableView; import org.guzz.orm.ShadowTableView; import org.guzz.orm.mapping.ObjectMappingUtil; import org.guzz.orm.mapping.POJOBasedObjectMapping; import org.guzz.orm.rdms.SimpleTable; import org.guzz.orm.rdms.TableColumn; import org.guzz.util.ArrayUtil; import org.guzz.util.Assert; import org.guzz.util.StringUtil; import org.guzz.util.javabean.BeanCreator; import org.guzz.util.javabean.BeanWrapper; import org.guzz.util.javabean.JavaBeanWrapper; import org.guzz.web.context.GuzzContextAware; /** * * parse orm definition in JPA2.0 annotation style. * * @author liu kaixuan(liukaixuan@gmail.com) */ public class JPA2AnnotationsBuilder { private static final Log log = LogFactory.getLog(JPA2AnnotationsBuilder.class); protected static void parseClassForAttributes(GuzzContextImpl gf, POJOBasedObjectMapping map, Business business, DBGroup dbGroup, SimpleTable st, Class domainClass) { //??? Class parentCls = domainClass.getSuperclass(); if (parentCls != null && parentCls.isAnnotationPresent(MappedSuperclass.class)) { parseClassForAttributes(gf, map, business, dbGroup, st, parentCls); } javax.persistence.Access access = (javax.persistence.Access) domainClass .getAnnotation(javax.persistence.Access.class); AccessType accessType = null; if (access == null) { //@Id@Idfieldproperty boolean hasColumnAOnField = false; boolean hasColumnAOnProperty = false; //detect from @Id, field first. Field[] fs = domainClass.getDeclaredFields(); for (Field f : fs) { if (f.isAnnotationPresent(Transient.class)) continue; if (f.isAnnotationPresent(javax.persistence.Id.class)) { accessType = AccessType.FIELD; break; } else if (f.isAnnotationPresent(javax.persistence.Column.class)) { hasColumnAOnField = true; } else if (f.isAnnotationPresent(org.guzz.annotations.Column.class)) { hasColumnAOnField = true; } } if (accessType == null) { Method[] ms = domainClass.getDeclaredMethods(); for (Method m : ms) { if (m.isAnnotationPresent(Transient.class)) continue; if (m.isAnnotationPresent(javax.persistence.Id.class)) { accessType = AccessType.PROPERTY; break; } else if (m.isAnnotationPresent(javax.persistence.Column.class)) { hasColumnAOnProperty = true; } else if (m.isAnnotationPresent(org.guzz.annotations.Column.class)) { hasColumnAOnProperty = true; } } } //@Id@Column@Columnfield? if (accessType == null) { if (hasColumnAOnField) { accessType = AccessType.FIELD; } else if (hasColumnAOnProperty) { accessType = AccessType.PROPERTY; } else { accessType = AccessType.FIELD; } } } else { accessType = access.value(); } //orm by field if (accessType == AccessType.FIELD) { Field[] fs = domainClass.getDeclaredFields(); for (Field f : fs) { if (f.isAnnotationPresent(Transient.class)) continue; if (Modifier.isTransient(f.getModifiers())) continue; if (Modifier.isStatic(f.getModifiers())) continue; if (f.isAnnotationPresent(javax.persistence.Id.class)) { addIdMapping(gf, map, st, dbGroup, f.getName(), domainClass, f); } else { addPropertyMapping(gf, map, st, f.getName(), f, f.getType()); } } } else { Method[] ms = domainClass.getDeclaredMethods(); for (Method m : ms) { if (m.isAnnotationPresent(Transient.class)) continue; if (Modifier.isTransient(m.getModifiers())) continue; if (Modifier.isStatic(m.getModifiers())) continue; if (Modifier.isPrivate(m.getModifiers())) continue; String methodName = m.getName(); String fieldName = null; if (m.getParameterTypes().length != 0) { continue; } else if (Void.TYPE.equals(m.getReturnType())) { continue; } if (methodName.startsWith("get")) { fieldName = methodName.substring(3); } else if (methodName.startsWith("is")) {//is boolean? Class retType = m.getReturnType(); if (boolean.class.isAssignableFrom(retType)) { fieldName = methodName.substring(2); } else if (Boolean.class.isAssignableFrom(retType)) { fieldName = methodName.substring(2); } } //not a javabean read method if (fieldName == null) { continue; } fieldName = java.beans.Introspector.decapitalize(fieldName); if (m.isAnnotationPresent(javax.persistence.Id.class)) { addIdMapping(gf, map, st, dbGroup, fieldName, domainClass, m); } else { addPropertyMapping(gf, map, st, fieldName, m, m.getReturnType()); } } } //?attribute override AttributeOverride gao = (AttributeOverride) domainClass.getAnnotation(AttributeOverride.class); AttributeOverrides gaos = (AttributeOverrides) domainClass.getAnnotation(AttributeOverrides.class); AttributeOverride[] aos = gao == null ? new AttributeOverride[0] : new AttributeOverride[] { gao }; if (gaos != null) { ArrayUtil.addToArray(aos, gaos.value()); } for (AttributeOverride ao : aos) { String name = ao.name(); Column col = ao.column(); TableColumn tc = st.getColumnByPropName(name); Assert.assertNotNull(tc, "@AttributeOverride cann't override a attribute that doesn't exist. The attribute is:" + name); //update is remove and add st.removeColumn(tc); //change the column name in the database. tc.setColName(col.name()); st.addColumn(tc); } } protected static void addIdMapping(GuzzContextImpl gf, POJOBasedObjectMapping map, SimpleTable st, DBGroup dbGroup, String name, Class domainClas, AnnotatedElement element) { javax.persistence.Column pc = (javax.persistence.Column) element .getAnnotation(javax.persistence.Column.class); org.guzz.annotations.Column gc = (org.guzz.annotations.Column) element .getAnnotation(org.guzz.annotations.Column.class); String type = gc == null ? null : gc.type(); String column = pc == null ? null : pc.name(); if (StringUtil.isEmpty(column)) { column = name; } TableColumn col = st.getColumnByPropName(name); boolean newId = false; if (col == null) { newId = true; col = new TableColumn(st); } else { log.info("override @Id in the parent class of [" + st.getBusinessName() + "]."); } col.setColName(column); col.setPropName(name); col.setType(type); col.setAllowInsert(true); col.setAllowUpdate(true); col.setLazy(false); map.initColumnMapping(col, null); if (newId) { st.addPKColumn(col); } //@Id generator javax.persistence.GeneratedValue pgv = (javax.persistence.GeneratedValue) element .getAnnotation(javax.persistence.GeneratedValue.class); if (pgv == null) { pgv = (javax.persistence.GeneratedValue) domainClas .getAnnotation(javax.persistence.GeneratedValue.class); } //If @GeneratedValue is not defined, use auto. GenerationType gt = GenerationType.AUTO; String generatorName = null; if (pgv != null) { gt = pgv.strategy(); generatorName = pgv.generator(); } Properties idProperties = new Properties(); String igCls; if (gt == GenerationType.AUTO) { //??guzz@GenericGenerator if (StringUtil.notEmpty(generatorName)) { GenericGenerator ggg = (GenericGenerator) element.getAnnotation(GenericGenerator.class); if (ggg != null && !generatorName.equals(ggg.name())) { ggg = null; } if (ggg == null) { //retreive @Id from GlobalContext Object g = gf.getGlobalIdGenerator(generatorName); //should be GenericGenerator if (!(g instanceof GenericGenerator)) { throw new IllegalParameterException("The Id Generator [" + generatorName + "] should be of type @org.guzz.annotations.GenericGenerator. domain class:" + domainClas.getName()); } ggg = (GenericGenerator) g; } igCls = ggg.strategy(); Parameter[] ps = ggg.parameters(); for (Parameter p : ps) { idProperties.setProperty(p.name(), p.value()); } } else { //nativegeneratordialect? igCls = "native"; } } else if (gt == GenerationType.IDENTITY) { igCls = "identity"; } else if (gt == GenerationType.SEQUENCE) { igCls = "sequence"; javax.persistence.SequenceGenerator psg = (javax.persistence.SequenceGenerator) element .getAnnotation(javax.persistence.SequenceGenerator.class); if (psg == null) { Object sg = gf.getGlobalIdGenerator(generatorName); Assert.assertNotNull(sg, "@javax.persistence.SequenceGenerator not found for sequenced @Id. domain class:" + domainClas.getName()); if (sg instanceof SequenceGenerator) { psg = (SequenceGenerator) sg; } else { throw new IllegalParameterException("The Id Generator [" + generatorName + "] should be of type @javax.persistence.SequenceGenerator. domain class:" + domainClas.getName()); } } idProperties.setProperty(SequenceIdGenerator.PARAM_SEQUENCE, psg.sequenceName()); idProperties.setProperty("catalog", psg.catalog()); idProperties.setProperty("allocationSize", String.valueOf(psg.allocationSize())); idProperties.setProperty("initialValue", String.valueOf(psg.initialValue())); //we need db_group param, but the JPA won't give us. } else if (gt == GenerationType.TABLE) { igCls = "hilo.multi"; TableGenerator pst = (TableGenerator) element.getAnnotation(TableGenerator.class); if (pst == null) { Object sg = gf.getGlobalIdGenerator(generatorName); Assert.assertNotNull(sg, "@javax.persistence.TableGenerator not found for hilo.multi @Id. domain class:" + domainClas.getName()); if (sg instanceof TableGenerator) { pst = (TableGenerator) sg; } else { throw new IllegalParameterException("The Id Generator [" + generatorName + "] should be of type @javax.persistence.TableGenerator. domain class:" + domainClas.getName()); } } idProperties.setProperty("catalog", pst.catalog()); idProperties.setProperty("schema", pst.schema()); idProperties.setProperty(TableMultiIdGenerator.TABLE, pst.table()); idProperties.setProperty(TableMultiIdGenerator.PK_COLUMN_NAME, pst.pkColumnName()); idProperties.setProperty(TableMultiIdGenerator.PK_COLUMN_VALUE, pst.pkColumnValue()); idProperties.setProperty(TableMultiIdGenerator.COLUMN, pst.valueColumnName()); idProperties.setProperty(TableMultiIdGenerator.MAX_LO, String.valueOf(pst.allocationSize())); //we need db_group param, but the JPA won't give us. idProperties.setProperty("initialValue", String.valueOf(pst.initialValue())); } else { throw new GuzzException("unknown @javax.persistence.GenerationType:" + gt); } if ("native".equals(igCls)) { igCls = dbGroup.getDialect().getNativeIDGenerator(); } String realClassName = (String) IdentifierGeneratorFactory.getGeneratorClass(igCls); Assert.assertNotNull(realClassName, "unknown Id generator:" + igCls); IdentifierGenerator ig = (IdentifierGenerator) BeanCreator.newBeanInstance(realClassName); if (ig instanceof Configurable) { ((Configurable) ig).configure(dbGroup.getDialect(), map, idProperties); } //register callback for GuzzContext's full starting. if (ig instanceof GuzzContextAware) { gf.registerContextStartedAware((GuzzContextAware) ig); } st.setIdentifierGenerator(ig); } protected static void addPropertyMapping(GuzzContextImpl gf, POJOBasedObjectMapping map, SimpleTable st, String name, AnnotatedElement element, Class dataType) { javax.persistence.Column pc = element.getAnnotation(javax.persistence.Column.class); javax.persistence.Basic pb = element.getAnnotation(javax.persistence.Basic.class); javax.persistence.Enumerated pe = element.getAnnotation(javax.persistence.Enumerated.class); org.guzz.annotations.Column gc = element.getAnnotation(org.guzz.annotations.Column.class); String type = gc == null ? null : gc.type(); String nullValue = gc == null ? null : gc.nullValue(); String column = pc == null ? null : pc.name(); boolean lazy = pb == null ? false : pb.fetch() == FetchType.LAZY; Class loader = gc == null ? null : gc.loader(); if (dataType.isEnum()) { EnumType etype = EnumType.ORDINAL; if (pe != null) { etype = pe.value(); } if (etype == EnumType.ORDINAL) { type = "enum.ordinal|" + dataType.getName(); } else { type = "enum.string|" + dataType.getName(); } } boolean insertIt = pc == null ? true : pc.insertable(); boolean updateIt = pc == null ? true : pc.updatable(); if (StringUtil.isEmpty(column)) { column = name; } TableColumn col = st.getColumnByPropName(name); if (col != null) { log.warn("field/property [" + name + "] already exsits in the parent class of [" + st.getBusinessName() + "]. Ignore it."); return; } if (loader == null || NullValue.class.isAssignableFrom(loader)) { loader = null; } try { col = ObjectMappingUtil.createTableColumn(gf, map, name, column, type, loader == null ? null : loader.getName()); col.setNullValue(nullValue); col.setAllowInsert(insertIt); col.setAllowUpdate(updateIt); col.setLazy(lazy); st.addColumn(col); } catch (DataTypeException dte) { //???JPA??Map, Set if (log.isDebugEnabled()) { log.debug("Unsupported data type is found in annotation, property is:[" + name + "], business is:[" + st.getBusinessName() + "]. Ignore this property.", dte); } else { log.warn("Ignore unsupported data type in annotation, property is:[" + name + "], business is:[" + st.getBusinessName() + "], msg is:" + dte.getMessage()); } } } /** * Build the {@link Business} and the {@link Table} information of the domain class. * <p/> * We have to seperate this operation from the {@link #parseClassForAttributes(GuzzContextImpl, POJOBasedObjectMapping, Business, DBGroup, SimpleTable, Class)} * to get the final "dbGroup" after the inherited tree. */ protected static void parseClassForEntityTable(DomainInfo info, Class domainClass) { //??? Class parentCls = domainClass.getSuperclass(); if (parentCls != null && parentCls.isAnnotationPresent(MappedSuperclass.class)) { parseClassForEntityTable(info, parentCls); } //? org.guzz.annotations.Entity ge = (org.guzz.annotations.Entity) domainClass .getAnnotation(org.guzz.annotations.Entity.class); org.guzz.annotations.Table gt = (org.guzz.annotations.Table) domainClass .getAnnotation(org.guzz.annotations.Table.class); javax.persistence.Table pt = (javax.persistence.Table) domainClass .getAnnotation(javax.persistence.Table.class); if (ge != null) { info.businessName = ge.businessName(); Class m_interpreter = ge.interpreter(); if (m_interpreter != null && !NullValue.class.isAssignableFrom(m_interpreter)) { info.interpreter = m_interpreter; } } if (pt != null) { if (StringUtil.notEmpty(pt.name())) { info.tableName = pt.name(); } } if (gt != null) { if (StringUtil.notEmpty(gt.dbGroup())) { info.dbGroup = gt.dbGroup(); } if (StringUtil.notEmpty(gt.name())) { info.tableName = gt.name(); } if (gt.shadow() != null) { info.shadow = gt.shadow(); } info.dynamicUpdate = gt.dynamicUpdate(); } } static class DomainInfo { public Class interpreter; public Class shadow; public String tableName; public boolean dynamicUpdate; public String dbGroup; public String businessName; } public static POJOBasedObjectMapping parseDomainClass(final GuzzContextImpl gf, String dbGroupName, String businessName, Class domainCls) throws ClassNotFoundException { javax.persistence.Entity pe = (javax.persistence.Entity) domainCls .getAnnotation(javax.persistence.Entity.class); if (pe == null) { throw new PersistenceException( "no @javax.persistence.Entity annotation found in class:[" + domainCls + "]"); } DomainInfo info = new DomainInfo(); parseClassForEntityTable(info, domainCls); //xml definition own high priority. String m_dbGroupName = StringUtil.isEmpty(dbGroupName) ? info.dbGroup : dbGroupName; String m_businessName = StringUtil.isEmpty(businessName) ? info.businessName : businessName; String tableName = info.tableName; Class shadow = info.shadow; boolean dynamicUpdate = info.dynamicUpdate; if (StringUtil.isEmpty(tableName)) { //According to the JPA's spefication, we use the class's short name as the table name. tableName = domainCls.getSimpleName(); } //The Business name must be defined either in annotation or guzz.xml, or both. Assert.assertNotEmpty(m_businessName, "business name must be defined. You can declare it either with org.guzz.annotations.Entity annotation in domain class or a-bussiness's attribute in guzz.xml"); Business business = gf.instanceNewGhost(m_businessName, m_dbGroupName, info.interpreter, domainCls); DBGroup dbGroup = gf.getDBGroup(business.getDbGroup()); Assert.assertNotNull(dbGroup, "unknown dbGroup:[" + business.getDbGroup() + "] for domain class:" + domainCls.getName()); final SimpleTable st = new SimpleTable(dbGroup.getDialect()); st.setTableName(tableName); st.setDynamicUpdate(dynamicUpdate); final POJOBasedObjectMapping map = new POJOBasedObjectMapping(gf, dbGroup, st); business.setTable(st); business.setMapping(map); //?business?? if (business.getName() != null) { st.setBusinessName(business.getName()); } else { st.setBusinessName(business.getDomainClass().getName()); } //table? JavaBeanWrapper configBeanWrapper = BeanWrapper.createPOJOWrapper(domainCls); business.setConfiguredBeanWrapper(configBeanWrapper); map.setBusiness(business); if (shadow != null && !NullValue.class.isAssignableFrom(shadow)) { ShadowTableView sv = (ShadowTableView) BeanCreator.newBeanInstance(shadow); sv.setConfiguredTableName(tableName); //CustomTableViewShadowTableView if (sv instanceof CustomTableView) { CustomTableView ctv = (CustomTableView) sv; ctv.setConfiguredObjectMapping(map); st.setCustomTableView(ctv); } st.setShadowTableView(sv); gf.registerShadowTableView(sv); } // parseClassForAttributes(gf, map, business, dbGroup, st, domainCls); //check that the class must own a @Id. Assert.assertNotNull(st.getIdentifierGenerator(), "no @javax.persistence.Id annotation found in class:[" + domainCls + "]"); return map; } /** * Only retrieve {@link SequenceGenerator} and {@link TableGenerator} in the type declaration. */ public static void parseForIdGenerators(final Map idGenerators, Class domainCls) throws ClassNotFoundException { javax.persistence.Entity pe = (javax.persistence.Entity) domainCls .getAnnotation(javax.persistence.Entity.class); javax.persistence.MappedSuperclass pm = (javax.persistence.MappedSuperclass) domainCls .getAnnotation(javax.persistence.MappedSuperclass.class); if (pe == null && pm == null) { log.debug("Parsing for id generator ends at class:" + domainCls.getName()); return; } //parse the super class first, then add/replace it from the subclass. Class superCls = domainCls.getSuperclass(); if (superCls != null && superCls.isAnnotationPresent(javax.persistence.MappedSuperclass.class)) { parseForIdGenerators(idGenerators, superCls); } //parse for this class. SequenceGenerator psg = (SequenceGenerator) domainCls.getAnnotation(SequenceGenerator.class); TableGenerator tsg = (TableGenerator) domainCls.getAnnotation(TableGenerator.class); GenericGenerator[] ggs = new GenericGenerator[0]; //add @GenericGenerators if (domainCls.getPackage().isAnnotationPresent(GenericGenerators.class)) { ggs = (GenericGenerator[]) ArrayUtil.addToArray(ggs, ((GenericGenerators) domainCls.getPackage().getAnnotation(GenericGenerators.class)).value()); } if (domainCls.isAnnotationPresent(GenericGenerators.class)) { ggs = (GenericGenerator[]) ArrayUtil.addToArray(ggs, ((GenericGenerators) domainCls.getAnnotation(GenericGenerators.class)).value()); } //add @GenericGenerator if (domainCls.getPackage().isAnnotationPresent(GenericGenerator.class)) { ggs = (GenericGenerator[]) ArrayUtil.addToArray(ggs, (GenericGenerator) domainCls.getPackage().getAnnotation(GenericGenerator.class)); } if (domainCls.isAnnotationPresent(GenericGenerator.class)) { ggs = (GenericGenerator[]) ArrayUtil.addToArray(ggs, (GenericGenerator) domainCls.getAnnotation(GenericGenerator.class)); } if (psg != null) { String name = psg.name(); if (idGenerators.get(name) != null && log.isDebugEnabled()) { log.debug("override @Id annotation:[" + idGenerators.get(name) + "] with @SequenceGenerator, name is:" + name); } idGenerators.put(name, psg); } if (tsg != null) { String name = tsg.name(); if (idGenerators.get(name) != null && log.isDebugEnabled()) { log.debug("override @Id annotation:[" + idGenerators.get(name) + "] with @TableGenerator, name is:" + name); } idGenerators.put(name, tsg); } if (ggs.length > 0) { for (GenericGenerator gg : ggs) { String name = gg.name(); if (idGenerators.get(name) != null && log.isDebugEnabled()) { log.debug("override @Id annotation:[" + idGenerators.get(name) + "] with @GenericGenerator, name is:" + name); } idGenerators.put(name, gg); } } } }