2018-12-10

JasperReports und REF_CURSOR

In vielen Reports zu JasperReports Migrationsprojekten werden die Daten per REF_CURSOR dem Report zur verfügung gestellt.

Wie diese Cursor in JasperReports verwendet werden möchte ich hier zeigen:

1. Die Beispiel-Routine in der Datenbank:


CREATE OR REPLACE PROCEDURE emps_holen(emp_cursor OUT sys_refcursor,anzahl in number) IS
BEGIN 
  OPEN emp_cursor FOR 
    SELECT first_name,last_name,email 
    FROM employees 
    WHERE rownum < anzahl; 
END; 

2. Report anlegen



als Platzhalter ein select * from dual

und speichern (Finish)

3. REF_CURSOR Aufruf einfügen

Wieder den Query-Editor öffnen 

Language auf plsql umstellen und folgenden Code hinzufügen:

{
  call emps_holen($P{ORACLE_REF_CURSOR},$P{anzahl})
}

Danach wieder den Dialog schließen

und neu öffnen. Durch das Schließen wird der Parameter automatisch angelegt. 

Hinweis: Wenn man den Parameter von Hand anlegt, kommt beim Schließen eine  Meldung, dass man einen eindeutigen Parameternamen eingeben soll. Um die Meldung zu vermeiden das Schließen und wieder Öffnen.

Nun können über Read Fields die Felder eingelesen werden.

Zur Demonstration wie man mit Parametern für die PL/SQL-Funktion umgeht legen wir den Zusätzlichen Parameter anzahl an, der an die PL/SQL-Prozedur übergeben wird.
Dann schließen wir den Query-Editor.

4. Report erstellen

Hier die Felder

(Wunderschönen) Report erstellen

Ausführen

Fertig!

2018-12-03

Solving Integrated Weblogic Coherence Issue in JDeveloper 12.2.1.3

Recently after a java update (Oracle JDK 1.8.0_144) and reinstallation of a oracle virtual box version (5.2.18 r), I stumbled across as severe issue regarding my integrated weblogic server as it would not start anymore. Instead it would repeatedly print the following lines:

<03.12.2018 14:51 Uhr MEZ> <Warning> <com.oracle.coherence> <BEA-000000> <2018-12-03 14:51:03.621/41.438 Oracle Coherence GE 12.2.1.3.0 <Warning> (thread=Cluster, member=n/a): This Member(Id=0, Timestamp=2018-12-03 14:50:32.801, Address=xxx.xxx.xxx.xxx:61846, MachineId=27972, Location=site:team-pb.de,machine:pcteam114,process:7564,member:DefaultServer, Role=WeblogicServer) has been attempting to join the cluster using WKA list [/xxx.xxx.xxx.xxx:7574] for 30 seconds without success; this could indicate a mis-configured WKA, or it may simply be the result of a busy cluster or active failover.>

<03.12.2018 14:53 Uhr MEZ> <Warning> <com.oracle.coherence> <BEA-000000> <2018-12-03 14:53:26.720/184.537 Oracle Coherence GE 12.2.1.3.0 <Warning> (thread=Cluster, member=n/a): Delaying formation of a new cluster; unicast networking appears to be inoperable on interface xxx.xxx.xxx.xxx as this process isn't receiving even its own transmissions; consider switching network interfaces>


which stated that the technologically mapped coherence cluster (which consists of only the DefaultServer) was not getting any responses (and thus could not connect to the cluster itself). As there is only one server in our coherence cluster, it will be always the unicast-master and thus needs to check initial pings on itself (as others would ping it in this scenario).

To fix this issue, you could do the following:
  1. Remove the coherence cluster as a whole (as this is the integrated WLS, it seems irrelevant for most cases anyway)
  2. Set the coherence cluster to be in multicast mode. This would lead to n:m pinging in the cluster, but as there is only one server, it does not bring performance issues as you might get in a real Worldwide clustered environment

Solution 1:

To remove the coherence cluster awareness of the default server, you just have to remove the coherence-cluster-system-resource tag from the domains config.xml file (which resides in your $DOMAIN_HOME/config folder).

<server>   
  <name>DefaultServer</name>
     …  
  <coherence-cluster-system-resource>defaultCoherenceCluster</coherence-cluster-system-resource>
</server>

Just remove the highlighted entry and the server should start (without any awareness of coherence).

Solution 2:

To change the type of coherence the cluster is using, you have to add a property inside the CoherenceSystemResource config file (which is located in the $DOMAIN_HOME/config/coherence folder). The correct *-coherence.xml is the one with the same name as the coherence systemResource as seen in Solution 1 (by default: defaultCoherenceCluster-coherence.xml).

Just add the following highlighted line in the coherence-cluster-params element-tag:

  <coherence-cluster-params>
    <cluster-listen-port>7574</cluster-listen-port>
    <clustering-mode>multicast</clustering-mode>
  </coherence-cluster-params>

This also should lead to the server starting again.

Cheers!

2018-04-13

Solving the "file too large" issue for oracle 12c db docker image

Prerequisites:

The challenge was to install a docker image with Oracle 12.2.0.1 Database Standard Edition to a OEL 7 server. The following prerequisites were set:
  • OEL 7.4
  • DOCKER 17.12
  • Openshift Origin v3-latest
For the Openshift cluster, the docker storage-type was set to 'overlay2' (which is the recommended setting). Construction of the image has been done via README.md given by

Oracle official Docker-Images

Issue:

Running through the issue, the following exception occured on step 7/17 of the Dockerfile:
chown: changing ownership of '/opt/oracle/install/linuxx64_12201_database.zip': File too large
Removing intermediate container d581497c401d
The command '/bin/sh -c chmod ug+x $INSTALL_DIR/*.sh && sync && 
$INSTALL_DIR/$CHECK_SPACE_FILE && $INSTALL_DIR/$SETUP_LINUX_FILE' returned a non-zero code: 1

Solution:

There seems to be an issue with the storage-type overlay2 and large files on OEL. Thus the first idea was to change the storage-type from 'overlay2' to 'devicemapper'. But this is not recommended for production-like environments. A more serious issue was that the openshift cluster was not able to start up anymore after changing the docker storage type.

Thus we tried another approach. Sometimes Docker locks up files (for chowning) that were introduced via COPY task in previous steps of the Dockerfile. A common workaround for this issue is to make a copy of an issued file, removing the original, doing the chown and moving the copy back to the original. This approach worked in this case. The problematic command can be found in the file

<Dockerfile-Home>/<Version_of_DB>/setupLinuxEnv.sh
change the line
chown -R oracle:dba $ORACLE_BASE
to 
cp $ORACLE_BASE/install/linuxx64_12201_database.zip $ORACLE_BASE/install/rename.zip
rm $ORACLE_BASE/install/linuxx64_12201_database.zip
chown -R oracle:dba $ORACLE_BASE
mv $ORACLE_BASE/install/rename.zip $ORACLE_BASE/install/linuxx64_12201_database.zip
This solves the issue and the docker build should run through.
Cheers!

2017-08-24

SQL "with"-Statements auf JasperServer

Möchte man in JasperReports with-Statements im Query verwenden, funktioniert das im Studio problemlos, deployed man den Report allerdings auf den Server liefert dieser beim Ausführen eine Security-Exception.

Hintergrund ist das Sicherheitskonzept des JasperServers, welches verhindern soll, dass etwa durch SQL-Injections Schaden an der Datenbank entstehen kann. Leider ist die Validator-Regel zunächst aber etwas zu restriktiv. Zum Glück kann man diese anpassen:

In der Datei

...\jasperserver\WEB-INF\classes\esapi\validation.properties

die Zeile

Validator.ValidSQL=(?is)^\\s*(select|call)\\s+[^;]+;?\\s*$

ersetzen durch

Validator.ValidSQL=(?is)^\\s*(select|call|with)\\s+[^;]+;?\\s*$ 

2017-08-10

Fixing Currency Behaviour in JDeveloper / ADF 12c

As many questions regarding Currency fields in ADF have come to me lately, let me just give you an example of how to tackle these.

It seems, that there is a common error in ADF for German users (or to any other language,where the decimal and grouping delimiter is exactly switched to the English definition).

English (US) Format: $ #,###.##

German Format #.###,##€

As you see, for germans the ',' is changing places wit the '.'

Normally, this would not be much of a deal, but in ADF there seems to be a "hard coded" interpretation somewhere.

So in a simple form, let us type in a salary of 9.999,99 which is a correct entry for the salary field in German.


In case you enter it as the hint provides (that means including the currency symbol) everything seems to work. But for many cases, users are not accustomed to entering the currency symbol.

So let us create our own converter class to fix this issue:

At first, create a java class that is implementing the javax.faces.convert.Converter Interface. This should be annotated by the FacesConverter tag to make it selectable in the UIComponents properties afterwards and removes the requirement to handle the faces-config.xml tracking.


package de.teampb.conv;

import java.math.BigDecimal;

import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;

import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.faces.convert.ConverterException;
import javax.faces.convert.FacesConverter;

import oracle.adf.share.logging.ADFLogger;

/**
 * Custom JSF Faces Converter to convert entries in Currency Input Texts in a correct way. Oracle ADF seems to be a bit
 * confused, if the grouping character in a Locale is '.' and the decimal delimiter is '.' (for example in Germany).
 */
@FacesConverter("de.teampb.conv.CurrencyConverter")
public class CurrencyConverter implements Converter {

    /**
     * Class logger.
     */
    private static final ADFLogger LOG = ADFLogger.createADFLogger(CurrencyConverter.class.getName());

    /**
     * Converter method from UI Entry to data value. Takes an Input String from the UI Component and converts it to a
     * BigDecimal value for data changes.
     *
     * @param facesContext current JSF Context
     * @param uIComponent Component that has a new value
     * @param string Entered String value (may contain groupings, delimiter or currency symbol)
     * @return correctly converted BigDecimal object for the given input
     */
    @Override
    public Object getAsObject(FacesContext facesContext, UIComponent uIComponent, String string) {
        LOG.entering("CurrencyConverter", "getAsObject", new Object[] { uIComponent, string });
        BigDecimal result;

        final Locale locale = facesContext.getViewRoot().getLocale();
        LOG.finest("Locale for Conversion: " + locale.getLanguage());

        if (string != null && !string.isEmpty()) {
            LOG.finest("Parsing numeric sanity of string...");
            Pattern regex = Pattern.compile("[&:;=?@#|]|[a-zA-Z]");
            Matcher matcher = regex.matcher(string);
            if (matcher.find()) {
                NumberFormat f = NumberFormat.getCurrencyInstance(locale);
                throw new ConverterException(f.format(123456.78));
            }
            LOG.finest("...done");

            String res = string;
            DecimalFormatSymbols symbols = new DecimalFormatSymbols(locale);

            // get Locale specific grouping and decimal seperators
            char sep = symbols.getDecimalSeparator();
            LOG.finest("Decimal Separator used: " + sep);
            char grp = symbols.getGroupingSeparator();
            LOG.finest("Grouping Separator used: " + grp);

            // remove groupings
            String valueConverted = string.replace(grp, Character.MIN_VALUE);
            LOG.finest("String removed of groups:" + valueConverted);

            // change decimal seperator to "US" standards
            valueConverted = valueConverted.replace(sep, ".".charAt(0));
            LOG.finest("String with changed decimal separator:" + valueConverted);

            // throw away any non numeric stuff
            res = valueConverted.replaceAll("[^\\d.]+", "");

            LOG.finest("Expected result:" + res);
            result = new BigDecimal(res);
        } else {
            LOG.finest("Input was empty, so create a zero object");
            // this is of course project specific, can also return null etc.
            result = new BigDecimal(0);
        }
        LOG.exiting("CurrencyConverter", "getAsObject", result);
        return result;
    }

    /**
     * Converter method to create a correct currency String for a given data object.
     *
     * @param facesContext current JSF Context
     * @param uIComponent UI Component that will get the String value
     * @param object data value that shall be converted
     * @return correct String representation of data to a set Locale
     */
    @Override
    public String getAsString(FacesContext facesContext, UIComponent uIComponent, Object object) {
        LOG.entering("CurrencyConverter", "getAsString", object);

        final Locale locale = facesContext.getViewRoot().getLocale();

        LOG.finest("Locale for Conversion: " + locale.getLanguage());
        NumberFormat f = NumberFormat.getCurrencyInstance(locale);
        String res = f.format(object);

        LOG.exiting("CurrencyConverter", "getAsString", res);
        return res;
    }
}

Hint: As you can see from the source code, the converter uses the Locale's specific grouping and decimal seperators, so this converter should be working for any given locale and currency representation.

Next, add this converter to a converter component inside the UI Field that should use this converter:



That's it, we can now just enter the values in simple fashion.

If we enter the value 9999,99 once again, we will see the estimated behaviour.





This entry shows, how easy it is to create a converter to a RichUIInput component, another nice use case could be, that you enter a value with a currency and then use the converter to calculate the currency exchange rate and thus convert it to the databases default currency.

Feel free to check out an example project from the German ADF Community (a subgroup of the German Enterprise Application Development Group) github:

ADFCommunityDE/ADFCustomCurrencyConverter

Thanks for the read, cheers!

2017-03-14

TAKE Four - Amazon Echo (Alexa) mit PL/SQL



Es ist wieder soweit, TEAM lädt zum vierten TAKE (TEAM After Work Knowledge). Wer sich schon immer Oracle Datenbank-orientiert mit dem Thema "Sprachgesteuerte Anwendungen mit Amazon Echo" auseinandersetzen oder generell einen Einblick in das Erstellen von Amazon Anwendungen (Skills) erhalten wollte ist hier genau richtig. Das Beste daran? Es ist vollkommen kostenfrei und sie dürfen selbst Hand an legen.

Was erwartet Sie genauer?

Lassen Sie sich schon vorab mit diesem Video von Wolf G. Beckmann in das Thema Amazon Echo (Alexa) mit PL/SQL einführen.
 
Mit Amazon Echo steuern Sie über die Spracherkennung Alexa Ihr Smart Home und alle damit verbundenen Geräte. Amazon Echo lässt sich aber auch für das eigene Unternehmen einsetzen – speziell mit der Oracle Datenbank-Welt.
 
Bei TAKE four schreiben wir einen Skill in PL/SQL, da Amazon Echo hervorragend und direkt mit der Oracle-Datenbank zusammen arbeitet.
 
Lassen Sie sich die Chance für Ihr Business nicht entgehen: Zur Agenda und Anmeldung
 
TAKE four – „Amazon Echo (Alexa) mit PL/SQL"
Dienstag, 28.03.17 bei TEAM in Paderborn

Beginn: 17:00 Uhr | Ende: 19:30 Uhr
Bitte bringen Sie zur Veranstaltung Ihren eigenen Laptop mit.
 
Wir freuen uns auf Ihr Kommen zu TAKE four!

2017-02-28

Delaying Attribute Validations in ADF for LOV-based fields until commit

I think many ADF developers have encountered the unlazy validation of JSF components during their UI implementations. Especially when creating a new record, the auto submit / partial trigger reactions are quite unpleasant for users. Take this for example:


In this, the user inserted the fields in tab-order. After leaving the field salary (doing a auto submit, triggering the job field), the job validator executes and marks a red border to inform the user of the issue. As the user has never inserted the field up to this point, this is not very nice. One valid change is to remove the mandatory flag from the attribute and delegate the validation towards entity level. But then, you will lose the "red border at attribute" response and get a FacesMessage leaving the user in question, which attribute to change to solve the invalid entity.

Andrejus Baranovskis has written a nice article, how to solve this issue when encountering attributes on the same entity, the validation is occuring on:

http://andrejusb.blogspot.de/2017/02/setting-invalid-fields-for-ui-in-adf-bc.html

So let us extend the usecase a bit.

Issue:

For many List of Value components, users do not want to see the key (for example JobId) in the input text, but the looked up value (JobTitle in this case).



To implement this, there are many options to take. To combine the lov requirement and the delayed attribute validation, let me show you one implementation, that works and is quite declarative.

Define the following use cases:

  • Users want to be able to change the Job of an employee using a list of values showing the title, not the id of the job
  • When changing the salary of an employee, the job will be nullified
  • An empty job leads to a validation, but this should only occur on save button or navigation

The second use case defines the usage of partial triggers / auto submit on JSF side later on.

Solution:

To start, we need the base Entity (Employees), a View Object on that entitity (EmployeesView) and a Lookup View Object (JobsLookupView).


Next create a Transient Attribute on Employees Entity, representing the JobName. This will be the target Attribute for the delayed validation and is the base attribute for the lov.

This Attribute uses an expression referring a ViewAccessor on JobsLookupView:




Expression:

JobId != null ? JobsLookupView.getAllRowsInRange().find{it['jobId']==JobId}['jobTitle'] : null

Next, we will add the entity validation (referring Andrejus' blog entry):


(opt. add failure message)

Add dependencies on Entity attributes:




Additionally remove the mandatory flag from the JobId Attribute (to allow entity validation to do the not null check)

Add LOV to EmployeesView JobName Attribute:


(rem: We can use the Employee-level view accessor in this case because we do not use dynamic query components in this example, so no additional view object instances needed).

Create UI

Finally just create a simple Form Layout on a page, removing the JobId Attribute and ensuring the InputListOfValues Component for JobName.




Then update the following attributes for Salary resp. JobName Components:



Additionally, drag the CreateInsert Operation onto the page, to create a new record.

When running the app, and creating a new employee again tabbing through the form, we will see, there is no validation when tabbing out of the salary field (because the showRequired=true instead of mandatory=true).



When commit or navigation is triggered, the entity validation will fire, but because of the special validation attribute, we will see this on the single attribute instead of a FacesMessage.


This is especially helpful, if you are editing data inside a popup.


TL;DR / Result:

Please feel free to checkout a demo application implementing the shown at the german ADFCommunity github repository

https://github.com/ADFCommunityDE/DelayedAttributeValidationOnLookup.git

Any questions regarding this topic or want to see a feature/idea implemented in ADF? Just send me a message ;)

mail:     mke@team-pb.de
twitter:  @MarkusKlenke

Cheers!

2017-02-13

Individual Frame Layout in ADF 12c via custom RichRenderer Class

In one of our Forms to ADF modernization projects, one of our customers wanted to keep the UI structure more or less the same. For many components, there are best practices to map one Oracle Forms UI Component to ADF Structures, but in our case, we had to provide a solution to the following UI-Container Pattern:

<<NamedFramePattern>>
The idea of this pattern is to provide help for the user to see a categorization of UI Items. In general ADF, this might be done via UI Categorization via UI Hints at View Object Level. Unfortunately, this resolves into af:group containers on UI, which are interpreted by the parent container to make sense out of the items. So all in all, we are limited by the frameworks default implementations.

To solve the problem, we thought about the general solution in free html/css context. Here the problem is easy to solve. See

http://jsfiddle.net/ZgEMM/685/

for a sample solution. To reach the explicit HTML code you need out of JSF components is (in most cases) not possible.

Again, ADF supports the creation of own declarative components, but typically we just join other JSF compontents to our liking.

So we have to go one step further. The next steps show you how to create a "component/renderer" mashup to achieve the NamedFramePattern in ADF.

1. Create a declarative container component that has only a panel group layout and a content facetRef. Be sure to add a ComponentClass to your Declarative component for easier access and uniqueness afterwards.







(The Group layout inside is necessary, since all declarative components are not rendered per definition of the UIXBaseComponent-Renderer because it is only a reference for a JSF include).

2. Create a RichRenderer that is based on the renderer for ADF Group Layouts and add additional HTML code to the rendered feature. This can be done by overriding the encodeAll(...) method of the parent class.


package de.teampb.ir.templates.nf;

import java.io.IOException;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;

import javax.faces.context.ResponseWriter;

import oracle.adf.view.rich.component.rich.fragment.RichDeclarativeComponent;
import oracle.adf.view.rich.component.rich.layout.RichPanelFormLayout;
import oracle.adf.view.rich.render.ClientComponent;
import oracle.adf.view.rich.render.RichRenderer;

import oracle.adfinternal.view.faces.renderkit.rich.DeclarativeComponentRenderer;

import org.apache.myfaces.trinidad.bean.FacesBean;
import org.apache.myfaces.trinidad.context.RenderingContext;
import org.apache.myfaces.trinidad.render.CoreRenderer;

import oracle.adfinternal.view.faces.renderkit.rich.PanelGroupLayoutRenderer;

public class NamedFrameRenderer extends PanelGroupLayoutRenderer {
    public NamedFrameRenderer() {
        super();
    }

    private static final String DEFAULT_COLOR = "#AAAAAA";

    private static final String IMPORTANT_COLOR = "#FFAAAA";

    @Override
    protected void encodeAll(FacesContext facesContext, RenderingContext renderingContext, UIComponent uIComponent,
                             ClientComponent clientComponent, FacesBean facesBean) throws IOException {
        ResponseWriter rw = facesContext.getResponseWriter();
        if (uIComponent.getParent() instanceof NamedFrame) {
            NamedFrame nf = (NamedFrame) uIComponent.getParent();
            rw.startElement("div", null);
            rw.writeAttribute("style", "border: 2px solid " + getColor(nf) + "; margin: 5px; padding: 5px", null);
            rw.startElement("h2", null);
            rw.writeAttribute("style",
                              "text-indent: 30px;\n" + "    margin-top: -10px;\n" + "    height: 10px;\n" +
                              "    line-height: 20px;\n" + "    font-size: 15px;", null);
            rw.startElement("span", null);
            rw.writeAttribute("style", "background-color:#FFFFFF; color:#000000;", null);
            rw.writeText(nf.getTitle(), null);
            rw.endElement("span");
            rw.endElement("h2");
            super.encodeAll(facesContext, renderingContext, uIComponent, clientComponent, facesBean);
            rw.endElement("div");
        } else
            super.encodeAll(facesContext, renderingContext, uIComponent, clientComponent, facesBean);
    }

    private String getColor(NamedFrame nf) {
        if ("important".equals(nf.getType())) {
            return IMPORTANT_COLOR;
        } else
            return DEFAULT_COLOR;
    }

}

The idea is that the renderer overrides the default Renderer for Panel Group layouts. So we have to keep the default implementation (the inside super.encodeAll line).

(The HTML code could be beautified by using classes instead of inline style, but I think you get the idea)

3. Add renderer to faces-Config.xml in the consuming ViewController Project.


4. Use declarative component as usual to achieve the layout addition by the renderer.






As you can see from the last image, since the JDeveloper design view uses the internal renderer classes we also get the advantage of what-you-see-is-what-you-get development. The resulting ADF application then looks as follows:




Please feel free to download the sources at

https://github.com/TEAMPB/ADF_POC.git

and pull the sources. To run the application, you need access to an HR schema. Then Run the hr-main.xml Task-Flow.

Cheers!

2016-09-13

Oracle JET 2.1.0. missing --web=true serve parameter in hybrid grunt template

Many developers try to work WYSIWYG-style on their projects. Especially in mobile/hybrid projects, many times it comes in handy to develop against a web browser instead of a mobile phone or emulator.

In Oracle JET 2.0.1 it was possible to develop a JET Hybrid application and serve the app via 

grunt serve --platform=android --web=true

which would prebuild the sources for android devices but create a local nodeJS server that runs the application. This meant very fast development, especially in trial and error phases of development.

Since Version 2.1.0 Oracle turned grunt tasks towards own oraclejet grunt tasks (coming with the npm package grunt-oraclejet), which seem not to work well with the old parameters. So the given command results in:

Warning: Flag 'web' not supported! Use --force to continue
Aborted due to warnings.

Even forcing does not change anything to this matter. There might be a "parameter passing" option to the oraclejet grunt build file, but I did not find anything like that. So to keep up the style of developing, we just want to create a new grunt task.

First, we need to use some additional npm packages:

grunt-contrib-connect
grunt-open

You may need to install the packages via 

npm install grunt-contrib-connect --save-dev

resp.

npm install grunt-open --save-dev

You can then change the Gruntfile.js of your project to be the following:


'use strict';

var path = require('path');
module.exports = function(grunt) {

  require("load-grunt-config")(grunt,
  {
    configPath: path.join(process.cwd(), "scripts/grunt/config")
  });

  grunt.loadNpmTasks("grunt-oraclejet");
  grunt.loadNpmTasks('grunt-contrib-connect');
  grunt.loadNpmTasks('grunt-open');

  grunt.initConfig({

  connect: {
    dev: {
      options: {
        port: 8090,
        base: 'hybrid/www',
        keepalive: true
      }
    }
  },
  open : {
    dev : {
      path: 'http://localhost:8090',
    }
  }
});

  grunt.registerTask("build", (buildType) => {
    grunt.task.run([`oraclejet-build:${buildType}`]);
  });

  grunt.registerTask("serve", (buildType) => {
    grunt.task.run([`oraclejet-serve:${buildType}`]);
  });

  grunt.registerTask("test", (buildType) => {
    grunt.task.run(["build","open:dev"]);
  })

  grunt.registerTask("startServer",(buildType) => {
    grunt.task.run(["connect:dev"]);
  })
};

With this gruntfile you can start a local nodeJS Server on your "hardcoded-but-default" web-target folder (hybrid/www) via 

grunt startServer

The command 

grund test

will clean and build your sources in the default platform (android for hybrid apps in OracleJET 2.1.0) and then open your default browser to load the newly build page. 

This is by far not as effective as the --web=true serve-parameter, but it is better than nothing ;)

Have fun to try it yourself, just scaffold your hybrid app (eg:  yo oraclejet:hybrid app --appName="Sample Oracle JET App" --template=navdrawer --platforms=android) and change the gruntfile.js.

Happy Coding! 

2016-08-17

Oracle JET Open Online Course

If you want to get in touch with the new Oracle Java-Script Framework to create some fancy user experience focused applications or want to see how easy it is to create hybrid mobile applications to your complex backend, you might want to enroll to the
Oracle JET MOOC

This online course (beginning August 22nd - ending September 12th) offers some video exercises a week, so it is your chance to keep up with a new Oracle Framework even after work. Some of the Features to be shown include:
  • Apply basic JET principles to create Oracle JET application including JET modules, layouts and components
  • Apply more advanced JET functionality, including navigation, routing, validation, layouts, and responsive design.
  • Create hybrid mobile applications with Cordova in combination with Oracle JET.
  • Integrate with the world outside Oracle JET, e.g., JQuery components and the Oracle Cloud.
  • Deal with the remaining enterprise-level challenges, i.e., internationalization, accessibility, and security.

So there should be some content for everybody out there. Hope to see you in class!

2016-05-31

LOV from Shared Application Modules with a different data source - table or view not found

In many cases lookups are being used to create a readable image of a key column inside a database table. Many of these lookups are globally defined for many applications inside the company. So in general it is a good idea to generate a base package / project containing business components for these lookup references.

Preparation:

A sampe project setup could look something like this.



In this case, we used one project for two application modules, the SharedModule stating the (session) shared application module from the base packages. This allows to use the same view object instances across a user session:



It references the oracle schema Salary with the table SALARY_STEPS (table def can be found in the database project, just import it to a different schema than HR).

The main data ApplicationModule HRService connects to the oracle default HR schema. Inside the ViewObject EmployeesView and a list of values is defined on the attribute Salary using the ViewObject instance from shared am instance:



Issue:

When starting the HRService Application Module with the AM-Tester (right-click -> run), the simple data model is shown.

 

Double clicking the EmployeesView Instance we will receive a ORA exception that the SALARY_STEPS table is not found and we see, that the lov of the Salary attribute is empty



Analysis:

The main issue is, that the Application Module SharedModule uses the same connection as the HRService, since (as a session shared application module) it is always nested inside the Root Application Module of the View Object that uses a view object instance as a view accessor from the shared AM. So per definition of session shared application modules, it is not possible declaratively to use a different data source to the main data source.

Solution:

If we go back to the Model.jpx and change the type of the shared application module from session scoped to application scoped, the behaviour of the shared module changes drastically.



Each shared application module in this scope will provide a separate singleton root application module, so one database connection will be opened for each shared application module. But this feature is necessary for declarative lov definition on shared application modules. So running the HRService with this settings, we see that the LOV is correctly defined and we can use it as any other LOV View Accessor






So even if you do only use one data source, it might come in handy to use a shared application module view object instance for your lookups, especially when encountering large result sets for the lov, which could be stored in memory for each user session or even across the whole application.

If you want to give it a try, head over to the new German ADFCommunity github [https://github.com/ADFCommunityDE/SharedAMMultiDataSource.git] and pull the sources :)

By the way, this Application was created using JDeveloper 12.2.1 but the topic is useable for 11g R1 and R2 as well!

Cheers!