/** * 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; import java.awt.image.BufferedImage; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.imageio.ImageIO; import org.apache.commons.lang3.RandomStringUtils; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; 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.SdiDuplicatePersonException; 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.ConverterImage; import ch.sdi.core.impl.mail.MailJobDefault; import ch.sdi.core.impl.mail.MailTextResolver; import ch.sdi.core.intf.CustomTargetJobContext; import ch.sdi.core.intf.FtpJob; import ch.sdi.core.intf.PasswordEncryptor; import ch.sdi.core.intf.TargetJob; import ch.sdi.plugins.oxwall.job.OxSqlJob; import ch.sdi.plugins.oxwall.sql.entity.OxUser; import ch.sdi.plugins.oxwall.sql.entity.OxUserUnapproved; /** * Implements the global business know how on the oxwall target platform. * <p> * It provides the target executor with the needed jobs. * <p> * In preparePerson which is called before a person is processed the needed data is prepared (like * password generation, formatting the avatar pictures) and a DB transaction is started. In finalizePerson * the DB transaction is committed or rollbacked (depending on the presence of an exception). * <p> * If Spring finds a implementation of CustomPreparePersonJob its execute() method is called in * preparePerson(). * * @version 1.0 (24.11.2014) * @author Heri */ @Component public class OxTargetJobContext implements CustomTargetJobContext { /** logger for this class */ private Logger myLog = LogManager.getLogger( OxTargetJobContext.class ); public static final String KEY_AVATAR_HASH = "person.avatar.hash"; public static final String KEY_PASSWORD = "person.password"; public static final String KEY_ENCRYPTED_PASSWORD = "person.enc_password"; public static final String KEY_PERSON_FULLNAME = "person.fullName"; /** the primary key of DB entity */ public static final String KEY_PERSON_USER_ID = "person.userId"; public static final String KEY_PERSON_PREPARED_AVATAR_FILES = "person.preparedAvatarFiles"; public static final String KEY_NEEDS_ACTIVATION = "person.needsActivation"; @Autowired private Environment myEnv; @Autowired private PasswordEncryptor myPasswordEncryptor; @Autowired private MailJobDefault myMailJob; @Autowired private FtpJob myFtpJob; @Autowired private OxSqlJob mySqlJob; private boolean myHasAvatar; /** * Constructor * */ public OxTargetJobContext() { super(); } /** * @see ch.sdi.core.intf.TargetJobContext#getJobs() */ @Override public Collection<? extends TargetJob> getJobs() throws SdiException { List<TargetJob> result = new ArrayList<TargetJob>(); // Basically I'd prefer to execute first the FTP job because in case of a failure in the // succeeding jobs the lowest damage is done in the system. But oxwalls avatar picture files // need the database ID of the new user for the filenames. result.add( mySqlJob ); if ( myHasAvatar ) { result.add( myFtpJob ); } result.add( myMailJob ); return result; } /** * @see ch.sdi.core.intf.TargetJobContext#prepare() */ @Override public void prepare() throws SdiException { myHasAvatar = ConfigUtils.getBooleanProperty( myEnv, OxTargetConfiguration.KEY_HAS_AVATAR ); } /** * @see ch.sdi.core.intf.TargetJobContext#preparePerson() */ @Override public void preparePerson( Person<?> aPerson ) throws SdiException { if ( mySqlJob.isAlreadyPresent( aPerson ) ) { OxUser user = mySqlJob.findPersonByEmail( aPerson.getEMail() ); OxUserUnapproved userUnapproved = mySqlJob.findUnapprovedUser( user.getId() ); if ( user != null && userUnapproved != null ) { aPerson.setProperty( KEY_NEEDS_ACTIVATION, Boolean.TRUE ); aPerson.setProperty( MailTextResolver.KEY_EXTRA_MAIL_BODY_FILE, "./input/mailbodyActivation.txt" ); myLog.debug( "Person " + aPerson.getEMail() + " is already present, but needs activation" ); mySqlJob.startTransaction(); return; } throw new SdiDuplicatePersonException( aPerson ); } preparePassword( aPerson ); if ( myHasAvatar ) { prepareAvatar( aPerson ); } String fullname = aPerson.getGivenname() + " " + ( StringUtils.hasText( aPerson.getMiddlename() ) ? (aPerson.getMiddlename() + " ") : "" ) + aPerson.getFamilyName(); aPerson.setProperty( KEY_PERSON_FULLNAME, fullname ); mySqlJob.startTransaction(); } /** * @param aPerson * @throws SdiException */ private void prepareAvatar( Person<?> aPerson ) throws SdiException { BufferedImage origImage = aPerson.getProperty( PersonKey.THING_IMAGE.getKeyName(), BufferedImage.class ); if ( origImage == null ) { myLog.debug( "No avatar available" ); return; } // if bufferedImage == null /* * Oxwall knows three avatar files: * - avatar_<userId>_<hash>.jpg (1) * - avatar_big_<userId>_<hash>.jpg (2) * - avatar_original_<userId>_<hash>.jpg (3) * where: * (1) 90x90 pixels, 96 dpi, 24 pixelBits * (2) 190x190 pixels, 96 dpi, 24 pixelBits * (3) any size (original uploaded) * Linux-Access-Rights: -rw-r--r-- * The hash is entered in table ow_base_avatar (see OxAvatar) */ Long hash; while ( true ) { /* Note: the hash field is defined as int(11), existing hashes all have 10 digits, but start * with '1'. Inserting a value which exceeds the Integer.MAX_VALUE is not possible. So we * only randomize 9 digits and prefix it with '1', so it is for sure below the limit. */ hash = Long.valueOf( "1" + RandomStringUtils.random( 9, "0123456789" ) ); if ( !mySqlJob.isAvatarHashPresent( hash ) ) { break; } } myLog.debug( "Generated hash for avatar " + hash ); aPerson.setProperty( KEY_AVATAR_HASH, hash ); // since the userId is part of the full filename, we prepare here only the input streams // associate with the file name prefix. The full file name is then composed in FtpJob Map<String, InputStream> filesToUpload = new HashMap<String, InputStream>(); filesToUpload.put( "avatar_", toInputStream( ConverterImage.resizeImage( origImage, 90, 90 ) ) ); filesToUpload.put( "avatar_big_", toInputStream( ConverterImage.resizeImage( origImage, 190, 190 ) ) ); filesToUpload.put( "avatar_original_", toInputStream( origImage ) ); aPerson.setProperty( KEY_PERSON_PREPARED_AVATAR_FILES, filesToUpload ); } /** * @param aImage * @return * @throws SdiException */ protected InputStream toInputStream( BufferedImage aImage ) throws SdiException { ByteArrayOutputStream os = new ByteArrayOutputStream(); try { ImageIO.write( aImage, "JPG", os ); } catch ( IOException t ) { throw new SdiException( "Problems writing image to stream", t, SdiException.EXIT_CODE_UNKNOWN_ERROR ); } return new ByteArrayInputStream( os.toByteArray() ); } /** * @param aPerson */ private void preparePassword( Person<?> aPerson ) { String password = RandomStringUtils.random( 8, true, true ); myLog.trace( "Generated password for user " + aPerson.getEMail() + ": " + password ); String encrypted = myPasswordEncryptor.encrypt( password ); aPerson.setProperty( KEY_PASSWORD, password ); aPerson.setProperty( KEY_ENCRYPTED_PASSWORD, encrypted ); } /** * @see ch.sdi.core.intf.TargetJobContext#finalizePerson() */ @Override public void finalizePerson( Person<?> aPerson, SdiException aException ) throws SdiException { if ( aException == null ) { mySqlJob.commitTransaction(); } else { mySqlJob.rollbackTransaction(); } // if..else aException == null } /** * @see ch.sdi.core.intf.TargetJobContext#release() */ @Override public void release( SdiException aException ) throws SdiException { } }