class UserActivityLoggerImpl implements IUserActivityLogger {

    @Override
    public ReturnValue<LoggingObject> log(ILoggingAction loggingAction, Class<?> callingClass, ILoggingResourceable... lriOrNull) {
        final ActionType actionType = stickyActionType_ != null ? stickyActionType_ : loggingAction.getResourceActionType();

        // don't log entries with loggingAction type 'tracking'
        if (isLogAnonymous_ && actionType.equals(ActionType.tracking)) {
            return ReturnValue.EMPTY;
        }

        // fetch some of the loggingAction fields - used for error logging below
        final CrudAction crudAction = loggingAction.getCrudAction();
        final ActionVerb actionVerb = loggingAction.getActionVerb();
        final String actionObject = loggingAction.getActionObject();

        // calculate the combined and ordered list of LoggingResourceables which should go
        // to the database below right away
        List<ILoggingResourceable> resourceInfos = getCombinedOrderedLoggingResourceables(lriOrNull);

        if (loggingAction.getTypeListDefinition() == null) {
            // this is a foul!
            log.warn("LoggingAction has no ResourceableTypeList defined: action=" + loggingAction + ", fieldId=" + loggingAction.getJavaFieldIdForDebug());
        } else {
            // good boy
            String errorMsg = loggingAction.getTypeListDefinition().executeCheckAndGetErrorMessage(resourceInfos);
            if (errorMsg != null) {
                // we found an inconsistency
                // lets make this a warn
                // removed throwable from warn logging due to overflow of the log files with stack traces - probably relate to OLAT-4653
                log.warn("LoggingAction reported an inconsistency: " + loggingAction.getActionVerb() + " " + loggingAction.getActionObject() + ", action="
                        + loggingAction + ", fieldId=" + loggingAction.getJavaFieldIdForDebug() + ", expected: " + loggingAction.getTypeListDefinition().toString()
                        + ", actual: " + convertLoggingResourceableListToString(resourceInfos));
            }
        }

        if (session_ == null) {
            // then I can't log - log information without session/user information isn't of much use
            // issue a log warn with a stacktrace for this
            // removed throwable from error logging due to overflow of the log files with stack traces
            log.error("No session available to UserActivityLogger. Cannot write log entry: " + crudAction.name() + ":" + actionVerb.name() + ", " + actionObject + ", "
                    + convertLoggingResourceableListToString(resourceInfos));
            return ReturnValue.EMPTY;
        }

        if (session_.getSessionInfo() == null || session_.getSessionInfo().getSession() == null || session_.getSessionInfo().getSession().getId() == null
                || session_.getSessionInfo().getSession().getId().length() == 0) {
            // no session Id available - odd
            // removed throwable from error logging due to overflow of the log files with stack traces
            log.error("No session information available to UserActivityLogger. Cannot write log entry: " + crudAction.name() + ":" + actionVerb.name() + ", "
                    + actionObject + ", " + convertLoggingResourceableListToString(resourceInfos));
            return ReturnValue.EMPTY;
        }
        final String sessionId = session_.getSessionInfo().getSession().getId();

        Identity identity = session_.getIdentity();
        if (identity == null) {
            // no identity available - odd
            // removed throwable from error logging due to overflow of the log files with stack traces
            log.error("No identity available to UserActivityLogger. Cannot write log entry: " + crudAction.name() + ":" + actionVerb.name() + ", " + actionObject + ", "
                    + convertLoggingResourceableListToString(resourceInfos));
            return ReturnValue.EMPTY;
        }
        Long identityKey = identity.getKey();

        if (actionType != ActionType.admin) {
            final String identityKeyStr = String.valueOf(identityKey);
            for (Iterator it = resourceInfos.iterator(); it.hasNext();) {
                ILoggingResourceable lr = (ILoggingResourceable) it.next();
                if (lr.getResourceableType() == StringResourceableType.targetIdentity) {
                    if (log.isDebugEnabled() && !lr.getId().equals(identityKeyStr)) {
                        // complain
                        final Writer strackTraceAsStringWriter = new StringWriter();
                        final PrintWriter printWriter = new PrintWriter(strackTraceAsStringWriter);
                        (new Exception("OLAT-4955 debug stacktrac")).printStackTrace(printWriter);
                        log.debug("OLAT-4955: Not storing targetIdentity for non-admin logging actions. A non-admin logging action wanted to store a user other than the one from the session: action="
                                + loggingAction + ", fieldId=" + loggingAction.getJavaFieldIdForDebug() + strackTraceAsStringWriter.toString());
                    }
                    // OLAT-4955: remove targetIdentity
                    it.remove();
                }
            }
        }

        String identityName;
        if (isLogAnonymous_ && (actionType != ActionType.admin)) {
            identityName = "";
        } else {
            identityName = identity.getName();
        }

        // start creating the LoggingObject
        final LoggingObject logObj = new LoggingObject(sessionId, identityKey, identityName, crudAction.name().substring(0, 1), actionVerb.name(), actionObject,
                LogModule.isLogAnonymous());

        // do simpleDuration calculation & storing
        LoggingObject lastLogObj = (LoggingObject) session_.getEntry(USESS_KEY_USER_ACTIVITY_LOGGING_LAST_LOG);
        if (lastLogObj != null) {
            // lastLogObj = (LoggingObject) DBFactory.getInstance().loadObject(lastLogObj);
            // DBFactory.getInstance().updateObject(lastLogObj);
            // Implementation Note:
            // we used to do loadObject(), updateObject() here - which is the preferred best practice hibernate way
            // for changing an existing object in the database.
            // in the setup @UZH we'll use BLACKHOLE as the storage engine for the o_loggingtable (this is to have
            // minimal work load on the Main OLAT DB and not have duplicate data on the Main OLAT DB and the Logging DB).
            // Using BLACKHOLE results in the 'lastLogObj' here, not to exist in the database anymore.
            // Hence we can't do a loadObject() nor an updateObject(). The latter does not work due to the fact
            // that Hibernate checks the number of updated rows and expect that to equal 1 - which is not the case
            // when using BLACKHOLE.

            // Workaround: - also compare with LoggingObject.hbm.xml docu
            //
            // We use the sql-update's feature check="none", which disables the above mentioned check.
            // Using this in combination with manual SQL code below seems to be the only feasible way
            // to have Hibernate not do any row-number-checks.

            // Implications of the workaround:
            //
            // * Manual SQL: It shouldn't be a big deal to have this manual SQL code as it is very standard.
            // * CANT USE updateObject(LoggingObject) EVER:
            // @TODO We might have to add a check which verifies that no one calls updateObject(LoggingObject)
            // if that would be called it would simply fail in the BLACKHOLE@UZH setup

            // calculate the duration - take the simple diff of the two creationDate fields
            Date currentTime = logObj.getCreationDate();
            Date lastTime = lastLogObj.getCreationDate();
            long duration;
            if (lastTime == null) {
                duration = -1;
            } else if (currentTime == null) {
                duration = System.currentTimeMillis() - lastTime.getTime();
            } else {
                duration = currentTime.getTime() - lastTime.getTime();
            }

            // ticket I-130413-0018 reported a red screen caused by a NPE since lastLogObj.getKey() was null
            // this can happen if the lastLogObj had not been saved to database
            // a solution should be found to ensure a correct sequential storage to db ...
            // for the time being we just dont't update the duration in this case
            if (lastLogObj.getKey() != null) {
                getActivityLoggingDao().updateDuration(lastLogObj, duration);
            } else {
                log.error("Trying to update duration of LoggingObject before it had been saved to database [" + lastLogObj + "]");
            }
        }

        // store the current logging object in the session - for duration calculation at next log
        session_.putEntry(USESS_KEY_USER_ACTIVITY_LOGGING_LAST_LOG, logObj);

        if (resourceInfos != null && resourceInfos.size() != 0) {
            // this should be the normal case - we do have LoggingResourceables which we can log
            // alongside the log message

            // check if we have more than 4 - if we do, issue a log and remove the middle ones
            if (resourceInfos.size() > 4) {
                log.warn("More than 4 resource infos set on a user activity log. Can only have 4. Having: " + resourceInfos.size());
                int diff = resourceInfos.size() - 4;
                for (int i = 0; i < diff; i++) {
                    resourceInfos.remove(3);
                }
            }

            // get the target resourceable
            ILoggingResourceable ri = resourceInfos.get(resourceInfos.size() - 1);
            logObj.setTargetResourceInfo(ri.getType(), ri.getId(), ri.getName());

            // now set parent - if applicable
            if (resourceInfos.size() > 1) {
                ri = resourceInfos.get(resourceInfos.size() - 2);
                logObj.setParentResourceInfo(ri.getType(), ri.getId(), ri.getName());
            }

            // and set the grand parent - if applicable
            if (resourceInfos.size() > 2) {
                ri = resourceInfos.get(resourceInfos.size() - 3);
                logObj.setGrandParentResourceInfo(ri.getType(), ri.getId(), ri.getName());
            }

            // and set the great grand parent - if applicable
            if (resourceInfos.size() > 3) {
                ri = resourceInfos.get(resourceInfos.size() - 4);
                logObj.setGreatGrandParentResourceInfo(ri.getType(), ri.getId(), ri.getName());
            }
        }

        // fill the remaining fields
        logObj.setBusinessPath(businessPath_);
        logObj.setSourceClass(callingClass.getCanonicalName());
        logObj.setSimpleDuration(-1);
        logObj.setResourceAdminAction(actionType.equals(ActionType.admin) ? true : false);
        Locale locale = i18nManager.getLocaleOrDefault(identity.getUser().getPreferences().getLanguage());
        // prepate the user properties, set them at once
        List<String> tmpUserProperties = new ArrayList<String>(12);
        for (Iterator<String> iterator = userProperties_.iterator(); iterator.hasNext();) {
            String userPropString = getUserService().getPropertyOrIdentityEnvAttribute(identity.getUser(), iterator.next(), locale);
            boolean shorten = false;
            try {
                if (userPropString != null && userPropString.getBytes("UTF-8").length > 254) {
                    shorten = true;
                }
            } catch (UnsupportedEncodingException uee) {
                log.error("error while calculating real string length: unsupported encoding: ", uee);
                shorten = true;
            }
            if (shorten) {
                log.error("Userproperty was too long for logging-table (shortened automatically). check that nothing valueable is lost! value before cut: "
                        + userPropString);
                userPropString = userPropString.substring(0, 255);
            }
            tmpUserProperties.add(userPropString);
        }
        logObj.setUserProperties(tmpUserProperties);
        getActivityLoggingDao().saveLogObject(logObj);
        return new ReturnValue(logObj);
    }

      UserActivityLoggerImpl(HttpServletRequest hReq);
      UserActivityLoggerImpl();
      UserActivityLoggerImpl(UserSession userSession, ActivityLoggingDao activityLoggingDao, boolean isLogAnonymous, Set<String> userProperties, I18nManager i18nManager);
    private  UserActivityLoggerImpl(IUserActivityLogger userActivityLogger, WindowControl wControl, boolean propageToThreadLocal);

}