/*
* Copyright (c) 1998-2011 Caucho Technology -- all rights reserved
*
* This file is part of Resin(R) Open Source
*
* Each copy or derived work must preserve the copyright notice and this
* notice unmodified.
*
* Resin Open Source is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Resin Open Source 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, or any warranty
* of NON-INFRINGEMENT. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with Resin Open Source; if not, write to the
*
* Free Software Foundation, Inc.
* 59 Temple Place, Suite 330
* Boston, MA 02111-1307 USA
*
* @author Scott Ferguson
*/
package com.caucho.amber.field;
import com.caucho.amber.expr.AmberExpr;
import com.caucho.amber.expr.OneToManyExpr;
import com.caucho.amber.expr.PathExpr;
import com.caucho.amber.query.QueryParser;
import com.caucho.amber.table.LinkColumns;
import com.caucho.amber.table.AmberTable;
import com.caucho.amber.type.EntityType;
import com.caucho.amber.type.AmberType;
import com.caucho.bytecode.JType;
import com.caucho.bytecode.JTypeWrapper;
import com.caucho.config.ConfigException;
import com.caucho.java.JavaWriter;
import com.caucho.util.CharBuffer;
import com.caucho.util.L10N;
import javax.persistence.CascadeType;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
/**
* Represents a field to a collection of objects where the target
* hold a back-link to the source entity.
*/
public class OneToManyField extends CollectionField {
private static final L10N L = new L10N(OneToManyField.class);
private static final Logger log
= Logger.getLogger(OneToManyField.class.getName());
private String _mapKey;
private ArrayList<String> _orderByFields;
private ArrayList<Boolean> _orderByAscending;
private ManyToOneField _sourceField;
public OneToManyField(EntityType entityType,
String name,
CascadeType[] cascadeTypes)
throws ConfigException
{
super(entityType, name, cascadeTypes);
}
public OneToManyField(EntityType entityType,
String name)
throws ConfigException
{
this(entityType, name, null);
}
public OneToManyField(EntityType entityType)
{
super(entityType);
}
/**
* Sets the order by.
*/
public void setOrderBy(ArrayList<String> orderByFields,
ArrayList<Boolean> orderByAscending)
{
_orderByFields = orderByFields;
_orderByAscending = orderByAscending;
}
/**
* Returns the source type as
* entity or mapped-superclass.
*/
@Override
public EntityType getEntitySourceType()
{
return (EntityType) getSourceType();
}
/**
* Returns the target type as
* entity or mapped-superclass.
*/
public EntityType getEntityTargetType()
{
return (EntityType) getTargetType();
}
/**
* Returns the target type as entity.
*/
@Override
public AmberType getTargetType()
{
return _sourceField.getSourceType();
}
/**
* Gets the source field.
*/
public ManyToOneField getSourceField()
{
return _sourceField;
}
/**
* Sets the source field.
*/
public void setSourceField(ManyToOneField sourceField)
{
_sourceField = sourceField;
}
/**
* Returns the link.
*/
@Override
public LinkColumns getLinkColumns()
{
return _sourceField.getLinkColumns();
}
/**
* Gets the map key.
*/
public String getMapKey()
{
return _mapKey;
}
/**
* Sets the map key.
*/
public void setMapKey(String mapKey)
{
_mapKey = mapKey;
}
/**
* Initialize.
*/
@Override
public void init()
{
// jpa/0gg2
if (_sourceField == null) // || getLinkColumns() == null)
throw new IllegalStateException();
}
/**
* Creates the expression for the field.
*/
@Override
public AmberExpr createExpr(QueryParser parser, PathExpr parent)
{
return new OneToManyExpr(parser, parent, getLinkColumns());
}
/**
* Generates the (pre) cascade operation from
* parent to this child. This field will only
* be cascaded first if the operation can be
* performed with no risk to break FK constraints.
*/
@Override
public void generatePreCascade(JavaWriter out,
String aConn,
CascadeType cascadeType)
throws IOException
{
if (cascadeType == CascadeType.PERSIST)
return;
generateInternalCascade(out, aConn, cascadeType);
}
/**
* Generates the (post) cascade operation from
* parent to this child. This field will only
* be cascaded first if the operation can be
* performed with no risk to break FK constraints.
*/
@Override
public void generatePostCascade(JavaWriter out,
String aConn,
CascadeType cascadeType)
throws IOException
{
if (cascadeType != CascadeType.PERSIST)
return;
generateInternalCascade(out, aConn, cascadeType);
}
@Override
protected void generateInternalCascade(JavaWriter out,
String aConn,
CascadeType cascadeType)
throws IOException
{
if (isCascade(cascadeType)) {
String getter = "_caucho_field_" + getGetterName(); // generateSuperGetterMethod();
out.println("if (" + getter + " == null && " + generateSuperGetter("this") + " != null)");
out.pushDepth();
out.println(getSetterName() + "(" + generateSuperGetter("this") + ");");
out.popDepth();
out.println();
out.println("if (" + getter + " != null) {");
out.pushDepth();
out.print("for (Object o : " + getter);
// jpa/0v04
if (Map.class.isAssignableFrom(getJavaClass()))
out.print(".values()");
out.println(") {");
out.pushDepth();
// XXX
out.println("if (o == null)");
out.println(" continue;");
if (_sourceField != null) {
String typeName = getEntityTargetType().getJavaTypeName();
String setter = _sourceField.getSetterName();
out.println("((" + typeName + ") o)." + setter + "(this);");
}
out.print(aConn + ".");
switch (cascadeType) {
case PERSIST:
out.print("persistFromCascade");
break;
case MERGE:
out.print("merge");
break;
case REMOVE:
out.print("remove");
break;
case REFRESH:
out.print("refresh");
break;
}
out.println("(o);");
out.popDepth();
out.println("}");
out.popDepth();
out.println("}");
}
}
/**
* Generates the set clause.
*/
@Override
public void generateStatementSet(JavaWriter out, String pstmt,
String obj, String index)
throws IOException
{
}
/**
* Generates the select clause.
*/
@Override
public String generateLoadSelect(String id)
{
return null;
}
/**
* Generates loading code after the basic fields.
*/
@Override
public int generatePostLoadSelect(JavaWriter out, int index)
throws IOException
{
if (! isLazy()) {
out.println(getGetterName() + "();");
return ++index;
}
else
return index;
}
/**
* Updates from the cached copy.
*/
@Override
public void generateCopyLoadObject(JavaWriter out,
String dst, String src,
int loadIndex)
throws IOException
{
}
/**
* Generates the target select.
*/
@Override
public String generateTargetSelect(String id)
{
CharBuffer cb = CharBuffer.allocate();
Id key = getEntityTargetType().getId();
cb.append(key.generateSelect(id));
String value = getEntityTargetType().generateLoadSelect(id);
if (cb.length() > 0 && value.length() > 0)
cb.append(", ");
cb.append(value);
return cb.close();
}
/**
* Generates the set property.
*/
@Override
public void generateGetterMethod(JavaWriter out)
throws IOException
{
String var = "_caucho_field_" + getGetterName();
boolean isSet = Set.class.isAssignableFrom(getJavaClass());
boolean isMap = false;
if (!isSet) {
isMap = Map.class.isAssignableFrom(getJavaClass());
}
JType type = getJavaType();
JType []paramArgs = type.getActualTypeArguments();
JType param = paramArgs.length > 0 ? paramArgs[0] : null;
JType param2 = paramArgs.length > 1 ? paramArgs[1] : null;
out.print("protected transient ");
String collectionImpl;
if (isSet)
collectionImpl = "com.caucho.amber.collection.SetImpl";
else if (isMap)
collectionImpl = "com.caucho.amber.collection.MapImpl";
else
collectionImpl = "com.caucho.amber.collection.CollectionImpl";
out.print(collectionImpl);
if (param != null) {
out.print("<");
out.print(param.getPrintName());
if (isMap) {
if (param2 != null) {
out.print(", ");
out.print(param2.getPrintName());
}
}
out.print(">");
}
out.println(" " + var + ";");
out.println();
out.println("public " + getJavaTypeName() + " " + getGetterName() + "()");
out.println("{");
out.pushDepth();
out.println("if (" + var + " != null) {");
out.pushDepth();
out.println("if (__caucho_state.isPersist()) {");
out.pushDepth();
out.println(var + ".setSession(__caucho_session);");
out.println("return " + var + ";");
out.popDepth();
out.println("}");
out.println();
// jpa/1621
out.println("if (" + var + ".getSession() != null");
out.println(" && " + var + ".getSession() == __caucho_session)");
out.println(" return " + var + ";");
out.popDepth();
out.println("}");
out.println();
out.println("com.caucho.amber.AmberQuery query = null;");
String newEmptyCollection = "new " + collectionImpl;
if (param != null) {
newEmptyCollection += "<" + param.getPrintName();
if (isMap) {
newEmptyCollection += ", ";
newEmptyCollection += param2.getPrintName();
}
newEmptyCollection += ">";
}
newEmptyCollection += "(query";
if (isMap) {
// jpa/0v00
newEmptyCollection += "," + getEntityTargetType().getBeanClass().getName();
newEmptyCollection += ".class.getDeclaredMethod(\"";
String getterMapKey = getMapKey();
// jpa/0j63
if (getterMapKey == null) {
getterMapKey = getEntityTargetType().getId().generateGet("this");
}
else {
getterMapKey = "get" + Character.toUpperCase(getterMapKey.charAt(0))
+ getterMapKey.substring(1);
}
newEmptyCollection += getterMapKey; // "getId");
newEmptyCollection += "\", (Class []) null)";
}
newEmptyCollection += ")";
out.println();
out.println("try {");
out.pushDepth();
out.println("if (__caucho_session == null) {");
out.pushDepth();
/*
out.println("if (" + var + " == null)");
out.println(" " + var + " = " + newEmptyCollection + ";");
// if (! isAbstract())
out.println();
out.println("return " + var + ";");
*/
out.println("return " + generateSuperGetter("this") + ";");
out.popDepth();
out.println("}");
out.println();
out.print("String sql=\"");
out.print("SELECT c");
out.print(" FROM " + getEntitySourceType().getName() + " o,");
out.print(" o." + getName() + " c");
out.print(" WHERE ");
out.print(getEntitySourceType().getId().generateRawWhere("o"));
if (_orderByFields != null) {
out.print(" ORDER BY ");
for (int i = 0; i < _orderByFields.size(); i++) {
if (i != 0)
out.print(", ");
out.print("c." + _orderByFields.get(i));
if (Boolean.FALSE.equals(_orderByAscending.get(i)))
out.print(" DESC");
}
}
out.println("\";");
out.println("query = __caucho_session.prepareQuery(sql);");
out.println("int index = 1;");
getEntitySourceType().getId().generateSet(out, "query", "index", "this");
// Ex: _caucho_getChildren = new com.caucho.amber.collection.CollectionImpl
out.print(var);
out.print(" = " + newEmptyCollection + ";");
/*
out.pushDepth();
//generateAdd(out);
//generateRemove(out);
//generateClear(out);
// generateSize(out);
out.popDepth();
out.println("};");
*/
// ejb/0aj2
if (getEntitySourceType().getPersistenceUnit().isJPA()) {
// jpa/0l43
out.println();
out.print("for (Object o : " + var);
// jpa/0v04
if (getJavaType().isAssignableTo(Map.class))
out.print(".values()");
out.println(")");
//out.println(" __caucho_session.makeTransactional((com.caucho.amber.entity.Entity) o);");
}
// jpa/0j70
out.println(generateSuperSetter("this", var) + ";");
out.println();
out.println("return " + var + ";");
out.popDepth();
out.println("} catch (Exception e) {");
out.println(" throw com.caucho.amber.AmberRuntimeException.create(e);");
out.println("}");
out.popDepth();
out.println("}");
}
/**
* Generates the size method.
*/
private void generateSize(JavaWriter out)
throws IOException
{
out.println("public int size()");
out.println("{");
out.pushDepth();
out.println("if (__caucho_session == null || isValid())");
out.println(" return super.size();");
out.println("try {");
out.pushDepth();
out.println("__caucho_session.flushNoChecks();");
out.print("String sql=\"");
out.print("SELECT count(*) FROM ");
out.print(getEntitySourceType().getName());
out.print(" AS o ");
out.print(" WHERE ");
// getKeyColumn().generateRawMatchArgWhere("o");
ArrayList<IdField> keys = getEntitySourceType().getId().getKeys();
for (int i = 0; i < keys.size(); i++) {
if (i != 0)
out.print(" AND ");
out.print("o." + keys.get(i).getName());
out.print("=?");
}
out.println("\";");
out.println("com.caucho.amber.AmberQuery query;");
out.println("query = __caucho_session.prepareQuery(sql);");
out.println("int index = 1;");
// ejb/06h0
getEntitySourceType().getId().generateSet(out, "query", "index", getEntitySourceType().getInstanceClassName() + ".this"); // "__ResinExt.this");
out.println("java.sql.ResultSet rs = query.executeQuery();");
out.println("if (rs.next())");
out.println(" return rs.getInt(1);");
out.println("else");
out.println(" return 0;");
out.popDepth();
out.println("} catch (java.sql.SQLException e) {");
out.println(" throw com.caucho.amber.AmberRuntimeException.create(e);");
out.println("}");
out.popDepth();
out.println("}");
}
/**
* Generates the set property.
*/
@Override
public void generateSetterMethod(JavaWriter out)
throws IOException
{
// commented out: jpa/0s2d
// JMethod setter = getSetterMethod();
//
// if (setter == null)
// return;
//
// JClass []paramTypes = setter.getParameterTypes();
JType type;
ClassLoader loader = getSourceType().getPersistenceUnit().getTempClassLoader();
if (! getEntitySourceType().isFieldAccess()) {
type = JTypeWrapper.create(getGetterMethod().getGenericReturnType(),
loader);
}
else {
Field field = EntityType.getField(getBeanClass(), getName());
type = JTypeWrapper.create(field.getGenericType(),
loader);
}
out.println();
// commented out: jpa/0s2d
// out.print("public void " + setter.getName() + "(");
// out.print(getJavaTypeName() + " value)");
out.print("public void " + getSetterName() + "(");
out.print(type.getName() + " value)");
out.println("{");
out.pushDepth();
// out.println("if (" + generateSuperGetterMethod() + " == value)");
// out.println(" return;");
// out.println();
//
// jpa/0j57 needs to generate the following snippet:
//
// _caucho___caucho_get_xAnnualReviews
// = new com.caucho.amber.collection.CollectionImpl<qa.XAnnualReview>(__caucho_session, null);
// _caucho___caucho_get_xAnnualReviews.addAll(0, value);
//
// jpa/0j57:
out.println("if (__caucho_session == null) {");
out.pushDepth();
out.println(generateSuperSetter("this", "value") + ";");
out.popDepth();
out.println("} else {");
out.pushDepth();
out.println("try {");
out.pushDepth();
String var = "_caucho_field_" + getGetterName();
out.print(var + " = new ");
type = getJavaType();
boolean isSet = type.isAssignableTo(Set.class);
boolean isMap = false;
if (!isSet) {
isMap = type.isAssignableTo(Map.class);
}
JType []paramArgs = type.getActualTypeArguments();
JType param = paramArgs.length > 0 ? paramArgs[0] : null;
JType param2 = paramArgs.length > 1 ? paramArgs[1] : null;
String collectionImpl;
if (isSet)
collectionImpl = "com.caucho.amber.collection.SetImpl";
else if (isMap)
collectionImpl = "com.caucho.amber.collection.MapImpl";
else
collectionImpl = "com.caucho.amber.collection.CollectionImpl";
out.print(collectionImpl);
if (param != null) {
out.print("<");
out.print(param.getPrintName());
if (isMap) {
if (param2 != null) {
out.print(", ");
out.print(param2.getPrintName());
}
}
out.print(">");
}
out.print("(__caucho_session, null");
if (isMap) {
out.print(", ");
out.print(getEntityTargetType().getBeanClass().getName());
out.print(".class.getDeclaredMethod(\"");
String getterMapKey = getMapKey();
// jpa/0j63
if (getterMapKey == null) {
getterMapKey = getEntityTargetType().getId().getKey().getGetterName();
}
else {
getterMapKey = "get" + Character.toUpperCase(getterMapKey.charAt(0))
+ getterMapKey.substring(1);
}
out.print(getterMapKey); // "getId");
out.print("\")");
}
out.println(");");
out.print(var + ".");
if (isMap)
out.println("putAll(value);");
else if (isSet)
out.println("addAll(value);");
else
out.println("addAll(0, value);");
out.popDepth();
out.println("} catch(Exception e) {");
out.println(" throw com.caucho.amber.AmberRuntimeException.create(e);");
out.println("}");
out.popDepth();
out.println("}");
out.popDepth();
out.println("}");
}
/**
* Generates code for foreign entity create/delete
*/
@Override
public void generateInvalidateForeign(JavaWriter out)
throws IOException
{
// XXX: jpa/0gg2
if (getEntitySourceType().getPersistenceUnit().isJPA())
return;
AmberTable table = getLinkColumns().getSourceTable();
out.println("if (\"" + table.getName() + "\".equals(table)) {");
out.pushDepth();
String var = "_caucho_field_" + getGetterName();
out.println("if (" + var + " != null)");
out.println(" " + var + ".update();");
out.popDepth();
out.println("}");
}
/**
* Generates the expire code
*
* ejb/06hi
*/
@Override
public void generateExpire(JavaWriter out)
throws IOException
{
String var = "_caucho_field_" + getGetterName();
out.println(var + " = null;");
}
}