I'm learning on how to use SQLData and having an issue with casting back to my object.
My Oracle Types looks something like this:
CREATE OR REPLACE TYPE activities_t AS OBJECT
(
list activity_list_t;
);
CREATE OR REPLACE TYPE activity_list_t AS TABLE OF activity_t;
CREATE OR REPLACE TYPE activity_t AS OBJECT
(
startDate DATE;
endDate DATE;
);
And my Java looks like this:
public class Activities implements SQLData {
private String sqlType = "ACTIVITIES_T";
List<Activity> list;
// must have default ctor!
public Activities() {
}
public String getSQLTypeName() throws SQLException
{
return sqlType;
}
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
public void readSQL(SQLInput stream, String typeName) throws SQLException
{
Array a = stream.readArray();
// :(
}
public void writeSQL(SQLOutput stream) throws SQLException
{
// stream.writeArray(this.list);
}
}
I've tried a few things in readSQL but I am not having much success - what am I missing?
I am calling a PLSQL stored procedure which has an OUT parameter of "activities_t" using JDBC:
Map map = connection.getTypeMap();
map.put("ACTIVITIES_T", Class.forName("Activities"));
connection.setTypeMap(map);
callableStatement = connection.prepareCall("{call GET_ACTIVITIES(?)}");
callableStatement.execute();
Thanks!
Steve
(most of the above is from memory as the code is at work...)
You'll need to add a type mapping for the type ACTIVITY_T as well as the one for ACTIVITIES_T. It's not clear from your question whether you've already done this.
Let's assume you've done this and created a class called Activity which implements SQLData as well. Once you've done that, the following should suffice to read the activity list within Activities:
public void readSQL(SQLInput stream, String typeName) throws SQLException {
Array array = stream.readArray();
this.list = new ArrayList<Activity>();
for (Object obj : (Object[])array.getArray()) {
list.add((Activity)obj);
}
}
Tips:
JDBC APIs are case-sensitive with regard to type names; you will see a Unable to resolve type error if your type name does not exactly match. Oracle will uppercase your type name unless you double-quoted the name in its create statement.
You may need to specify SCHEMA.TYPE_NAME if the type isn't in your default schema.
Remember to grant execute on types if the user you are connecting with is not the owner.
If you have execute on the package, but not the type, getArray() will throw an exception when it tries to look for type metadata.
getArray()
My solution is essentially the same as Luke's. However, I needed to provide a type mapping when getting the array: array.getArray(typeMap)
You can also set a default type map on the Connection, but this didn't work for me.
When calling getArray() you get an array of the object type, i.e. the SQLData implementation you created that represents activity_t
Here is a generic function you might find useful:
public static <T> List<T> listFromArray(Array array, Class<T> typeClass) throws SQLException {
if (array == null) {
return Collections.emptyList();
}
// Java does not allow casting Object[] to T[]
final Object[] objectArray = (Object[]) array.getArray(getTypeMap());
List<T> list = new ArrayList<>(objectArray.length);
for (Object o : objectArray) {
list.add(typeClass.cast(o));
}
return list;
}
writeArray()
Figuring out how to write an array was frustrating, Oracle APIs require a Connection to create an Array, but you don't have an obvious Connection in the context of writeSQL(SQLOutput sqlOutput). Fortunately, this blog has a trick/hack to get the OracleConnection, which I've used here.
When you create an array with createOracleArray() you specify the list type for the type name, NOT the object type. i.e. activity_list_t
Here's a generic function for writing arrays. In your case, listType would be "activity_list_t" and you would pass in a List<Activity>
public static <T> void writeArrayFromList(SQLOutput sqlOutput, String listType, #Nullable List<T> list) throws SQLException {
final OracleSQLOutput out = (OracleSQLOutput) sqlOutput;
OracleConnection conn = (OracleConnection) out.getSTRUCT().getJavaSqlConnection();
conn.setTypeMap(getTypeMap()); // not needed?
if (list == null) {
list = Collections.emptyList();
}
final Array array = conn.createOracleArray(listType, list.toArray());
out.writeArray(array);
}
Note: at one point I thought setTypeMap was required, but now when I remove that line my code still works, so I'm not sure if it's necessary.
Related
Good afternoon.
I'm trying to bind a list with strings to the query that the IN operator uses.
im use Oracle.
I did following the example that was described by the link:
How to use IN operator with JDBI?
List<String> ms = new ArrayList();
ms.add("Novosibirsk");
ms.add("Perm");
public interface CityDAO {
#RegisterMapper(CitiesMapper.class)
#SqlQuery("SELECT *
FROM Universities
WHERE Location IN (:cities)")
List<cities> getItems(#Bind("cities") List<String> cities);}
}
I created a ListArgumentFactory
public class ListArgumentFactory implements ArgumentFactory<List> {
#Override
public boolean accepts(Class<?> expectedType, Object value, StatementContext ctx) {
return value instanceof List;
}
#Override
public Argument build(Class<?> expectedType, final List value, StatementContext ctx) {
return new Argument() {
#Override
public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException {
String type = null;
if(value.get(0).getClass() == String.class){
type = "varchar";
} else if(value.get(0).getClass() == Integer.class){
// For integer and so on...
} else {
// throw error.. type not handled
}
Array array = ctx.getConnection().createArrayOf(type, value.toArray());
statement.setArray(position, array);
}
};
}
}
I registered the factory
public class DBI extends AbstractModule {
private DBI dbi;
#Override
protected void configure() {
this.dbi = new DBI(provideConfig().url());
this.dbi.registerArgumentFactory(new ListArgumentFactory());
}
}
But when I make a request I get an exception
org.skife.jdbi.v2.exceptions.UnableToCreateStatementException: Exception while binding 'cities' [statement:"SELECT * FROM Universities WHERE Location IN (:cities)", arguments:{ positional:{}, named {cities:factory.ListArgumentFactory$1#6788168c}, finder:[]}]
Help me figure out what I'm doing wrong
According to the JDBI documentation, achieving something like that using Oracle could be quite complex so might be a better idea to use the first approach described (UseStringTemplate3StatementLocator):
Oracle supports something similar, but you need to use Oracle specific APIs and oracle.sql.ARRAY instances. In the Oracle case you have to pre-declare the array type in the database first, and as it stores the array in the database, free it after the call.
Having said that, there is a simple approach that can be used to make this work in Oracle which is to join the elements in the list with a comma. I have modified the ListArgumentFactory using the Java 8 String.join method:
public class ListArgumentFactory implements ArgumentFactory<List> {
#Override
public boolean accepts(Class<?> expectedType, Object value, StatementContext ctx) {
return value instanceof List;
}
#Override
public Argument build(Class<?> expectedType, final List value, StatementContext ctx) {
return new Argument() {
#Override
public void apply(int position, PreparedStatement statement, StatementContext ctx) throws SQLException {
statement.setString(position, String.join(",", value));
}
};
}
}
I have tried the approach described in the JDBI documentation to use oracle.sql.ARRAY and a custom TYPE in the Oracle DB but was not successful for me.
My company has an application server that receives sets of instructions in their own bespoke XTML syntax. As this is limited, there's a special "drop to Java" command that sends arguments to a JVM (1.6.0_39). Arguments are passed as "in" only, or "in/out", where the special "in/out" variables are a library of mutables for use with this platform.
Previously the only way to receive external configuration was to use a different special command to read from an XTML file. For reasons not worth delving into, this method of configuration is difficult to scale, so I'm working on a way to do this with Java.
The syntax for this configuration was two-tuples of (String,T) where String was the property name in the XTML file, and T was the in/out mutable that the application server would assign the property value to.
I'm attempting to make this transition as seamless as possible, and not have to do annoying string parsing in the application server.
I already have a function
public String[] get(String ... keys)
That retrieves the values from the application servers' keys, but What I really need is a function
public static void get(T ... args)
that accepts the two-tuples. However, note it needs to be static in order to be called from the application server, and my understanding is that T can't be used in a static context.
I'm at a loss for how to approach this problem in a way that doesn't require (at least) two steps, and there is no way to loop over the arguments in the application server.
I know I'm working within a tight set of constraints here, so if the answer is "you have to some messed up stuff", that's fine - I'd just like any insight into another way.
-- edit --
Editing a more specific example.
The configuration is a set of key-value pairs, and can be in a database or a file. The get function is:
public JSONObject get(String ... keys) throws ClassNotFoundException, SQLException, KeyNotFoundException, FileNotFoundException, IOException {
JSONObject response = new JSONObject();
if(this.isDatabase) {
for(int i=0;i<keys.length;i++){
PreparedStatement statement = this.prepare("SELECT value FROM "+this.databaseSchema+"."+this.settingsTableName+" WHERE key = ? LIMIT 1");
statement.setString(1, keys[i]);
ResultSet results = statement.executeQuery();
boolean found = false;
while(results.next()){
String value = results.getString("value");
value = value.replace("\"","");
response.put(keys[i], value);
found = true;
}
if(!found){
throw new KeyNotFoundException(keys[i]);
}
}
} else if (this.isFile) {
boolean[] found = new boolean[keys.length];
BufferedReader br = new BufferedReader(new FileReader(this.settingsFile));
String line;
while((line = br.readLine()) != null ){
String key;
String value;
for(int i=0;i<line.length();i++){
if(line.charAt(i) == '='){
key = line.substring(0,i);
value = line.substring(i+1,line.length());
if(indexOfString(keys,key) != -1){
value = value.replace("\"","");
found[indexOfString(keys,key)] = true;
response.put(key,value);
if(allFound(found)==-1){
return response;
}
}
break;
}
}
}
if(allFound(found)!=-1){
throw new KeyNotFoundException(keys[allFound(found)]);
}
}
return response;
If I had my way, it would look like ...
// ConfigurationReader.java
public class ConfigurationReader{
public ConfigurationReader( ... ){}
public static JSONObject get(String key){
// Get the key
}
}
// ConfigurationInterface.java
public static void get(T ... args){
ConfigurationReader cfgReader = new ConfigurationReader( ... );
for(var i=0;i<args.length;i+=2){
in = args[i];
out = args[i+1];
out = cfgReader.get(in);
}
}
You can use generic types in a static context. Your question is somewhat vague/unclear about how you intend to do this, but consider the example below:
public class Example {
public static void main(String[] args) {
Type t1 = new Type("foo");
Type t2 = new Type("bar");
Type t3 = new Type("baz");
Printer.<Type> printNames(t1, t2, t3);
}
public static class Printer {
#SafeVarargs
public static <T extends Type> void printNames(T... objs) {
for (T obj : objs) {
System.out.println(obj);
}
}
}
public static class Type {
private final String name;
public Type(String name) {
this.name = name;
}
#Override
public final String toString() {
return name;
}
}
}
Printer.<Type> printNames(t1, t2, t3) makes a static reference to the printNames method, parameterized with the Type generic type.
Note that this is type-safe. Attempting to pass an object of a different type into that parameterized method will fail at compile-time (assuming the type is known to be different at that point):
Example.java:8: error: method printNames in class Printer cannot be applied to given types;
Printer.<Type> printNames(t1, t2, t3, "test");
^
required: T[]
found: Type,Type,Type,String
reason: varargs mismatch; String cannot be converted to Type
where T is a type-variable:
T extends Type declared in method <T>printNames(T...)
Edit
Based on your comment, the issue isn't that you're trying use a generic type for your method argument (in the Java-sense of the word generic, anyway); you're simply looking for any non-specific, parent class that both String and your custom type inherit from. There's only one such class: Object.
I'd strongly recommend reconsidering your design if you have any flexibility, since this will make for poor API design. However you can have your method accept an arbitrary number of arbitrarily-typed objects using Object... objs.
For example:
public class Example {
public static void main(String[] args) {
Printer.printNames("a", "b", new Type("foo"), new Type("bar"));
}
public static class Printer {
public static void printNames(Object... objs) {
for (Object obj : objs) {
if (obj instanceof String) {
System.out.println(((String) obj).toUpperCase());
}
else if (obj instanceof Type) {
System.out.println(obj);
}
}
}
}
public static class Type {
private final String name;
public Type(String name) { this.name = name; }
public final String toString() { return name; }
}
}
Based on #nbrooks work, I found a solution. I made a temporary MutableString (to be replaced by the classes provided by the library).
public static class MutableString {
public String value;
public MutableString(){}
}
// One for every mutable type
public static void Pair(String key, MutableString mutable, ApplicationConfiguration appConfig) throws Exception{
mutable.value = appConfig.get(key).toString();
}
public static void Retrieve(Object ... args) throws Exception {
ApplicationConfiguration appConfig = new ApplicationConfiguration( ##args## );
for(int i=0;i<args.length;i+=2){
if(args[i+1].getClass().equals(new MutableString().getClass())){
ApplicationConfiguration.Pair( (String) args[i], (MutableString) args[i+1], appConfig);
} // One for every mutable type
}
}
The following code is the one I was using for updating some field's value in the database
public void updatesomeField(String registrationID) {
ContentValues objValues;
try {
objDatabase=this.getWritableDatabase();
objValues = new ContentValues();
objValues.put(COLUMN_REGISTRATION_ID,registrationID);
objDatabase.update(CUSTOMER_USERS_TABLE_NAME, objValues,null,null);
objDatabase.close();
} catch(Exception errorException) {
Log.d("error",""+ errorException);
}
}
Then I decided to use a generic approach and hence written the above code like
public <T> void update(String tableName, String columnName, T value) {
ContentValues objValues;
try {
objDatabase=this.getWritableDatabase();
objValues = new ContentValues();
objValues.put(columnName,value); ///here comes the error BECAUSE THE 'value'
objDatabase.update(tableName, objValues,null,null);
objDatabase.close();
} catch(Exception errorException) {
Log.d("error",""+ errorException);
}
}
Error is because ContentValues is final and I cannot extend it to create my new own class to store my generic variable type. What optimization should be performed so that I can get rid of the error by having the same code?
ANOTHER VERSION OF THE SAME QUESTION (IF THE ABOVE ASKED THING IS DIFFICULT TO UNDERSTAND)
I have a final predefined class ContentValues having method put which take parameters in the form of key and values.
However I want to implement a generic functionality and want to decide it on run time that what should be the type of the value
You could write an Adapter which does the dispatching for you:
class ContentValuesAdapter {
private ContentValues values;
public ContentValuesAdapter(ContentValues values) {
this.values = values;
}
public void put(String columnName, Object value) {
if(value instanceof String) {
values.put(columnName, (String) value);
} else if ( ... ) {
...
}
}
public ContentValues getContentValues() {
return this.values;
}
/* Delegate all other methods to the ContentValue instance. */
}
Now you can use this class instead of the original class, and keep your using code clean.
I use generic types throughout my applications, and have to say that one of the simplest (possibly not the best, or most efficient) ways of determining object type is instanceof - For e.g:
if(obj instanceof SiteContact){
buildContactDropdownList((SiteContact)obj);
}else if(obj instanceof Delivery){
buildDeliveryList((Delivery)obj);
}
You could quite easily wrap this up inside a helper class too.
If the ContentValues class belong to a third-party library and you have no access to its source code, then I'm afraid you're stuck with it.
Apparently, ContentValues contains a put(String,String) method. What you could do, is call the toString() method of value:
objValues.put(columnName, value.toString());
Since the toString() method is defined already in the class Object, you don't need to use generics for this. So you can just call your method:
public void update(String tableName, String columnName, Object value)
{
...
}
I have some Data Objects e.g. Task, Resource etc.
These Objects hold domain data e.g.
public class Task{
private int Id;
private String taskName;
.......
//getters and setters here
//in addition they have a special method dynamically to get values i.e. There is a reason for this
public static String runGetter(Task task, String getter) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
for (Method method : task.getClass().getMethods()) {
if (method.getName().toLowerCase().equalsIgnoreCase(getter.toLowerCase())) {
if (method.getReturnType().isPrimitive()) {
StringBuilder sb = new StringBuilder();
sb.append(method.invoke(task));
return sb.toString();
}
if (method.invoke(task) != null) {
return method.invoke(task).toString();
}
}
}
return null;
}
}
}
Now I have some methods that take these objects and write them out to streams
e.g.
public class WriterUtil{
public void write(Task task, File outputFile){
//write the task object out.
}
public void write(Resource resource, File outputFile){
//write the resource object out
}
....
}
The write methods call another method to get data out of the object as follows. (Yes, it can be made more efficient but it is not the core of my problem)
public class WriterUtil {
.....
public static String extractLine(Task task, LinkedHashMap<String, String> columns, String delimiter) throws IllegalAccessException,
IllegalArgumentException, InvocationTargetException {
StringBuilder sb = new StringBuilder();
Iterator<String> itr = columns.keySet().iterator();
while (itr.hasNext()) {
String getter = "get" + itr.next();
String value = Task.runGetter(task, getter);
if (value == null)
value = "";
sb.append(value + delimiter + " ");
}
return sb.toString().substring(0, sb.lastIndexOf(delimiter)).trim();
}
......
}
My Main problem is this given the described scenario above, I find myself writing the same identical code for each domain object e.g.
public void write(Task task, File outputFile)
public void write(Resource resource, File outputFile)
//etc ....
I repeat the same for extractLine.
As you can see I am duplicating the same code for each domain object. Where the only thing varying is the actual domain object. These methods do the exact same thing with each domain object.
My Question is; if I am to refactor these methods and write one method each to apply to every domain object, what are my best options.
Should I have the domain objects implement an interface? This seems rather cumbersome and I am not sure it is the right course of action.
Can I use generics? I expect it is probably the best practice but I have very limited experience with how to go about generifying (Is that a word?) my Domain Objects and these common methods. Can someone offer a re-write of my above code on how they would modify them for generic?
Do I have a third option?
Move the reflection code into a utility type and change the signature to:
public static String runGetter(Object bean, String getter)
The Task type isn't used at all inside the method.
Likewise, the only reason you need a Task type here is because the other call requires it:
public static String extractLine(Object bean, Map<String, String> columns,
String delimiter)
You'll need to use an interface; generics can't be employed here (you could do it in C++ with templates, but not in Java).
If you don't want you objects to implement the interface, you can create helper objects for each of your domain classes; those helper objects would implement an interface with the extractLine() function:
class TaskExtractLine implements IExtractLine
{
public TaskExtractLine(Task task)
{
this.task = task;
}
public String extractLine(LinkedHashMap<String, String> columns, String delimiter)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
return WriterUtil.extractLine(task, columns, delimiter);
}
private Task task;
}
Then you'll have the write function like this: public void write(IExtractLine extractLineHelper, File outputFile) and call it like this: write(new TaskExtractLine(task), outputFile).
Can I write a generic method to trim all strings within an complex object (object containing other objects)? Should java reflection api be used to achieve this?Thanks.
I have provided a sample below. However in reality there could be multiple objects within objects. Each object might contain a collection of String or collection of other objects which may contain String. Is there a way to trim the Strings - ones directly with the objects and ones within collection.
public class School{
private List<Course> courses;
private List<Student> students;
// Getters and Setters
}
public class Course{
private String name;
private String xxx;
private String yyy;
private List<String> zzzList;
}
public class Student{
private Map<String,String> xxx;
private List<Course> courseList;
}
Yes, reflection is the way. Basically, you need to:
get the class of the top level object (with [object].getClass())
get all the fields of the object (with clazz.getFields() - beware, it works only with public fields)
check if the field is String (either get field.getType() and check it's a string, or do a field.get(the object) and a instanceof String)
if it's the case, replace the string in the object with the trimmed one, using field.set([your object],[trimmed string])
if the field is an object but not a string, call your method recursively
That will do the trick.
---- just seen your update
Trimming strings in collection will be more tricky, since the strings are not exposed as public fields of the collection (List for example).
You will need something more clever, that will check if an object is an instance of List, or Map, or etc... (or a derived class!).
Main problem is also that java generics are done with erasing type at compile type. So you cannot know that your field is List[String] or List[Integer] or whatever. Every List[?] becomes List.
Still you can try to do it like that:
if field type is List
iterate through the list values
if a value is instanceof String, you have to remove it from the list and insert in place the trimmed version
if a value is an object, there you go again recursively with your method.
Not very interesting in real life samples, but more on a library side maybe.
Long way to go though!
Yes, you can do that with reflection, quite easily. Just check if the field is instanceof String.
The exact way to do it depends on your object structure.
/*********************************************************************************************
* Trim first level children of string type in this object
* #param obj which all string properties to be trimmed
*********************************************************************************************/
public static void trimAll(final Object obj)
throws LocalException
{
if (obj==null) return;
final Class c = obj.getClass();
final Method[] methods = c.getMethods();
final Class[] SETTER_ARGS = new Class[]{String.class};
final Object[] SETTER_VAL = new Object[1];
final String SET = "set";
final String GET = "get";
final String SPACE = "\u0020";
final String TAB = "\t";
for (final Method m:methods)
{
try
{
final String name=m.getName();
if (
name.length()>GET.length()
&& name.indexOf(GET)==0
&& m.getReturnType().equals(String.class)
&& m.getParameterTypes().length==0)
{
final String v = (String)m.invoke(obj);
if (v!=null && (v.contains(SPACE) || v.contains(TAB)) )
{
final Method setter=c.getMethod(SET+name.substring(3),SETTER_ARGS);
if (setter!=null)
{
SETTER_VAL[0]=v.trim();
setter.invoke(obj,SETTER_VAL);
}
}
}
}
catch (final Throwable e)
{
throw new LocalException(LocalException.EC_GENERAL_EXCEPTION,e);
}
}
}
We can also use Jackson to serialize and then deserialize the object. While deserializing we can use custom deserializer to trim all the String values.
Create a deserializer like this:
public class TrimStringToNullDeserializer extends JsonDeserializer<String> {
#Override
public String deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException {
String value = jsonParser.getValueAsString();
if (isNull(value)) {
return null;
}
value = value.trim();
if (value.length() == 0) {
value = null;
}
return value;
}
And then we can use Jackson to trim all values:
public class TrimStringToNullConfiguration {
private ObjectMapper objectMapper;
public Client trimToNull(Client inputClient) throws JsonProcessingException {
return getObjectMapper().readValue(getObjectMapper().writeValueAsString(inputClient), Client.class);
}
private ObjectMapper getObjectMapper() {
if (isNull(objectMapper)) {
objectMapper = new ObjectMapper();
SimpleModule module = new SimpleModule();
module.addDeserializer(String.class, new TrimStringToNullDeserializer());
objectMapper.registerModule(module);
}
return objectMapper;
}
I have placed a working example over here.
private <T> T toTrim(T t) {
Field[] fields = t.getClass().getFields();
for (Field field : fields) {
try {
if (field.get(t) instanceof String) {
Object o = field.get(t);
String s = (String) o;
field.set(t, s.trim().toUpperCase());
}
} catch (IllegalAccessException e) {
log.info("Error converting field "+ field.getName() );
}
}
return t;
}
if (yourObject instanceof String){
yourObject = yourObject.trim();
}
Hope it helps :)