Overriding user_action.jsp failing after upgrading to Liferay 7.3 - liferay

We implemented a corejsphook module to customize user_action.jsp in Liferay 7.0.6, which worked fine.
After upgrading to Liferay 7.3.6 it stopped working. I have made sure the module that we are replacing the jsp into is correct by searching for it in the gogo shell. I also looked at the source code to confirm the package that contains the .jsp is right. And I updated the version number to the Liferay 7.3 bundle version (5.0.53). The entry in my bnd.bnd is:
Fragment-Host: com.liferay.users.admin.web;bundle-version="5.0.53"
When I deploy the module, it does not restart the fragment host bundle as it should (and did in Liferay 7.0). Restarting that bundle manually doesn't work either - the modified .jsp isn't used.
As a side note, we have other corejsphooks that work fine. This is the only one that doesn't work, so I must be missing something here.
Update: I am including the code as requested. Here are the four files in the module (not counting the default files like build.gradle and Language_en.properties that are created when the module is created) in the module.
bnd.bnd
Bundle-Version: 7.3.6.1
Fragment-Host: com.liferay.users.admin.web;bundle-version="5.0.53"
-sources: true
-jsp: *.jsp,*.jspf
user_action.jsp
<%--
/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
--%>
<%# include file="/init.jsp" %>
<%# page import="com.liferay.petra.encryptor.Encryptor" %>
<%# page import="com.liferay.portal.kernel.util.HtmlUtil" %>
<%
UserSearch searchContainer = (UserSearch)request.getAttribute("liferay-ui:search:searchContainer");
String redirect = searchContainer.getIteratorURL().toString();
ResultRow row = (ResultRow)request.getAttribute(WebKeys.SEARCH_CONTAINER_RESULT_ROW);
User user2 = (User)row.getObject();
long userId = user2.getUserId();
String userLandingPage = null;
User currentUser = user2;
List<Organization> userOrganizations = currentUser.getOrganizations();
if (Validator.isNotNull(userOrganizations) && !userOrganizations.isEmpty())
{
// If user is member of more than one organization then it will take
// first organization from list
Organization organization = userOrganizations.get(0);
if (Validator.isNotNull(organization))
{
Group organizationGroup = organization.getGroup();
if (organizationGroup.getPrivateLayoutsPageCount() > 0)
{
String privateGroupURL = PropsUtil.get(PropsKeys.LAYOUT_FRIENDLY_URL_PRIVATE_GROUP_SERVLET_MAPPING);
String groupFriendlyURL = organizationGroup.getFriendlyURL();
userLandingPage = privateGroupURL + groupFriendlyURL;
String encDoAsUserId = Encryptor.encrypt(
company.getKeyObj(), String.valueOf(userId));
userLandingPage = userLandingPage + "?doAsUserId=" + HtmlUtil.escapeURL(encDoAsUserId);
System.out.println("userLandingPage is: " + userLandingPage);
}
else
{
System.out.println(organizationGroup.getName() + " organization site doesn't have any private page. So default landing page will be used");
}
}
}
// System.out.println("Landing page is: " + userLandingPage);
%>
<liferay-ui:icon-menu
direction="left-side"
icon="<%= StringPool.BLANK %>"
markupView="lexicon"
message="<%= StringPool.BLANK %>"
showWhenSingleIcon="<%= true %>"
>
<%
boolean hasUpdatePermission = UserPermissionUtil.contains(permissionChecker, userId, ActionKeys.UPDATE);
%>
<c:if test="<%= hasUpdatePermission %>">
<portlet:renderURL var="editUserURL">
<portlet:param name="mvcRenderCommandName" value="/users_admin/edit_user" />
<portlet:param name="redirect" value="<%= redirect %>" />
<portlet:param name="p_u_i_d" value="<%= String.valueOf(userId) %>" />
</portlet:renderURL>
<liferay-ui:icon
message="edit"
url="<%= editUserURL %>"
/>
</c:if>
<c:if test="<%= UserPermissionUtil.contains(permissionChecker, userId, ActionKeys.PERMISSIONS) %>">
<liferay-security:permissionsURL
modelResource="<%= User.class.getName() %>"
modelResourceDescription="<%= user2.getFullName() %>"
resourcePrimKey="<%= String.valueOf(userId) %>"
var="permissionsUserURL"
windowState="<%= LiferayWindowState.POP_UP.toString() %>"
/>
<liferay-ui:icon
message="permissions"
method="get"
url="<%= permissionsUserURL %>"
useDialog="<%= true %>"
/>
</c:if>
<c:if test="<%= (PropsValues.LAYOUT_USER_PRIVATE_LAYOUTS_ENABLED || PropsValues.LAYOUT_USER_PUBLIC_LAYOUTS_ENABLED) && hasUpdatePermission %>">
<%
PortletURL managePagesURL = PortletProviderUtil.getPortletURL(request, user2.getGroup(), Layout.class.getName(), PortletProvider.Action.EDIT);
managePagesURL.setParameter("redirect", redirect);
%>
<liferay-ui:icon
message="manage-pages"
url="<%= managePagesURL.toString() %>"
/>
</c:if>
<c:if test="<%= !PropsValues.PORTAL_JAAS_ENABLE && PropsValues.PORTAL_IMPERSONATION_ENABLE && (userId != user.getUserId()) && !themeDisplay.isImpersonated() && UserPermissionUtil.contains(permissionChecker, userId, ActionKeys.IMPERSONATE) %>">
<liferay-security:doAsURL
doAsUserId="<%= userId %>"
var="impersonateUserURL"
/>
<liferay-ui:icon
message="impersonate-user"
target="_blank"
url="<%= Validator.isNotNull(userLandingPage) ? userLandingPage : impersonateUserURL %>"
/>
</c:if>
<c:if test="<%= UserPermissionUtil.contains(permissionChecker, userId, ActionKeys.DELETE) %>">
<c:if test="<%= !user2.isActive() %>">
<portlet:actionURL name="/users_admin/edit_user" var="restoreUserURL">
<portlet:param name="<%= Constants.CMD %>" value="<%= Constants.RESTORE %>" />
<portlet:param name="redirect" value="<%= redirect %>" />
<portlet:param name="deleteUserIds" value="<%= String.valueOf(userId) %>" />
</portlet:actionURL>
<liferay-ui:icon
message="activate"
url="<%= restoreUserURL %>"
/>
</c:if>
<portlet:actionURL name="/users_admin/edit_user" var="deleteUserURL">
<portlet:param name="<%= Constants.CMD %>" value="<%= user2.isActive() ? Constants.DEACTIVATE : Constants.DELETE %>" />
<portlet:param name="redirect" value="<%= redirect %>" />
<portlet:param name="deleteUserIds" value="<%= String.valueOf(userId) %>" />
</portlet:actionURL>
<c:if test="<%= userId != user.getUserId() %>">
<c:choose>
<c:when test="<%= user2.isActive() %>">
<liferay-ui:icon-deactivate
url="<%= deleteUserURL %>"
/>
</c:when>
<c:when test="<%= !user2.isActive() && PropsValues.USERS_DELETE %>">
<liferay-ui:icon-delete
url="<%= deleteUserURL %>"
/>
</c:when>
</c:choose>
</c:if>
</c:if>
</liferay-ui:icon-menu>
corejsphook.UserActionCustomJspBag.java
/**
* Copyright 2000-present Liferay, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package corejsphook;
import com.liferay.portal.deploy.hot.CustomJspBag;
import com.liferay.portal.kernel.url.URLContainer;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
/**
* #author Scott McIntosh, ICF
*/
#Component(
immediate = true,
property = {
"context.id=UserActionCustomJspBag",
"context.name=User Action Custom JSP Bag",
"service.ranking:Integer=111"
}
)
public class UserActionCustomJspBag implements CustomJspBag
{
#Override
public String getCustomJspDir() {
return "META-INF/resources/";
}
#Override
public List<String> getCustomJsps() {
System.out.println("In getCustomJsps(). _customJsps is null?");
System.out.println(_customJsps == null);
return _customJsps;
}
#Override
public URLContainer getURLContainer() {
System.out.println("In getURLContainer()");
return _urlContainer;
}
#Override
public boolean isCustomJspGlobal() {
System.out.println("In isCustomJspGlobal()");
return true;
}
#Activate
protected void activate(BundleContext bundleContext) {
System.out.println("In activate()");
bundle = bundleContext.getBundle();
_customJsps = new ArrayList<>();
Enumeration<URL> entries = bundle.findEntries(
getCustomJspDir(), "*.jsp", true);
while (entries.hasMoreElements()) {
URL url = entries.nextElement();
System.out.println("Processing jsp: " + url);
_customJsps.add(url.getPath());
}
}
private List<String> _customJsps;
private final URLContainer _urlContainer = new URLContainer() {
#Override
public Set<String> getResources(String path) {
System.out.println("In getResources()");
Set<String> paths = new HashSet<>();
for (String entry : _customJsps) {
if (entry.startsWith(path)) {
paths.add(entry);
}
}
return paths;
}
#Override
public URL getResource(String name) {
System.out.println("In getResource()");
return bundle.getEntry(name);
}
};
private Bundle bundle;
}
gov.nysdot.rideshare.NYRideshareEnUsResourceBundle.java
ackage gov.nysdot.rideshare;
import com.liferay.portal.kernel.language.UTF8Control;
import java.util.Enumeration;
import java.util.ResourceBundle;
import org.osgi.service.component.annotations.Component;
#Component(
property = { "language.id=en_US" },
service = ResourceBundle.class
)
public class NYRideshareEnUsResourceBundle extends ResourceBundle {
#Override
protected Object handleGetObject(String key) {
return _resourceBundle.getObject(key);
}
#Override
public Enumeration<String> getKeys() {
return _resourceBundle.getKeys();
}
private final ResourceBundle _resourceBundle = ResourceBundle.getBundle(
"content.Language_en_US", UTF8Control.INSTANCE);
}
Update:
I have started from scratch, but I'm getting the same result.
I created a new module using:
$ blade create -t fragment gov.nysdot.user-action-hook --host-bundle-symbolic-name com.liferay.users.admin.web --host-bundle-version 5.0.53
I then copied user_action.jsp from source (/modules/apps/users-admin/users-admin-web/src/main/resources/META-INF/resources/user_action.jsp).
To test, I simply duplicated the separator line at line 156 () so that I'd have a visual clue. If the override worked I'd see two separator lines.
I built with 'grade build' and deployed to my server. It did not restart the com.liferay.users.admin.web bundle, and no changed were present.
Now, there are practically no changes from what the module creation script created.

What strikes me in your code/description:
The jsp is a module's jsp, not a core jsp. Thus I wouldn't expect a CustomJspBag, but rather follow the module_jsp_override sample
A fragment bundle will never be ACTIVE, only RESOLVED (as in OSGi bundle lifecycles). Thus, it can deliver files that override a host bundle's files, but none of its own code. You're providing separate services, that certainly wouldn't be available and started - they should live in other bundles. Consider a fragment bundle as passive.
Thus, without turning your files into a project to try, I'd assume that this is the problem at hand.
Convert the bundle to a module-jsp-override project (no java code, pay attention to bnd.bnd). Make sure that the JSP is still what it needs to be: You can't just copy the 7.0 JSP to 7.3 and assume that those changes still accurately implement what you need. Rather, you'll need a 3-way-merge between the stock 7.0 version, your modifications, and the stock 7.3 jsp.

Related

Blazor, how can I change a component in MainLayout when I change page

I want to add a toolbar inside website, the toolbar change inside component on each page. For now, I have this but I want my toolbar to be like this. How could i make this toolbar to update depend on the page the user go ?
The toolbar would be in the MainLayout and need to change content with a switch (not the best option I think) or is it possible to give new content to MainLayout from the page content ?
This is the code for the banner component :
<div class="extend-space" style="left:#($"-{Convert.ToInt32(offsetX)}px")">
<div class="banner" style="left:#(Convert.ToInt32(offsetX)+"px");width:#(Convert.ToInt32(width)+"px");">
<div class="banner-title">
#if (Icon != null)
{<i id="banner-title-icon" class="icon fas fa-#Icon"></i>}
<h3 class="title">#Title</h3>
</div>
<div class="toolbar">
<span id="arrow-left" class="scrollable" onclick="lastTool()">
<i class="fas fa-angle-left arrow"></i>
</span>
<span id="toolbar">
#ChildContent
</span>
<span id="arrow-right" class="scrollable" onclick="nextTool()">
<i class="fas fa-angle-right arrow"></i>
</span>
</div>
</div>
ChildContent should be a list of buttons with function onclick on it so this is the part that need to update on each page.
I add an example of how I use it on a page :
<XLBanner Title="Catégories" Icon="sitemap">
<XLButton Icon="plus" Content="#SharedLocalizer["Add"]" OnClickFunction="#AddCategorie" />
<XLButton Icon="save" Content="#SharedLocalizer["Save"]" OnClickFunction="#Save" disabled="#(!UnsavedChanges)" />
<XLButton Icon="redo" Content="#SharedLocalizer["Reset"]" OnClickFunction="#DeleteUnsavedChanges" disabled="#(SelectedCategorie == null)" />
<XLButton Icon="trash-alt" Content="#SharedLocalizer["Remove"]" OnClickFunction="#SuppCategorie" disabled="#(SelectedCategorie == null)" />
<XLButton Icon="copy" Content="#SharedLocalizer["Copy"]" OnClickFunction="#CopyCategorie" disabled="#(SelectedCategorie == null)" />
<XLButton Icon="download" Content="#SharedLocalizer["Export"]" OnClickFunction="#Export" /
</XLBanner>
What would be needed to update is the XLButton and the OnClickFunction.
My banner has differents tools depend on the page exemple dashboard page, exemple categorie page
If I understand the question correctly a version of this should work for you.
Basically:
Create a simple service that holds the menu data and has an event that is raised whenever the menu data changes and register it.
Use a DynamicComponent in Layout that plugs into the service.
Trigger StateHasChanged on the Layout whenever the service raises a menu change event.
Set the menu you want in each page in OnInitialized.
Two "Menus" to work with:
Menu1.razor
<h3>Menu1</h3>
Menu2.razor
<h3>Menu2</h3>
A simple LayoutService
public class LayoutService
{
public Type MenuControl { get; private set; } = typeof(Menu1);
public Dictionary<string, object>? MenuParameters { get; private set; }
public event EventHandler? MenuChanged;
public void ChangeMenu(Type menu)
{
this.MenuControl = menu;
MenuChanged?.Invoke(this, EventArgs.Empty);
}
public void ChangeMenu(Type menu, Dictionary<string, object> parameters)
{
this.MenuParameters = parameters;
this.MenuControl = menu;
MenuChanged?.Invoke(this, EventArgs.Empty);
}
}
registered in Program.cs:
builder.Services.AddScoped<LayoutService>();
MainLayout.razor
#inherits LayoutComponentBase
#inject LayoutService LayoutService;
#implements IDisposable
<PageTitle>BlazorApp1</PageTitle>
<DynamicComponent Type=this.LayoutService.MenuControl Parameters=this.LayoutService.MenuParameters />
<div class="page">
<div class="sidebar">
<NavMenu />
</div>
<main>
<div class="top-row px-4">
About
</div>
<article class="content px-4">
#Body
</article>
</main>
</div>
#code {
protected override void OnInitialized()
=> this.LayoutService.MenuChanged += this.MenuChanged;
private void MenuChanged(object? sender, EventArgs e)
=> this.InvokeAsync(StateHasChanged);
public void Dispose()
=> this.LayoutService.MenuChanged -= this.MenuChanged;
}
And example page:
#page "/"
#inject LayoutService LayoutService
Page Content
#code {
protected override void OnInitialized()
{
this.LayoutService.ChangeMenu(typeof(Menu1));
}
Don't get too focused on the layout as a single entity that you have to use for every page in the whole site. You can have as many Layout components as you want, and you can nest them just like you would with any class and derived class.
https://blazor-university.com/layouts/nested-layouts/

Edit message portlet in liferay

import java.io.IOException;
import javax.portlet.ActionRequest;
import javax.portlet.ActionResponse;
import javax.portlet.PortletException;
import javax.portlet.PortletPreferences;
import javax.portlet.ReadOnlyException;
import javax.portlet.RenderRequest;
import javax.portlet.RenderResponse;
import javax.portlet.ValidatorException;
import com.liferay.util.bridges.mvc.MVCPortlet;
public class GreetingMessage extends MVCPortlet {
public static final String GREETING = "greeting";
public static final String DEFAULT_GREETING = "Hello! It's my default greeting message";
#Override
public void render(RenderRequest request, RenderResponse response)
throws IOException, PortletException {
PortletPreferences preferences = request.getPreferences();
request.setAttribute(GREETING,
preferences.getValue(GREETING, DEFAULT_GREETING));
super.render(request, response);
}
public void updateGreeting(ActionRequest request, ActionResponse response)
throws ValidatorException, IOException, ReadOnlyException {
String greeting = request.getParameter("greeting");
PortletPreferences prefs = request.getPreferences();
if (greeting != null) {
prefs.setValue(GREETING, greeting);
prefs.store();
request.setAttribute(GREETING, greeting);
}
}
}
view.jsp:
<%# taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<portlet:defineObjects />
<p>${greeting}</p>
<portlet:renderURL var="editGreetingURL">
<portlet:param name="mvcPath" value="/html/greetingmessage/edit.jsp"/>
</portlet:renderURL>
<p>Edit greeting</p>
edit.jsp
<%# taglib uri="http://java.sun.com/portlet_2_0" prefix="portlet" %>
<%# taglib uri="http://liferay.com/tld/aui" prefix="aui" %>
<%# page import="javax.portlet.PortletPreferences" %>
<portlet:defineObjects />
<portlet:actionURL name="updateGreeting" var="updateGreetingURL">
</portlet:actionURL>
<aui:form action="<%= updateGreetingURL %>" method="post">
<aui:input label="greeting" name="greeting" type="text" value="${greeting}" />
<aui:button type="submit" />
</aui:form>
<portlet:renderURL var="viewGreetingURL">
<portlet:param name="mvcPath" value="/html/greetingmessage/view.jsp" />
</portlet:renderURL>
<p>← AND NOW IT'S BACK</p>
It's tested code of my "edit-greeting" portlet. The question is, how can I make localization??? I've read a lot of docs but it's all for nothing. I created in WEB-INF folder src/language.properties and src/language_es.properties. What should I do next? Help me please. #Shivam
To Answer your question
1)You can handle your attributes and portlet preferences in render method and set them as attributes in render request,which can be subsequently read in your jsp via some scripting language like jstl
2)There is no need to make changes in portlet.xml file.
The init params as the name suggests are added to provide some params needed for initializing a portlet view.
You need to make the below changes to the render method
public void render(RenderRequest req,RenderResponse res) throws IOException, PortletException
{
String greeting = req.getParameter("greeting");
PortletPreferences prefs = req.getPreferences();
String defaultGreeting="Hello! Welcome to our portalOLOLOLOLOL.";
if(prefs.getValue("greeting","true")==null)
{
prefs.setValue("greeting", defaultGreeting);
}
if (greeting != null)
{
prefs.setValue("greeting", greeting);
prefs.store();
req.setAttribute("greeting", prefs.getValue("greeting","true"));
}
else
{
req.setAttribute("greeting", prefs.getValue("greeting","true"));
}
super.render(req,res);
}
There will not be any changes required in view.jsp and edit.jsp(apart from removing code),Hence I forgot to mention the same.
As for the render method,the best approach would be defintely to use action url and use action method,but since it seems you are looking to try out some approach and to make mininmum changes to your,I have kept it render only.
As for the code,the prefs.getValue("greeting","true") checks whether a certain attribute is present in portlet preferences or not.
Updated with process action
public class NewPortlet extends MVCPortlet {
public static final String GREETING="greeting";
#Override
public void render(RenderRequest req,RenderResponse res) throws IOException, PortletException
{
PortletPreferences prefs = req.getPreferences();
String defaultGreeting="Hello! Welcome to our portalOLOLOLOLOL.";
if(prefs.getValue(GREETING,"true")==null)
{
prefs.setValue(GREETING, defaultGreeting);
prefs.store();
}
req.setAttribute(GREETING, prefs.getValue(GREETING,"true"));
super.render(req,res);
}
public void updateGreeting(ActionRequest req,ActionResponse res) throws ValidatorException, IOException, ReadOnlyException
{
String greeting = req.getParameter("greeting");
PortletPreferences prefs = req.getPreferences();
if (greeting != null)
{
prefs.setValue(GREETING, greeting);
prefs.store();
req.setAttribute(GREETING, greeting);
}
}
}
Updates in edit jsp
<portlet:actionURL name="updateGreeting" var="updateGreetingURL">
</portlet:actionURL>
<aui:form action="<%= updateGreetingURL %>" method="post">
<aui:input label="greeting" name="greeting" type="text" value="${greeting}" />
<aui:button type="submit" />
</aui:form>

Panel created dynamically is shown on XHTML but missing on getViewRoot

Panel is created and added to xhtml right but when I get it using findComponent("-formBotones-wizardEventContainer").getChildren() the result is null because its container got 1 children (id=tempTextIdLayer) when really html source show several children.
I've tried a solution with visitTree() but vars target and panel are the same, wizarEventContainer, and the got only 1 children (input hidden)
HtmlPanelGroup panel=(HtmlPanelGroup)FacesContextWrapper.getCurrentInstance().getViewRoot().findComponent("formBotones-wizardEventContainer");
panel.visitTree(VisitContext.createVisitContext(FacesContext.getCurrentInstance()),new VisitCallback() {
#Override
public VisitResult visit(VisitContext context, UIComponent target) {
if (target instanceof HtmlPanelGroup) {
HtmlPanelGroup layer = (HtmlPanelGroup) target;
System.out.println("id: " + layer.getId()); //wizardEventContainer // Collect them in an arraylist orso.
}
return VisitResult.ACCEPT;
}
});
Main xhtml code
<h:form id="formBotones" prependId="true">
<h:panelGroup layout="block" id="wizardEventContainer" styleClass="wizardEventContainer">
<h:inputHidden id="tempTextIdLayer" value="#{eventProvider.tempTextIdLayer}" />
</h:panelGroup>
</h:form>
HTML when panel is added
<form id="formBotones">
<input type="hidden" value="formBotones" name="formBotones">
<div class="wizardEventContainer" id="formBotones-wizardEventContainer">
<div data-widget="widget_formBotones_text_11_0" id="formBotones-text_11_0">
<div class="ui-panel-content ui-widget-content" id="formBotones-text_11_0_content">
<span id="formBotones-editor_text_11_0">Dynamic text</span>
</div>
<div class="ui-resizable-handle ui-resizable-e" style="z-index: 90;"></div>
<div class="ui-resizable-handle ui-resizable-s" style="z-index: 90;"></div>
<div class="ui-resizable-handle ui-resizable-se ui-icon ui-icon-gripsmall-diagonal-se" style="z-index: 90;"></div>
</div>
<input type="hidden" value="text_11_0" name="formBotones-tempTextIdLayer" id="formBotones-tempTextIdLayer">
</div>
<input type="hidden" value="e5s4" id="javax.faces.ViewState" name="javax.faces.ViewState">
</form>
Create layers code
Here, it's a method which is setted to button's actionListener. It create a resizable and draggable panel with a span where I'll write dynamic text through javaScript.
public void createTextLayer() throws IOException{
logger.entry("EventProvider.createTextLayer()");
Application app=FacesContextWrapper.getCurrentInstance().getApplication();
UIComponent parent=FacesContextWrapper.getCurrentInstance().getViewRoot().findComponent("formBotones-wizardEventContainer");
CommandButton textBtn=(CommandButton)FacesContextWrapper.getCurrentInstance().getViewRoot().findComponent("formControl-textLayerBtn");
String finalId=null;
if(tempObjs!=null && tempObjs.size()>0 && tempObjs.containsValue("text")){
finalId="text_" + space.getIdSpace() + "_" + createFinalIndex("text");
}
else{
finalId="text_" + space.getIdSpace() + "_" + spaceBo.getDao().countSectionPages(space.getIdSpace(),4);
}
if(parent!=null){
HtmlPanelGroup panel=(HtmlPanelGroup)app.createComponent(HtmlPanelGroup.COMPONENT_TYPE);
panel.setId(finalId);
panel.setStyleClass(panel.getId());
panel.setStyle("display:inline-block;min-width:100px;min-height:100px;background:red;overflow:hidden;");
setTempTextIdLayer(finalId);
HtmlOutputText text=(HtmlOutputText) app.createComponent(HtmlOutputText.COMPONENT_TYPE);
text.setId("editor_" + panel.getId());
panel.getChildren().add(text);
Resizable resizable=(Resizable)app.createComponent(Resizable.COMPONENT_TYPE);
resizable.setFor(panel.getId());
resizable.setMaxWidth(new Integer(800));
resizable.setMaxHeight(new Integer(600));
resizable.setMinWidth(new Integer(50));
resizable.setMinHeight(new Integer(50));
resizable.setContainment(false);
resizable.setFor(panel.getId());
panel.getChildren().add(resizable);
Draggable drag=(Draggable)app.createComponent(Draggable.COMPONENT_TYPE);
drag.setFor(panel.getId());
drag.setOpacity(new Double(0.6));
drag.setContainment(new String("parent"));
drag.setSnap(true);
drag.setSnapMode(new String("outer"));
drag.setSnapTolerance(new Integer(5));
drag.setFor(panel.getId());
panel.getChildren().add(drag);
tempObjs.put(panel,"text");
}
updateViewRoot();
//Disabling text button
textBtn.setDisabled(true);
}
Add layer to container code
Here I get the container, wizardEventContainer, and I add every panel which are saved on tempObjs var.
public void updateViewRoot(){
logger.entry("EventProvider.updateViewRoot()");
UIComponent parent=FacesContextWrapper.getCurrentInstance().getViewRoot().findComponent("formBotones-wizardEventContainer");
if(parent!=null){
for (Entry<UIComponent, String> entry : tempObjs.entrySet()) {
parent.getChildren().add(entry.getKey());
}
}
}
As getViewRoot() only return elements which are written on xhtml file I've decided to save dynamic components in a map and when I place this dragabble component in the right site the I pass css style and other properties through managed bean properties, so every time a new panel is added on xhtml I create a new dynamic component with new properties setted and I replace for old dynamic component saved in the map.
It's a no elegant way but works fine at the moment for me.
public void saveTextLayer(){
logger.entry("EventProvider.saveTextLayer()");
List<UIComponent> list=null;
if(tempObjs.containsValue("text"))
list=getKeysByValue(tempObjs,"text");
for (UIComponent c:list){
if (c instanceof HtmlPanelGroup){
HtmlPanelGroup panel=(HtmlPanelGroup)c;
if(panel.getId().equals(getTempTextIdLayer())){
panel.setStyle(getTempCSS());
if(panel.getChildren().get(0) instanceof HtmlOutputText){
((HtmlOutputText)panel.getChildren().get(0)).setValue(getTempHTML());
}
//Adding delete layer
Application app=FacesContextWrapper.getCurrentInstance().getApplication();
HtmlPanelGroup deleteLayer=(HtmlPanelGroup)app.createComponent(HtmlPanelGroup.COMPONENT_TYPE);
deleteLayer.setId("delete_" + panel.getId());
deleteLayer.setStyle("display:inline-block;width:5px;height:5px;background:white;");
deleteLayer.setOnclick("deleteLayer($(this));");
panel.getChildren().add(0,deleteLayer);
//Replacing UIComponent
tempObjs.remove(c);
tempObjs.put(panel, "text");
updateViewRoot();
//Deleting temp vars
setTempCSS("");
setTempTextIdLayer("");
setTempHTML("");
}
}
}

PrimeFaces fileDownload method not called?

I've got multiple pages that allow the download of the same resource (retrieved from my DB).
The problem is that the download works just on some of them, even with the SAME code and calling the SAME bean.
This thing is getting quite annoying because, on the non working pages, clicking on the download link will just reload the page without any message/exception, so I can't find out what's happening.
Here's my BEAN code :
package ManagedBeans;
import ejb.DispensaManagerLocal;
import entity.Dispensa;
import entity.Utente;
import java.io.ByteArrayInputStream;
import javax.ejb.EJB;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.ManagedProperty;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import org.primefaces.event.RateEvent;
import org.primefaces.model.DefaultStreamedContent;
import org.primefaces.model.StreamedContent;
/**
*
* #author stefano
*/
#ManagedBean
#RequestScoped
public class DispensaBean {
#EJB
private DispensaManagerLocal dispensaManager;
#ManagedProperty(value = "#{loginBean.utente}")
private Utente utente;
public Utente getUtente() {
return utente;
}
public void setUtente(Utente utente) {
this.utente = utente;
}
/**
* Creates a new instance of DispensaBean
*/
public DispensaBean() {
}
public StreamedContent getDownload() {
String id = FacesContext.getCurrentInstance().getExternalContext().getRequestParameterMap().get("dispensaId");
System.out.println("________" + id);
Dispensa d = dispensaManager.findById(Integer.parseInt(id));
String type = getMimeFromByte(d.getDatiFile());
String estensione = "";
if(type.equals("application/pdf")){
estensione = ".pdf";
} else if(type.equals("application/zip")) {
estensione = ".zip";
} else if(type.equals("application/vnd.ms-powerpoint")) {
estensione = ".ppt";
}
return new DefaultStreamedContent(new ByteArrayInputStream(d.getDatiFile()), type, d.getTitolo() + estensione);
}
private String getMimeFromByte(byte[] src) {
if (src[0] == 0x25 && src[1] == 0x50 && src[2] == 0x44 && src[3] == 0x46) {
return "application/pdf";
}
if (src[0] == 0x50 && src[1] == 0x4b) {
return "application/zip";
}
if (src[0] == 0xd0 && src[1] == 0xcf && src[2] == 0x11 && src[3] == 0xe0 && src[4] == 0xa1 && src[5] == 0xb1 && src[6] == 0x1a && src[7] == 0xe1) {
return "application/vnd.ms-powerpoint";
}
return "application/octet-stream";
}
}
Now, on the NON working pages, the getDownload() method is NOT called, as it doesn't print anything.
Here's the download button code
<h:form style="float: right">
<pou:commandLink id="downloadDispensa" ajax="false" disabled="#{!loginBean.logged}">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
<pou:fileDownload value="#{dispensaBean.getDownload()}"/>
<f:param name="dispensaId" value="#{dispensa.id}"/>
</pou:commandLink>
</h:form>
What I've noticed is that the download link just RELOADS the page instead of calling the method, and this happens only in the pages in which #{dispensa.id} depends on a GET parameter.
For example, I've got a page called dispensa.xhtml that displays all my files in the DB if no GET parameters are passed.
Indeed, dispensa.xhtml?id=5 will display just the file with id=5.
Clicking on the download link, in the first case, works without problems.
Doing it in the second case will reload the page and will lose the GET parameter, so the it will load dispensa.xhtml instead of dispensa.xhtml?id=5.
I'd think that there's some problem in using the GET parameter, but..yesterday it WORKED and I did NOT change this code!
The other NON working page is ricerca.xhtml which shows the (multiple) results of a query given by ricerca.xhtml?key=query.
Finally, to mess things up, the download in profile.xhtml?user=username WORKS.
This destroys my whole theory about GET parameters.
To avoid having a null byte[] datiFile, I'ved edited my Dispensa entity this way :
#Basic(optional = true, fetch=FetchType.EAGER)
#Lob
#Column(name = "datiFile")
private byte[] datiFile;
I don't know what to do because it doesn't say what's going wrong, it just reloads the page, bypassing my download!
EDIT :
I've tried changing my getDownload() method to return a File which is on my HD, to understand if the problem is caused by a null data on the db but it still doesn't work as I said!
Seems that I solved this by using an alternative solution.
I've changed all
<h:form style="float: right">
<pou:commandLink id="downloadDispensa" ajax="false" disabled="#{!loginBean.logged}">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
<pou:fileDownload value="#{dispensaBean.getDownload()}"/>
<f:param name="dispensaId" value="#{dispensa.id}"/>
</pou:commandLink>
</h:form>
to
<h:form style="float: right">
<h:outputLink id="downloadDispensa" disabled="#{!loginBean.logged}" target="_blank" value="./download.xhtml?id=#{dispensa.id}">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
</h:outputLink>
</h:form>
where download.xhtml has this code :
<script type="text/javascript">
if(document.referrer == "" || document.referrer == "download.xhtml"){
self.location='./index.xhtml';
}
document.onblur = new Function('self.close()');
</script>
<h:body onload="document.getElementsByClassName('downloadDispensa')[0].click();" rendered="#{loginBean.logged}">
<h:form>
<h:commandLink class="downloadDispensa" id="downloadDispensa" style="display: none">
<pou:graphicImage value="./resources/images/download.png" height="30"/>
<pou:fileDownload value="#{dispensaBean.download}"/>
<f:param name="dispensaId" value="#{request.getParameter('id')}"/>
</h:commandLink>
</h:form>
</h:body>
<h:body onload="self.location='./index.xhtml';" rendered="#{!loginBean.logged}">
</h:body>
So it loads the download page, autoclicks on the download link and it autocloses the page when the download dialog is shown.
I have faced the same issue. I have debugged it and came to know that there is form inside form as I have included template inside another template as it is a summary screen.So I have removed all h:form tags in the inner templates except the root xhtml page which has all these templates and it worked.

JSF 2.0: Validate equality of 2 InputSecret Fields (confirm password) without writing Code?

I'm developing a pure JavaEE6 application with JSF 2.0 and Glassfish.
My JSF implementation is Primefaces (beside Mojarra provided by Glassfish).
I want to verify if the values of 2 password fields in a JSF form are equal.
With Seam, there is the neat component <s:validateEquality for="pw1"/>.
I want do to the same without Seam, just using JSF (or maybe a component of a JSF library). Until now i only saw examples which validate the form with a custom validator. But i would like to compare the fields without writing Java code or Javascript code.
Is that possible?
This what it looks like with Seam:
...
<h:inputSecret id="passwort" value="#{personHome.instance.password}"
redisplay="true" required="true">
<f:validateLength minimum="8"/>
<a:support event="onblur" reRender="passwortField" bypassUpdates="true" ajaxSingle="true" />
</h:inputSecret>
...
<h:inputSecret id="passwort2" required="true" redisplay="true">
<!-- find the JSF2.0-equivalent to this tag: -->
<s:validateEquality for="passwort"/>
<a:support event="onblur" reRender="passwort2Field" bypassUpdates="true" ajaxSingle="true" />
</h:inputSecret>
...
You may use Primefaces tag in this very simple way:
<p:password id="password" value="#{bean.password}" match="repeated_password" />
<p:password id="repeated_password" value="#{bean.password}" />
The Seam3 Faces module will support "Cross-field form validation" in it's imminent Alpha3 release. This is your best bet for a minimal code solution, see this blog for a howto.
Alternatively I've done this programmatically by using the f:attribute tag to pass the clientId of another form field to a custom validator, then using the UIComponent passed into the custom validator to access the other filed by id.
Here's the facelet file:
<h:outputLabel value="Enter your email address" rendered="#{!cc.attrs.registration.subRegistration}" />
<h:inputText label="Email" id="textEmail1" value="#{cc.attrs.registration.email}" rendered="#{!cc.attrs.registration.subRegistration}" required="true" maxlength="128" size="35"></h:inputText>
<h:message for="textEmail1" rendered="#{!cc.attrs.registration.subRegistration}"></h:message>
<h:outputLabel value="Re-enter your email address confirmation:" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}" />
<h:inputText label="Email repeat" id="textEmail2" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}" maxlength="64" size="35">
<f:validator validatorId="duplicateFieldValidator" />
<f:attribute name="field1Id" value="#{component.parent.parent.clientId}:textEmail1" />
</h:inputText>
<h:message for="textEmail2" rendered="#{!cc.attrs.registration.subRegistration and cc.attrs.duplicateEmailRequired}"></h:message>
Here's the validator class:
package ca.triumf.mis.trevents.jsf.validator;
import javax.faces.application.FacesMessage;
import javax.faces.component.UIComponent;
import javax.faces.component.UIInput;
import javax.faces.context.FacesContext;
import javax.faces.validator.FacesValidator;
import javax.faces.validator.Validator;
import javax.faces.validator.ValidatorException;
#FacesValidator(value="duplicateFieldValidator")
public class DuplicateFieldValidator implements Validator {
#Override
public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException {
// Obtain the client ID of the first field from f:attribute.
System.out.println(component.getFamily());
String field1Id = (String) component.getAttributes().get("field1Id");
// Find the actual JSF component for the client ID.
UIInput textInput = (UIInput) context.getViewRoot().findComponent(field1Id);
if (textInput == null)
throw new IllegalArgumentException(String.format("Unable to find component with id %s",field1Id));
// Get its value, the entered text of the first field.
String field1 = (String) textInput.getValue();
// Cast the value of the entered text of the second field back to String.
String confirm = (String) value;
// Check if the first text is actually entered and compare it with second text.
if (field1 != null && field1.length() != 0 && !field1.equals(confirm)) {
throw new ValidatorException(new FacesMessage("E-mail addresses are not equal."));
}
}
}
I had to use a mixture of both answers to succeed.
I used ifischers short solution but my bean password field was null.
So I used the lines from Brian Leathem to get the UIInput from the context:
public void passwordValidator(FacesContext context, UIComponent toValidate, Object value) {
UIInput passwordField = (UIInput) context.getViewRoot().findComponent("registerForm:password");
if (passwordField == null)
throw new IllegalArgumentException(String.format("Unable to find component."));
String password = (String) passwordField.getValue();
String confirmPassword = (String) value;
if (!confirmPassword.equals(password)) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Passwords do not match!", "Passwords do not match!");
throw new ValidatorException(message);
}
}
If you're using JSF utility library OmniFaces, then you could use <o:validateEqual>. It also allows setting a custom message. The showcase has a live example demonstrating the common usecase of validating the password confirmation. You don't even need ajax to update the model before invoking the validator (as your own approach does).
Here's the minimum necessary code:
<h:inputSecret id="password" value="#{personHome.person.password}" />
<h:message for="password" />
<h:inputSecret id="password2" />
<h:message for="password2" />
<o:validateEqual components="password password2"
message="Passwords do not match!" showMessageFor="password2" />
No Java code needed.
This is the way i finally did it, which i like cause it's short and easy. The only problem is that it's not really re-usable, but as i only need this in one case, i rather save some LOCs and do it this way.
Snippet from my view:
<h:inputSecret id="password" value="#{personHome.person.password}">
<f:ajax event="blur" render="passwordError" />
</h:inputSecret>
<h:message for="password" errorClass="invalid" id="passwordError" />
<h:inputSecret id="password2" validator="#{personHome.validateSamePassword}">
<f:ajax event="blur" render="password2Error" />
</h:inputSecret>
<h:message for="password2" errorClass="invalid" id="password2Error" />
My Backing Bean (just the important part):
#Named #ConversationScoped
public class PersonHome {
private Person person;
public Person getPerson() {
if (person == null) return new Person();
else return person;
}
public void validateSamePassword(context:FacesContext, toValidate:UIComponent, value:Object) {
String confirmPassword = (String)value;
if (!confirmPassword.equals(person.getPassword()) {
FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Passwords do not match!", "Passwords do not match!")
throw new Validatorexception(message);
}
}
You can do it easily with Apache MyFaces ExtVal.
Without solution, I was forced to do the validation in a ugly way (not recommended). At least it works till I found better solution.
In the method that returns the action, I check both values, in case of different values, I add error messages on context and return null to the navigation handler.
package com.jsf.beans.user;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.component.html.HtmlInputSecret;
import org.apache.commons.lang.StringUtils;
import com.pichler.jsf.beans.base.JsfViewBean;
#ManagedBean(name = "changePassword")
#RequestScoped
public class ChangePassword extends JsfViewBean {
private HtmlInputSecret inputSecret1, inputSecret2;
/**
* #return the inputSecret1
*/
public HtmlInputSecret getInputSecret1() {
return inputSecret1;
}
/**
* #param inputSecret1
* the inputSecret1 to set
*/
public void setInputSecret1(HtmlInputSecret inputSecret1) {
this.inputSecret1 = inputSecret1;
}
/**
* #return the inputSecret2
*/
public HtmlInputSecret getInputSecret2() {
return inputSecret2;
}
/**
* #param inputSecret2
* the inputSecret2 to set
*/
public void setInputSecret2(HtmlInputSecret inputSecret2) {
this.inputSecret2 = inputSecret2;
}
private String password1, password2;
public String alterar() {
if (!StringUtils.equals(password1, password2)) {
addErrorMessage(inputSecret1.getClientId(),
"As senhas não coincidem");
addErrorMessage(inputSecret2.getClientId(),
"As senhas não coincidem");
return null;
}
return null;
}
/**
* #return the password1
*/
public String getPassword1() {
return password1;
}
/**
* #param password1
* the password1 to set
*/
public void setPassword1(String password1) {
this.password1 = password1;
}
/**
* #return the password2
*/
public String getPassword2() {
return password2;
}
/**
* #param password2
* the password2 to set
*/
public void setPassword2(String password2) {
this.password2 = password2;
}
}
*JsfViewBean is just a class that has some common methods, as "addMessages".

Resources