diff --git a/checkstyle.xml b/checkstyle.xml index 2e94cee8..b4ac18a1 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -96,7 +96,7 @@ - + diff --git a/forms/pom.xml b/forms/pom.xml index ebbbd2e6..5548dc25 100644 --- a/forms/pom.xml +++ b/forms/pom.xml @@ -30,15 +30,15 @@ UTF-8 1.8 - 8.6.1 - 8.6.0 - 2.0.10 + 8.8.0 + 8.7.0 + 2.0.11 1.13 - 2.0.15 - v20190528 + 2.0.17 + v20200406 3.1 2.4.8 - 1.80.0 + 1.82.0 @@ -66,21 +66,21 @@ 0.0.1-SNAPSHOT - - org.devgateway.toolkit - reporting - 0.0.1-SNAPSHOT - - - bcprov-jdk14 - bouncycastle - - - bcprov-jdk14 - org.bouncycastle - - - + + + + + + + + + + + + + + + org.devgateway.toolkit @@ -120,17 +120,6 @@ - - org.springframework.boot - spring-boot-starter-integration - - - xercesImpl - xerces - - - - org.springframework.boot spring-boot-starter-web @@ -152,11 +141,6 @@ spring-boot-starter-security - - org.springframework.data - spring-data-rest-hal-browser - - org.springframework.boot spring-boot-starter-test @@ -292,6 +276,7 @@ **/*.css **/*.js **/*.png + **/*.svg **/*.gif **/*.html **/*.properties @@ -397,10 +382,6 @@ derby ${derby.version} - - org.hibernate - hibernate-ehcache - org.hibernate hibernate-entitymanager diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java b/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java index abad38dc..789cca44 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/FormsSecurityConfig.java @@ -48,7 +48,7 @@ public void configure(final WebSecurity web) throws Exception { web.ignoring().antMatchers("/img/**", "/css*/**", "/js*/**", "/assets*/**", "/wicket/resource/**/*.js", "/wicket/resource/**/*.css", "/wicket/resource/**/*.png", "/wicket/resource/**/*.jpg", "/wicket/resource/**/*.woff", "/wicket/resource/**/*.woff2", "/wicket/resource/**/*.ttf", - "/favicon.ico", + "/favicon.ico", "/wicket/resource/**/*.svg", "/wicket/resource/**/*.gif", "/login/**", "/forgotPassword/**", "/resources/**", "/resources/public/**"); } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java b/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java index 40112e28..9dee3c26 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/WebConstants.java @@ -30,7 +30,14 @@ private WebConstants() { public static final String PARAM_VIEW_MODE = "viewMode"; + public static final String DISABLE_FORM_LEAVING_JS + = "if(typeof disableFormLeavingConfirmation === 'function') disableFormLeavingConfirmation();"; + + public static final String PARAM_PRINT = "print"; + public static final String PARAM_ID = "id"; + public static final String V_POSITION = "vPosition"; + public static final String MAX_HEIGHT = "maxPosition"; public static final String PARAM_REVISION_ID = "revisionId"; public static final String PARAM_ENTITY_CLASS = "class"; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java b/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java index 3acaedc6..220de14d 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/security/SecurityConstants.java @@ -23,5 +23,11 @@ public final class SecurityConstants { public static final class Roles { public static final String ROLE_ADMIN = "ROLE_ADMIN"; public static final String ROLE_USER = "ROLE_USER"; + public static final String ROLE_VALIDATOR = "ROLE_VALIDATOR"; + } + + public static final class Action { + public static final String EDIT = "EDIT"; + public static final String VIEW = "VIEW"; } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java index c72b584e..0d668aa8 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/util/MarkupCacheService.java @@ -11,13 +11,14 @@ *******************************************************************************/ package org.devgateway.toolkit.forms.util; -import net.sf.ehcache.Cache; -import net.sf.ehcache.CacheManager; -import net.sf.ehcache.Element; + import org.apache.wicket.markup.Markup; import org.apache.wicket.markup.MarkupCache; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import javax.cache.Cache; +import javax.cache.CacheManager; import java.util.Collection; /** @@ -30,6 +31,10 @@ */ @Component public class MarkupCacheService { + + @Autowired + private CacheManager cm; + /** * start-key used to identify the reports markup */ @@ -61,12 +66,11 @@ public final void flushMarkupCache() { */ public void addPentahoReportToCache(final String outputType, final String reportName, final String parameters, final byte[] buffer) { - final CacheManager cm = CacheManager.getInstance(); // get the reports cache "reportsCache", declared in ehcache.xml - final Cache cache = cm.getCache("reportsCache"); + final Cache cache = cm.getCache("reportsCache", String.class, byte[].class); - cache.put(new Element(createCacheKey(outputType, reportName, parameters), buffer)); + cache.put(createCacheKey(outputType, reportName, parameters), buffer); } /** @@ -78,15 +82,14 @@ public void addPentahoReportToCache(final String outputType, final String report * @return */ public byte[] getPentahoReportFromCache(final String outputType, final String reportName, final String parameters) { - final CacheManager cm = CacheManager.getInstance(); // get the reports cache "reportsCache", declared in ehcache.xml - final Cache cache = cm.getCache("reportsCache"); + final Cache cache = cm.getCache("reportsCache", String.class, byte[].class); final String key = createCacheKey(outputType, reportName, parameters); - if (cache.isKeyInCache(key)) { - return (byte[]) cache.get(key).getObjectValue(); + if (cache.containsKey(key)) { + return cache.get(key); } return null; @@ -96,10 +99,9 @@ public byte[] getPentahoReportFromCache(final String outputType, final String re * Remove from cache all reports content */ public void clearPentahoReportsCache() { - final CacheManager cm = CacheManager.getInstance(); // get the reports cache "reportsCache", declared in ehcache.xml - final Cache cache = cm.getCache("reportsCache"); + final Cache cache = cm.getCache("reportsCache"); if (cache != null) { cache.removeAll(); @@ -110,16 +112,15 @@ public void clearPentahoReportsCache() { * Remove from cache all APIs/Services content. */ public void clearAllCaches() { - final CacheManager cm = CacheManager.getInstance(); // get the reports cache "reportsApiCache", declared in ehcache.xml - final Cache cache = cm.getCache("reportsApiCache"); + final Cache cache = cm.getCache("reportsApiCache"); if (cache != null) { cache.removeAll(); } // get the reports cache "excelExportCache", declared in ehcache.xml - final Cache excelExportCache = cm.getCache("excelExportCache"); + final Cache excelExportCache = cm.getCache("excelExportCache"); if (excelExportCache != null) { excelExportCache.removeAll(); } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/OptionallyRequiredTextAreaFieldComponent.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/OptionallyRequiredTextAreaFieldComponent.java new file mode 100644 index 00000000..4b629141 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/form/OptionallyRequiredTextAreaFieldComponent.java @@ -0,0 +1,64 @@ +/** + * Copyright (c) 2015 Development Gateway, Inc and others. + *

+ * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + *

+ * Contributors: + * Development Gateway - initial API and implementation + */ +/** + * + */ +package org.devgateway.toolkit.forms.wicket.components.form; + +import org.apache.wicket.markup.html.form.TextArea; +import org.apache.wicket.model.IModel; +import org.apache.wicket.validation.validator.StringValidator; +import org.devgateway.toolkit.forms.WebConstants; + +/** + * @author mpostelnicu + * + * A {@link TextAreaFieldBootstrapFormComponent} that has TextArea{@link #isRequired()} exposed + * + */ +public abstract class OptionallyRequiredTextAreaFieldComponent extends TextAreaFieldBootstrapFormComponent { + private StringValidator validator = WebConstants.StringValidators.MAXIMUM_LENGTH_VALIDATOR_ONE_LINE_TEXTAREA; + + private static final long serialVersionUID = 1L; + + public OptionallyRequiredTextAreaFieldComponent(final String id, final IModel labelModel, + final IModel model) { + super(id, labelModel, model); + } + + public OptionallyRequiredTextAreaFieldComponent(final String id, final IModel labelModel) { + super(id, labelModel, null); + } + + /** + * @param id + */ + public OptionallyRequiredTextAreaFieldComponent(final String id) { + super(id); + } + + public boolean isRequired() { + return false; + } + + @Override + protected TextArea inputField(final String id, final IModel model) { + TextArea textArea = new TextArea(id, initFieldModel()) { + @Override + public boolean isRequired() { + return OptionallyRequiredTextAreaFieldComponent.this.isRequired(); + } + }; + return textArea; + } + +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/SelectFilteredBootstrapPropertyColumn.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/SelectFilteredBootstrapPropertyColumn.java index 6b2b957b..c34709b5 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/SelectFilteredBootstrapPropertyColumn.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/SelectFilteredBootstrapPropertyColumn.java @@ -1,12 +1,15 @@ package org.devgateway.toolkit.forms.wicket.components.table; -import org.apache.wicket.AttributeModifier; import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.ChoiceFilteredPropertyColumn; import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterForm; import org.apache.wicket.model.IModel; import org.devgateway.toolkit.forms.wicket.components.form.Select2ChoiceBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.providers.GenericChoiceProvider; +import org.wicketstuff.select2.ChoiceProvider; import java.util.List; @@ -17,28 +20,92 @@ * @since 12/20/16 */ public class SelectFilteredBootstrapPropertyColumn extends ChoiceFilteredPropertyColumn { + private DataTable dataTable; + private boolean disableFilter = false; + private ChoiceProvider choiceProvider; public SelectFilteredBootstrapPropertyColumn(final IModel displayModel, final S sortProperty, final String propertyExpression, - final IModel> filterChoices) { - super(displayModel, sortProperty, propertyExpression, filterChoices); + final ChoiceProvider choiceProvider, + final DataTable dataTable) { + this(displayModel, sortProperty, propertyExpression, choiceProvider, dataTable, false); + } + + public SelectFilteredBootstrapPropertyColumn(final IModel displayModel, + final S sortProperty, + final String propertyExpression, + final IModel> filterChoices, + final DataTable dataTable) { + this(displayModel, sortProperty, propertyExpression, filterChoices, dataTable, false); } public SelectFilteredBootstrapPropertyColumn(final IModel displayModel, + final S sortProperty, + final String propertyExpression, + final ChoiceProvider choiceProvider, + final DataTable dataTable, + final boolean disableFilter) { + super(displayModel, sortProperty, propertyExpression, null); + this.disableFilter = disableFilter; + this.dataTable = dataTable; + this.choiceProvider = choiceProvider; + } + + + public SelectFilteredBootstrapPropertyColumn(final IModel displayModel, + final S sortProperty, final String propertyExpression, - final IModel> filterChoices) { - super(displayModel, propertyExpression, filterChoices); + final IModel> filterChoices, + final DataTable dataTable, + final boolean disableFilter) { + super(displayModel, sortProperty, propertyExpression, filterChoices); + this.disableFilter = disableFilter; + this.dataTable = dataTable; } @Override public Component getFilter(final String componentId, final FilterForm form) { + ChoiceProvider provider; + + if (choiceProvider != null) { + provider = choiceProvider; + } else { + provider = new GenericChoiceProvider<>((List) getFilterChoices().getObject()); + } final Select2ChoiceBootstrapFormComponent selectorField = - new Select2ChoiceBootstrapFormComponent<>(componentId, - new GenericChoiceProvider<>((List) getFilterChoices().getObject()), - getFilterModel(form)); + new Select2ChoiceBootstrapFormComponent<>(componentId, provider, getFilterModel(form)); selectorField.hideLabel(); - selectorField.getField().add(AttributeModifier.replace("onchange", "this.form.submit();")); + if (disableFilter) { + selectorField.setEnabled(false); + } + + selectorField.getField().add(new AjaxComponentUpdatingBehavior(form, "change")); + return selectorField; } + + private class AjaxComponentUpdatingBehavior extends AjaxFormComponentUpdatingBehavior { + private final FilterForm form; + + AjaxComponentUpdatingBehavior(final FilterForm form, final String event) { + super(event); + this.form = form; + } + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + // update the table component + dataTable.setCurrentPage(0); + target.add(dataTable); + } + } + + @Override + public void detach() { + super.detach(); + if (choiceProvider != null) { + choiceProvider.detach(); + } + } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/SelectMultiFilteredBootstrapPropertyColumn.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/SelectMultiFilteredBootstrapPropertyColumn.java new file mode 100644 index 00000000..f3ac7afb --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/table/SelectMultiFilteredBootstrapPropertyColumn.java @@ -0,0 +1,104 @@ +package org.devgateway.toolkit.forms.wicket.components.table; + +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.form.AjaxFormComponentUpdatingBehavior; +import org.apache.wicket.extensions.markup.html.repeater.data.table.DataTable; +import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.ChoiceFilteredPropertyColumn; +import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.FilterForm; +import org.apache.wicket.model.IModel; +import org.devgateway.toolkit.forms.wicket.components.form.Select2MultiChoiceBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.providers.GenericChoiceProvider; +import org.wicketstuff.select2.ChoiceProvider; + +import java.util.Collection; +import java.util.List; + +/** + * @author idobre + * @since 2019-03-11 + * + * A ChoiceFilteredPropertyColumn that uses Select2MultiChoiceBootstrapFormComponent as a filter. + */ +public class SelectMultiFilteredBootstrapPropertyColumn extends ChoiceFilteredPropertyColumn { + private final DataTable dataTable; + private boolean disableFilter = false; + private ChoiceProvider choiceProvider; + + public SelectMultiFilteredBootstrapPropertyColumn(final IModel displayModel, + final S sortProperty, + final String propertyExpression, + final ChoiceProvider choiceProvider, + final DataTable dataTable) { + this(displayModel, sortProperty, propertyExpression, choiceProvider, dataTable, false); + } + + public SelectMultiFilteredBootstrapPropertyColumn(final IModel displayModel, + final S sortProperty, + final String propertyExpression, + final ChoiceProvider choiceProvider, + final DataTable dataTable, + final boolean disableFilter) { + super(displayModel, sortProperty, propertyExpression, null); + this.disableFilter = disableFilter; + this.dataTable = dataTable; + this.choiceProvider = choiceProvider; + } + + public SelectMultiFilteredBootstrapPropertyColumn(final IModel displayModel, + final S sortProperty, + final String propertyExpression, + final IModel> filterChoices, + final DataTable dataTable) { + super(displayModel, sortProperty, propertyExpression, filterChoices); + + this.dataTable = dataTable; + } + + public SelectMultiFilteredBootstrapPropertyColumn(final IModel displayModel, + final String propertyExpression, + final IModel> filterChoices, + final DataTable dataTable) { + super(displayModel, propertyExpression, filterChoices); + + this.dataTable = dataTable; + } + + @Override + public Component getFilter(final String componentId, final FilterForm form) { + ChoiceProvider provider; + + if (choiceProvider != null) { + provider = choiceProvider; + } else { + provider = new GenericChoiceProvider<>((List) getFilterChoices().getObject()); + } + + + final Select2MultiChoiceBootstrapFormComponent selectorField = + new Select2MultiChoiceBootstrapFormComponent<>(componentId, + provider, + (IModel>) getFilterModel(form)); + selectorField.hideLabel(); + + selectorField.getField().add(new AjaxComponentUpdatingBehavior(form, "change")); + + return selectorField; + } + + private class AjaxComponentUpdatingBehavior extends AjaxFormComponentUpdatingBehavior { + private final FilterForm form; + + AjaxComponentUpdatingBehavior(final FilterForm form, final String event) { + super(event); + this.form = form; + } + + @Override + protected void onUpdate(final AjaxRequestTarget target) { + // update the table component + dataTable.setCurrentPage(0); + target.add(dataTable); + } + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/util/ComponentUtil.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/util/ComponentUtil.java index 4159bdfe..48640689 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/util/ComponentUtil.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/util/ComponentUtil.java @@ -28,6 +28,9 @@ import org.devgateway.toolkit.persistence.service.TextSearchableService; import java.io.Serializable; +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.Date; /** * @author idobre @@ -49,6 +52,21 @@ public static boolean isViewMode() { .toBoolean(false); } + /** + * Returns true if the {@link WebConstants#PARAM_PRINT} is used as a parameter + * + * @return + */ + public static boolean isPrintMode() { + return RequestCycle.get().getRequest().getRequestParameters().getParameterValue(WebConstants.PARAM_PRINT) + .toBoolean(false); + } + + public static Date getDateFromLocalDate(final LocalDate localDate) { + return Date.from(localDate.atStartOfDay(ZoneId.systemDefault()).toInstant()); + } + + public static void enableDisableEvent(final Component c, final IEvent event) { if (event.getPayload() instanceof EditingDisabledEvent) { c.setEnabled(false); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/util/FormSecurityUtil.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/util/FormSecurityUtil.java new file mode 100644 index 00000000..e2122a67 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/components/util/FormSecurityUtil.java @@ -0,0 +1,93 @@ +package org.devgateway.toolkit.forms.wicket.components.util; + +import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession; +import org.devgateway.toolkit.persistence.dao.Person; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; + +import java.security.Principal; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; + +import static org.devgateway.toolkit.forms.security.SecurityConstants.Roles.ROLE_ADMIN; + +public class FormSecurityUtil { + + protected FormSecurityUtil() { + } + + + public static boolean rolesContainsAny(final Collection roleSet, final String... roles) { + for (final String role : roles) { + if (roleSet.contains(role)) { + return true; + } + } + return false; + } + + public static boolean rolesContainsAny(final String... roles) { + return rolesContainsAny(Objects.requireNonNull(getStringRolesForCurrentPerson()), roles); + } + + public static boolean rolesContainsAll(final Collection roleSet, final String... roles) { + return roleSet.containsAll(Arrays.asList(roles)); + } + + public static boolean rolesContainsAll(final String... roles) { + return rolesContainsAll(Objects.requireNonNull(getStringRolesForCurrentPerson()), roles); + } + + /** + * returns the principal object. In our case the principal should be + * {@link Person} + * + * @return the principal or null + * @see Principal + */ + public static Person getCurrentAuthenticatedPerson() { + if (SecurityContextHolder.getContext().getAuthentication() == null) { + return null; + } + final Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + if (authentication == null) { + return null; + } + final Object principal = authentication.getPrincipal(); + if (principal instanceof Person) { + return (Person) principal; + } + return null; + } + + public static Set getStringRolesForCurrentPerson() { + if (!AbstractAuthenticatedWebSession.get().isSignedIn()) { + return Collections.emptySet(); + } + return AbstractAuthenticatedWebSession.get().getRoles(); + } + + public static boolean hasAnyUserRoles(String... roles) { + List rList = Arrays.asList(roles); + return getStringRolesForCurrentPerson().stream().anyMatch(rList::contains); + } + + public static boolean hasUserRole(String role) { + return hasAnyUserRoles(role); + } + + public static boolean isCurrentUserAdmin() { + return hasUserRole(ROLE_ADMIN); + } + + public static boolean isCurrentRoleOnlyUser(String userRole, String validatorRole) { + if (hasAnyUserRoles(ROLE_ADMIN, validatorRole)) { + return false; + } + return hasUserRole(userRole); + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java index b81f4942..c24f0f93 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.java @@ -33,6 +33,7 @@ import org.apache.wicket.markup.head.CssHeaderItem; import org.apache.wicket.markup.head.IHeaderResponse; import org.apache.wicket.markup.head.JavaScriptHeaderItem; +import org.apache.wicket.markup.head.MetaDataHeaderItem; import org.apache.wicket.markup.head.filter.HeaderResponseContainer; import org.apache.wicket.markup.html.GenericWebPage; import org.apache.wicket.markup.html.TransparentWebMarkupContainer; @@ -46,6 +47,7 @@ import org.apache.wicket.protocol.http.WebSession; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.JavaScriptResourceReference; +import org.apache.wicket.request.resource.PackageResourceReference; import org.apache.wicket.resource.JQueryResourceReference; import org.apache.wicket.util.string.StringValue; import org.devgateway.toolkit.forms.WebConstants; @@ -94,24 +96,6 @@ public Boolean fluidContainer() { return false; } - public static class HALRedirectPage extends RedirectPage { - private static final long serialVersionUID = -750983217518258464L; - - public HALRedirectPage() { - super(WebApplication.get().getServletContext().getContextPath() + "/api/browser/"); - } - - } - - public static class JminixRedirectPage extends RedirectPage { - private static final long serialVersionUID = -750983217518258464L; - - public JminixRedirectPage() { - super(WebApplication.get().getServletContext().getContextPath() + "/jminix/"); - } - - } - public static class UIRedirectPage extends RedirectPage { private static final long serialVersionUID = -750983217518258464L; @@ -213,6 +197,16 @@ protected List newSubMenuButtons(final String buttonMarkupId) { return languageDropDown; } + protected MetaDataHeaderItem getFavicon() { + PackageResourceReference faviconRef = + new PackageResourceReference(BaseStyles.class, "assets/img/icons/toolkit-favicon.svg"); + MetaDataHeaderItem icon = MetaDataHeaderItem.forLinkTag("icon", + urlFor(faviconRef, null).toString()); + icon.addTagAttribute("type", "image/svg+xml"); + return icon; + + } + protected NavbarButton newLogoutMenu() { // logout menu final NavbarButton logoutMenu = @@ -288,24 +282,6 @@ JavamelodyPage.class, new StringResourceModel("navbar.javamelody", new StringResourceModel("navbar.springendpoints", this, null)) .setIconType(FontAwesomeIconType.anchor)); - list.add(new MenuBookmarkablePageLink(JminixRedirectPage.class, null, - new StringResourceModel("navbar.jminix", this, null)).setIconType(FontAwesomeIconType.bug)); - - final MenuBookmarkablePageLink halBrowserLink = - new MenuBookmarkablePageLink(HALRedirectPage.class, null, - new StringResourceModel("navbar.halbrowser", this, null)) { - private static final long serialVersionUID = 1L; - - @Override - protected void onComponentTag(final ComponentTag tag) { - super.onComponentTag(tag); - tag.put("target", "_blank"); - } - }; - halBrowserLink.setIconType(FontAwesomeIconType.rss).setEnabled(true); - - list.add(halBrowserLink); - final MenuBookmarkablePageLink uiBrowserLink = new MenuBookmarkablePageLink( UIRedirectPage.class, null, new StringResourceModel("navbar.ui", this, null)) { @@ -331,7 +307,7 @@ protected void onComponentTag(final ComponentTag tag) { }; adminMenu.setIconType(FontAwesomeIconType.cog); - MetaDataRoleAuthorizationStrategy.authorize(adminMenu, Component.RENDER, SecurityConstants.Roles.ROLE_ADMIN); + MetaDataRoleAuthorizationStrategy.authorize(adminMenu, Component.RENDER, SecurityConstants.Roles.ROLE_USER); return adminMenu; } @@ -352,6 +328,8 @@ protected Navbar newNavbar(final String markupId) { * @see org.devgateway.toolkit.forms.wicket.styles.BaseStyles */ navbar.setPosition(Navbar.Position.TOP); + navbar.setBrandImage(new PackageResourceReference(BaseStyles.class, "assets/img/toolkit-logo-0048.png"), + new StringResourceModel("brandImageAltText", this, null)); navbar.setInverted(true); navbar.addComponents(NavbarComponents.transform(Navbar.ComponentPosition.RIGHT, newHomeMenu(), newAdminMenu(), @@ -366,10 +344,13 @@ protected Navbar newNavbar(final String markupId) { public void renderHead(final IHeaderResponse response) { super.renderHead(response); + //favicon + response.render(getFavicon()); + // Load Styles. - response.render(CssHeaderItem.forReference(BaseStyles.INSTANCE)); response.render(CssHeaderItem.forReference(BootstrapCssReference.instance())); response.render(CssHeaderItem.forReference(FontAwesomeCssReference.instance())); + response.render(CssHeaderItem.forReference(BaseStyles.INSTANCE)); // Load Scripts. response.render(RespondJavaScriptReference.headerItem()); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties index 084d268d..5651c713 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/BasePage.properties @@ -20,8 +20,8 @@ navbar.ui=React UI navbar.adminSettings=Settings navbar.springendpoints=Spring Endpoints navbar.lang=Language -navbar.jminix=JMX Console navbar.javamelody=Javamelody +brandImageAltText=DG-Toolkit fileUnit.B=B fileUnit.KB=KB diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html index 8585bf67..bd60a80b 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.html @@ -13,7 +13,10 @@

+
+ + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java index 8c7988b3..e28bf7a1 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.java @@ -5,8 +5,10 @@ import org.apache.wicket.model.StringResourceModel; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.spring.injection.annot.SpringBean; +import org.apache.wicket.validation.validator.RangeValidator; import org.devgateway.toolkit.forms.security.SecurityConstants; import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxToggleBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.TextFieldBootstrapFormComponent; import org.devgateway.toolkit.forms.wicket.page.edit.AbstractEditPage; import org.devgateway.toolkit.persistence.dao.AdminSettings; import org.devgateway.toolkit.persistence.service.AdminSettingsService; @@ -26,6 +28,8 @@ public class EditAdminSettingsPage extends AbstractEditPage { private CheckBoxToggleBootstrapFormComponent rebootServer; + private TextFieldBootstrapFormComponent autosaveTime; + @SpringBean private AdminSettingsService adminSettingsService; @@ -52,5 +56,10 @@ protected void onInitialize() { rebootServer = new CheckBoxToggleBootstrapFormComponent("rebootServer"); editForm.add(rebootServer); + + autosaveTime = new TextFieldBootstrapFormComponent<>("autosaveTime"); + autosaveTime.integer().required(); + autosaveTime.getField().add(RangeValidator.range(1, 60)); + editForm.add(autosaveTime); } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties index ac8cb69d..e412a67c 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/EditAdminSettingsPage.properties @@ -1,3 +1,4 @@ page.title=Admin settings systemTitle=System Settings rebootServer.label=Enable server reboot warning +autosaveTime.label=Autosave Time (minutes) \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java index 66e48448..8a321760 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditPage.java @@ -47,7 +47,7 @@ import org.devgateway.toolkit.forms.wicket.page.BasePage; import org.devgateway.toolkit.persistence.dao.GenericPersistable; import org.devgateway.toolkit.persistence.service.BaseJpaService; -import org.devgateway.toolkit.reporting.spring.util.ReportsCacheService; +import org.devgateway.toolkit.web.util.SettingsUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.dao.DataIntegrityViolationException; @@ -119,8 +119,11 @@ private T newInstance() { @SpringBean private EntityManager entityManager; - @SpringBean(required = false) - private ReportsCacheService reportsCacheService; + @SpringBean + protected SettingsUtils settingsUtils; + +// @SpringBean(required = false) +// private ReportsCacheService reportsCacheService; @SpringBean(required = false) private MarkupCacheService markupCacheService; @@ -134,9 +137,9 @@ public EntityManager getEntityManager() { } public void flushReportingCaches() { - if (reportsCacheService != null) { - reportsCacheService.flushCache(); - } +// if (reportsCacheService != null) { +// reportsCacheService.flushCache(); +// } if (markupCacheService != null) { markupCacheService.flushMarkupCache(); @@ -198,6 +201,9 @@ protected void onEvent(final AjaxRequestTarget target) { return modal; } + protected void afterSaveEntity(final T saveable) { + } + /** * Traverses all fields and refreshes the ones that are not valid, so that * we can see the errors @@ -319,6 +325,8 @@ protected void onSubmit(final AjaxRequestTarget target) { // save the object and go back to the list page T saveable = editForm.getModelObject(); + beforeSaveEntity(saveable); + // saves the entity and flushes the changes jpaService.saveAndFlush(saveable); @@ -329,6 +337,8 @@ protected void onSubmit(final AjaxRequestTarget target) { // we flush the mondrian/wicket/reports cache to ensure it gets rebuilt flushReportingCaches(); + afterSaveEntity(saveable); + // only redirect if redirect is true if (redirectToSelf) { // we need to close the blockUI if it's opened and enable all @@ -443,7 +453,7 @@ protected void updateAjaxAttributes(final AjaxRequestAttributes attributes) { * * @return */ - public SaveEditPageButton getSaveEditPageButton() { + protected SaveEditPageButton getSaveEditPageButton() { return new SaveEditPageButton("save", new StringResourceModel("saveButton", this, null)); } @@ -537,5 +547,10 @@ protected void onInitialize() { if (model != null) { editForm.setCompoundPropertyModel(model); } + + afterLoad(model); + } + + protected void afterLoad(final IModel model) { } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.html b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.html new file mode 100644 index 00000000..efa99fb7 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.html @@ -0,0 +1,90 @@ + + + + + + + +
+
+
+ +
+
+
+ +
+ + Status Label +
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + +
#Status changeCommentUserDate & Time
+ + + + + + + + + +
+
+
+
+ + + + +
+
+ AutoSaveLabel + CheckedOutTo +
+
+ +
+
+
+
+
+ + + + + + + + + + + + + + + diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.java new file mode 100644 index 00000000..ca42f8c4 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.java @@ -0,0 +1,775 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.toolkit.forms.wicket.page.edit; + +import de.agilecoders.wicket.core.markup.html.bootstrap.button.Buttons; +import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.TextContentModal; +import de.agilecoders.wicket.core.markup.html.bootstrap.form.BootstrapCheckbox; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.icon.FontAwesomeIconType; +import de.agilecoders.wicket.extensions.markup.html.bootstrap.ladda.LaddaAjaxButton; +import org.apache.wicket.AttributeModifier; +import org.apache.wicket.Component; +import org.apache.wicket.ajax.AbstractAjaxTimerBehavior; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.authroles.authorization.strategies.role.metadata.MetaDataRoleAuthorizationStrategy; +import org.apache.wicket.event.Broadcast; +import org.apache.wicket.event.IEvent; +import org.apache.wicket.markup.head.IHeaderResponse; +import org.apache.wicket.markup.head.OnDomReadyHeaderItem; +import org.apache.wicket.markup.html.TransparentWebMarkupContainer; +import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.HiddenField; +import org.apache.wicket.markup.html.form.TextArea; +import org.apache.wicket.markup.html.form.TextField; +import org.apache.wicket.markup.html.list.ListItem; +import org.apache.wicket.markup.html.list.ListView; +import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.model.CompoundPropertyModel; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.StringResourceModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.apache.wicket.util.string.Strings; +import org.apache.wicket.util.time.Duration; +import org.apache.wicket.util.visit.IVisit; +import org.apache.wicket.util.visit.IVisitor; +import org.devgateway.toolkit.forms.WebConstants; +import org.devgateway.toolkit.forms.security.SecurityConstants; +import org.devgateway.toolkit.forms.wicket.components.form.BootstrapSubmitButton; +import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.CheckBoxYesNoToggleBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.GenericBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.form.OptionallyRequiredTextAreaFieldComponent; +import org.devgateway.toolkit.forms.wicket.components.form.TextAreaFieldBootstrapFormComponent; +import org.devgateway.toolkit.forms.wicket.components.util.ComponentUtil; +import org.devgateway.toolkit.forms.wicket.components.util.FormSecurityUtil; +import org.devgateway.toolkit.forms.wicket.events.EditingDisabledEvent; +import org.devgateway.toolkit.forms.wicket.page.BasePage; +import org.devgateway.toolkit.persistence.dao.AbstractStatusAuditableEntity; +import org.devgateway.toolkit.persistence.dao.DBConstants; +import org.devgateway.toolkit.persistence.dao.StatusChangedComment; +import org.springframework.util.ObjectUtils; +import org.wicketstuff.datetime.markup.html.basic.DateLabel; +import org.wicketstuff.select2.Select2Choice; + +/** + * @author mpostelnicu + * Page used to make editing easy, extend to get easy access to one entity for editing + */ +public abstract class AbstractEditStatusEntityPage + extends AbstractEditPage implements DefaultValidatorRoleAssignable, ResourceLockable { + + protected Fragment entityButtonsFragment; + + private SaveEditPageButton saveSubmitButton; + + protected SaveEditPageButton submitAndNext; + + private SaveEditPageButton saveApproveButton; + + private SaveEditPageButton saveDraftContinueButton; + + protected SaveEditPageButton revertToDraftPageButton; + + private CheckBoxYesNoToggleBootstrapFormComponent visibleStatusComments; + + private TransparentWebMarkupContainer statusCommentsWrapper; + + private ListView statusComments; + + protected TextAreaFieldBootstrapFormComponent newStatusComment; + + private Label statusLabel; + + private String previousStatus; + + private Label autoSaveLabel; + + private Label checkedOutToLabel; + + private HiddenField verticalPosition; + + private HiddenField maxHeight; + + protected PageParameters afterSubmitNextParameters; + + protected Fragment extraStatusEntityButtonsFragment; + private CheckBoxBootstrapFormComponent removeLock; + + public AbstractEditStatusEntityPage(final PageParameters parameters) { + super(parameters); + + } + + + @Override + public AbstractStatusAuditableEntity getLockableResource() { + return editForm.getModelObject(); + } + + public class ButtonContentModal extends TextContentModal { + private final Buttons.Type buttonType; + private LaddaAjaxButton button; + private IModel buttonModel; + private ModalSaveEditPageButton modalSavePageButton; + + public ButtonContentModal(String markupId, IModel model, IModel buttonModel, + Buttons.Type buttonType) { + super(markupId, model); + addCloseButton(); + this.buttonModel = buttonModel; + this.buttonType = buttonType; + } + + public ButtonContentModal modalSavePageButton(ModalSaveEditPageButton modalSavePageButton) { + this.modalSavePageButton = modalSavePageButton; + return this; + } + + @Override + protected void onInitialize() { + super.onInitialize(); + button = new LaddaAjaxButton("button", buttonType) { + @Override + protected void onSubmit(AjaxRequestTarget target) { + modalSavePageButton.continueSubmit(target); + } + }; + addButton(button); + button.setDefaultFormProcessing(false); + button.setLabel(buttonModel); + } + } + + protected ButtonContentModal createTerminateModal() { + ButtonContentModal buttonContentModal = new ButtonContentModal( + "terminateModal", + Model.of("Are you sure you want to TERMINATE the contracting process?"), + Model.of("TERMINATE"), Buttons.Type.Danger); + return buttonContentModal; + } + + public class ModalSaveEditPageButton extends SaveEditPageButton { + private TextContentModal modal; + + public ModalSaveEditPageButton(String id, IModel model, TextContentModal modal) { + super(id, model); + this.modal = modal; + } + + + @Override + protected String getOnClickScript() { + return WebConstants.DISABLE_FORM_LEAVING_JS; + } + + @Override + protected void onSubmit(AjaxRequestTarget target) { + modal.show(true); + target.add(modal); + } + + public void continueSubmit(AjaxRequestTarget target) { + super.onSubmit(target); + } + } + + @Override + protected void beforeSaveEntity(T saveable) { + super.beforeSaveEntity(saveable); + beforeSaveLockableResource(); + + afterSubmitNextParameters = parametersAfterSubmitAndNext(); + } + + @Override + protected void onInitialize() { + super.onInitialize(); + + if (!canEditLockableResource()) { + setResponsePage(listPageClass); + } + + addAutosaveLabel(); + addCheckedOutTo(); + addRemoveLock(); + addVerticalMaxPositionFields(); + + statusLabel = addStatusLabel(); + editForm.add(statusLabel); + + visibleStatusComments = getVisibleStatusComments(); + editForm.add(visibleStatusComments); + + statusCommentsWrapper = new TransparentWebMarkupContainer("statusCommentsWrapper"); + statusCommentsWrapper.setOutputMarkupId(true); + statusCommentsWrapper.setOutputMarkupPlaceholderTag(true); + statusCommentsWrapper.setVisibilityAllowed(false); + editForm.add(statusCommentsWrapper); + + statusComments = getStatusCommentsListView(); + newStatusComment = getNewStatusCommentField(); + statusCommentsWrapper.add(statusComments); + editForm.add(newStatusComment); + + entityButtonsFragment = new Fragment("extraButtons", "entityButtons", this); + editForm.replace(entityButtonsFragment); + + Fragment fragment = new Fragment("extraStatusEntityButtons", "noButtons", this); + entityButtonsFragment.add(fragment); + + saveSubmitButton = getSaveSubmitPageButton(); + entityButtonsFragment.add(saveSubmitButton); + + submitAndNext = getSubmitAndNextPageButton(); + submitAndNext.setVisibilityAllowed(false); + entityButtonsFragment.add(submitAndNext); + + saveApproveButton = getSaveApprovePageButton(); + entityButtonsFragment.add(saveApproveButton); + + saveDraftContinueButton = getSaveDraftAndContinueButton(); + entityButtonsFragment.add(saveDraftContinueButton); + + revertToDraftPageButton = getRevertToDraftPageButton(); + entityButtonsFragment.add(revertToDraftPageButton); + + applyDraftSaveBehavior(saveButton); + applyDraftSaveBehavior(saveDraftContinueButton); + applyDraftSaveBehavior(revertToDraftPageButton); + + setButtonsPermissions(); + + enableDisableAutosaveFields(null); + } + + @Override + protected void afterSaveEntity(final T saveable) { + super.afterSaveEntity(saveable); + + getPageParameters().set(WebConstants.V_POSITION, verticalPosition.getValue()) + .set(WebConstants.MAX_HEIGHT, maxHeight.getValue()); + } + + @Override + protected void afterLoad(final IModel entityModel) { + super.afterLoad(entityModel); + previousStatus = entityModel.getObject().getStatus(); + } + + @Override + protected void onBeforeRender() { + super.onBeforeRender(); + + checkAndSendEventForDisableEditing(); + + this.statusLabel.setVisibilityAllowed(editForm.getModelObject().getVisibleStatusLabel()); + } + + protected void checkAndSendEventForDisableEditing() { + if (isDisableEditingEvent()) { + send(getPage(), Broadcast.BREADTH, new EditingDisabledEvent()); + } + } + + public boolean isDisableEditingEvent() { + return !Strings.isEqual(editForm.getModelObject().getStatus(), DBConstants.Status.DRAFT) || isViewMode(); + } + + + protected boolean isViewMode() { + return false; +// return SecurityConstants.Action.VIEW +// .equals(permissionEntityRenderableService.getAllowedAccess(this, editForm.getModelObject())); + } + + private void addCheckedOutTo() { + if (getLockableResource().getCheckedOutUser() == null) { + checkedOutToLabel = new Label("checkedOutTo", Model.of("")); + } else { + checkedOutToLabel = new Label("checkedOutTo", + new StringResourceModel("checkedOutToMessage", this) + .setParameters(getCheckedOutUsername())); + checkedOutToLabel.setOutputMarkupPlaceholderTag(true); + checkedOutToLabel.setOutputMarkupId(true); + } + editForm.add(checkedOutToLabel); + } + + private void addRemoveLock() { + removeLock = new CheckBoxBootstrapFormComponent("removeLock"); + removeLock.setVisibilityAllowed(getLockableResource().getCheckedOutUser() != null); + editForm.add(removeLock); + MetaDataRoleAuthorizationStrategy.authorize(removeLock, Component.RENDER, SecurityConstants.Roles.ROLE_ADMIN); + } + + private void addAutosaveLabel() { + autoSaveLabel = new Label("autoSaveLabel", + new StringResourceModel("autoSaveLabelMessage", this).setParameters(settingsUtils.getAutosaveTime())); + autoSaveLabel.setVisibilityAllowed(false); + autoSaveLabel.setOutputMarkupPlaceholderTag(true); + autoSaveLabel.setOutputMarkupId(true); + editForm.add(autoSaveLabel); + } + + private void addVerticalMaxPositionFields() { + verticalPosition = new HiddenField<>("verticalPosition", new Model<>(), Double.class); + verticalPosition.setOutputMarkupId(true); + editForm.add(verticalPosition); + + maxHeight = new HiddenField<>("maxHeight", new Model<>(), Double.class); + maxHeight.setOutputMarkupId(true); + editForm.add(maxHeight); + } + + protected void enableDisableAutosaveFields(final AjaxRequestTarget target) { + addAutosaveBehavior(target); + + saveButton.setEnabled(true); + saveDraftContinueButton.setEnabled(true); + submitAndNext.setEnabled(true); + saveSubmitButton.setEnabled(true); + + if (target != null) { + target.add(saveButton, saveSubmitButton, saveDraftContinueButton, submitAndNext); + } + } + + private void addAutosaveBehavior(final AjaxRequestTarget target) { + // enable autosave + if (!ComponentUtil.isPrintMode() + && Strings.isEqual(editForm.getModelObject().getStatus(), DBConstants.Status.DRAFT)) { + saveDraftContinueButton.add(getAutosaveBehavior()); + autoSaveLabel.setVisibilityAllowed(true); + if (target != null) { + target.add(autoSaveLabel); + } + } + } + + private AbstractAjaxTimerBehavior getAutosaveBehavior() { + final AbstractAjaxTimerBehavior ajaxTimerBehavior = new AbstractAjaxTimerBehavior( + Duration.minutes(settingsUtils.getAutosaveTime())) { + @Override + protected void onTimer(final AjaxRequestTarget target) { + // display block UI message until the page is reloaded + target.prependJavaScript(getShowBlockUICode()); + + // disable all fields from js and lose focus (execute this javascript code before components processed) + target.prependJavaScript("$(document.activeElement).blur();"); + + // invoke autosave from js (execute this javascript code before components processed) + target.prependJavaScript("$('#" + maxHeight.getMarkupId() + "').val($(document).height()); " + + "$('#" + verticalPosition.getMarkupId() + "').val($(window).scrollTop()); " + + "$('#" + saveDraftContinueButton.getMarkupId() + "').click();"); + + // disable all buttons from js + target.prependJavaScript("$('#" + editForm.getMarkupId() + " button').prop('disabled', true);"); + } + }; + + return ajaxTimerBehavior; + } + + private Label addStatusLabel() { + statusLabel = new Label("statusLabel", editForm.getModelObject().getStatus()); + statusLabel.add(new AttributeModifier("class", new Model<>("label " + getStatusLabelClass()))); + statusLabel.setVisibilityAllowed(editForm.getModelObject().getVisibleStatusLabel()); + return statusLabel; + } + + private String getStatusLabelClass() { + if (editForm.getModelObject().getStatus() == null) { + return ""; + } + + switch (editForm.getModelObject().getStatus()) { + case DBConstants.Status.APPROVED: + return "label-success"; + case DBConstants.Status.DRAFT: + return "label-danger"; + case DBConstants.Status.SUBMITTED: + return "label-warning"; + default: + return ""; + } + } + + private CheckBoxYesNoToggleBootstrapFormComponent getVisibleStatusComments() { + final CheckBoxYesNoToggleBootstrapFormComponent checkBoxBootstrapFormComponent = + new CheckBoxYesNoToggleBootstrapFormComponent("visibleStatusComments") { + @Override + protected void onUpdate(final AjaxRequestTarget target) { + statusCommentsWrapper.setVisibilityAllowed(editForm.getModelObject() + .getVisibleStatusComments()); + target.add(statusCommentsWrapper); + } + + @Override + public void onEvent(final IEvent event) { + // do nothing - keep this field enabled + } + }; + checkBoxBootstrapFormComponent.setVisibilityAllowed(!isViewMode()); + return checkBoxBootstrapFormComponent; + } + + private TextAreaFieldBootstrapFormComponent getNewStatusCommentField() { + final TextAreaFieldBootstrapFormComponent comment = + new OptionallyRequiredTextAreaFieldComponent("newStatusComment") { + + @Override + public void onEvent(final IEvent event) { + // do nothing - keep this field enabled + } + }; + comment.setShowTooltip(true); + return comment; + } + + private ListView getStatusCommentsListView() { + final ListView statusComments = new ListView("statusComments") { + @Override + protected void populateItem(final ListItem item) { + item.setModel(new CompoundPropertyModel<>(item.getModel())); + item.add(new Label("commentIdx", item.getIndex())); + item.add(new Label("status")); + item.add(new Label("comment")); + item.add(new Label("createdBy", item.getModelObject().getCreatedBy().get())); + item.add(DateLabel.forDateStyle("created", + Model.of(ComponentUtil + .getDateFromLocalDate(item.getModelObject().getCreatedDate().get().toLocalDate())), + "SS")); + } + }; + statusComments.setReuseItems(true); + statusComments.setOutputMarkupId(true); + + return statusComments; + } + + /** + * Use this function to get the block UI message while the form is saved. + */ + private String getShowBlockUICode() { + return "blockUI('" + + new StringResourceModel("autosave_message", AbstractEditStatusEntityPage.this, null).getString() + + "')"; + } + + /******************************************************************************* + * Buttons Behavior + *******************************************************************************/ + private void applyDraftSaveBehavior(final BootstrapSubmitButton button) { + // disable form validation + button.setDefaultFormProcessing(false); + } + + @Override + protected SaveEditPageButton getSaveEditPageButton() { + final SaveEditPageButton button = new SaveEditPageButton("save", + new StringResourceModel("saveButton", this, null)) { + + @Override + protected String getOnClickScript() { + return WebConstants.DISABLE_FORM_LEAVING_JS; + } + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + editForm.visitChildren(GenericBootstrapFormComponent.class, + new AllowNullForCertainInvalidFieldsVisitor()); + setStatusAppendComment(DBConstants.Status.DRAFT); + super.onSubmit(target); + } + }; + + return button; + } + + private SaveEditPageButton getSaveSubmitPageButton() { + final SaveEditPageButton button = new SaveEditPageButton("saveSubmit", + new StringResourceModel("saveSubmit", this, null)) { + @Override + protected String getOnClickScript() { + return WebConstants.DISABLE_FORM_LEAVING_JS; + } + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + setStatusAppendComment(DBConstants.Status.SUBMITTED); + super.onSubmit(target); + } + }; + + button.setIconType(FontAwesomeIconType.send); + return button; + } + + private SaveEditPageButton getSubmitAndNextPageButton() { + final SaveEditPageButton button = new SaveEditPageButton("submitAndNext", + new StringResourceModel("submitAndNext", this, null)) { + @Override + protected String getOnClickScript() { + return WebConstants.DISABLE_FORM_LEAVING_JS; + } + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + setStatusAppendComment(DBConstants.Status.SUBMITTED); + super.onSubmit(target); + } + + @Override + protected Class getResponsePage() { + return pageAfterSubmitAndNext(); + } + + @Override + protected PageParameters getParameterPage() { + return afterSubmitNextParameters; + } + }; + + button.setIconType(FontAwesomeIconType.tasks); + return button; + } + + /** + * Override this function in order to redirect the user to the next page after clicking on submitAndNext button. + */ + protected Class pageAfterSubmitAndNext() { + return (Class) getPage().getClass(); + } + + /** + * Override this function in order to redirect the user to the next page with parameters + * after clicking on submitAndNext button. + */ + protected PageParameters parametersAfterSubmitAndNext() { + return getPageParameters(); + } + + private SaveEditPageButton getSaveDraftAndContinueButton() { + final SaveEditPageButton button = new SaveEditPageButton("saveContinue", + new StringResourceModel("saveContinue", this, null)) { + + @Override + protected String getOnClickScript() { + return WebConstants.DISABLE_FORM_LEAVING_JS; + } + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + editForm.visitChildren(GenericBootstrapFormComponent.class, + new AllowNullForCertainInvalidFieldsVisitor()); + setStatusAppendComment(DBConstants.Status.DRAFT); + super.onSubmit(target); + } + + @Override + protected Class getResponsePage() { + return (Class) getPage().getClass(); + } + + @Override + protected PageParameters getParameterPage() { + return getPageParameters(); + } + }; + + button.setIconType(FontAwesomeIconType.tasks); + return button; + } + + private SaveEditPageButton getSaveApprovePageButton() { + final SaveEditPageButton saveEditPageButton = new SaveEditPageButton("approve", + new StringResourceModel("approve", this, null)) { + + @Override + protected String getOnClickScript() { + return WebConstants.DISABLE_FORM_LEAVING_JS; + } + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + setStatusAppendComment(DBConstants.Status.APPROVED); + super.onSubmit(target); + } + }; + saveEditPageButton.setIconType(FontAwesomeIconType.thumbs_up); + return saveEditPageButton; + } + + protected SaveEditPageButton getRevertToDraftPageButton() { + final SaveEditPageButton saveEditPageButton = new SaveEditPageButton("revertToDraft", + new StringResourceModel("revertToDraft", this, null)) { + @Override + protected String getOnClickScript() { + return WebConstants.DISABLE_FORM_LEAVING_JS; + } + + @Override + protected void onSubmit(final AjaxRequestTarget target) { + setStatusAppendComment(DBConstants.Status.DRAFT); + editForm.getModelObject().setRemoveLock(true); + super.onSubmit(target); + target.add(editForm); + setButtonsPermissions(); + onAfterRevertToDraft(target); + } + }; + saveEditPageButton.setIconType(FontAwesomeIconType.thumbs_down); + return saveEditPageButton; + } + + protected void onAfterRevertToDraft(AjaxRequestTarget target) { + + } + + protected void setStatusAppendComment(final String status) { + final T saveable = editForm.getModelObject(); + + // do not save an empty comment if previous status is same as current status and comment box is empty + if (status.equals(saveable.getStatus()) && ObjectUtils.isEmpty(saveable.getNewStatusComment())) { + saveable.setStatus(status); + return; + } + saveable.setStatus(status); + + final StatusChangedComment comment = new StatusChangedComment(); + comment.setStatus(status); + comment.setComment(editForm.getModelObject().getNewStatusComment()); + saveable.getStatusComments().add(comment); + } + + protected void setButtonsPermissions() { + addSaveButtonsPermissions(saveButton); + addSaveButtonsPermissions(saveDraftContinueButton); + addSaveButtonsPermissions(submitAndNext); + addSaveButtonsPermissions(saveSubmitButton); + addApproveButtonPermissions(saveApproveButton); + addSaveRevertButtonPermissions(revertToDraftPageButton); + addDeleteButtonPermissions(deleteButton); + + // no need to display the buttons on print view so we overwrite the above permissions + if (ComponentUtil.isPrintMode()) { + saveDraftContinueButton.setVisibilityAllowed(false); + submitAndNext.setVisibilityAllowed(false); + saveSubmitButton.setVisibilityAllowed(false); + saveApproveButton.setVisibilityAllowed(false); + revertToDraftPageButton.setVisibilityAllowed(false); + } + } + + protected void addDeleteButtonPermissions(final Component button) { + MetaDataRoleAuthorizationStrategy.authorize(button, Component.RENDER, SecurityConstants.Roles.ROLE_ADMIN); + button.setVisibilityAllowed(entityId != null && !isViewMode()); + MetaDataRoleAuthorizationStrategy.authorize( + button, Component.RENDER, getCommaCombinedRoles()); + } + + protected void addSaveRevertButtonPermissions(final Component button) { + addDefaultAllButtonsPermissions(button); + MetaDataRoleAuthorizationStrategy.authorize(button, Component.RENDER, getValidatorRole()); + MetaDataRoleAuthorizationStrategy.authorize(button, Component.RENDER, getCommaCombinedRoles()); + button.setVisibilityAllowed(button.isVisibilityAllowed() + && !DBConstants.Status.DRAFT.equals(editForm.getModelObject().getStatus())); + + // additionally normal users should not revert anything that was already validated + if (FormSecurityUtil.isCurrentRoleOnlyUser(getUserRole(), getValidatorRole()) + && DBConstants.Status.APPROVED.equals(editForm.getModelObject().getStatus())) { + button.setVisibilityAllowed(false); + } else + + //admins can revert anything + if (FormSecurityUtil.isCurrentUserAdmin() + && DBConstants.Status.APPROVED.equals(editForm.getModelObject().getStatus())) { + button.setVisibilityAllowed(true); + } + } + + protected void addApproveButtonPermissions(final Component button) { + addDefaultAllButtonsPermissions(button); + MetaDataRoleAuthorizationStrategy.authorize( + button, Component.RENDER, getValidatorRole()); + button.setVisibilityAllowed(button.isVisibilityAllowed() + && DBConstants.Status.SUBMITTED.equals(editForm.getModelObject().getStatus())); + } + + protected void addSaveButtonsPermissions(final Component button) { + addDefaultAllButtonsPermissions(button); + MetaDataRoleAuthorizationStrategy.authorize(button, Component.RENDER, getCommaCombinedRoles()); + button.setVisibilityAllowed(button.isVisibilityAllowed() + && DBConstants.Status.DRAFT.equals(editForm.getModelObject().getStatus())); + } + + + protected void addDefaultAllButtonsPermissions(final Component button) { + MetaDataRoleAuthorizationStrategy.authorize(button, Component.RENDER, SecurityConstants.Roles.ROLE_ADMIN); + } + + + private void scrollToPreviousPosition(final IHeaderResponse response) { + response.render(OnDomReadyHeaderItem.forScript(String.format( + "var vPosition= +%s, mHeight = +%s, cmHeight=$(document).height();" + + "if(mHeight!=0) $(window).scrollTop(vPosition*cmHeight/mHeight)", + getPageParameters().get(WebConstants.V_POSITION).toDouble(0), + getPageParameters().get(WebConstants.MAX_HEIGHT).toDouble(0) + ))); + } + + @Override + public void renderHead(final IHeaderResponse response) { + super.renderHead(response); + + scrollToPreviousPosition(response); + } + + /** + * Allow null saving for draft entities even if the field is required. + * Bypass validation for this purpose. + * + * @author mpostelnicu + */ + public class AllowNullForCertainInvalidFieldsVisitor + implements IVisitor, Void> { + @Override + public void component(final GenericBootstrapFormComponent object, final IVisit visit) { + // we found the GenericBootstrapFormComponent, stop doing useless + // things like traversing inside the GenericBootstrapFormComponent itself + visit.dontGoDeeper(); + + // do not process disabled fields + if (!object.isEnabledInHierarchy() || object.getField() instanceof BootstrapCheckbox) { + return; + } + object.getField().processInput(); + + // we try validate the field + object.getField().validate(); + + // still, if the field is invalid, its input is null, and field is + // of a certain type, we turn + // the input into a null. This helps us to save empty REQUIRED + // fields when saving as draft + if (!object.getField().isValid() && Strings.isEmpty(object.getField().getInput())) { + // for text/select fields we just make the object model null + if (object.getField() instanceof TextField || object.getField() instanceof TextArea + || object.getField() instanceof Select2Choice) { + object.getField().getModel().setObject(null); + } + + } + } + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.properties b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.properties new file mode 100644 index 00000000..22d1a3cc --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/AbstractEditStatusEntityPage.properties @@ -0,0 +1,26 @@ +############################################################################### +# Copyright (c) 2015 Development Gateway, Inc and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the MIT License (MIT) +# which accompanies this distribution, and is available at +# https://opensource.org/licenses/MIT +# +# Contributors: +# Development Gateway - initial API and implementation +############################################################################### +approve=Approve +revertToDraft=Reject to draft +saveButton=Save Draft +saveSubmit=Save & Submit +submitAndNext=Save and Next +saveContinue=Save Draft & Continue +newStatusComment.label=Update Comment +newStatusComment.help=Add a comment to describe this update during validation or data entry. This comment can be useful for other people editing or validating this form. \ + PLEASE NOTE, text entered here will be viewable by anyone with access to this page. +visibleStatusComments.label=View comments and form history +autoSaveLabelMessage=Form auto-saved less than {0,number} minutes ago +checkedOutToMessage=Checked out to user {0}. +removeLock.label=Force form check in +removeLock.help=This will check back in a form that has been checked out by another user. Use this only if you know \ + the user is no longer editing the form and you want to allow other users access to the form. diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/DefaultValidatorRoleAssignable.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/DefaultValidatorRoleAssignable.java new file mode 100644 index 00000000..cd0d4474 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/DefaultValidatorRoleAssignable.java @@ -0,0 +1,17 @@ +package org.devgateway.toolkit.forms.wicket.page.edit; + + +import org.devgateway.toolkit.forms.security.SecurityConstants; + +public interface DefaultValidatorRoleAssignable extends EditorValidatorRoleAssignable { + + @Override + default String getUserRole() { + return SecurityConstants.Roles.ROLE_USER; + } + + @Override + default String getValidatorRole() { + return SecurityConstants.Roles.ROLE_VALIDATOR; + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java index 5d4435a3..8e92a3c3 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditTestFormPage.java @@ -47,7 +47,7 @@ @AuthorizeInstantiation(SecurityConstants.Roles.ROLE_USER) @MountPath("/editTestForm") -public class EditTestFormPage extends AbstractEditPage { +public class EditTestFormPage extends AbstractEditStatusEntityPage { private static final long serialVersionUID = 1L; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditorValidatorRoleAssignable.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditorValidatorRoleAssignable.java new file mode 100644 index 00000000..966d9b7e --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/EditorValidatorRoleAssignable.java @@ -0,0 +1,21 @@ +package org.devgateway.toolkit.forms.wicket.page.edit; + +import com.google.common.collect.Sets; +import org.apache.commons.lang3.StringUtils; + +import java.util.Set; + +public interface EditorValidatorRoleAssignable { + + String getUserRole(); + + String getValidatorRole(); + + default Set getCombinedRoles() { + return Sets.newHashSet(getUserRole(), getValidatorRole()); + } + + default String getCommaCombinedRoles() { + return StringUtils.join(getCombinedRoles(), ","); + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/ResourceLockable.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/ResourceLockable.java new file mode 100644 index 00000000..7aa17e6d --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/edit/ResourceLockable.java @@ -0,0 +1,68 @@ +package org.devgateway.toolkit.forms.wicket.page.edit; + + +import org.apache.commons.lang3.BooleanUtils; +import org.devgateway.toolkit.forms.security.SecurityUtil; +import org.devgateway.toolkit.persistence.dao.AbstractStatusAuditableEntity; +import org.devgateway.toolkit.persistence.dao.DBConstants; +import org.devgateway.toolkit.persistence.dao.Person; + +public interface ResourceLockable { + + AbstractStatusAuditableEntity getLockableResource(); + + default boolean checkoutResource() { + if (!SecurityUtil.isCurrentUserAdmin() && isResourceCheckedOut() && !isCurrentUserLockOwner()) { + return false; + } + getLockableResource().setCheckedOutUser(SecurityUtil.getCurrentAuthenticatedPerson()); + return true; + } + + default boolean canEditLockableResource() { + return SecurityUtil.isCurrentUserAdmin() || getLockableResource().getCheckedOutUser() == null + || isCurrentUserLockOwner(); + } + + default String getCheckedOutUsername() { + if (getLockableResource().getCheckedOutUser() == null) { + return null; + } + return getLockableResource().getCheckedOutUser().getUsername(); + } + + default boolean beforeSaveLockableResource() { + if (BooleanUtils.isTrue(getLockableResource().getRemoveLock())) { + return checkinResource(); + } + if (!DBConstants.Status.DRAFT.equals(getLockableResource().getStatus())) { + return checkinResource(); + } + if (DBConstants.Status.DRAFT.equals(getLockableResource().getStatus()) && !isCurrentUserLockOwner()) { + return checkoutResource(); + } + return false; + } + + default boolean isCurrentUserLockOwner() { + Person currentAuthenticatedPerson = SecurityUtil.getCurrentAuthenticatedPerson(); + Person checkedOutUser = getLockableResource().getCheckedOutUser(); + return checkedOutUser != null && checkedOutUser.equals(currentAuthenticatedPerson); + } + + default boolean isResourceCheckedOut() { + return getLockableResource().getCheckedOutUser() != null; + } + + default boolean checkinResource() { + if (!isResourceCheckedOut()) { + return true; + } else { + if (isCurrentUserLockOwner() || SecurityUtil.isCurrentUserAdmin()) { + getLockableResource().setCheckedOutUser(null); + return true; + } + return false; + } + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java index bee2a7ea..00fac73d 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListPage.java @@ -27,7 +27,7 @@ import org.apache.wicket.extensions.markup.html.repeater.data.table.filter.IFilteredColumn; import org.apache.wicket.markup.html.WebMarkupContainer; import org.apache.wicket.markup.html.form.Form; -import org.apache.wicket.markup.html.panel.Panel; +import org.apache.wicket.markup.html.panel.GenericPanel; import org.apache.wicket.markup.repeater.Item; import org.apache.wicket.model.IModel; import org.apache.wicket.model.Model; @@ -74,7 +74,7 @@ public abstract class AbstractListPage> editPageClass; - private AjaxFallbackBootstrapDataTable dataTable; + protected AjaxFallbackBootstrapDataTable dataTable; protected List> columns; @@ -156,8 +156,10 @@ public void populateItem(final Item> cellItem, final String co add(editPageLink); } - public class ActionPanel extends Panel { + public class ActionPanel extends GenericPanel { private static final long serialVersionUID = 5821419128121941939L; + protected PageParameters pageParameters; + protected BootstrapBookmarkablePageLink editItemPageLink; /** * @param id @@ -174,11 +176,12 @@ public ActionPanel(final String id, final IModel model) { pageParameters.set(WebConstants.PARAM_ID, entity.getId()); } - BootstrapBookmarkablePageLink editPageLink = + editItemPageLink = new BootstrapBookmarkablePageLink<>("edit", editPageClass, pageParameters, Buttons.Type.Info); - editPageLink.setIconType(FontAwesomeIconType.edit).setSize(Size.Small) + editItemPageLink.setIconType(FontAwesomeIconType.edit).setSize(Size.Small) .setLabel(new StringResourceModel("edit", AbstractListPage.this, null)); - add(editPageLink); + add(editItemPageLink); + add(getPrintButton(pageParameters)); diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListStatusEntityPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListStatusEntityPage.java new file mode 100644 index 00000000..462d5a20 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/AbstractListStatusEntityPage.java @@ -0,0 +1,82 @@ +/******************************************************************************* + * Copyright (c) 2015 Development Gateway, Inc and others. + * + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the MIT License (MIT) + * which accompanies this distribution, and is available at + * https://opensource.org/licenses/MIT + * + * Contributors: + * Development Gateway - initial API and implementation + *******************************************************************************/ +package org.devgateway.toolkit.forms.wicket.page.lists; + +import de.agilecoders.wicket.core.markup.html.bootstrap.components.TooltipBehavior; +import org.apache.wicket.model.IModel; +import org.apache.wicket.model.Model; +import org.apache.wicket.model.util.ListModel; +import org.apache.wicket.request.mapper.parameter.PageParameters; +import org.devgateway.toolkit.forms.wicket.components.table.SelectFilteredBootstrapPropertyColumn; +import org.devgateway.toolkit.forms.wicket.page.edit.ResourceLockable; +import org.devgateway.toolkit.persistence.dao.AbstractStatusAuditableEntity; +import org.devgateway.toolkit.persistence.dao.DBConstants; + +import java.util.List; + +/** + * This class can be use to display a list of Entities that have a status {@link AbstractStatusAuditableEntity}. + * + * @author idobre + * @since 2019-04-01 + */ +public abstract class AbstractListStatusEntityPage + extends AbstractListPage { + + + public AbstractListStatusEntityPage(final PageParameters parameters) { + super(parameters); + } + + + public class LockableResourceActionPanel extends ActionPanel implements ResourceLockable { + + /** + * @param id + * @param model + */ + public LockableResourceActionPanel(final String id, final IModel model) { + super(id, model); + editItemPageLink.setEnabled(canEditLockableResource()); + if (getLockableResource().getCheckedOutUser() != null) { + add(new TooltipBehavior(Model.of("Checked out to user " + getCheckedOutUsername()))); + } + } + + @Override + public AbstractStatusAuditableEntity getLockableResource() { + return getModelObject(); + } + } + + @Override + public ActionPanel getActionPanel(final String id, final IModel model) { + return new LockableResourceActionPanel(id, model); + } + + + @Override + protected void onInitialize() { + addStatusColumn(); + + super.onInitialize(); + } + + private List getStatusDropdownValues() { + return DBConstants.Status.ALL_LIST; + } + + private void addStatusColumn() { + columns.add(1, new SelectFilteredBootstrapPropertyColumn<>(new Model<>("Status"), + "status", "status", new ListModel<>(getStatusDropdownValues()), dataTable)); + } +} diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java index 63c31c91..71fb0863 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/lists/ListTestFormPage.java @@ -25,9 +25,9 @@ import org.devgateway.toolkit.persistence.service.TestFormService; import org.wicketstuff.annotation.mount.MountPath; -@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_ADMIN) +@AuthorizeInstantiation(SecurityConstants.Roles.ROLE_USER) @MountPath(value = "/listTestForm") -public class ListTestFormPage extends AbstractListPage { +public class ListTestFormPage extends AbstractListStatusEntityPage { private static final long serialVersionUID = -324298525712620234L; @SpringBean diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/AbstractReportPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/AbstractReportPage.java index afd04050..d08ae58d 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/AbstractReportPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/AbstractReportPage.java @@ -38,27 +38,6 @@ import org.devgateway.toolkit.forms.wicket.page.BasePage; import org.devgateway.toolkit.forms.wicket.styles.BlockUiReportsJavaScript; import org.devgateway.toolkit.forms.wicket.styles.ReportsStyles; -import org.devgateway.toolkit.reporting.ReportUtil; -import org.pentaho.reporting.engine.classic.core.MasterReport; -import org.pentaho.reporting.engine.classic.core.ReportProcessingException; -import org.pentaho.reporting.engine.classic.core.layout.output.AbstractReportProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.pageable.base.PageableReportProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.pageable.pdf.PdfOutputProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.table.base.FlowReportProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.table.base.StreamReportProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.table.html.AllItemsHtmlPrinter; -import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlOutputProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.table.html.HtmlPrinter; -import org.pentaho.reporting.engine.classic.core.modules.output.table.html.StreamHtmlOutputProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.table.rtf.FlowRTFOutputProcessor; -import org.pentaho.reporting.engine.classic.core.modules.output.table.xls.FlowExcelOutputProcessor; -import org.pentaho.reporting.libraries.repository.ContentIOException; -import org.pentaho.reporting.libraries.repository.ContentLocation; -import org.pentaho.reporting.libraries.repository.DefaultNameGenerator; -import org.pentaho.reporting.libraries.repository.file.FileRepository; -import org.pentaho.reporting.libraries.resourceloader.Resource; -import org.pentaho.reporting.libraries.resourceloader.ResourceException; -import org.pentaho.reporting.libraries.resourceloader.ResourceManager; import java.io.ByteArrayOutputStream; import java.io.File; @@ -154,33 +133,33 @@ public void onClick() { @Override public void write(final OutputStream output) throws IOException { - try { - if (canRenderReport()) { - // first try to fetch the report from cache, - // otherwise create the report and cache it - byte[] reportContent = markupCacheService.getPentahoReportFromCache(outputType.name(), - FilenameUtils.getName(AbstractReportPage.this.reportResourceName).replace(".prpt", - ""), - getPageParameters().toString()); - if (reportContent == null) { - ByteArrayOutputStream baos = new ByteArrayOutputStream(); - generateReport(outputType, baos); - reportContent = baos.toByteArray(); - - if (caching) { - markupCacheService - .addPentahoReportToCache(outputType.name(), - FilenameUtils.getName(AbstractReportPage.this.reportResourceName) - .replace(".prpt", ""), - getPageParameters().toString(), reportContent); - } - } - - output.write(reportContent); - } - } catch (IllegalArgumentException | ReportProcessingException e) { - e.printStackTrace(); - } +// try { +// if (canRenderReport()) { +// // first try to fetch the report from cache, +// // otherwise create the report and cache it +// byte[] reportContent = markupCacheService.getPentahoReportFromCache(outputType.name(), +// FilenameUtils.getName(AbstractReportPage.this.reportResourceName).replace(".prpt", +// ""), +// getPageParameters().toString()); +// if (reportContent == null) { +// ByteArrayOutputStream baos = new ByteArrayOutputStream(); +// generateReport(outputType, baos); +// reportContent = baos.toByteArray(); +// +// if (caching) { +// markupCacheService +// .addPentahoReportToCache(outputType.name(), +// FilenameUtils.getName(AbstractReportPage.this.reportResourceName) +// .replace(".prpt", ""), +// getPageParameters().toString(), reportContent); +// } +// } +// +// output.write(reportContent); +// } +// } catch (IllegalArgumentException | ReportProcessingException e) { +// e.printStackTrace(); +// } } }; @@ -219,20 +198,21 @@ public ResourceStreamPanel(final String id, final AbstractReportPage parent) { */ @Override public IResourceStream getMarkupResourceStream(final MarkupContainer container, final Class containerClass) { - StringBuilder panelMarkup = new StringBuilder(); - panelMarkup.append(""); - ByteArrayOutputStream htmlStreamData = new ByteArrayOutputStream(); - try { - if (canRenderReport()) { - generateReport(OutputType.HTML, htmlStreamData); - } - } catch (IllegalArgumentException | ReportProcessingException e) { - e.printStackTrace(); - } - String content = new String(htmlStreamData.toByteArray()); - panelMarkup.append(content); - panelMarkup.append(""); - return new StringResourceStream(panelMarkup.toString()); +// StringBuilder panelMarkup = new StringBuilder(); +// panelMarkup.append(""); +// ByteArrayOutputStream htmlStreamData = new ByteArrayOutputStream(); +// try { +// if (canRenderReport()) { +// generateReport(OutputType.HTML, htmlStreamData); +// } +// } catch (IllegalArgumentException | ReportProcessingException e) { +// e.printStackTrace(); +// } +// String content = new String(htmlStreamData.toByteArray()); +// panelMarkup.append(content); +// panelMarkup.append(""); +// return new StringResourceStream(panelMarkup.toString()); + return null; } @Override @@ -307,22 +287,22 @@ public Component getLazyLoadComponent(final String id) { * * @return the report definition used by thus report generator */ - public MasterReport getReportDefinition() { - try { - // Using the classloader, get the URL to the reportDefinition file - final ClassLoader classloader = this.getClass().getClassLoader(); - final URL reportDefinitionURL = classloader.getResource(reportResourceName); - - // Parse the report file - final ResourceManager resourceManager = new ResourceManager(); - final Resource directly = resourceManager.createDirectly(reportDefinitionURL, MasterReport.class); - - return (MasterReport) directly.getResource(); - } catch (ResourceException e) { - e.printStackTrace(); - } - return null; - } +// public MasterReport getReportDefinition() { +// try { +// // Using the classloader, get the URL to the reportDefinition file +// final ClassLoader classloader = this.getClass().getClassLoader(); +// final URL reportDefinitionURL = classloader.getResource(reportResourceName); +// +// // Parse the report file +// final ResourceManager resourceManager = new ResourceManager(); +// final Resource directly = resourceManager.createDirectly(reportDefinitionURL, MasterReport.class); +// +// return (MasterReport) directly.getResource(); +// } catch (ResourceException e) { +// e.printStackTrace(); +// } +// return null; +// } /** * Returns the set of parameters that will be passed to the report @@ -352,111 +332,112 @@ public MasterReport getReportDefinition() { * indicates an error generating the report */ - public void generateReport(final OutputType outputType, final OutputStream outputStream) - throws IllegalArgumentException, ReportProcessingException { - if (outputStream == null) { - throw new IllegalArgumentException("The output stream was not specified"); - } - - // Get the report and data factory - final MasterReport report = getReportDefinition(); - - // Add any parameters to the report - final Map reportParameters = getReportParameters(); - if (reportParameters == null) { - return; - } - - for (String key : reportParameters.keySet()) { - report.getParameterValues().put(key, reportParameters.get(key)); - } - - // Prepare to generate the report - AbstractReportProcessor reportProcessor = null; - try { - // Greate the report processor for the specified output type - switch (outputType) { - case PDF: - final PdfOutputProcessor targetPdf = new PdfOutputProcessor(report.getConfiguration(), - outputStream, report.getResourceManager()); - reportProcessor = new PageableReportProcessor(report, targetPdf); - reportProcessor.processReport(); - break; - - case EXCEL: - final FlowExcelOutputProcessor targetExcel = new FlowExcelOutputProcessor(report.getConfiguration(), - outputStream, report.getResourceManager()); - reportProcessor = new FlowReportProcessor(report, targetExcel); - reportProcessor.processReport(); - break; - - case RTF: - final FlowRTFOutputProcessor targetRtf = new FlowRTFOutputProcessor(report.getConfiguration(), - outputStream, report.getResourceManager()); - reportProcessor = new FlowReportProcessor(report, targetRtf); - reportProcessor.processReport(); - break; - - case HTML: - ContentLocation targetRoot = null; - File tempDir = null; - try { - - // we manually make the folder to drop all exported html - // files into - tempDir = ReportUtil.createTemporaryDirectory("tmpreport"); - targetRoot = new FileRepository(tempDir).getRoot(); - } catch (ContentIOException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } - - // we create a folder content resource for the entire tmpdir. - // This dir will only hold the fields for this export - FolderContentResource fcr = new FolderContentResource(tempDir); - - // we always have an authenticated web app - AuthenticatedWebApplication authApp = (AuthenticatedWebApplication) getApplication(); - - // we add the folder resource as a shared resource - authApp.getSharedResources().add(tempDir.getName(), fcr); - SharedResourceReference folderResourceReference = new SharedResourceReference(tempDir.getName()); - authApp.mountResource(tempDir.getName(), folderResourceReference); - - final HtmlOutputProcessor outputProcessor = - new StreamHtmlOutputProcessor(report.getConfiguration()); - final HtmlPrinter printer = new AllItemsHtmlPrinter(report.getResourceManager()); - printer.setContentWriter(targetRoot, new DefaultNameGenerator(targetRoot, "index", "html")); - - printer.setDataWriter(targetRoot, new DefaultNameGenerator(targetRoot, "content")); //$NON-NLS-1$ - - // we use a special URL Rewriter that knows how to speak Wicket - // :-) - printer.setUrlRewriter(new WicketResourceURLRewriter(folderResourceReference)); - outputProcessor.setPrinter(printer); - reportProcessor = new StreamReportProcessor(report, outputProcessor); - reportProcessor.processReport(); - - // we plug the html file stream into the output stream - FileInputStream indexFileStream = - new FileInputStream(tempDir.getAbsolutePath() + File.separator + "index.html"); - IOUtils.copy(indexFileStream, outputStream); - indexFileStream.close(); - - break; - - default: - throw new RuntimeException("Unknown output type provided!"); - } - } catch (IOException e) { - e.printStackTrace(); - } finally { - if (reportProcessor != null) { - reportProcessor.close(); - } - } - } +// public void generateReport(final OutputType outputType, final OutputStream outputStream) +// throws IllegalArgumentException, ReportProcessingException { +// if (outputStream == null) { +// throw new IllegalArgumentException("The output stream was not specified"); +// } +// +// // Get the report and data factory +// final MasterReport report = getReportDefinition(); +// +// // Add any parameters to the report +// final Map reportParameters = getReportParameters(); +// if (reportParameters == null) { +// return; +// } +// +// for (String key : reportParameters.keySet()) { +// report.getParameterValues().put(key, reportParameters.get(key)); +// } +// +// // Prepare to generate the report +// AbstractReportProcessor reportProcessor = null; +// try { +// // Greate the report processor for the specified output type +// switch (outputType) { +// case PDF: +// final PdfOutputProcessor targetPdf = new PdfOutputProcessor(report.getConfiguration(), +// outputStream, report.getResourceManager()); +// reportProcessor = new PageableReportProcessor(report, targetPdf); +// reportProcessor.processReport(); +// break; +// +// case EXCEL: +// final FlowExcelOutputProcessor targetExcel = new FlowExcelOutputProcessor( +// report.getConfiguration(), +// outputStream, report.getResourceManager()); +// reportProcessor = new FlowReportProcessor(report, targetExcel); +// reportProcessor.processReport(); +// break; +// +// case RTF: +// final FlowRTFOutputProcessor targetRtf = new FlowRTFOutputProcessor(report.getConfiguration(), +// outputStream, report.getResourceManager()); +// reportProcessor = new FlowReportProcessor(report, targetRtf); +// reportProcessor.processReport(); +// break; +// +// case HTML: +// ContentLocation targetRoot = null; +// File tempDir = null; +// try { +// +// // we manually make the folder to drop all exported html +// // files into +// tempDir = null; //ReportUtil.createTemporaryDirectory("tmpreport"); +// targetRoot = new FileRepository(tempDir).getRoot(); +// } catch (ContentIOException e) { +// e.printStackTrace(); +// } catch (Exception e) { +// e.printStackTrace(); +// } +// +// // we create a folder content resource for the entire tmpdir. +// // This dir will only hold the fields for this export +// FolderContentResource fcr = new FolderContentResource(tempDir); +// +// // we always have an authenticated web app +// AuthenticatedWebApplication authApp = (AuthenticatedWebApplication) getApplication(); +// +// // we add the folder resource as a shared resource +// authApp.getSharedResources().add(tempDir.getName(), fcr); +// SharedResourceReference folderResourceReference = new SharedResourceReference(tempDir.getName()); +// authApp.mountResource(tempDir.getName(), folderResourceReference); +// +// final HtmlOutputProcessor outputProcessor = +// new StreamHtmlOutputProcessor(report.getConfiguration()); +// final HtmlPrinter printer = new AllItemsHtmlPrinter(report.getResourceManager()); +// printer.setContentWriter(targetRoot, new DefaultNameGenerator(targetRoot, "index", "html")); +// +// printer.setDataWriter(targetRoot, new DefaultNameGenerator(targetRoot, "content")); //$NON-NLS-1$ +// +// // we use a special URL Rewriter that knows how to speak Wicket +// // :-) +// printer.setUrlRewriter(new WicketResourceURLRewriter(folderResourceReference)); +// outputProcessor.setPrinter(printer); +// reportProcessor = new StreamReportProcessor(report, outputProcessor); +// reportProcessor.processReport(); +// +// // we plug the html file stream into the output stream +// FileInputStream indexFileStream = +// new FileInputStream(tempDir.getAbsolutePath() + File.separator + "index.html"); +// IOUtils.copy(indexFileStream, outputStream); +// indexFileStream.close(); +// +// break; +// +// default: +// throw new RuntimeException("Unknown output type provided!"); +// } +// } catch (IOException e) { +// e.printStackTrace(); +// } finally { +// if (reportProcessor != null) { +// reportProcessor.close(); +// } +// } +// } @Override public void renderHead(final IHeaderResponse response) { diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/WicketResourceURLRewriter.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/WicketResourceURLRewriter.java index 1a7e1f35..2c1a6515 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/WicketResourceURLRewriter.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/reports/WicketResourceURLRewriter.java @@ -18,18 +18,16 @@ import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.SharedResourceReference; import org.devgateway.toolkit.forms.util.FolderContentResource; -import org.pentaho.reporting.engine.classic.core.modules.output.table.html.URLRewriteException; -import org.pentaho.reporting.engine.classic.core.modules.output.table.html.URLRewriter; -import org.pentaho.reporting.libraries.repository.ContentEntity; /** * @author mpostelnicu This {@link URLRewriter} will translate local folder * resources into wicket encoded resources using the * {@link SharedResourceReference} to {@link FolderContentResource} */ -public class WicketResourceURLRewriter implements URLRewriter { +public class WicketResourceURLRewriter { + //implements URLRewriter - private SharedResourceReference folderResourceReference; + private final SharedResourceReference folderResourceReference; /* * (non-Javadoc) @@ -44,11 +42,11 @@ public WicketResourceURLRewriter(final SharedResourceReference folderResourceRef this.folderResourceReference = folderResourceReference; } - @Override - public String rewrite(final ContentEntity sourceDocument, final ContentEntity dataEntity) - throws URLRewriteException { - PageParameters parameters = new PageParameters(); - parameters.add(FolderContentResource.PARAM_FILE_NAME, dataEntity.getName()); - return RequestCycle.get().urlFor(folderResourceReference, parameters).toString(); - } +// @Override +// public String rewrite(final ContentEntity sourceDocument, final ContentEntity dataEntity) +// throws URLRewriteException { +// PageParameters parameters = new PageParameters(); +// parameters.add(FolderContentResource.PARAM_FILE_NAME, dataEntity.getName()); +// return RequestCycle.get().urlFor(folderResourceReference, parameters).toString(); +// } } diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java index 351539a0..9d68a9ee 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/EditUserPage.java @@ -11,7 +11,7 @@ *******************************************************************************/ package org.devgateway.toolkit.forms.wicket.page.user; -import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.wicket.Component; import org.apache.wicket.Page; import org.apache.wicket.ajax.AjaxRequestTarget; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java index d76bb293..28d37ec8 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/page/user/LoginPage.java @@ -16,7 +16,7 @@ import de.agilecoders.wicket.core.markup.html.bootstrap.common.NotificationPanel; import de.agilecoders.wicket.core.markup.html.bootstrap.form.BootstrapForm; -import org.apache.commons.lang.BooleanUtils; +import org.apache.commons.lang3.BooleanUtils; import org.apache.wicket.ajax.AjaxRequestTarget; import org.apache.wicket.authroles.authentication.AbstractAuthenticatedWebSession; import org.apache.wicket.extensions.ajax.markup.html.IndicatingAjaxButton; diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaServiceDataProvider.java b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaServiceDataProvider.java index 075e6888..578845a1 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaServiceDataProvider.java +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/providers/SortableJpaServiceDataProvider.java @@ -58,7 +58,9 @@ protected Sort translateSort() { if (getSort() == null) { return null; } - return new Sort(getSort().isAscending() ? Direction.ASC : Direction.DESC, getSort().getProperty()); + + return Sort.by(getSort().isAscending() ? Direction.ASC : Direction.DESC, + getSort().getProperty()); } /** diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css index 8a2bca13..a9286a7b 100644 --- a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/BaseStyles.css @@ -42,6 +42,10 @@ body { margin-bottom: 20px; } +.navbar-brand { + padding: 0px; +} + .mainContainer { /* * The main page content should have some height. @@ -85,6 +89,7 @@ body { .autosave { display: block; margin-bottom: 12px; + color: #ADADAD; } hr.edit-separator { diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/assets/img/icons/toolkit-favicon.svg b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/assets/img/icons/toolkit-favicon.svg new file mode 100644 index 00000000..cd261bc4 --- /dev/null +++ b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/assets/img/icons/toolkit-favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/assets/img/toolkit-logo-0048.png b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/assets/img/toolkit-logo-0048.png new file mode 100644 index 00000000..54a0b2fe Binary files /dev/null and b/forms/src/main/java/org/devgateway/toolkit/forms/wicket/styles/assets/img/toolkit-logo-0048.png differ diff --git a/persistence/pom.xml b/persistence/pom.xml index 7983af04..e79f290f 100644 --- a/persistence/pom.xml +++ b/persistence/pom.xml @@ -19,12 +19,12 @@ UTF-8 org.devgateway.toolkit.persistence.spring.PersistenceApplication 1.8 - 2.10.6 + 3.8.1 0.3.8 3.2.1 28.1-jre - 1.9.3 - 6.1.0.Final + 1.9.4 + 6.1.4.Final @@ -73,11 +73,16 @@ - net.sf.ehcache + org.ehcache ehcache ${ehcache.version} + + org.springframework.boot + spring-boot-starter-cache + + org.springframework spring-context-support @@ -106,13 +111,12 @@ org.hibernate - hibernate-ehcache - - - ehcache-core - net.sf.ehcache - - + hibernate-jcache + + + + javax.cache + cache-api diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/application.properties b/persistence/src/main/java/org/devgateway/toolkit/persistence/application.properties index f1c602a5..f95731a7 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/application.properties +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/application.properties @@ -33,7 +33,8 @@ spring.servlet.multipart.enabled = false spring.jpa.properties.org.hibernate.envers.global_with_modified_flag=true spring.jpa.hibernate.ddl-auto=update -spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory +spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.jcache.JCacheRegionFactory +spring.jpa.properties.hibernate.javax.cache.missing_cache_strategy=create spring.datasource.max-active=3000 spring.datasource.max-idle=8 spring.datasource.min-idle=8 @@ -46,5 +47,7 @@ spring.datasource.transaction-isolation=2 dg-toolkit.datasource.jndi-name=toolkitDS dg-toolkit.derby.port=1527 +spring.cache.jcache.config=classpath:ehcache.xml + spring.data.rest.base-path=/rest spring.profiles.active=integration diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AbstractStatusAuditableEntity.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AbstractStatusAuditableEntity.java new file mode 100644 index 00000000..a7399f86 --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AbstractStatusAuditableEntity.java @@ -0,0 +1,109 @@ +package org.devgateway.toolkit.persistence.dao; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.envers.Audited; + +import javax.persistence.CascadeType; +import javax.persistence.FetchType; +import javax.persistence.ManyToOne; +import javax.persistence.MappedSuperclass; +import javax.persistence.OneToMany; +import javax.persistence.OrderColumn; +import javax.persistence.Transient; +import javax.validation.constraints.NotNull; +import java.util.ArrayList; +import java.util.List; + +@MappedSuperclass +public abstract class AbstractStatusAuditableEntity extends AbstractAuditableEntity implements Statusable { + @NotNull + @Audited + private String status = DBConstants.Status.DRAFT; + + @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY) + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + @OrderColumn(name = "index") + @JsonIgnore + protected List statusComments = new ArrayList<>(); + + @Transient + @JsonIgnore + private String newStatusComment; + + @JsonIgnore + @Transient + private Boolean removeLock = false; + + @Transient + @JsonIgnore + private Boolean visibleStatusComments = false; + + @Transient + @JsonIgnore + private Boolean visibleStatusLabel = true; + + @JsonIgnore + @ManyToOne(fetch = FetchType.LAZY) + @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) + private Person checkedOutUser; + + @Override + public String getStatus() { + return status; + } + + public void setStatus(final String status) { + this.status = status; + } + + public List getStatusComments() { + return statusComments; + } + + public void setStatusComments(final List statusComments) { + this.statusComments = statusComments; + } + + public String getNewStatusComment() { + return newStatusComment; + } + + public void setNewStatusComment(final String newStatusComment) { + this.newStatusComment = newStatusComment; + } + + public Boolean getVisibleStatusComments() { + return visibleStatusComments; + } + + public void setVisibleStatusComments(final Boolean visibleStatusComments) { + this.visibleStatusComments = visibleStatusComments; + } + + public Boolean getVisibleStatusLabel() { + return visibleStatusLabel; + } + + public void setVisibleStatusLabel(final Boolean visibleStatusLabel) { + this.visibleStatusLabel = visibleStatusLabel; + + } + + public Person getCheckedOutUser() { + return checkedOutUser; + } + + public void setCheckedOutUser(final Person checkedOutUser) { + this.checkedOutUser = checkedOutUser; + } + + public Boolean getRemoveLock() { + return removeLock; + } + + public void setRemoveLock(Boolean removeLock) { + this.removeLock = removeLock; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java index 919bd13b..b43751b8 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/AdminSettings.java @@ -19,6 +19,8 @@ public class AdminSettings extends AbstractAuditableEntity { private static final long serialVersionUID = -1051140524022133178L; private Boolean rebootServer = false; + private Integer autosaveTime; + @Override public AbstractAuditableEntity getParent() { return null; @@ -31,4 +33,12 @@ public Boolean getRebootServer() { public void setRebootServer(final Boolean rebootServer) { this.rebootServer = rebootServer; } + + public Integer getAutosaveTime() { + return autosaveTime; + } + + public void setAutosaveTime(Integer autosaveTime) { + this.autosaveTime = autosaveTime; + } } diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/DBConstants.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/DBConstants.java index 73926da0..47a40fd9 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/DBConstants.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/DBConstants.java @@ -11,12 +11,25 @@ *******************************************************************************/ package org.devgateway.toolkit.persistence.dao; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + public final class DBConstants { private DBConstants() { } + public static final class Status { + public static final String DRAFT = "DRAFT"; + public static final String SUBMITTED = "SUBMITTED"; + public static final String APPROVED = "APPROVED"; + + public static final String[] ALL = {DRAFT, SUBMITTED, APPROVED}; + public static final List ALL_LIST = Collections.unmodifiableList(Arrays.asList(ALL)); + } + public static final int MAX_DEFAULT_TEXT_LENGTH = 32000; public static final int STD_DEFAULT_TEXT_LENGTH = 255; public static final int MAX_DEFAULT_TEXT_LENGTH_ONE_LINE = 3000; diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/StatusChangedComment.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/StatusChangedComment.java new file mode 100644 index 00000000..f3cd6a2a --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/StatusChangedComment.java @@ -0,0 +1,42 @@ +package org.devgateway.toolkit.persistence.dao; + +import org.hibernate.annotations.Cache; +import org.hibernate.annotations.CacheConcurrencyStrategy; +import org.hibernate.envers.Audited; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.Index; +import javax.persistence.Table; + +@Cache(usage = CacheConcurrencyStrategy.READ_WRITE) +@Entity +@Audited +@Table(indexes = {@Index(columnList = "status")}) +public class StatusChangedComment extends AbstractAuditableEntity { + private String status; + + @Column(length = DBConstants.MAX_DEFAULT_TEXT_AREA) + private String comment; + + @Override + public AbstractAuditableEntity getParent() { + return null; + } + + public String getStatus() { + return status; + } + + public void setStatus(final String status) { + this.status = status; + } + + public String getComment() { + return comment; + } + + public void setComment(final String comment) { + this.comment = comment; + } +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Statusable.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Statusable.java new file mode 100644 index 00000000..b8cbdd2d --- /dev/null +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/Statusable.java @@ -0,0 +1,15 @@ +package org.devgateway.toolkit.persistence.dao; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author mihai + *

+ * Assigned to objects that provide a status, in our case, objects derived from + * {@link AbstractStatusAuditableEntity} + */ +public interface Statusable { + + String getStatus(); + +} diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java index cc2441d5..c37960c4 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/dao/TestForm.java @@ -41,7 +41,7 @@ @Cache(usage = CacheConcurrencyStrategy.READ_WRITE) @Entity @Audited -public class TestForm extends AbstractAuditableEntity { +public class TestForm extends AbstractStatusAuditableEntity { private static final long serialVersionUID = 1L; diff --git a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java index ac41640a..94ff4d9b 100644 --- a/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java +++ b/persistence/src/main/java/org/devgateway/toolkit/persistence/spring/CacheConfiguration.java @@ -14,12 +14,14 @@ */ package org.devgateway.toolkit.persistence.spring; -import net.sf.ehcache.management.ManagementService; +import org.hibernate.cache.jcache.ConfigSettings; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.cache.CacheManager; +import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer; import org.springframework.cache.annotation.EnableCaching; import org.springframework.cache.ehcache.EhCacheCacheManager; import org.springframework.cache.ehcache.EhCacheManagerFactoryBean; +import org.springframework.cache.jcache.JCacheCacheManager; +import org.springframework.cache.jcache.JCacheManagerFactoryBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Profile; @@ -27,6 +29,7 @@ import org.springframework.data.auditing.DateTimeProvider; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +import javax.cache.CacheManager; import javax.management.MBeanServer; import java.time.ZonedDateTime; import java.util.Optional; @@ -49,23 +52,9 @@ public DateTimeProvider dateTimeProvider() { private MBeanServer mbeanServer; @Bean - public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() { - final EhCacheManagerFactoryBean ehCacheManagerFactoryBean = new EhCacheManagerFactoryBean(); - ehCacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml")); - ehCacheManagerFactoryBean.setShared(true); - return ehCacheManagerFactoryBean; + public HibernatePropertiesCustomizer hibernateSecondLevelCacheCustomizer(final JCacheCacheManager cacheManager) { + return (properties) -> properties.put(ConfigSettings.CACHE_MANAGER, cacheManager.getCacheManager()); } - @Bean - public CacheManager cacheManager(final EhCacheManagerFactoryBean factory) { - return new EhCacheCacheManager(factory.getObject()); - } - @Bean(destroyMethod = "dispose", initMethod = "init") - @Profile("!integration") - public ManagementService ehCacheManagementService(final EhCacheManagerFactoryBean factory) { - final ManagementService managementService = - new ManagementService(factory.getObject(), mbeanServer, true, true, true, true); - return managementService; - } } diff --git a/persistence/src/main/resources/ehcache.xml b/persistence/src/main/resources/ehcache.xml index 30aee7b9..828f8f14 100644 --- a/persistence/src/main/resources/ehcache.xml +++ b/persistence/src/main/resources/ehcache.xml @@ -1,53 +1,17 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + 100000 + + + + + 100000 + + + + \ No newline at end of file diff --git a/persistence/src/main/resources/liquibase-changelog.xml b/persistence/src/main/resources/liquibase-changelog.xml index 7ebe70e1..3250e485 100644 --- a/persistence/src/main/resources/liquibase-changelog.xml +++ b/persistence/src/main/resources/liquibase-changelog.xml @@ -60,4 +60,28 @@ + + + + select count(*) > 0 from test_form + + + + + status is null + + + + + + + select autosave_time is null from admin_settings + + + + + autosave_time is null + + + \ No newline at end of file diff --git a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileImportDefaultTest.java b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileImportDefaultTest.java index 4c3e2ac1..d5e642a1 100644 --- a/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileImportDefaultTest.java +++ b/persistence/src/test/java/org/devgateway/toolkit/persistence/excel/ExcelFileImportDefaultTest.java @@ -125,6 +125,11 @@ public String[] getBeanNamesForType(ResolvableType resolvableType) { return new String[0]; } + @Override + public String[] getBeanNamesForType(ResolvableType resolvableType, boolean b, boolean b1) { + return new String[0]; + } + @Override public String[] getBeanNamesForType(Class aClass) { return new String[0]; @@ -220,6 +225,11 @@ public Class getType(String s) throws NoSuchBeanDefinitionException { return null; } + @Override + public Class getType(String s, boolean b) throws NoSuchBeanDefinitionException { + return null; + } + @Override public String[] getAliases(String s) { return new String[0]; diff --git a/pom.xml b/pom.xml index acd77c89..4c511dfb 100644 --- a/pom.xml +++ b/pom.xml @@ -23,8 +23,8 @@ UTF-8 1.8 - 3.8.3 - 2.1.11.RELEASE + 3.8.9 + 2.2.6.RELEASE 10.14.2.0 4.0.1 devgateway/toolkit @@ -32,13 +32,14 @@ 3.0.0 2.22.1 2.5.3 + 3.27.0-GA persistence web ui - reporting + forms persistence-mongodb @@ -76,6 +77,11 @@ + + jcenter-snapshots + jcenter + http://oss.jfrog.org/artifactory/oss-snapshot-local/ + jcenter https://jcenter.bintray.com/ @@ -120,6 +126,16 @@ derby ${derby.version} + + org.javassist + javassist + ${javassist.version} + + + org.springframework.plugin + spring-plugin-core + 2.0.0.RELEASE + diff --git a/reporting/pom.xml b/reporting/pom.xml index e1ac42d5..749837dd 100644 --- a/reporting/pom.xml +++ b/reporting/pom.xml @@ -221,6 +221,12 @@ org.apache.xmlgraphics batik-bridge 1.7 + + + xalan + xalan + + diff --git a/web/pom.xml b/web/pom.xml index 48bae37d..0aa14c9f 100644 --- a/web/pom.xml +++ b/web/pom.xml @@ -18,8 +18,7 @@ org.devgateway.toolkit.web.spring.WebApplication 1.8 1.4 - 2.9.2 - 1.2.0 + 3.0.0-SNAPSHOT @@ -78,12 +77,6 @@ test - - org.jminix - jminix - ${jminix.version} - - de.flapdoodle.embed de.flapdoodle.embed.mongo @@ -136,6 +129,12 @@ ${swagger.version} + + io.springfox + springfox-spring-webmvc + ${swagger.version} + + io.springfox springfox-swagger-ui diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/JMXConfiguration.java b/web/src/main/java/org/devgateway/toolkit/web/spring/JMXConfiguration.java deleted file mode 100644 index 410f55a8..00000000 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/JMXConfiguration.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.devgateway.toolkit.web.spring; - -import org.jminix.console.application.MiniConsoleApplication; -import org.jminix.console.servlet.SpringMiniConsoleServlet; -import org.jminix.server.WebSpringServerConnectionProvider; -import org.springframework.boot.web.servlet.ServletRegistrationBean; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; - -@Configuration -public class JMXConfiguration { - - @Bean - public WebSpringServerConnectionProvider jMiniXConnectionProvider() { - return new WebSpringServerConnectionProvider(); - } - - @Bean - public MiniConsoleApplication miniConsoleApplication() { - final MiniConsoleApplication mca = new MiniConsoleApplication(); - mca.setServerConnectionProvider(jMiniXConnectionProvider()); - return mca; - } - - @Bean - public ServletRegistrationBean jminiXServletRegistration(final MiniConsoleApplication miniConsoleApplication) { - final ServletRegistrationBean registration = new ServletRegistrationBean(new SpringMiniConsoleServlet()); - registration.addUrlMappings("/jminix/*"); - return registration; - } -} diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/SwaggerConfig.java b/web/src/main/java/org/devgateway/toolkit/web/spring/SwaggerConfig.java index 36f6466c..eaeedab5 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/SwaggerConfig.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/SwaggerConfig.java @@ -7,16 +7,16 @@ import springfox.documentation.service.ApiInfo; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; -import springfox.documentation.swagger2.annotations.EnableSwagger2; +import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc; import static springfox.documentation.builders.PathSelectors.regex; @Configuration -@EnableSwagger2 +@EnableSwagger2WebMvc public class SwaggerConfig { @Bean public Docket yaliApi() { - return new Docket(DocumentationType.SWAGGER_2).groupName("Api").apiInfo(yaliApiInfo()) + return new Docket(DocumentationType.SWAGGER_2).groupName("Api").apiInfo(apiInfo()) .select().apis(RequestHandlerSelectors.any()).paths(regex("/api/.*")).build(); } @@ -26,9 +26,9 @@ public Docket manageApi() { .select().apis(RequestHandlerSelectors.any()).paths(regex("/manage/.*")).build(); } - private ApiInfo yaliApiInfo() { - return new ApiInfoBuilder().title("Application API") - .description("These endpoints are used to feed reports").license("MIT License") + private ApiInfo apiInfo() { + return new ApiInfoBuilder().title("DG-Toolkit Application API") + .description("Endpoints description").license("MIT License") .licenseUrl("https://opensource.org/licenses/MIT").version("1.0").build(); } diff --git a/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java b/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java index a892d722..688896fc 100644 --- a/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java +++ b/web/src/main/java/org/devgateway/toolkit/web/spring/WebSecurityConfig.java @@ -66,6 +66,7 @@ public HttpFirewall allowUrlEncodedSlashHttpFirewall() { final StrictHttpFirewall firewall = new StrictHttpFirewall(); firewall.setAllowUrlEncodedSlash(true); firewall.setAllowSemicolon(true); + firewall.setAllowUrlEncodedDoubleSlash(true); return firewall; } diff --git a/web/src/main/java/org/devgateway/toolkit/web/util/SettingsUtils.java b/web/src/main/java/org/devgateway/toolkit/web/util/SettingsUtils.java new file mode 100644 index 00000000..a2ee7cfe --- /dev/null +++ b/web/src/main/java/org/devgateway/toolkit/web/util/SettingsUtils.java @@ -0,0 +1,83 @@ +package org.devgateway.toolkit.web.util; + +import org.devgateway.toolkit.persistence.dao.AdminSettings; +import org.devgateway.toolkit.persistence.repository.AdminSettingsRepository; +import org.devgateway.toolkit.persistence.service.AdminSettingsService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.util.ObjectUtils; + +import java.util.List; + +/** + * @author idobre + * @since 6/22/16 + */ +@Service +public class SettingsUtils { + protected static Logger logger = LoggerFactory.getLogger(SettingsUtils.class); + + public static final int AUTOSAVE_TIME_DEFAULT = 10; + + @Autowired + private AdminSettingsService adminSettingsService; + + private AdminSettings setting; + + @Value("${googleAnalyticsTrackingId:#{null}}") + private String googleAnalyticsTrackingId; + + public String getGoogleAnalyticsTrackingId() { + return googleAnalyticsTrackingId; + } + + public int getAutosaveTime() { + init(); + if (ObjectUtils.isEmpty(setting.getAutosaveTime())) { + return AUTOSAVE_TIME_DEFAULT; + } + return setting.getAutosaveTime(); + } + + public boolean getRebootServer() { + init(); + if (setting.getRebootServer() == null) { + return false; + } + return setting.getRebootServer(); + } + + public AdminSettings getSetting() { + init(); + return setting; + } + + private void init() { + final List list = adminSettingsService.findAll(); + if (list.size() == 0) { + setting = new AdminSettings(); + } else { + setting = list.get(0); + } + } + + + public static final String DEFAULT_LANGUAGE = "en_US"; + + @Autowired + private AdminSettingsRepository adminSettingsRepository; + + public AdminSettings getSettings() { + List list = adminSettingsRepository.findAll(); + if (list.size() == 0) { + return new AdminSettings(); + } else { + return list.get(0); + } + } + + +}