I can't make Spring MVC bind my path variables to my custom object.
Given following controller method:
#GetMapping(value = "/customer/{type}/{id}/account/{accountNo}/operation")
public Mono<Response> getAccountHistory(CustomerData customerData) {
System.out.println("Customer data: " + customerData);
return Mono.just(Response.builder().build());
}
DTO class:
public class CustomerData {
private String id;
private String type;
private String accountNo;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getAccountNo() {
return accountNo;
}
public void setAccountNo(String accountNo) {
this.accountNo = accountNo;
}
#Override
public String toString() {
return "CustomerData{" +
"id='" + id + '\'' +
", type='" + type + '\'' +
", accountNo='" + accountNo + '\'' +
'}';
}
And test snippet
client.get().uri(uriBuilder -> uriBuilder
.path("/customer/{type}/{id}/account/{accountNo}/operation")
.build("TYPE", "12345678", "11102055610000350203650330")
).accept(APPLICATION_JSON)
.exchange()
I get output
Customer data: CustomerData(id=null, type=null, accountNo=null)
So no path variable is bound to the object.
However binding to simple types using #PathVariable works like a charm - changing the controller method definition to
#GetMapping(value = "/customer/{type}/{id}/account/{accountNo}/operation")
public Mono<Response> getAccountHistory(#PathVariable("type") String type, #PathVariable("id") String id,
#PathVariable("accountNo") String accountNo) {
val customerData = new CustomerData();
customerData.setAccountNo(accountNo);
customerData.setType(type);
customerData.setId(id);
System.out.println("Customer data: " + customerData);
return Mono.just(Response.builder().build());
}
Produces the expected output:
Customer data: CustomerData{id='12345678', type='TYPE', accountNo='11102055610000350203650330'}
What am I doing wrong?
My Controller class is annotated with
#RestController
public class Controller {
I just copied your code to an empty Spring Boot project and it works fine in the browser when accessing it like so:
http://localhost:8080/customer/someType/someId/account/someAccount/operation
Your testing snippet, however, looks incomplete/wrong and does not set all parameters.
#GetMapping(value = "/customer/{type}/{id}/account/{accountNo}/operation")
public Mono<Response> getAccountHistory(#PathParam("type") String type, #PathParam("id") String id, #PathParam("account") String account) {
CustomerData customerData = new CustomerData();
customerData.setType(type);
customerData.setId(id);
customerData.setAccount(account);
System.out.println("Customer data: " + customerData); return Mono.just(Response.builder().build()); }
I am using JBoss 7.1.Final as application server and Oracle as database. We are using Spring framework 3.x and Java 6. trying to pass in an array of Strings and convert them inside the stored proc to array of varchars. I haven't found a good example for this yet. Please provide a pointer if you can to any documentation or previous forum post. I have searched and not found one that seems to apply.
The stored proc is defined as:
CREATE OR REPLACE PROCEDURE GET_TEST_CONTENTS
(IN_RR_ARRAY IN RR_ARRAY,
IN_ORDER_STATE IN VARCHAR2,
OUT_FLAG OUT VARCHAR2,
OUT_RETURN_CODE OUT VARCHAR2,
OUT_RETURN_DESC OUT VARCHAR2,
OUT_RETURN_TYPE OUT VARCHAR2,
OUT_RETURN_VAL OUT NUMBER
)
Type RR_ARRAY is defined as:
create or replace
type RR_ARRAY as table of varchar2(15);
Within my java code I have:
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.getTestContents = new SimpleJdbcCall(jdbcTemplate)
.withCatalogName("STAR")
.withoutProcedureColumnMetaDataAccess()
.withProcedureName("GET_TEST_CONTENTS")
.declareParameters(
new SqlParameter("IN_RR_ARRAY", OracleTypes.ARRAY,
"RR_ARRAY"),
new SqlParameter("IN_ORDER_STATE", OracleTypes.VARCHAR), new SqlOutParameter("OUT_FLAG",
OracleTypes.VARCHAR),
new SqlOutParameter("OUT_RETURN_VAL", OracleTypes.INTEGER),
new SqlOutParameter("OUT_RETURN_CODE", OracleTypes.VARCHAR),
new SqlOutParameter("OUT_RETURN_DESC", OracleTypes.VARCHAR),
new SqlOutParameter("OUT_RETURN_TYPE", OracleTypes.VARCHAR));
//I get a different error here so creating new connection for testing
//conn = jdbcTemplate.getDataSource().getConnection();
Class.forName("oracle.jdbc.driver.OracleDriver").newInstance();
conn = DriverManager.getConnection(jdbcURL, user, passwd);
ArrayDescriptor desc = new ArrayDescriptor("STAR.RR_ARRAY", conn);
ARRAY arr = new ARRAY(desc, conn, testArray); // testArray is just
// String[] with 2 values
Map<String, Object> hm = new HashMap<String, Object>();
hm.put("IN_RR_ARRAY", arr);
hm.put("IN_ORDER_STATE", stateCode);
hm.put("OUT_FLAG", Types.VARCHAR);
hm.put("OUT_RETURN_CODE", Types.VARCHAR);
hm.put("OUT_RETURN_DESC", Types.VARCHAR);
hm.put("OUT_RETURN_TYPE", Types.VARCHAR);
SqlParameterSource in = new MapSqlParameterSource().addValues(hm);
Map out = getTestContents .execute(in);
The stack trace returned is:
11:24:43,691 ERROR [com.test.repository.TestContentsDao] (http-localhost-127.0.0.1-8080-1) Error while calling GET_TEST_CONTENTS Stored procedure: org.springframework.jdbc.UncategorizedSQLException: CallableStatementCallback; uncategorized SQLException for SQL [{call STAR.GET_TEST_CONTENTS(?, ?, ?, ?, ?, ?, ?)}]; SQL state [99999]; error code [17059]; Fail to convert to internal representation: oracle.sql.ARRAY#2a081f8f; nested exception is java.sql.SQLException: Fail to convert to internal representation: oracle.sql.ARRAY#2a081f8f
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:83) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at org.springframework.jdbc.support.AbstractFallbackSQLExceptionTranslator.translate(AbstractFallbackSQLExceptionTranslator.java:80) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at org.springframework.jdbc.core.JdbcTemplate.execute(JdbcTemplate.java:969) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at org.springframework.jdbc.core.JdbcTemplate.call(JdbcTemplate.java:1003) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at org.springframework.jdbc.core.simple.AbstractJdbcCall.executeCallInternal(AbstractJdbcCall.java:388) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at org.springframework.jdbc.core.simple.AbstractJdbcCall.doExecute(AbstractJdbcCall.java:351) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at org.springframework.jdbc.core.simple.SimpleJdbcCall.execute(SimpleJdbcCall.java:181) [spring-jdbc-3.0.7.RELEASE.jar:3.0.7.RELEASE]
at com.test.repository.TestContentsDao.isGood(TestContentsDao.java:147) [classes:]
Any advice or pointers to examples or docs will be appreciated
I found the fix for this. Now I use this List of Strings:
List<String> ndcList;
I changed the array parameter from OracleTypes.ARRAY to java.sql.stypes.ARRAY and specified schema prefix on array name. And changed the code and created a few new convenience methods at the bottom.
I needed the wrapped connection and had to add this dependency to my pom:
<dependency>
<groupId>jboss</groupId>
<artifactId>jboss-common-jdbc-wrapper</artifactId>
<version>3.2.3</version>
</dependency>
---- method code starts here------------
jdbcTemplate = new JdbcTemplate(dataSource);
jdbcTemplate.setResultsMapCaseInsensitive(true);
this.getTestContents = new SimpleJdbcCall(jdbcTemplate)
.withCatalogName("STAR")
.withoutProcedureColumnMetaDataAccess()
.withProcedureName("GET_TEST_CONTENTS")
.declareParameters(
new SqlParameter("IN_RR_ARRAY", java.sql.types.ARRAY, "STAR.RR_ARRAY"),
new SqlParameter("IN_ORDER_STATE", OracleTypes.VARCHAR),
new SqlOutParameter("OUT_FLAG", OracleTypes.VARCHAR),
new SqlOutParameter("OUT_RETURN_VAL", OracleTypes.INTEGER),
new SqlOutParameter("OUT_RETURN_CODE", OracleTypes.VARCHAR),
new SqlOutParameter("OUT_RETURN_DESC", OracleTypes.VARCHAR),
new SqlOutParameter("OUT_RETURN_TYPE", OracleTypes.VARCHAR));
Map<String, Object> hm = new HashMap<String, Object>();
hm.put("IN_RR_ARRAY", new ScriptArray(ndcList));
hm.put("IN_ORDER_STATE", stateCode);
hm.put("OUT_FLAG", Types.VARCHAR);
hm.put("OUT_RETURN_CODE", Types.VARCHAR);
hm.put("OUT_RETURN_DESC", Types.VARCHAR);
hm.put("OUT_RETURN_TYPE", Types.VARCHAR);
SqlParameterSource in = new MapSqlParameterSource().addValues(hm);
Map out = getTestContents.execute(in);
---- method code ends here------------
public class ScriptArray extends AbstractSqlTypeValue {
private List<String> values;
public ScriptArray(List<String> values) {
this.values = values;
}
public Object createTypeValue(Connection con, int sqlType,
String typeName) throws SQLException {
oracle.jdbc.OracleConnection wrappedConnection = con
.unwrap(oracle.jdbc.OracleConnection.class);
con = wrappedConnection;
ArrayDescriptor desc = new ArrayDescriptor(typeName, con);
return new ARRAY(desc, con,
(String[]) values.toArray(new String[values.size()]));
}
}
Been fighting the similar issue for a day. This article helped me.
Here's code backup, in case page will be unavailable:
-- custom type
create or replace TYPE "MY_TYPE"
as object(name varchar(255),
value varchar(255))
-- array of MY_TYPE
create or replace
TYPE "MY_ARRAY"
as table of MY_TYPE
-- echo like SP, doesn't do too much
create or replace
procedure foo(
i_array in MY_ARRAY,
o_array out MY_ARRAY)
as
begin
o_array := MY_ARRAY();
for i in 1 .. i_array.count loop
o_array.extend;
o_array(i) := MY_TYPE(i_array(i).name, i_array(i).value);
end loop;
end;
Java code:
public class FooStoredProcedure {
private static final String SP_NAME = "FOO";
private static final String MY_ARRAY = "MY_ARRAY";
private static final String MY_TYPE = "MY_TYPE";
private static final String I_ARRAY = "i_array";
private static final String O_ARRAY = "o_array";
private final StoredProcedure storedProcedure;
public FooStoredProcedure(DataSource dataSource) {
storedProcedure = new StoredProcedure(dataSource, SP_NAME) {
{
declareParameter(new SqlParameter(I_ARRAY, Types.ARRAY, MY_ARRAY));
declareParameter(new SqlOutParameter(O_ARRAY, Types.ARRAY, MY_ARRAY, new SqlReturnType() {
#Override
public Object getTypeValue(CallableStatement cs, int paramIndex,
int sqlType, String typeName) throws SQLException {
Connection connection = cs.getConnection();
Map<String, Class<?>> typeMap = connection.getTypeMap();
typeMap.put(MY_TYPE, MyType.class);
return cs.getObject(paramIndex);
}
}));
compile();
}
};
}
/**
* #return array of {#link MyType} objects or <code>null</code>
*/
public MyType[] execute(final MyType[] values) {
Map<String, Object> params = new HashMap<String, Object>();
params.put(I_ARRAY, new AbstractSqlTypeValue() {
#Override
protected Object createTypeValue(Connection con, int sqlType, String typeName) throws SQLException {
ArrayDescriptor descriptor = new ArrayDescriptor(typeName, con);
return new ARRAY(descriptor, con, values);
}
});
Map<?, ?> result = storedProcedure.execute(params);
if ((!result.containsKey(O_ARRAY) || result.get(O_ARRAY) == null)) {
return null;
}
try {
Object[] resultArray = (Object[]) ((ARRAY) result.get(O_ARRAY)).getArray();
return Arrays.copyOf(resultArray, resultArray.length, MyType[].class);
} catch (SQLException e) {
throw new DataRetrievalFailureException("Unable to retrieve array", e);
}
}
public static class MyType implements SQLData {
private String name;
private String value;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String getSQLTypeName() throws SQLException {
return MY_TYPE;
}
#Override
public void readSQL(SQLInput stream, String typeName) throws SQLException {
name = stream.readString();
value = stream.readString();
}
#Override
public void writeSQL(SQLOutput stream) throws SQLException {
stream.writeString(name);
stream.writeString(value);
}
#Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
}
}
}
I looked at the internet and it was very difficult to get it working with the solution many people have provided.. here is working code example.. in pom.xml create this dependency.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-oracle</artifactId>
<version>2.0.0.M1</version>
</dependency>
oracle sample code
create table employee (EMPNO number(12) not null, FNAME varchar2(60), LNAME varchar2(60), EMAIL varchar2(120));
CREATE SEQUENCE empno_seq START WITH 1 INCREMENT BY 1 NOCACHE NOCYCLE;
CREATE OR REPLACE TYPE employee_type
AS OBJECT (EMPNO number(12), FNAME varchar2(60), LNAME varchar2(60), EMAIL varchar2(120));
/
CREATE OR REPLACE TYPE employee_table_type AS TABLE OF employee_type;
/
create or replace PROCEDURE SAVE_EMPLOYEES(p_emp_insert_array in employee_table_type) AS
BEGIN
FORALL i IN p_emp_insert_array.first .. p_emp_insert_array.last
insert into employee(
empno,
FNAME,
LNAME,
EMAIL)
values (
empno_seq.nextval,
p_emp_insert_array(i).FNAME,
p_emp_insert_array(i).LNAME,
p_emp_insert_array(i).EMAIL
);
END SAVE_EMPLOYEES;
/
import com.abc.employeepoc.domain.Employee;
import org.springframework.data.jdbc.support.oracle.StructMapper;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Struct;
/**
*
* #author rsharma
*/
public class EmployeeStructMapper implements StructMapper<Employee> {
#Override
public Struct toStruct(Employee emp, Connection conn, String oracleTypeName) throws SQLException {
Object[] attributes = {
emp.getEmpno(),
emp.getFirstName(),
emp.getLastName(),
emp.getEmailAddress()
};
return conn.createStruct(oracleTypeName, attributes);
}
#Override
public Employee fromStruct(Struct struct) throws SQLException {
Employee emp= new Employee();
Object[] attributes = struct.getAttributes();
emp.setEmpno(((Number) attributes[0]).longValue());
emp.setFirstName(String.valueOf(attributes[1]));
emp.setLastName(String.valueOf(attributes[2]));
emp.setEmailAddress(String.valueOf(attributes[3]));
return emp;
}
}
SqlStructArrayValue in spring has some issue with OracleConnection casting so i created my own one similar to them.
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Struct;
import oracle.jdbc.OracleConnection;
import org.springframework.dao.InvalidDataAccessApiUsageException;
import org.springframework.data.jdbc.support.oracle.SqlStructArrayValue;
import org.springframework.data.jdbc.support.oracle.StructMapper;
/**
*
* #author rsharma
*/
public class OracleSqlStructArrayValue<T> extends SqlStructArrayValue<T> {
private T[] values;
/**
* The object that will do the mapping *
*/
private StructMapper<T> mapper;
/**
* The type name of the STRUCT *
*/
private String structTypeName;
/**
* The type name of the ARRAY *
*/
private String arrayTypeName;
public OracleSqlStructArrayValue(T[] values, StructMapper<T> mapper, String structTypeName) {
super(values, mapper, structTypeName);
this.values = values;
this.mapper = mapper;
this.structTypeName = structTypeName;
}
public OracleSqlStructArrayValue(T[] values, StructMapper<T> mapper, String structTypeName, String arrayTypeName) {
super(values, mapper, structTypeName, arrayTypeName);
this.values = values;
this.mapper = mapper;
this.structTypeName = structTypeName;
this.arrayTypeName = arrayTypeName;
}
#Override
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
if (typeName == null && arrayTypeName == null) {
throw new InvalidDataAccessApiUsageException(
"The typeName for the array is null in this context. Consider setting the arrayTypeName.");
}
Struct[] structValues = new Struct[values.length];
for (int i = 0; i < values.length; i++) {
structValues[i] = mapper.toStruct(values[i], conn, structTypeName);
}
OracleConnection oracleConn = (OracleConnection) conn;
return oracleConn.createOracleArray(typeName != null ? typeName : arrayTypeName, structValues);
}
}
Now in your DAO Class do the following...
public class EmployeeDAO {
private static final Logger logger = LoggerFactory.getLogger(EmployeeDAO.class);
#Autowired
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
private SimpleJdbcCall saveEmployeesArrayCall;
#PostConstruct
private void postConstruct() {
jdbcTemplate = new JdbcTemplate(dataSource);
this.saveEmployeesArrayCall =
new SimpleJdbcCall(dataSource).withProcedureName(SQLConstants.SAVE_EMPLOYEES_STORE_PROC)
.withoutProcedureColumnMetaDataAccess()
.declareParameters(new SqlParameter("p_emp_insert_array", Types.ARRAY, SQLConstants.EMPLOYEE_OBJ_TABLE_TYPE));
}
public void saveEmployees(List<Employee> employees) {
Map<String, Object> in = new HashMap<>();
in.put("p_emp_insert_array", new OracleSqlStructArrayValue<>(employees.toArray(new Employee[0]), new EmployeeStructMapper(), SQLConstants.EMPLOYEE_OBJ_TYPE));
saveEmployeesArrayCall.execute(in);
}
}
import io.swagger.annotations.ApiModelProperty;
import java.util.Objects;
import org.springframework.data.annotation.Id;
/**
*
* #author rsharma
*/
public class Employee implements java.io.Serializable{
#Id
#ApiModelProperty(notes = "The database generated Employee Number")
private Long empno;
#ApiModelProperty(notes = "First Name of the Employee", required = true)
private String firstName;
#ApiModelProperty(notes = "Last Name of the Employee")
private String lastName;
private String emailAddress;
public Employee() {
super();
}
public Employee(Long empno, String emailAddress, String firstName, String lastName) {
this.empno = empno;
this.emailAddress = emailAddress;
this.firstName = firstName;
this.lastName = lastName;
}
public Long getEmpno() {
return empno;
}
public void setEmpno(Long empno) {
this.empno = empno;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmailAddress() {
return emailAddress;
}
public void setEmailAddress(String emailAddress) {
this.emailAddress = emailAddress;
}
#Override
public String toString() {
return "Employee{" + "empno=" + empno + ", firstName=" + firstName + ", lastName=" + lastName + ", emailAddress=" + emailAddress + '}';
}
#Override
public int hashCode() {
int hash = 7;
hash = 59 * hash + Objects.hashCode(this.empno);
return hash;
}
#Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Employee other = (Employee) obj;
if (!Objects.equals(this.empno, other.empno)) {
return false;
}
return true;
}
}
I am having problem deserializing xml to multiple class objects. when i try yo deserialize
i am getting " was not expected."
Here is my calling code
StringReader strReader = new StringReader(xml);
XmlTextReader reader = new XmlTextReader(strReader);
reader.ReadToDescendant("book");
var temp = DeserializeFromXml<book>(reader.ReadOuterXml());
public static T DeserializeFromXml<T>(string xml)
{
T result;
XmlRootAttribute xRoot = new XmlRootAttribute();
XmlSerializer ser = new XmlSerializer(typeof(T));
using (TextReader tr = new StringReader(xml))
{
result = (T)ser.Deserialize(tr);
}
return result;
}
xml is string message
<?xml version="1.0" encoding="UTF-8"?><books xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" >
<book genre="novel">
<title>The Handmaid's Tale</title>
<price>19.95</price>
<ISBN>1-861003-78</ISBN>
<style>hardcover</style>
</book>
<library genre="novel">
<name>Oxford</name>
<location>london</location>
<ISBN>1-8888888-88</ISBN>
<address>12th main, chesmedia</address>
</library>
When i try to execute this code i get this inner exception " was not expected."
Here is my class code
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.233")]
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://tempuri.org/SampleBooks.xsd")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://tempuri.org/SampleBooks.xsd", IsNullable = false)]
public partial class books
{
private book bookField;
private library libraryField;
private static System.Xml.Serialization.XmlSerializer serializer;
public books()
{
this.libraryField = new library();
this.bookField = new book();
}
[System.Xml.Serialization.XmlElementAttribute(Order = 0)]
public book book
{
get
{
return this.bookField;
}
set
{
this.bookField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Order = 1)]
public library library
{
get
{
return this.libraryField;
}
set
{
this.libraryField = value;
}
}
private static System.Xml.Serialization.XmlSerializer Serializer
{
get
{
if ((serializer == null))
{
serializer = new System.Xml.Serialization.XmlSerializer(typeof(books));
}
return serializer;
}
}
}
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.233")]
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://tempuri.org/SampleBooks.xsd")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://tempuri.org/SampleBooks.xsd", IsNullable = false)]
public partial class book
{
private title titleField;
private decimal priceField;
private ISBN iSBNField;
private style styleField;
private bookGenre genreField;
private static System.Xml.Serialization.XmlSerializer serializer;
[System.Xml.Serialization.XmlElementAttribute(Order = 0)]
public title title
{
get
{
return this.titleField;
}
set
{
this.titleField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Order = 1)]
public decimal price
{
get
{
return this.priceField;
}
set
{
this.priceField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Order = 2)]
public ISBN ISBN
{
get
{
return this.iSBNField;
}
set
{
this.iSBNField = value;
}
}
[System.Xml.Serialization.XmlElementAttribute(Order = 3)]
public style style
{
get
{
return this.styleField;
}
set
{
this.styleField = value;
}
}
[System.Xml.Serialization.XmlAttributeAttribute()]
public bookGenre genre
{
get
{
return this.genreField;
}
set
{
this.genreField = value;
}
}
private static System.Xml.Serialization.XmlSerializer Serializer
{
get
{
if ((serializer == null))
{
serializer = new System.Xml.Serialization.XmlSerializer(typeof(book));
}
return serializer;
}
}
}
Simply take off the Namespace = from the attribute of your Book class.
So your code will be like
[System.CodeDom.Compiler.GeneratedCodeAttribute("System.Xml", "4.0.30319.233")]
[System.SerializableAttribute()]
[System.ComponentModel.DesignerCategoryAttribute("code")]
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://tempuri.org/SampleBooks.xsd")]
[System.Xml.Serialization.XmlRootAttribute(IsNullable = false)]
public partial class book
I have a model (simplified for relevance) like this:
public abstract class TitleCreateModel : ICreateModel
{
[Required]
[MaxLength(400)]
public string TitleName { get; set; }
[Required]
[MaxLength(4)]
public DateTime ReleaseDate { get; set; }
[Required]
[MaxLength(5)]
public string Test { get; set; }
[Required]
[MaxLength(2)]
public int Wut { get; set; }
}
Then I have a custom HTML helper class and an Expression extension class (both unabridged):
public class InputHelper
{
public static HtmlString Input<T>(Expression<Func<T, Object>> expression, string id, string label)
{
var req = expression.GetAttribute<T, Object, RequiredAttribute>();
var max = expression.GetAttribute<T, Object, MaxLengthAttribute>();
var required = "";
var maxlength = "";
if(req!=null)
{
required = "req";
}
if(max!=null)
{
maxlength = "maxlength='" + max.Length + "'";
}
return new HtmlString("<div class=\"clearfix\"><label for=\""+id+"\">" + label + "</label>" +
"<div class=\"input\"><input id=\""+id+"\" class=\""+required+"\" type=\"text\" "+maxlength+"/></div></div>");
}
}
public static class ExpressionExtensions
{
public static TAttribute GetAttribute<TIn, TOut, TAttribute>(this Expression<Func<TIn, TOut>> expression) where TAttribute : Attribute
{
var memberExpression = expression.Body as MemberExpression;
if (memberExpression != null)
{
var attributes = memberExpression.Member.GetCustomAttributes(typeof(TAttribute), true);
return attributes.Length > 0 ? attributes[0] as TAttribute : null;
}
return null;
}
}
In my Razor script, I make the following calls:
#(InputHelper.Input<string>(m => Model.Title.TitleName, "titlename", "Title Name"))
#(InputHelper.Input<string>(m => Model.Title.Test, "testfield", "Test Field"))
#(InputHelper.Input<int>(m => Model.Title.Wut, "tester", "Test Field 2"))
#(InputHelper.Input<DateTime>(m => Model.Title.ReleaseDate, "release_year", "Release Year"))
For some reason, the GetAttribute method only finds the attributes for TitleName and Test, both of which are string properties for TitleCreateModel. It's unable to find the attributes for ReleaseDate and Wut, and I have no idea why.
Modify the expression used in Input method to Expression<Func<T, TOut>> expression
public static HtmlString Input<T, TOut>(Expression<Func<T, TOut>> expression, string id, string label)
{
var req = expression.GetAttribute<T, TOut, RequiredAttribute>();
var max = expression.GetAttribute<T, TOut, MaxLengthAttribute>();
var required = "";
var maxlength = "";
if(req!=null)
{
required = "req";
}
if(max!=null)
{
maxlength = "maxlength='" + max.Length + "'";
}
return new HtmlString("<div class=\"clearfix\"><label for=\""+id+"\">" + label + "</label>" +
"<div class=\"input\"><input id=\""+id+"\" class=\""+required+"\" type=\"text\" "+maxlength+"/></div></div>");
}
With the DateTime and int, the Expression is not of type MemberExpression, due to the boxing I believe, so this line:
var memberExpression = expression.Body as MemberExpression;
will return a null.
There's a good question and answer on this topic here, which just involves modifying your Func generic as Eranga as specified.
I have the following class:
#PersistenceCapable(identityType = IdentityType.APPLICATION, detachable = "true")
public class TclRequest implements Comparable<TclRequest> {
#PrimaryKey
private String id;
#Persistent(types = { DNSTestData.class, POP3TestData.class, PPPoETestData.class, RADIUSTestData.class }, defaultFetchGroup = "true")
#Columns({ #Column(name = "dnstestdata_fk"), #Column(name = "pop3testdata_fk"), #Column(name = "pppoetestdata_fk"), #Column(name = "radiustestdata_fk") })
private TestData testData;
public String getId() {
return id;
}
public TestData getTestData() {
return testData;
}
public void setId(String id) {
this.id = id;
}
public void setTestData(TestData testData) {
this.testData = testData;
}
}
The TestData interface looks like this:
#PersistenceCapable(detachable = "true")
public interface TestData {
#PrimaryKey
public String getId();
public void setId(String id);
}
Which is implemented by many classed including this one:
#PersistenceCapable(detachable = "true")
public class RADIUSTestData implements TestData {
#PrimaryKey
private String id;
private String password;
private String username;
public RADIUSTestData() {
}
public RADIUSTestData(String password, String username) {
super();
this.password = password;
this.username = username;
}
#Override
public String getId() {
return id;
}
#Override
public void setId(String id) {
this.id = id;
}
}
When I try to persiste the TclRequest class, after constructing it of course and using the RADIUSTestData:
//'o' is the constructed TclRequest object.
PersistenceManager pm = null;
Transaction t = null;
try {
pm = getPM();
t = pm.currentTransaction();
t.begin();
pm.makePersistent(o);
t.commit();
} catch (Exception e) {
e.printStackTrace();
if (t != null && t.isActive()) {
t.rollback();
}
} finally {
closePM(pm);
}
The interface field isn't persisted. And the column is not created in the table ! I enabled the debug mode and found 2 catchy things:
1)
-Class com.skycomm.cth.beans.ixload.radius.TestData specified to use "application identity" but no "objectid-class" was specified. Reverting to javax.jdo.identity.StringIdentity
2)
-Performing reachability on PC field "com.skycomm.cth.beans.TclRequest.testData"
-Could not find StateManager for PC object "" at field "com.skycomm.cth.beans.TclRequest.testData" - ignoring for reachability
What could this mean ?
Thanks in advance.
I have figured out how to do it. It's not very much scalable but it works for now.
These are the annotations for the interface member variable. Note that the order of declared types, columns and class names in the extension value is important to be maintaned:
#Persistent(types = { RADIUSTestData.class, POP3TestData.class, PPPoETestData.class, DNSTestData.class }, defaultFetchGroup = "true")
#Columns({ #Column(name = "radiustestdata_fk"), #Column(name = "pop3testdata_fk"), #Column(name = "pppoetestdata_fk"),
#Column(name = "dnstestdata_fk") })
#Extension(vendorName = "datanucleus", key = "implementation-classes", value = "com.skycomm.cth.tcl.beans.radius.RADIUSTestData, com.skycomm.cth.tcl.beans.pop3.POP3TestData, com.skycomm.cth.tcl.beans.pppoe.PPPoETestData, com.skycomm.cth.tcl.beans.dns.DNSTestData")
A sample class implementing one of the interfaces (Just it's "header"):
#PersistenceCapable(detachable = "true")
public class RADIUSTestData implements TestData {
So it's pretty normal here.