2020-04-20

Spring: Implicit CLOB to String

So you have a stored procedure, which has an OUT parameter, with the type of CLOB.
You want to get the contents. It seems easy, right?

Well, you just need to actually ask Hibernate to turn it into a String for you in one easy step, using org.hibernate.type.MaterializedClobType:

PROCEDURE that_one_procedure(
    P_ARG1        IN  VARCHAR2,
    P_ARG2        IN  VARCHAR2,
    P_ARG3        IN  VARCHAR2,
    X_SOME_OUTPUT OUT NOCOPY CLOB
);

// Excerpt of an Entity class...
import org.hibernate.type.MaterializedClobType;

@NamedStoredProcedureQuery(name = "querySomeProcedure", procedureName = "MY_PACKAGE.THAT_ONE_PROCEDURE",
 parameters = {
   @StoredProcedureParameter(name = "P_ARG1", 
     type = String.class),
   @StoredProcedureParameter(name = "P_ARG2", 
     type = String.class),
   @StoredProcedureParameter(name = "P_ARG3", 
     type = String.class),
   @StoredProcedureParameter(name = "X_SOME_OUTPUT", 
     mode = ParameterMode.OUT, 
     type = MaterializedClobType.class) /* <-- !!! */

Now my Repository can look like this:
// Excerpt of a Repository...
@Procedure(name = "querySomeProcedure", outputParameterName = "X_SOME_OUTPUT")
 String querySomeProcedure(@Param("P_ARG1") String someArg,
   @Param("P_ARG2") String thatAnotherArg,
   @Param("P_ARG3") String alsoAnArg);

See, all the magic is done by Hibernate.

2019-07-31

Oracle: JDBC Auto-Commit is On by Default

Ever wondered what's the default on this?

Well, here it is: JDBC Developer's Guide

Scroll to section C.2.1 Disabling Auto-Commit Mode, and it says:
By default, new connection objects are in auto-commit mode.

2019-01-10

JDeveloper: JboConfigUtil Exception

So you were tinkering with JDeveloper (any version), and while trying to use an online database for your new entity / whatever, you instead got this:

SEVERE: null at oracle.jbo.dt.objects.config.JboConfigUtil.getConfigurationNameList(JboConfigUtil.java:235)
java.lang.NullPointerException
 at oracle.jbo.dt.objects.config.JboConfigUtil.getConfigurationNameList(JboConfigUtil.java:235)
 at oracle.jbo.dt.objects.config.JboConfigUtil.getConfigurationNameList(JboConfigUtil.java:225)
 at oracle.jbo.dt.jdevx.ui.pkg.XPKConnectPanel.updateConfigurations(XPKConnectPanel.java:703)
 at oracle.jbo.dt.jdevx.ui.pkg.XPKConnectPanel.onFinish(XPKConnectPanel.java:684)
 at oracle.jbo.dt.ui.main.dlg.DtuWizardPanelDialog.okAction(DtuWizardPanelDialog.java:362)
 at oracle.jbo.dt.ui.main.dlg.DtjDialog.dismissDialog(DtjDialog.java:221)
 (...)

You've already created the data source, set it in Project Properties [on the model project] / ADF Business Components, reported the feedback, saw it will be fixed in JDeveloper 13... but until then:

Go to the .jpx file of your model project (the file with the two cogwheels icon), General / Connection, select the connection.

2018-09-18

Chrome 69: Revert the ugly UI to normal

Is the new Chrome ugly as hell?

Go to chrome://flags/#top-chrome-md

Change "UI Layout for the browser's top chrome" to "Normal". Relaunch.

If it still won't change back to normal, disable this: "Use all upcoming UI features".

If still no luck, you need to disable also this: "New Tab Page Material Design Icons" chrome://flags/#ntp-icons

2018-07-03

Some Court Rulings from the Past

Ever wondered why people can resell OEM licences?

This is why: "An author of software cannot oppose the resale of his ‘used’ licences allowing the use of his programs downloaded from the internet"

Sources:
1. Court of Justice of the European Union - Press Release
2. Judgment of the Court


2018-04-19

Java 8: JDBC ResultSet to Stream

Streams API is a gift.
But JDBC is still the old-school one.
Let's wrap it!

package org.example.jdbc.stream;

import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

public final class StreamHelper {
    public static class Record {
        private final Map<String, Object> fields = new HashMap<>(16);
        private final long count;

        private Record(final ResultSet resultSet) throws SQLException {
            final ResultSetMetaData metaData = resultSet.getMetaData();
            count = metaData.getColumnCount();
            for (int i = 1; i <= count; ++i) {
                fields.put(metaData.getColumnName(i), resultSet.getObject(i));
            }
        }

        /**
         * Is there a column named like this?
         *
         * @param columnName is the column name in the query.
         * @return True if found.
         */
        public boolean contains(final String columnName) {
            return fields.containsKey(columnName);
        }

        /**
         * Number of columns.
         *
         * @return Numer of columns.
         */
        public long count() {
            return count;
        }

        /**
         * Get value casted to the requested type.
         * <p>
         * No type checking happens inside. It is your job to know the datatype in the database.
         * <p>
         * Example:<br>
         * {@code record.get("COLUMN1", Long.class); // returns a Long}
         *
         * @param columnName is the column name in the query.
         * @param type is Java type of the column.
         * @return The value casted to the Java type.
         */
        public <T> T get(final String columnName, final Class<T> type) {
            return type.cast(getObject(columnName));
        }

        /**
         * Get columns in the record.
         *
         * @return Collection of the column names.
         */
        public Set<String> getColumns() {
            return Collections.unmodifiableSet(fields.keySet());
        }

        /**
         * Get value as an object.
         *
         * @param columnName is the column name in the query.
         * @return The value.
         */
        public Object getObject(final String columnName) {
            return fields.get(columnName);
        }

        /**
         * Get value as string.
         *
         * @param columnName is the column name in the query.
         * @return Value as string.
         */
        public String getString(final String columnName) {
            return Objects.toString(fields.get(columnName));
        }

        /**
         * Is the given cell null?
         *
         * @param columnName is the column name in the query.
         * @return True if null.
         */
        public boolean isNull(final String columnName) {
            return getObject(columnName) == null;
        }

        @Override
        public String toString() {
            return fields.entrySet().stream().map(e -> e.getKey() + ": " + e.getValue())
                    .collect(Collectors.joining(", "));
        }
    }

    /**
     * Wrap a ResultSet in a Stream.
     * <p>
     * The wrapper consumes the result set. The caller must close the result set after the stream
     * processing was finished.
     *
     * @param resultSet is the open result set to streamline.
     * @return A stream of rows.
     */
    public static Stream<Record> asStream(final ResultSet resultSet) {
        // "est = Long.MAX_VALUE if infinite, unknown, or too expensive to compute."
        return StreamSupport.stream(new Spliterators.AbstractSpliterator<Record>(Long.MAX_VALUE,
                Spliterator.NONNULL | Spliterator.IMMUTABLE) {
            @Override
            public boolean tryAdvance(final Consumer<? super Record> action) {
                try {
                    if (!resultSet.next()) {
                        return false;
                    }
                } catch (@SuppressWarnings("unused") final SQLException e) {
                    return false;
                }
                try {
                    action.accept(new Record(resultSet));
                } catch (@SuppressWarnings("unused") final SQLException e) {
                    return false;
                }
                return true;
            }
        }, true).parallel();
    }

    private StreamHelper() { /* Hidden. */ }
}
Usage example:
import org.example.jdbc.stream.StreamHelper;
import org.example.jdbc.stream.StreamHelper.Record;
// ...
    @GET
    @Path("test")
    public Response test() {
        try (Connection connection = Database.connect();
                Statement statement = connection.createStatement();
                ResultSet resultSet = statement.executeQuery("SELECT * FROM MY_TABLE")) {
            return Response.ok(StreamHelper.asStream(resultSet).map(Record::toString)
                    .collect(Collectors.joining(", "))).build();
        } catch (final SQLException | NamingException e) {
            return Response.serverError().entity(e.toString()).build();
        }
    }

2018-04-05

Java EE7 ScheduledManagedExecutorService



 CustomWorkManager
 
  MINTHREAD
  10
 
 
  MAXTHREAD
  150
 
 
  MAXCAPACITY
  10000
 



 FooSMES
 CustomWorkManager
 10



 concurrent/FooSMES
 FooSMES



 concurrent/FooSMES
 javax.enterprise.concurrent.ManagedScheduledExecutorService

ScheduledExecutorService mySMES = InitialContext.doLookup("java:comp/env/concurrent/FooSMES");

ScheduledFuture<?> future = mySMES.scheduleAtFixedRate(SomeClass::someMethod, 0, 5, TimeUnit.MINUTES);
// Starts immediately (delay 0 minutes), run every 5 minutes.

// How to stop:
if (!future.isCancelled()) {
 future.cancel(true);
}
// true == If the Runnable is running at the moment, the Thread will likely receive an interrupt.
// Inside SomeClass's someMethod method.

// How to handle thread interruption:
// 1) Periodically check Thread.interrupted(). If it's true, please quickly clean up, and then return.
// 2) catch InterruptedException -- this is not available at all places.

// Example:
while(!Thread.interrupted()) {
 // Do repetitive tasks that took very long if you consider all the iterations combined.
}
// We get here when interrupted. Clean up everything and leave.

2018-04-04

Oracle: Safe, Inline, is_numeric Check

So there was a character column, but I wanted to get the rows where it is not actually a number.

TO_NUMBER(REGEXP_SUBSTR(MY_COLUMN, '^\d+')) IS NULL

If the input is empty, it will return null. When will the input be empty? If the cell is a not number-only string.

2017-12-28

Java EE7 ManagedExecutorService

So you have a WebLogic and want to try out the fancy "new" feature of having managed threads.

Have the ExecutorService configured first in weblogic.xml.
<!-- weblogic.xml -->
<managed-executor-service>
 <name>CustomMES</name>
 <max-concurrent-long-running-requests>10</max-concurrent-long-running-requests>
</managed-executor-service>
<resource-env-description>
 <resource-env-ref-name>concurrent/CustomMES</resource-env-ref-name>
 <resource-link>CustomMES</resource-link>
</resource-env-description>

Then name it in web.xml.
<!-- web.xml -->
<resource-env-ref>
 <resource-env-ref-name>concurrent/CustomMES</resource-env-ref-name>
 <resource-env-ref-type>javax.enterprise.concurrent.ManagedExecutorService</resource-env-ref-type>
</resource-env-ref>

Now, you can ask for it.
public static ExecutorService getExecutorService() {
 try {
  // Get our own, defined in weblogic.xml
  return InitialContext.doLookup("java:comp/env/concurrent/CustomMES");
 } catch (final NamingException e) {}
 try {
  // Fallback level 1: Get the Java EE 7 default MES
  return InitialContext.doLookup("java:comp/DefaultManagedExecutorService");
 } catch (final NamingException e) {}
 // Fallback level 2: Return the default pool. 
 // This always exists. In fact, every Java8 threaded feature uses this.
 return ForkJoinPool.commonPool();
}
In the latter case, you could use the @Resource annotation with the mappedBy attribute, if you have properly configured beans.

The ExecutorService is already an Executor, so you can pass it to eg. a CompletableFuture:
CompletableFuture<void> future = CompletableFuture
            .supplyAsync(Frobolator::aSupplier, getExecutorService())
            .thenAccept(result -> bar(result));

// Later, wait for the Future, if necessary.
future.join();

But properly usingCompletableFutures are another topic...

Implementation of the ManagedExecutorService in WebLogic is here: $ORACLE_HOME\wlserver\modules\com.oracle.weblogic.concurrent.jar