/** * Copyright (c) 2014 by the original author or authors. * * This code is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * The above copyright notice and this permission notice shall be included in all copies or * substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING * BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package ch.sdi.plugins.oxwall.job; import java.util.ArrayList; import java.util.Date; import java.util.List; import javax.persistence.TypedQuery; import javax.persistence.criteria.CriteriaBuilder; import javax.persistence.criteria.CriteriaQuery; import javax.persistence.criteria.ParameterExpression; import javax.persistence.criteria.Root; import org.apache.commons.lang3.reflect.MethodUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.hibernate.ejb.HibernateEntityManager; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.env.Environment; import org.springframework.stereotype.Component; import org.springframework.util.StringUtils; import ch.sdi.core.exc.SdiException; import ch.sdi.core.impl.cfg.ConfigUtils; import ch.sdi.core.impl.data.Person; import ch.sdi.core.impl.data.PersonKey; import ch.sdi.core.impl.data.converter.ConverterNumberList; import ch.sdi.core.intf.SdiMainProperties; import ch.sdi.core.intf.SqlJob; import ch.sdi.plugins.oxwall.OxTargetConfiguration; import ch.sdi.plugins.oxwall.OxTargetJobContext; import ch.sdi.plugins.oxwall.OxUtils; import ch.sdi.plugins.oxwall.profile.OxProfileQuestion; import ch.sdi.plugins.oxwall.profile.OxProfileQuestionDate; import ch.sdi.plugins.oxwall.profile.OxProfileQuestionNumber; import ch.sdi.plugins.oxwall.profile.OxProfileQuestionString; import ch.sdi.plugins.oxwall.profile.OxQuestionFactory; import ch.sdi.plugins.oxwall.profile.OxQuestionType; import ch.sdi.plugins.oxwall.sql.entity.OxAvatar; import ch.sdi.plugins.oxwall.sql.entity.OxProfileData; import ch.sdi.plugins.oxwall.sql.entity.OxUser; import ch.sdi.plugins.oxwall.sql.entity.OxUserGroupMembership; import ch.sdi.plugins.oxwall.sql.entity.OxUserRole; import ch.sdi.plugins.oxwall.sql.entity.OxUserUnapproved; import ch.sdi.report.ReportMsg; import ch.sdi.report.ReportMsg.ReportType; /** * SQL job implementation for the oxwall platform. * * @version 1.0 (24.11.2014) * @author Heri */ @Component public class OxSqlJob implements SqlJob { /** logger for this class */ private Logger myLog = LogManager.getLogger( OxSqlJob.class ); public static final String KEY_PREFIX_PROFILE_QUESTION = "ox.target.qn."; public static final String KEY_DEFAULT_GROUPS = "ox.target.defaultGroups"; public static final String KEY_DEFAULT_ROLES = "ox.target.defaultRoles"; public static final String KEY_GROUP_PRIVACY = "ox.target.groups.privacy"; @Autowired private Environment myEnv; @Autowired private OxQuestionFactory myQuestionFactory; // does not work: @PersistenceContext(unitName="oxwall") protected HibernateEntityManager myEntityManager; private boolean myDryRun; private boolean myCheckDuplicateOnDryRun; private long myDummyId = 1; private List<OxProfileQuestion> myProfileQuestions; private List<Long> myDefaultGroups; private List<Long> myDefaultRoles; private String myGroupPrivacy; private String myUserAccountType; private Integer myJoinIp; private boolean myEmailVerify; /** * Constructor * */ public OxSqlJob() { super(); } /** * @see ch.sdi.core.intf.TargetJob#execute(ch.sdi.core.impl.data.Person) */ @Override public void execute( Person<?> aPerson ) throws SdiException { List<Object> dbEntities = new ArrayList<Object>(); if ( aPerson.containsProperty( OxTargetJobContext.KEY_NEEDS_ACTIVATION ) ) { OxUser user = findPersonByEmail( aPerson.getEMail() ); if ( user == null ) { throw new SdiException( "person has property " + OxTargetJobContext.KEY_NEEDS_ACTIVATION + " but we cannot find the user entity by email address", SdiException.EXIT_CODE_UNKNOWN_ERROR ); } String msg; if ( myDryRun ) { msg = "Skip deleting the approve flag, since dryrun is true"; } else { msg = "Deleting entry in ow_base_user_disapprove for user " + user.getId(); OxUserUnapproved userUnapproved = findUnapprovedUser( user.getId() ); myEntityManager.remove( userUnapproved ); } myLog.info( msg ); return; } myLog.debug( "creating new user entity" ); OxUser user = new OxUser(); user.setUsername( aPerson.getStringProperty( PersonKey.THING_ALTERNATENAME.getKeyName() ) ); user.setAccountType( myUserAccountType ); user.setActivityStamp( OxUtils.dateToLong( new Date() ) ); user.setEmail( aPerson.getEMail() ); user.setJoinIp( myJoinIp.longValue() ); user.setJoinStamp( OxUtils.dateToLong( new Date() ) ); user.setPassword( aPerson.getStringProperty( OxTargetJobContext.KEY_ENCRYPTED_PASSWORD )); user.setEmailVerify( myEmailVerify ); saveEntity( dbEntities, user ); aPerson.setProperty( OxTargetJobContext.KEY_PERSON_USER_ID, user.getId() );; List<Long> roles = resolveRoles( aPerson ); if ( roles.size() > 0 ) { myLog.debug( "creating group membership entities" ); for ( Long role : roles ) { OxUserRole roleEntity = new OxUserRole(); roleEntity.setUserId( user.getId() ); roleEntity.setRoleId( role ); saveEntity( dbEntities, roleEntity ); } } // if groups.size() > 0 myLog.debug( "creating profile question entities" ); for ( OxProfileQuestion question : myProfileQuestions ) { if ( !question.hasValue( aPerson ) ) { continue; } // if !question.hasValue( aPerson ) OxProfileData profileData = new OxProfileData(); profileData.setUserId( user.getId() ); question.fillValues( profileData, aPerson ); saveEntity( dbEntities, profileData ); } Long avatarHash = aPerson.getProperty( OxTargetJobContext.KEY_AVATAR_HASH, Long.class ); if ( avatarHash != null ) { myLog.debug( "creating avatar entities" ); OxAvatar avatar = new OxAvatar(); avatar.setUserId( user.getId() ); avatar.setHash( avatarHash ); saveEntity( dbEntities, avatar ); } // if StringUtils.hasText( avatarHash ) List<Long> groups = resolveMembership( aPerson ); if ( groups.size() > 0 ) { myLog.debug( "creating group membership entities" ); for ( Long group : groups ) { OxUserGroupMembership groupEntity = new OxUserGroupMembership(); groupEntity.setGroupId( group ); groupEntity.setUserId( user.getId() ); groupEntity.setTimeStamp( OxUtils.dateToLong( new Date() ) ); groupEntity.setPrivacy( myGroupPrivacy ); saveEntity( dbEntities, groupEntity ); } } // if groups.size() > 0 ReportMsg msg = new ReportMsg( ReportType.SQL_TARGET, aPerson.getEMail(), dbEntities.toArray() ); myLog.info( msg ); } /** * @param aPerson * @return */ private List<Long> resolveRoles( Person<?> aPerson ) { List<Long> result = new ArrayList<Long>(); // TODO: add roles per user, as with group membership myLog.debug( "collected roles of person: " + result ); result.addAll( myDefaultRoles ); myLog.debug( "resolved roles of person: " + result ); return result; } /** * @param aPerson * @return * @throws SdiException */ private List<Long> resolveMembership( Person<?> aPerson ) throws SdiException { List<Long> result = aPerson.getLongListProperty( PersonKey.PERSON_MEMBEROF.getKeyName() ); myLog.debug( "collected groups of person: " + result ); result.addAll( myDefaultGroups ); myLog.debug( "resolved groups of person: " + result ); return result; } /** * @param dbEntities * @param aEntity * @throws SdiException */ private void saveEntity( List<Object> dbEntities, Object aEntity ) throws SdiException { if ( myDryRun ) { myLog.trace( "DryRun: Going to save entity: " + aEntity ); try { MethodUtils.invokeExactMethod( aEntity, "setId", new Object[] { Long.valueOf( myDummyId ) } ); } catch ( Throwable t ) { throw new SdiException( "Entity has no 'setId( Long )' method", t, SdiException.EXIT_CODE_UNKNOWN_ERROR ); } myDummyId++; } else { myLog.trace( "Going to save entity: " + aEntity ); myEntityManager.persist( aEntity ); } // if..else myDryRun dbEntities.add( aEntity ); } /** * @see ch.sdi.core.intf.SqlJob#isAlreadyPresent(ch.sdi.core.impl.data.Person) */ @Override public boolean isAlreadyPresent( Person<?> aPerson ) throws SdiException { if ( myDryRun && !myCheckDuplicateOnDryRun ) { myLog.debug( "DryRun is active. Not checking for duplicate person" ); return false; } // if myDryRun OxUser oxUser = findPersonByEmail( aPerson.getEMail() ); if ( oxUser != null ) { myLog.debug( "given Person is already present: " + oxUser ); return true; } // if results.size() > 0 return false; } /** * @param aEmail * @return */ public OxUser findPersonByEmail( String aEmail ) { CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); CriteriaQuery<OxUser> criteria = cb.createQuery(OxUser.class); Root<OxUser> root = criteria.from(OxUser.class); ParameterExpression<String> mailParam = cb.parameter(String.class); criteria.select(root).where(cb.equal( root.get("email"), mailParam )); TypedQuery<OxUser> queryEMail = myEntityManager.createQuery(criteria); queryEMail.setParameter( mailParam, aEmail ); List<OxUser> results = queryEMail.getResultList(); OxUser oxUser = null; if ( results.size() > 0 ) { oxUser = results.get( 0 ); } // if results.size() > 0 return oxUser; } /** * @param aUserId * @return */ public OxUserUnapproved findUnapprovedUser( Long aUserId ) { CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); CriteriaQuery<OxUserUnapproved> criteria = cb.createQuery(OxUserUnapproved.class); Root<OxUserUnapproved> root = criteria.from(OxUserUnapproved.class); ParameterExpression<Long> param = cb.parameter(Long.class); criteria.select(root).where(cb.equal( root.get("userId"), param )); TypedQuery<OxUserUnapproved> queryEMail = myEntityManager.createQuery(criteria); queryEMail.setParameter( param, aUserId ); List<OxUserUnapproved> results = queryEMail.getResultList(); OxUserUnapproved userUnapproved = null; if ( results.size() > 0 ) { userUnapproved = results.get( 0 ); } // if results.size() > 0 return userUnapproved; } /** * Checks if the given hash is present in the ow_base_avatar table * * @param aHash * @return */ public boolean isAvatarHashPresent( Long aHash ) { if ( myDryRun ) { myLog.debug( "DryRun is active. Not checking for duplicate avatar hash" ); return false; } // if myDryRun CriteriaBuilder cb = myEntityManager.getCriteriaBuilder(); CriteriaQuery<OxAvatar> criteria = cb.createQuery(OxAvatar.class); Root<OxAvatar> root = criteria.from(OxAvatar.class); ParameterExpression<Long> avatarParam = cb.parameter(Long.class); avatarParam = cb.parameter(Long.class); criteria.select(root).where(cb.equal( root.get("hash"), avatarParam )); TypedQuery<OxAvatar> query = myEntityManager.createQuery(criteria); query.setParameter( avatarParam, aHash ); List<OxAvatar> results = query.getResultList(); if ( results.size() > 0 ) { myLog.debug( "given avatar hash is already present: " + aHash ); return true; } // if results.size() > 0 return false; } /** * @see ch.sdi.core.intf.TargetJob#init() */ @Override public void init() throws SdiException { myDryRun = ConfigUtils.getBooleanProperty( myEnv, SdiMainProperties.KEY_DRYRUN, false ); myCheckDuplicateOnDryRun = ConfigUtils.getBooleanProperty( myEnv, SdiMainProperties.KEY_CHECK_DUPLICATE_ON_DRYRUN, false ); myGroupPrivacy = myEnv.getProperty( KEY_GROUP_PRIVACY, "everybody" ); myUserAccountType = myEnv.getProperty( OxTargetConfiguration.KEY_USER_ACCOUNT_TYPE ); myJoinIp = ConfigUtils.getIntProperty( myEnv, OxTargetConfiguration.KEY_USER_JOINIP, 12345 ); myEmailVerify = ConfigUtils.getBooleanProperty( myEnv, OxTargetConfiguration.KEY_USER_EMAIL_VERIFY ); myEntityManager = EntityManagerProvider.getEntityManager( "oxwall" ); if ( myEntityManager == null ) { throw new SdiException( "Problems initializing EntityManager", SdiException.EXIT_CODE_CONFIG_ERROR ); } // if em == null initProfileQuestions(); initDefaultGroups(); initDefaultRoles(); } /** * */ private void initDefaultRoles() throws SdiException { String configured = myEnv.getProperty( KEY_DEFAULT_ROLES, "" ); myDefaultRoles = ConverterNumberList.toLongList( configured, "," ); myLog.debug( "Configured default roles: " + myDefaultRoles ); } /** * @throws SdiException */ private void initDefaultGroups() throws SdiException { String configured = myEnv.getProperty( KEY_DEFAULT_GROUPS, "" ); myDefaultGroups = ConverterNumberList.toLongList( configured, "," ); myLog.debug( "Configured default groups: " + myDefaultGroups ); } /** * Checks the configuration if there are profile questions configured and populates the * myProfileQuestions member with the found configurations. * <p> * @throws SdiException */ private void initProfileQuestions() throws SdiException { myProfileQuestions = new ArrayList<OxProfileQuestion>(); for ( String personKey : PersonKey.getKeyNames() ) { String key = KEY_PREFIX_PROFILE_QUESTION + personKey; myLog.trace( "Looking up profile question configuration " + key ); String configured = myEnv.getProperty( key ); if ( !StringUtils.hasText( configured ) ) { continue; } // if !StringUtils.hasText( configured ) myLog.trace( "Found profile question configuration " + configured ); String[] values = configured.trim().split( ":" ); if ( values.length < 2 ) { throw new SdiException( "Profile question not configured correctly: " + configured, SdiException.EXIT_CODE_CONFIG_ERROR ); } OxQuestionType type; try { type = OxQuestionType.valueOf( values[0] ); } catch ( IllegalArgumentException t ) { throw new SdiException( "Profile question not configured correctly: " + configured + "; " + "type cannot be resolved", SdiException.EXIT_CODE_CONFIG_ERROR ); } OxProfileQuestion question = null; switch ( type ) { case text: question = new OxProfileQuestionString( values[1], personKey ); break; case number: question = new OxProfileQuestionNumber( values[1], personKey ); break; case date: question = new OxProfileQuestionDate( values[1], personKey ); break; case custom: if ( values.length < 3 ) { throw new SdiException( "Profile question not configured correctly: " + values, SdiException.EXIT_CODE_CONFIG_ERROR ); } question = myQuestionFactory.getCustomQuestion( values[1], values[2], personKey ); break; default: throw new SdiException( "unhandled question type: " + type, SdiException.EXIT_CODE_UNKNOWN_ERROR ); } question.init( myEnv ); myLog.debug( "Adding profile question for key '" + personKey + "': " + question ); myProfileQuestions.add( question ); } } /** * @see ch.sdi.core.intf.TargetJob#close() */ @Override public void close() throws SdiException { if ( myEntityManager != null ) { myEntityManager.close(); } // if em != null } /** * @see ch.sdi.core.intf.SqlJob#startTransaction() */ @Override public void startTransaction() { myEntityManager.getTransaction().begin(); } /** * @see ch.sdi.core.intf.SqlJob#commitTransaction() */ @Override public void commitTransaction() { if ( myEntityManager != null ) { if ( myEntityManager.getTransaction().isActive() ) { myEntityManager.getTransaction().commit(); } // if myEntityManager.getTransaction().isActive() } // if em != null } /** * @see ch.sdi.core.intf.SqlJob#rollbackTransaction() */ @Override public void rollbackTransaction() { if ( myEntityManager != null ) { if ( myEntityManager.getTransaction().isActive() ) { myEntityManager.getTransaction().rollback(); } // if myEntityManager.getTransaction().isActive() } // if em != null } }