Edit the pom.xml file in your project and change the value of the openxava.version property to point to the latest OpenXava version, thus:
<properties>
<openxava.version>7.4.1</openxava.version>
...
</properties>
Rebuild your project:
Now the PDFs generated in list mode have a new element that shows the number of records present in the report. Therefore, if you check report details in JUnit tests, you need to make some changes. Previously, it was:
assertPopupPDFLinesCount(7);
assertPopupPDFLine(2, "Name");
Now it is:assertPopupPDFLinesCount(8); // +1
assertPopupPDFLine(2, "Record count: 3"); // THIS NEW ROW WAS ADDED
assertPopupPDFLine(3, "Name");
Now collections will have a new action in the list and in each row, Collection.deleteSelected. Therefore, if you check the actions present in the JUnit tests, it is necessary to make some changes, adding the new action and in some cases, removing Collection.removeSelected if the collection has (cascade=CascadeTYPE.REMOVE). For example:
String [] actions = {
"Collection.new",
"Collection.edit",
"Collection.removeSelected", // REMOVE THIS ENTRY IF THE COLLECTION HAS CASCADE REMOVE
"Collection.deleteSelected" // ADD THIS ENTRY
};
assertActions(actions);
Similarly, if the collection has cascade delete, you must change the
action call to Collection.deleteSelected.
public void testDeleteElementInCollection() throws Exception {
....
execute("Collection.removeSelected", "row=0,viewObject=xava_view_humans"); // REMOVE
execute("Collection.deleteSelected", "row=0,viewObject=xava_view_humans"); // CHANGE FOR
...
}
Collection.deleteSelected also affects if you have a
collection with (cascade=CascadeType.REMOVE) and @RemoveSelectedAction
customizing the Remove action.
In this case, you should change the @RemoveSelectedAction
annotation to @DeleteSelectedAction and also change the
extends of the action's class from RemoveSelectedInCollectionAction
to DeleteSelectedInCollectionAction. This is because with
cascade remove, the Collection.removeSelected action behaves
the same as Collection.deleteSelected and therefore the former
will not appear in the view.
@OneToMany (mappedBy="delivery", cascade=CascadeType.REMOVE)
@RemoveSelectedAction(forViews="DEFAULT, MoreSections", value="DeliveryDetail.removeSelected") // REMOVE
@DeleteSelectedAction(forViews="DEFAULT, MoreSections", value="DeliveryDetail.deleteSelected") // CHANGE FOR @DeleteSelectedAction,
// ACTION'S NAME IN CONTROLLER DOES NOT NEED TO BE CHANGED
private Collection<Detail> details;
public class DeleteSelectedDeliveryDetailsAction
extends RemoveSelectedInCollectionAction { // REMOVE
extends DeleteSelectedInCollectionAction { // CHANGE FOR
Now each row in the collection has a new action, CollectionOpenInNewTab.openInNewTab. So if you test for all present actions in your JUnit tests, you have to modify it and add the new action. For example:
String [] actions = {
"Collection.edit",
"CollectionOpenInNewTab.openInNewTab" // ADD THIS ENTRY
};
assertActions(actions);
If you haven't overridden the Collection controller, you can skip this step. Otherwise, you must manually add previous and next actions to the controller:
<action name="next"
image="next.gif"
icon="skip-next"
class="org.openxava.actions.EditElementInCollectionAction">
<set property="openDialog" value="false"/>
<set property="nextValue" value="1"/>
</action>
<action name="previous"
image="previous.gif"
icon="skip-previous"
class="org.openxava.actions.EditElementInCollectionAction">
<set property="openDialog" value="false"/>
<set property="nextValue" value="-1"/>
</action>
Now the dialogs from collections have two new actions, Collection.previous and Collection.next. So if you test for all present actions in your JUnit tests, you have to modify it and add the new ones. For example:
String [] bottomActions = {
"Collection.save",
"Collection.hideDetail",
"Collection.next", // ADD THIS ENTRY
"Collection.previous" // ADD THIS ENTRY
};
assertActions(bottomActions);
If you have to write your own custom editors or views and you're using you own JavaScript code, keep in mind the eval() is not longer supported for security reasons. You have to rewrite your JavaScript code to not use eval().
Anyways, if you're using a third party library or the amount of code to rewrite is huge and your application does not required high security standards, you can enable the support of eval() for your OpenXava application adding the next entry to your xava.properties file:
unsafeEvalInScripts=true
If you have to write your own custom editors or views and you're using onclick, onfocus, onblur, etc., they will no longer work. The href="javascript: " don't work either. This is for security reasons. You need to remove the HTML events and move them to a JavaScript file.
That is, if you have code like this in your editor:
<a class="ox-card"
onclick="openxava.executeAction('<%=request.getParameter("application")%>', '<%=request.getParameter("module")%>', false, false, '<%=action%>', '<%="row=" + (i++)%>');">
Remove the onclick event, leaving it like this:
<a class="ox-card" data-action="<%=action%>" data-row="<%=i++%>">
Note that we've added data- attributes to store dynamic data that used to be in inline JavaScript, so we can read these attributes from our JavaScript code later.
Next, add a JS file, let's call it cardEditor.js (if the JSP editor is named cardEditor.jsp), in the src/main/webapp/xava/editors/js directory of your project (create the folder if it doesn't exist yet), with the following content:
openxava.addEditorInitFunction(function() {
$('.ox-card').off('click').click(function() {
openxava.executeAction(openxava.lastApplication, openxava.lastModule, false, false,
$(this).data('action'), "row=" + $(this).data('row'));
});
});
Notice how we use the class, .ox-card in this case, to add the event using JavaScript code. We also use data() to retrieve the values of the data attributes we've stored in the HTML element. This code is secure because it is downloaded from the server hosting the application.
jQuery has been updated to version 3.7.1 from 1.11.2, and jQuery UI to version 1.13.2 from v1.11.2. This update has been made for security reasons. If you only use the user interface generated by OpenXava, you don't have to do anything. However, if you have custom views or your own editors that use jQuery, you should take a look at the jQuery 3 upgrade guide. You may need to make some adjustments.
This is for security reasons. Normally, the JavaScript used in custom editors or custom views is included in the xava/editors/js folder, and in this case, no changes need to be made. However, on some occasions, the JavaScript file has been included in the editor or view directly. If this is the case, you have several options.
The best alternative is to remove the <script> from the JSP and copy your JS file to the xava/editors/js folder.
If this is not possible, because, for example, the JS is generated dynamically by a servlet, the solution is to remove the <script> from the JSP and to add an openxava.getString() in the JS file of your editor. For instance, for the Discussion editor, we have a discussion.js located in xava/editors/js where we have the code:
openxava.getScript(openxava.contextPath + "/dwr/interface/Discussion.js");
If you editor or custom view has no a JS file yet, just create one and put it in xava/editors/js, no matter that by now its sole function is to load another JS.
The last option is to keep the <script> in the JSP and to add a nonce attribute to it. In other words, if in the JSP code of your editor or view, you have something like this:
<script type='text/javascript' src='<%=contextPath%>/dwr/interface/Discussion.js?ox=<%=version%>'></script>
You should change it to:
<script type='text/javascript' <xava:nonce/> src='<%=contextPath%>/dwr/interface/Discussion.js?ox=<%=version%>'></script>
Note the <xava:nonce/> we have added. Although this option is the fastest to implement, the two JavaScript ways mentioned above work better.
We have had to upgrade to the latest HtmlUnit version because of a critical security vulnerability in 2.x versions. If you use the methods of ModuleTestBase in your tests you don't need to change anything. However, if you use the HtmlUnit API directly, using getHtmlPage() or getWebClient() in your tests, you have to do some migration adjustments.
Follow the instructions from HtmlUnit documentation to migrate from HtmlUnit 2.x.x to 3.x.x.
This is for security reasons. It means that you have to move the style from HTML to CSS in your custom views, editors, formatters, servlets, etc.
For example, if you have this HTML code:
<div style="padding: 20px; background-color: green;">
The style part is no longer recognized. Now, you have to change style by a class:
<div class="my-background">
And add the class to your CSS file (your theme CSS or custom.css):
.my-background {
padding: 20px;
background-color: green;
}
Also you can change the style by other classic HTML tags, like <b><i> or <font>. Especially when you need to use some dynamic value, like a color from a database. For example, you could change this Java code that generates HTML:
return "<i class='mdi mdi-square' style='color: " + colorHexValue + "'></i>";
By this:
return "<font color='" + colorHexValue + "'><i class='mdi mdi-square ox-color-inherit'></i></font>";
Note the use of <font> to indicate the color. In this case we also use ox-color-inherit class (included in OpenXava) to overwrite the color given by OpenXava CSS to <i> elements.
This is for security reasons. Your servlets require to set the content type explicitly. So, if you don't do so, you have to add the next line to your servlerts:
response.setContentType("text/html");
You have to indicate the corresponding mime type, in this example
"text/html" for a HTML document.This is for security reasons. You rarely are using inline JavaScript in your code in OpenXava applications, given that they do not work since OpenXava 3.1 for editors or custom views. However, if you have custom code outside a regular OpenXava module, like welcome.jsp, or some custom header or footer for your application, maybe you have some inline scripts, in this case you have to add an attribute nonce to your inline script using the new <xava:nonce/> taglib.
For example, if your welcome.jsp page has this code:
<script type="text/javascript">
var button = document.getElementById('welcome_go_signin');
button.onclick = function () { window.location='m/SignIn'; }
</script>
You should change it by:
<%@include file="../xava/imports.jsp"%>
...
<script type="text/javascript" <xava:nonce/>>
var button = document.getElementById('welcome_go_signin');
button.onclick = function () { window.location='m/SignIn'; }
</script>
Note the new <xava:nonce> element, and that you have to include imports.jsp.
Still better, you could look for an alternative that does not use inline script at all. In this case, for example, we could change the onclick event with a window.location by a plain <a ref="" />, if possible.
Inline events still work in custom editors and views in v7.1; however, if you have custom code outside a regular OpenXava module, such as welcome.jsp, you may have inline events like onclick, onfocus, onblur, etc., that will no longer work. This is for security reasons. You need to remove the HTML events and move them to a JS file.
That is, if you have a code like this:
<input type="button" tabindex="1" onclick="window.location='m/SignIn'" value="<xava:label key='SignIn'/>">
Remove the onclick and add an id to the element, leaving it in this way:
<input id="welcome_go_signin" type="button" tabindex="1" value="<xava:label key='SignIn'/>">
Then add a JS file, let's say events.js (or whatever name you prefer), in src/main/webapp/xava/editors/js (create the folder if it does no exist yet) of your project, with the next content:
var button = document.getElementById('welcome_go_signin');
button.onclick = function () { window.location='m/SignIn'; }
Note as we use the id to add the event using JavaScript code, a secure code because it's downloaded from the server that hosts the application.
Given the new security restrictions it's not possible to redirect to JavaScript, so if you use IForwardAction to redirect to a URL starting with javascript: to execute code, that is not going to work. To execute JavaScript use IJavaScriptPostAction instead. That is, if you have an action like this:
public class SayHelloAction extends BaseAction implements IForwardAction {
public void execute() throws Exception {
}
public String getForwardURI() {
return "javascript:alert('Hello')";
}
public boolean inNewWindow() {
return false;
}
}
Rewrite it in this way:
public class SayHelloAction extends BaseAction implements IJavaScriptPostAction {
public void execute() throws Exception {
}
public String getPostJavaScript() {
return "alert('Hello')";
}
}
We noticed that labels in list and collection when are translated to English, they can be understandable, but could be better. For this, we changed the order of the words and this affect JUnit code that use method like assertValue(), assertLabelInList(), assertValueInCollection(), and more that used to verify labels. For example, if you have something like this:
assertLabelInList(0, "Year of invoice");
Change it by:
assertLabelInList(0, "Invoice year");
If labels were before:
customer.address = Address of customer
customer.address.street = Street of address of customer
customer.address.street.number = Number of street of address of customer
Now they are:
customer.address = Customer address
customer.address.street = Customer address street
customer.address.street.number = Customer address street number
getWebClient().getOptions().setCssEnabled(true); // If you do this then...
reload(); // ...you have to add this line
String [] actions= {
"CRUD.new",
"CRUD.save",
"CRUD.refresh",
"Mode.list",
"Reference.search",
"Reference.createNew",
"Reference.modify",
"Reference.clear", // ADD THIS ENTRY
"Sections.change"
};
assertActions(actions);
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
...
<!-- Add the next parameters to compile with Java 8 -->
<init-param>
<param-name>compilerSourceVM</param-name>
<param-value>1.8</param-value>
</init-param>
<init-param>
<param-name>compilerTargetVM</param-name>
<param-value>1.8</param-value>
</init-param>
...
</servlet>
<class>com.openxava.naviox.model.SSORecord</class>
We fixed a bug about that scale=0 for @Column does not work, and given that when scale is not defined its default value is 0, now @Column(length=x) has scale=0 as default value instead of the scale=2. That is, you have to change this code:
@Column(length=6)
BigDecimal factor;
By this one:
@Column(length=6, scale=2) // ADD scale=2
BigDecimal factor;
If you do not define length or precision the value of scale is still 2, as always, so if you have:
@Column(column="FCTR")// scale is 2 by default, as always
BigDecimal factor;
You don't need to change anything. Moreover, if @Column annotation is missing altogether scale is still 2, as always.
To solve a bug, the combos for references as @DescriptionsList in filter part of list and collections no longer use the key, but the value of the field. This is something internal, it does not affect the application behavior. But it's possible you have to adapt some code. If you have a code like this in an action:
getTab().setConditionValue("seller.name", 1);
Change it by:
getTab().setConditionValue("seller.name", "JOHN SMITH");
Note as now for seller.name we use the actual seller name, not the id. We did it to fix a bug, but as a lateral effect now it's more natural and moreover it works in the same way of a reference with no @DescriptionsList, so adding or removing a @DescriptionsList to your reference does not affect the code of your actions.
Also you have to adapt your JUnit code that set value to list filter if a reference with @DescriptionsList is used. That is, change:
setConditionValues("", "1");
setConditionValues("", "CAR");
Here you're changing the id by the description or the name. Do the same for the JUnit code that verify the combo content. Change:
String [][] validValues = {
{ "", "" },
{ "2:_:LAMPPOST", "LAMPPOST" },
{ "0:_:HOUSE", "HOUSE" },
{ "3:_:DOOR", "DOOR" },
{ "1:_:CAR", "CAR" }
};
assertValidValues("conditionValue___3", validValues);
By:
String [][] validValues = {
{ "", "" },
{ "LAMPPOST", "LAMPPOST" },
{ "HOUSE", "HOUSE" },
{ "DOOR", "DOOR" },
{ "CAR", "CAR" }
};
assertValidValues("descriptionsListValue", validValues);
Note that we have removed the prefix id:_: for the key part of the map. Now, key and value have the same value.
OpenXava 7 is Maven compatible, so the classic structure of an OpenXava project is no longer recognized. You have to turn your project into a standard Maven project. Fortunately, all your current code is valid, only that it have to be placed in different folders. If you're not familiar with Maven, look at the standard directory layout of a Maven project.
The easier way to migrate to Maven is creating a new project using OpenXava 7.0, and then copying the source code and resources from your current project to the new one. So, first create a new OpenXava project using Openxava Studio 7, for that you can follow the instructions in Getting started guide.
The next step is to copy all your source code and resources from the old project to the corresponding folder in the new project, as following:
The test code is in a different folder in Maven, so you should copy:
The above is enough for most applications.
Also copy:
The servlets.xml, filters.xml and listeners.xml files are no longer supported. You can copy their content to the web.xml, that now is empty and ready for your own things, in src/main/webapp/WEB-INF. If you use @WebServlet, @WebFilter and @WebListener annotations you don't need to do any change.
If you use an extra third party library in your application you have to add a dependency in the pom.xml file in the root of your project. You no longer are going to copy jars in web/WEB/lib. If you're not familiar with Maven read about Maven dependecy mechanism.
If you use a database other than HSQLDB you have to add the dependency for your JDBC driver. For example, if you use MySQL add the next entry to your pom.xml:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
For other database just ask Google, something like "maven dependency oracle" for example. In order your JDBC driver will be downloaded and included in your project you have to execute "mvn package" in your project. You can do it from command line (if you have Maven installed in your machine) or from OpenXava Studio (not needed to have Maven installed), with Run As > Maven Build... in your project and typing "package" as goal. This step is not needed if you use HSQLDB as database.
Now you can run your application. Put your mouse on src/main/java of your project and choose Run As > Java Application. From now on, you can modify your code and relaunch your application as always, even if you launch it in debug mode you can modify the code and see the result without relaunch the application. That it, you can work in development as you're used to do.
We no longer use Ant. Now you do everything using Maven commands. If you're not familiar with Maven read some Maven introduction documentation. For example, to create the war for deploying in production you use "mvn package", and get the war ready to be deployed in Tomcat in the target folder of your project. You can run all maven commands from OpenXava Studio, with the option Run As on your project.
Note that when you created the project with OpenXava the project name is in lowercase, this is because the nomenclature for Maven is to use lowercase for the name of artifactId, that matches with project name and deliverable (the generated war). For new projects you should user lowercase for project name. However, for years we have promoted first uppercase letter for project name in documentation, so probably you have your old project with its name starting with uppercase. You can choose migrating to lowercase and adopt the Maven standard way, but keep in mind that it would change the browser URL used by your users.<artifactId>accounting</artifactId>
<artifactId>Accounting</artifactId>
<finalName>accounting</finalName>
<finalName>Accounting</finalName>
<application name="accounting">
<application name="Accounting">
AppServer.run("accounting");
AppServer.run("Accounting");
We have done a hard work to adapt OpenXava in order it works nicely with Hibernate 5.6. However, you can find problems in your own JPA/Hibernate code. If you use JPA API everything should work as it, but there are new bugs in Hibernate 5.6 that can cause your current code to fail. We found some of them.
With Hibernate 5.3 when you use a property of a reference and this reference is part of the key, you don't need to add an explicit join in the query. This is in this way no matter the deep level. In Hibernate 5.6, you don't need to add the join if you have only one level of reference, but if you have a second level, you have to add it, if the second level has a composite key.
Saying it with code. If you have:
@Entity
public class TransportCharge {
@Id @ManyToOne
private Delivery delivery;
...
}
@Entity
public class Delivery {
@Id
private int number;
@Id @ManyToOne
private Invoice invoice;
...
}
@Entity
public class Invoice {
@Id
private int year;
@Id
private int number;
...
}
With the above code the next query run nicely with Hibernate 5.3:
String queryString="from TransportCharge o where o.delivery.invoice.year = :delivery_invoice_year";
Query query = XPersistence.getManager().createQuery(queryString);
query.setParameter("delivery_invoice_year", 2022);
But it fails with Hibernate 5.6. Curiously if you do the query against Delivery asking by invoice.year it works, it only fails with 2 levels or more. Moreover, if Invoice would have a single key, instead of a composite one, it works too. Hibernate 5.6 fails when you combine more than one level with composite keys.
Don't worry, the solution is easy, just add an explicit join for the first reference. That it, in the next way it works with Hibernate 5.6:
String queryString="from TransportCharge o join fetch o.delivery d where d.invoice.year = :delivery_invoice_year";
Query query = XPersistence.getManager().createQuery(queryString);
query.setParameter("delivery_invoice_year", 2022);
The solution is the join fetch o.delivery d. Conclusion, if some query fails for you with Hibernate 5.6 try to add an explicit join to your reference.
A new bug of Hibernate 5.6 is that it no longer recognizes javax.persistence.create-database-schemas, so if you have the next configuration in your persistence.xml:
<persistence-unit name="default">
...
<properties>
<property name="javax.persistence.schema-generation.database.action" value="update"/>
<property name="javax.persistence.create-database-schemas" value="true"/>
<property name="hibernate.default_schema" value="MYSCHEMA"/>
</properties>
</persistence-unit>
With Hibernate 5.3 it created the MYSCHEMA schema and then the tables inside, however with Hibernate 5.6 the MYSCHEMA schema is not created, hence the tables are not create neither. The solution is to create the MYSCHEMA schema by hand. You have to connect to your database with your database explorer and execute a CREATE SCHEMA MYSCHEMA (or equivalent) sentence. After it, you can start the application and the tables will be created.
XHibernate utility class has been removed because it was used in XML components applications, in JPA applications we use XPersistence instead, and we have removed XML components support in v7.0. Hibernate API is still available. In the rare case you use XHibernate in some point of your code, change:
Session session = XHibernate.getSession();
session.save(customer);
By:
Session session = (Session) XPersistence.getManager().getDelegate();
session.save(customer);
That is, you can get a Hibernate Session object from the JPA manager. Another alternative would be to get the Hibernate session directly in the Hibernate way, but in this case you have to create the SessionFactory by yourself and be responsible of closing the transaction and session.
uploadFile("scripts", "reports/Corporation.html");
uploadFile("scripts", "src/main/resources/reports/Corporation.html");
HtmlElement card = body.getElementsByAttribute("div", "class", "ox-card").get(2);
assertEquals("Unit price: 0.00, Unit price in pesetas: 0"), card.asText());
HtmlElement card = body.getElementsByAttribute("div", "class", "ox-card").get(2);
assertEquals("Unit price: 0.00, Unit price in pesetas: 0"), card.asNormalizedText());
assertDiscussionCommentText("discussion", 0, Strings.multiline("admin - Now", "Hi, it's me"));
assertDiscussionCommentText("discussion", 0, "admin - Now\nHi, it's me");
assertValueInList(2, "XAVA\r\n3\r\nUnit price: 0.00");
assertValueInList(2, "XAVA\n3\nUnit price: 0.00");
assertEquals(
"javascript:openxava.executeAction('openxavatest', 'Carrier', 'Effacer l"
+ (char) 145 // ANSI
+"entité courante: Etes-vous sûr(e) ?', false, 'CRUD.delete')",
deleteLink.getHrefAttribute());
assertEquals(
"javascript:openxava.executeAction('openxavatest', 'Carrier', 'Effacer l"
+ (char) 8216 // UNICODE
+"entité courante: Etes-vous sûr(e) ?', false, 'CRUD.delete')",
deleteLink.getHrefAttribute());
assertValue("description", "This is the big jUnit discussion");
assertValue("description", "<p>This is the big jUnit discussion</p>");
assertValue("city", "46540 ");
assertValue("city", "46540");
getWebClient().getOptions().setCssEnabled(true); // If you do this then...
reload(); // ...you have to add this line
CellType.NUMERIC // Instead of Cell.CELL_TYPE_NUMERIC
HyperlinkType.FILE // Instead of Hyperlink.LINK_URL
FillPatternType.SOLID_FOREGROUND // Instead of CellStyle.SOLID_FOREGROUND
HSSFColor.HSSFColorPredefined.RED // Instead of HSSFColor.RED
BorderStyle.THIN // Instead of CellStyle.BORDER_THIN
HorizontalAlignment.LEFT // Instead of CellStyle.ALIGN_LEFT
Cell cell ...
// cell.setCellType(Cell.CELL_TYPE_FORMULA); // No longer exist, not needed
cell.setCellFormula(text);
font.setBoldweight(Font.BOLDWEIGHT_BOLD);
font.setBold(true);
For migrating to OpenXava 6.6.3 from any older OpenXava version (since 1.0):
Follow the OpenXava 6.x migration instructions