All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.imixs.marty.profile.UserController Maven / Gradle / Ivy

/*******************************************************************************
 *  Imixs Workflow 
 *  Copyright (C) 2001, 2011 Imixs Software Solutions GmbH,  
 *  http://www.imixs.com
 *  
 *  This program is free software; you can redistribute it and/or 
 *  modify it under the terms of the GNU General Public License 
 *  as published by the Free Software Foundation; either version 2 
 *  of the License, or (at your option) any later version.
 *  
 *  This program is distributed in the hope that it will be useful, 
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 
 *  General Public License for more details.
 *  
 *  You can receive a copy of the GNU General Public
 *  License at http://www.gnu.org/licenses/gpl.html
 *  
 *  Project: 
 *  	http://www.imixs.org
 *  	http://java.net/projects/imixs-workflow
 *  
 *  Contributors:  
 *  	Imixs Software Solutions GmbH - initial API and implementation
 *  	Ralph Soika - Software Developer
 *******************************************************************************/

package org.imixs.marty.profile;

import java.io.Serializable;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.ListIterator;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.logging.Logger;

import jakarta.annotation.PostConstruct;
import jakarta.ejb.EJB;
import jakarta.ejb.EJBException;
import jakarta.enterprise.context.SessionScoped;
import jakarta.enterprise.event.Event;
import jakarta.enterprise.event.Observes;
import jakarta.faces.context.FacesContext;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import jakarta.inject.Provider;
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.imixs.marty.security.UserGroupService;
import org.imixs.workflow.FileData;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AccessDeniedException;
import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.ProcessingErrorException;
import org.imixs.workflow.faces.data.WorkflowController;
import org.imixs.workflow.faces.data.WorkflowEvent;
import org.imixs.workflow.faces.util.LoginController;

/**
 * This backing beans handles the Profile entity for the current user and
 * provides a application scoped access to all other profiles through the
 * ProfileService EJB.
 * 
 * A new user profile will be created automatically if no profile yet exists!
 * The user is identified by its principal user name. This name is mapped to the
 * attribute txtname.
 * 
 * The UserController provides the user 'locale' and 'language' which is used in
 * JSF Pages to display pages using the current user settings.
 * 
 * With the methods mark() and unmark() workitems can be added into the users
 * profile favorite list.
 * 

* The controller allows to store up to 50 favorite workitem IDs in the profile. * * * @author rsoika */ @Named("userController") @SessionScoped public class UserController implements Serializable { public static final String LINK_PROPERTY = "$workitemref"; public static final String LINK_PROPERTY_DEPRECATED = "txtworkitemref"; public final static String ITEM_USER_ICON = "user.icon"; public final static String ITEM_SIGNATURE_IMAGE = "signature.image"; public final static int MAX_FAVORITE_ENTRIES = 50; public final static int UPDATE_PROJECT_ACTIVITY_ID = 10; public final static String DEFAULT_LOCALE = "de_DE"; public final static String COOKIE_LOCALE = "imixs.workflow.locale"; @EJB protected ProfileService profileService; @EJB protected UserGroupService userGroupService; @EJB protected WorkflowService workflowService; @Inject protected LoginController loginController; @Inject @ConfigProperty(name = "profile.login.event", defaultValue = "0") private Provider profileLoginEvent; @Inject protected WorkflowController workflowController; @Inject protected Event profileEvents; private static final long serialVersionUID = 1L; private ItemCollection workitem = null; private boolean profileLoaded = false; private Locale locale; private static Logger logger = Logger.getLogger(UserController.class.getName()); public UserController() { super(); } /** * The init method is used to load a user profile or automatically create a new * one if no profile for the user is available. A new Profile will be filled * with default values. * * This method did not use the internal cache of the ProfileService to lookup * the user profile, to make sure that the entity is uptodate when a user logs * in. * * @throws ProcessingErrorException * @throws AccessDeniedException */ @PostConstruct public void init() throws AccessDeniedException, ProcessingErrorException { // test user is logged-in and automatically create profile if no profile // exists yet if (this.loginController.isAuthenticated() && !profileLoaded) { // try to load the profile for the current user ItemCollection profile = profileService.lookupProfileById(loginController.getUserPrincipal()); if (profile == null || profile.getModelVersion().isEmpty()) { try { profile = profileService.createProfile(loginController.getUserPrincipal(), getLocale().toString()); } catch (RuntimeException | PluginException | ModelException e) { logger.severe("unable to create profile for userid '" + loginController.getUserPrincipal() + "': " + e.getMessage()); // logout user!! logger.severe("logout current userid '" + loginController.getUserPrincipal() + "'..."); loginController.doLogout(null); throw new ProcessingErrorException(UserController.class.getName(), ProcessingErrorException.INVALID_WORKITEM, " unable to create profile!", e); } } else { // check if profile.login.event is defined logger.fine("profile.login.event=" + profileLoginEvent); if (profileLoginEvent.get() > 0) { // fire ProfileEvent on login so that a client can intercept.... if (profileEvents != null) { ProfileEvent event = new ProfileEvent(loginController.getUserPrincipal(), profile, ProfileEvent.ON_PROFILE_LOGIN); profileEvents.fire(event); profile = event.getProfile(); } else { logger.warning("CDI Support is missing - ProfileEvent will not be fired"); } // process profile... profile.setEventID(profileLoginEvent.get()); try { profile = workflowService.processWorkItem(profile); } catch (PluginException | ModelException | ProcessingErrorException | EJBException e) { logger.warning("Unable to process profile.login.event=" + profileLoginEvent + " - please check configuration!"); } } } this.setWorkitem(profile); profileLoaded = true; // Now reset current locale based on the profile information updateLocaleFromProfile(); logger.info("profile '" + loginController.getUserPrincipal() + "' initialized."); } } /** * This method returns the current users userprofile entity. The method verifies * if the profile was yet loaded. if not the method tries to initiate the * profile - see method init(); * * @return * @throws ProcessingErrorException * @throws AccessDeniedException */ public ItemCollection getWorkitem() throws AccessDeniedException, ProcessingErrorException { // test if current users profile was loaded if (!profileLoaded) init(); if (workitem == null) workitem = new ItemCollection(); return workitem; } public void setWorkitem(ItemCollection aworkitem) { this.workitem = aworkitem; } /** * This method returns the current user locale. If the user is not logged in the * method try to get the locale out from the cookie. If no cockie is set the * method defaults to "de_DE" * * @return - ISO Locale format */ public Locale getLocale() { // if no locale is set try to get it from cookie or set default if (locale == null) { FacesContext facesContext = FacesContext.getCurrentInstance(); HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext() .getRequest(); String cookieName = null; Cookie cookie[] = ((HttpServletRequest) facesContext.getExternalContext().getRequest()).getCookies(); if (cookie != null && cookie.length > 0) { for (int i = 0; i < cookie.length; i++) { cookieName = cookie[i].getName(); if (cookieName.equals(COOKIE_LOCALE)) { String sLocale = cookie[i].getValue(); if (sLocale != null && !"".equals(sLocale)) { // split locale StringTokenizer stLocale = new StringTokenizer(sLocale, "_"); if (stLocale.countTokens() == 1) { // only language variant String sLang = stLocale.nextToken(); String sCount = sLang.toUpperCase(); locale = new Locale(sLang, sCount); } else { // language and country String sLang = stLocale.nextToken(); String sCount = stLocale.nextToken(); locale = new Locale(sLang, sCount); } } break; } } } // still no value found? - default to "en" if (locale == null || "".equals(locale.getLanguage())) { Locale ldefault = request.getLocale(); if (ldefault != null) { locale = ldefault; } else { locale = new Locale(DEFAULT_LOCALE); } } } return locale; } public void setLocale(Locale alocale) { if (alocale == null || "".equals(alocale)) locale = new Locale(DEFAULT_LOCALE); else this.locale = alocale; // update cookie HttpServletResponse response = (HttpServletResponse) FacesContext.getCurrentInstance().getExternalContext() .getResponse(); HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext() .getRequest(); Cookie cookieLocale = new Cookie(COOKIE_LOCALE, locale.toString()); if (request.getContextPath().isEmpty()) { cookieLocale.setPath("/"); } else { cookieLocale.setPath(request.getContextPath()); } // 30 days cookieLocale.setMaxAge(2592000); response.addCookie(cookieLocale); } /** * returns the user language * * @return */ public String getLanguage() { return getLocale().getLanguage(); } /** * This method returns a cached cloned version of a user profile for a given * useraccount. The profile is cached in the current user session * * @param aName * @return */ public ItemCollection getProfile(String aAccount) { return profileService.findProfileById(aAccount); } /** * This method returns the username (displayname) for a useraccount. If no * Username is set in the profile then we return the useraccount. * * @param aName * @return */ public String getUserName(String aAccount) { // use internal cache ItemCollection profile = getProfile(aAccount); if (profile == null) { return null; } else { return profile.getItemValueString("txtuserName"); } } /** * This method returns the email for a useraccount * * @param aName * @return */ public String getEmail(String aAccount) { // use internal cache ItemCollection profile = getProfile(aAccount); if (profile == null) { return null; } else { return profile.getItemValueString("txtemail"); } } /** * removes the current user icon * */ public void removeUserIcon() { String userIcon = workflowController.getWorkitem().getItemValueString(ITEM_USER_ICON); // support deprecated user icon item name if (userIcon.isEmpty() && !"".equals(workflowController.getWorkitem().getItemValueString("txtusericon"))) { userIcon = workflowController.getWorkitem().getItemValueString("txtusericon"); } workflowController.getWorkitem().removeFile(userIcon); workflowController.getWorkitem().replaceItemValue("txtusericon", ""); workflowController.getWorkitem().replaceItemValue(ITEM_USER_ICON, ""); } /** * removes the current user signature * */ public void removeSignature() { String userSignature = workflowController.getWorkitem().getItemValueString(ITEM_SIGNATURE_IMAGE); workflowController.getWorkitem().removeFile(userSignature); workflowController.getWorkitem().replaceItemValue(ITEM_SIGNATURE_IMAGE, ""); } /** * WorkflowEvent listener listens to WORKITEM events to reset the current * username workitem if processed. *

* The method also updates the user Locale *

* In case a new image is uplaoded the method set the item user.icon. The * deprecated item name 'txtusericon' is still supported.. *

* Optional Signature images can be uploaded if the file name starts with * 'signatrue.'. If a signature image exists the item 'signature.image' is set *

* A profile can hole one user.icon and one signature.image. Deprecated images * will be removed by this method automatically. * * @param workflowEvent * **/ public void onWorkflowEvent(@Observes WorkflowEvent workflowEvent) { String userID = null; String userIcon = null; String signatureImage = null; if (workflowEvent == null || workflowEvent.getWorkitem() == null) { return; } String sType = workflowEvent.getWorkitem().getItemValueString("type"); // skip if not a profile... if (!sType.startsWith("profile")) return; // Update usericon, signature image imformation if ("profile".equals(sType) && WorkflowEvent.WORKITEM_BEFORE_PROCESS == workflowEvent.getEventType()) { userID = workflowEvent.getWorkitem().getItemValueString("txtname"); userIcon = getWorkitem().getItemValueString(ITEM_USER_ICON); // support deprecated user icon item name if (userIcon.isEmpty() && !"".equals(getWorkitem().getItemValueString("txtusericon"))) { userIcon = getWorkitem().getItemValueString("txtusericon"); getWorkitem().replaceItemValue(ITEM_USER_ICON, userIcon); } signatureImage = workflowEvent.getWorkitem().getItemValueString(ITEM_SIGNATURE_IMAGE); /* * Test new uploaded images. we support two images, a user icon and a signature * image. The method removes deprecated entries. */ List fileDataList = workflowEvent.getWorkitem().getFileData(); // reverse list - newest first Collections.sort(fileDataList, new FileDataComparator()); boolean signatureFound = false; boolean usericonFound = false; for (FileData fileData : fileDataList) { String filenametest = fileData.getName().toLowerCase(); // test if the image is a signature.image... if (filenametest.startsWith("signature.") && signatureFound == false) { signatureFound = true; if (!signatureImage.equals(fileData.getName())) { signatureImage = fileData.getName(); workflowEvent.getWorkitem().replaceItemValue(ITEM_SIGNATURE_IMAGE, signatureImage); logger.info("... '" + userID + "' new signature image upload: " + signatureImage); } } // test if the image is a user.icon... if (!filenametest.startsWith("signature.") && (filenametest.endsWith(".png") || filenametest.endsWith(".gif") || filenametest.endsWith(".jpg")) && usericonFound == false) { usericonFound = true; if (!userIcon.equals(fileData.getName())) { userIcon = fileData.getName(); workflowEvent.getWorkitem().replaceItemValue(ITEM_USER_ICON, userIcon); logger.info("... '" + userID + "' new user icon upload: " + userIcon); // support deprecated image workflowEvent.getWorkitem().replaceItemValue("txtusericon", userIcon); } } } // remove deprecated images files which are not user.icon or signature.image ListIterator iter = fileDataList.listIterator(); while (iter.hasNext()) { FileData fileData = iter.next(); String fileName = fileData.getName(); if (fileName.startsWith("signature.") && !fileName.equals(signatureImage)) { // iter.remove(); workflowEvent.getWorkitem().removeFile(fileName); } if (!fileName.startsWith("signature.") && !fileName.equals(userIcon)) { // iter.remove(); workflowEvent.getWorkitem().removeFile(fileName); } } } // discard cached user profile and update locale if ("profile".equals(sType) && WorkflowEvent.WORKITEM_AFTER_PROCESS == workflowEvent.getEventType()) { // check if current user profile was processed.... String sName = workflowEvent.getWorkitem().getItemValueString("txtName"); if (sName.equals(this.getWorkitem().getItemValueString("txtName"))) { logger.finest("......reload current user profile"); setWorkitem(workflowEvent.getWorkitem()); // update locale updateLocaleFromProfile(); } } } /** * Returns true if the uniqueid is stored in the profile favorites * * @param id * @return */ public boolean isFavorite(String id) { return getFavoriteIds().contains(id); } /** * Returns a list with all uniqueids stored in the profile favorites * * @return */ @SuppressWarnings("unchecked") public List getFavoriteIds() { if (getWorkitem() == null) return new ArrayList(); // support deprecated ref field if (!workitem.hasItem(LINK_PROPERTY) && workitem.hasItem(LINK_PROPERTY_DEPRECATED)) { return workitem.getItemValue(LINK_PROPERTY_DEPRECATED); } else { return workitem.getItemValue(LINK_PROPERTY); } } public void addFavorite(String id) { if (getWorkitem() == null) return; List list = getFavoriteIds(); // we expect that the id is in the list-.. if (!list.contains(id)) { logger.finest("......add WorkitemRef:" + id); list.add(id); workitem.replaceItemValue(LINK_PROPERTY, list); // allow maximum 100 entries !!! while (list.size()>MAX_FAVORITE_ENTRIES) { list.remove(0); } workitem = workflowService.getDocumentService().save(workitem); } } public void removeFavorite(String id) { if (getWorkitem() == null) return; List list = getFavoriteIds(); // we expect that the id is in the list-.. if (list.contains(id)) { logger.finest("......remove WorkitemRef:" + id); list.remove(id); workitem.replaceItemValue(LINK_PROPERTY, list); workitem = workflowService.getDocumentService().save(workitem); } } /** * This method returns true if the user may be using a mobile device * * @return */ public boolean isMobileUser() { HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext() .getRequest(); String userAgent = request.getHeader("user-agent").toLowerCase(); return (userAgent.indexOf("mobile") > -1); } /* * HELPER METHODS */ /** * This method updates user locale stored in the user profile entity to the * faces context. * * @throws ProcessingErrorException * @throws AccessDeniedException * */ private void updateLocaleFromProfile() throws AccessDeniedException, ProcessingErrorException { Locale profileLocale = null; // Verify if Locale is available in profile String sLocale = getWorkitem().getItemValueString("txtLocale"); if ("".equals(sLocale)) { // get default value profileLocale = getLocale(); getWorkitem().replaceItemValue("txtLocale", profileLocale.toString()); } else { if (sLocale.indexOf('_') > -1) { String language = sLocale.substring(0, sLocale.indexOf('_')); String country = sLocale.substring(sLocale.indexOf('_') + 1); profileLocale = new Locale(language, country); } else { profileLocale = new Locale(sLocale); } } logger.fine("update user locale: " + profileLocale); // reset locale to update cookie setLocale(profileLocale); // set locale for context FacesContext.getCurrentInstance().getViewRoot().setLocale(profileLocale); } /** * Compares two Filedata by its creation date value. This functionality should * be covered by the ItemCollection. * * @author rsoika * */ class FileDataComparator implements Comparator { @SuppressWarnings("unused") private final Collator collator; public FileDataComparator() { this.collator = Collator.getInstance(Locale.getDefault()); } @SuppressWarnings("rawtypes") public int compare(FileData a, FileData b) { Date dateA = null; Date dateB = null; List la = (List) a.getAttribute("$created"); if (la != null && la.size() > 0) { dateA = (Date) la.get(0); } List lb = (List) b.getAttribute("$created"); if (lb != null && lb.size() > 0) { dateB = (Date) lb.get(0); } if (dateB == null && dateA != null) { return 1; } if (dateA == null && dateB != null) { return -1; } if (dateA == null && dateB == null) { return 0; } int result = dateA.compareTo(dateB); return result; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy