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

org.imixs.marty.profile.ProfileService 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.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.eclipse.microprofile.config.inject.ConfigProperty;
import org.imixs.workflow.ItemCollection;
import org.imixs.workflow.engine.DocumentService;
import org.imixs.workflow.engine.WorkflowService;
import org.imixs.workflow.exceptions.AccessDeniedException;
import org.imixs.workflow.exceptions.InvalidAccessException;
import org.imixs.workflow.exceptions.ModelException;
import org.imixs.workflow.exceptions.PluginException;
import org.imixs.workflow.exceptions.ProcessingErrorException;
import org.imixs.workflow.exceptions.QueryException;

import jakarta.annotation.PostConstruct;
import jakarta.annotation.Resource;
import jakarta.annotation.security.DeclareRoles;
import jakarta.annotation.security.RolesAllowed;
import jakarta.ejb.ConcurrencyManagement;
import jakarta.ejb.ConcurrencyManagementType;
import jakarta.ejb.EJB;
import jakarta.ejb.SessionContext;
import jakarta.ejb.Singleton;
import jakarta.enterprise.event.Event;
import jakarta.inject.Inject;

/**
 * The Marty ProfileService is a sigelton EJB providing user attributes like the
 * username and user email. The service is used to cache names application wide.
 * 
 * 
 * @author rsoika
 */

@DeclareRoles({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
        "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
        "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@RolesAllowed({ "org.imixs.ACCESSLEVEL.NOACCESS", "org.imixs.ACCESSLEVEL.READERACCESS",
        "org.imixs.ACCESSLEVEL.AUTHORACCESS", "org.imixs.ACCESSLEVEL.EDITORACCESS",
        "org.imixs.ACCESSLEVEL.MANAGERACCESS" })
@Singleton
@ConcurrencyManagement(ConcurrencyManagementType.BEAN)
public class ProfileService {
    public final static int START_PROFILE_PROCESS_ID = 200;
    public final static int CREATE_PROFILE_ACTIVITY_ID = 5;

    final int DEFAULT_CACHE_SIZE = 100;

    final int MAX_SEARCH_COUNT = 1;
    private Cache cache;

    private static Logger logger = Logger.getLogger(ProfileService.class.getName());

    @Inject
    @ConfigProperty(name = "security.userid.input.mode", defaultValue = "LOWERCASE")
    String userInputMode;

    @EJB
    private DocumentService documentService;

    @Inject
    @ConfigProperty(name = "setup.system.model")
    Optional modelVersion;

    @EJB
    protected WorkflowService workflowService;

    @Inject
    protected Event profileEvents;

    @Resource
    private SessionContext ctx;

    /**
     * PostContruct event - loads the imixs.properties.
     */
    @PostConstruct
    void init() {
        // initialize cache...
        reset();
    }

    /**
     * resets the profile cache..
     */
    public void reset() {
        // try to lookup cache size...
        int iCacheSize = DEFAULT_CACHE_SIZE;
        // early initialization did not work in Wildfly because of security
        // problem
        // see issue-#59
        // initialize cache
        cache = new Cache(iCacheSize);
    }

    /**
     * This method returns a profile by its id. The method uses an internal cache.
     * The method returns null if no Profile for this name was found
     * 
     * The returned workitem is a cloned version of the profile entity and can not
     * be processed or updated. Use lookupProfile to get the full entity of a
     * profile.
     * 
     * @param userid - the profile id
     * @return cloned workitem
     */
    public ItemCollection findProfileById(String userid) {
        return findProfileById(userid, false);

    }

    /**
     * This method returns a profile by its id. The method uses an internal cache.
     * The method returns in any case a user profile, even if no Profile for this
     * name was found. In this case a dummy profile will be created. The userid is
     * case sensitive.
     * 

* The returned workitem is a cloned version of the profile entity and can not * be processed or updated. Use lookupProfile to get the full entity of a * profile. *

* If the boolean 'refresh' is true the method lookup the user in any case with * a search query and updates the cache. * * @param userid - the profile id * @param refresh - boolean indicates if the internal cache should be used * @return cloned workitem */ public ItemCollection findProfileById(String userid, boolean refresh) { boolean debug = logger.isLoggable(Level.FINE); if (userid == null || userid.isEmpty()) { return null; } // userid inputmode? if ("UPPERCASE".equalsIgnoreCase(userInputMode)) { userid = userid.toUpperCase(); } if ("LOWERCASE".equalsIgnoreCase(userInputMode)) { userid = userid.toLowerCase(); } // try to get name out from cache ItemCollection userProfile = null; // use cache? if (!refresh) { if (debug) { logger.info("......lookup profile '" + userid + "' from cache..."); } Map> profileMap = cache.get(userid); if (profileMap != null) { userProfile = new ItemCollection(profileMap); if (debug) { logger.info("......return profile '" + userid + "' from cache"); } return userProfile; } } // no profile found or refresh==true userProfile = lookupProfileById(userid); if (userProfile != null) { // clone workitem.... userProfile = cloneWorkitem(userProfile); } else { if (debug) { logger.info("......profile '" + userid + "' not found, create a 'default' profile..."); } // put dummy entry into cache to avoid next lookup! userProfile = new ItemCollection(); userProfile.replaceItemValue("txtname", userid); userProfile.replaceItemValue("txtusername", userid); computeInitials(userProfile); } // cache profile if (debug) { logger.info("......put profile '" + userid + "' into cache"); } cache.put(userid, userProfile.getAllItems()); return userProfile; } /** * This method returns a profile by its id. In different to the findProfileById * method this method lookups the profile and returns the full entity. The * returned workItem can be processed. The userId is case sensitive. *

* Use findProfileById to work with the internal cache if there is no need to * update the profile. *

* The method also fires a ProfileEvent which can be observed by clients to * provide a different behavior. For example a LDAPService can lookup the data * in a LDAP directory if a local profile was not found (see issue #343) * * @param userid - the profile id * @return profile workitem */ public ItemCollection lookupProfileById(String userid) { boolean debug = logger.isLoggable(Level.FINE); if (userid == null || userid.isEmpty()) { logger.warning("lookupProfileById - no id provided!"); return null; } // userid inputmode? if ("UPPERCASE".equalsIgnoreCase(userInputMode)) { userid = userid.toUpperCase(); } if ("LOWERCASE".equalsIgnoreCase(userInputMode)) { userid = userid.toLowerCase(); } // try to get name out from cache ItemCollection userProfile = null; if (debug) { logger.info("......lookupProfileById '" + userid + "'"); } // lookup user profile.... String sQuery = "(type:\"profile\" AND txtname:\"" + userid + "\")"; if (debug) { logger.info("......search: " + sQuery); } Collection col; try { col = documentService.find(sQuery, 1, 0); } catch (QueryException e) { throw new InvalidAccessException(InvalidAccessException.INVALID_ID, e.getMessage(), e); } if (col.size() > 0) { userProfile = col.iterator().next(); } else { if (debug) { logger.finest("......no profile '" + userid + "' found, fire ProfileEvent LOOKUP..."); } // fire ProfileEvent so that a client can intercept.... if (profileEvents != null) { ProfileEvent event = new ProfileEvent(userid, null, ProfileEvent.ON_PROFILE_LOOKUP); profileEvents.fire(event); userProfile = event.getProfile(); if (userProfile == null) { if (debug) { logger.warning("ProfileEvent returned a null object for '" + userid + "'"); } } } else { logger.warning("CDI Support is missing - ProfileEvent wil not be fired"); } } return userProfile; } /** * This method removes a profile from the cache. * * @param userid */ public void discardCache(String userid) { cache.remove(userid); } /** * This method closes a profile entity and computes the attributes txtUsername * and txtInitials * * @param aWorkitem * @return */ public ItemCollection cloneWorkitem(ItemCollection aWorkitem) { ItemCollection clone = new ItemCollection(); // clone the standard WorkItem properties clone.replaceItemValue("Type", aWorkitem.getItemValue("Type")); clone.replaceItemValue("$UniqueID", aWorkitem.getItemValue("$UniqueID")); clone.replaceItemValue("$ModelVersion", aWorkitem.getItemValue("$ModelVersion")); clone.replaceItemValue("$ProcessID", aWorkitem.getItemValue("$ProcessID")); clone.replaceItemValue("$Created", aWorkitem.getItemValue("$Created")); clone.replaceItemValue("$Modified", aWorkitem.getItemValue("$Modified")); clone.replaceItemValue("$isAuthor", aWorkitem.getItemValue("$isAuthor")); clone.replaceItemValue("$WorkflowStatus", aWorkitem.getItemValue("$WorkflowStatus")); clone.replaceItemValue("$WorkflowSummary", aWorkitem.getItemValue("$WorkflowSummary")); clone.replaceItemValue("$WorkflowAbstract", aWorkitem.getItemValue("$WorkflowAbstract")); clone.replaceItemValue("txtEmail", aWorkitem.getItemValue("txtEmail")); clone.replaceItemValue("namdeputy", aWorkitem.getItemValue("namdeputy")); clone.replaceItemValue("txtusericon", aWorkitem.getItemValue("txtusericon")); clone.replaceItemValue("txtinitials", aWorkitem.getItemValue("txtinitials")); // deprecated fields clone.replaceItemValue("txtWorkflowStatus", aWorkitem.getItemValue("txtWorkflowStatus")); clone.replaceItemValue("txtWorkflowSummary", aWorkitem.getItemValue("txtWorkflowSummary")); clone.replaceItemValue("txtWorkflowAbstract", aWorkitem.getItemValue("txtWorkflowAbstract")); // get accountName String sAccountName = aWorkitem.getItemValueString("txtName"); clone.replaceItemValue("txtName", sAccountName); // test txtuserName String sUserName = aWorkitem.getItemValueString("txtUserName"); if (!sUserName.isEmpty()) { clone.replaceItemValue("txtUserName", sUserName); } else { // use account name clone.replaceItemValue("txtUserName", sAccountName); } // construct initials (2 digits) computeInitials(clone); return clone; } /** * Helper method to compute the user initials if no txtinitials exist. *

* The initials are computed by the 1st char of the first-last name *

* The initials are uppercase. * * @param profile */ private static void computeInitials(ItemCollection profile) { // construct initials (2 digits) if (profile.getItemValueString("txtinitials").isEmpty()) { String sAccountName = profile.getItemValueString("txtName"); String sUserName = profile.getItemValueString("txtUserName"); // default String sInitials = "-"; if (!sUserName.isEmpty() && sUserName.length() > 2) { int iPos = sUserName.indexOf(' '); if (iPos > -1 && iPos + 1 < sUserName.length()) { // test for space sInitials = sUserName.substring(0, 1); // test length after space if (iPos + 2 <= sUserName.length()) { sInitials = sInitials + sUserName.substring(iPos + 1, iPos + 2); } } else { sInitials = sUserName.substring(0, 1); } } else { // if we do not have initials, then we take the first letter of the account name if (sAccountName != null && !sAccountName.isEmpty()) { sInitials = sAccountName.substring(0, 1); } } profile.replaceItemValue("txtinitials", sInitials.toUpperCase()); } } /** * Creates a new profile document * * @param userid * @return * @throws ModelException * @throws PluginException * @throws ProcessingErrorException * @throws AccessDeniedException */ public ItemCollection createProfile(String userid, String locale) throws AccessDeniedException, ProcessingErrorException, PluginException, ModelException { // userid inputmode? if ("UPPERCASE".equalsIgnoreCase(userInputMode)) { userid = userid.toUpperCase(); } if ("LOWERCASE".equalsIgnoreCase(userInputMode)) { userid = userid.toLowerCase(); } logger.info("create new profile for userid '" + userid + "'.... "); // create new Profile for current user ItemCollection profile = new ItemCollection(); profile.replaceItemValue("type", "profile"); profile.replaceItemValue("$processID", START_PROFILE_PROCESS_ID); if (modelVersion.isPresent()) { profile.replaceItemValue("$modelversion", modelVersion.get()); logger.info("...creating profile based on modelversion = " + modelVersion.get()); } else { throw new ModelException(ModelException.UNDEFINED_MODEL_VERSION, "System Model version is missing - verify property 'setup.system.model'"); } // the workflow group can not be guessed here... // profile.replaceItemValue("$workflowgroup", "Profil"); profile.replaceItemValue("txtName", userid); profile.replaceItemValue("txtLocale", locale); // set default group (fixed deprecated role name 'IMIXS-WORKFLOW-Author' issue // #378 profile.replaceItemValue("txtgroups", "org.imixs.ACCESSLEVEL.AUTHORACCESS"); // process new profile... profile.setEventID(CREATE_PROFILE_ACTIVITY_ID); // fire ProfileEvent so that a client can intercept.... if (profileEvents != null) { ProfileEvent event = new ProfileEvent(userid, profile, ProfileEvent.ON_PROFILE_CREATE); profileEvents.fire(event); profile = event.getProfile(); } else { logger.warning("CDI Support is missing - ProfileEvent wil not be fired"); } profile = workflowService.processWorkItem(profile); logger.finest("......new profile created for userid '" + userid + "'"); return profile; } /** * Cache implementation to hold config entities * * @author rsoika * */ class Cache extends ConcurrentHashMap>> implements Serializable { private static final long serialVersionUID = 1L; private final int capacity; public Cache(int capacity) { super(capacity + 1, 1.1f); this.capacity = capacity; } protected boolean removeEldestEntry(Entry eldest) { return size() > capacity; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy