# DomainEventPlayerService.java

    /**
     *
     * Copyright 2009-2010 Rickard Öberg AB
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    package org.qi4j.library.eventsourcing.domain.replay;

    import java.lang.reflect.Method;
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import org.json.JSONObject;
    import org.json.JSONTokener;
    import org.qi4j.api.entity.EntityComposite;
    import org.qi4j.api.injection.scope.Structure;
    import org.qi4j.api.mixin.Mixins;
    import org.qi4j.api.service.ServiceComposite;
    import org.qi4j.api.structure.Module;
    import org.qi4j.api.unitofwork.NoSuchEntityException;
    import org.qi4j.api.unitofwork.UnitOfWork;
    import org.qi4j.api.unitofwork.UnitOfWorkFactory;
    import org.qi4j.api.usecase.UsecaseBuilder;
    import org.qi4j.api.value.ValueComposite;
    import org.qi4j.library.eventsourcing.domain.api.DomainEventValue;
    import org.qi4j.library.eventsourcing.domain.api.UnitOfWorkDomainEventsValue;
    import org.qi4j.spi.Qi4jSPI;
    import org.qi4j.spi.entity.EntityState;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;

    /**
     * DomainEventValue player
     */
    @Mixins(DomainEventPlayerService.Mixin.class)
    public interface DomainEventPlayerService
            extends DomainEventPlayer, ServiceComposite
    {
        class Mixin
                implements DomainEventPlayer
        {
            final Logger logger = LoggerFactory.getLogger( DomainEventPlayer.class );

            @Structure
            UnitOfWorkFactory uowf;

            @Structure
            Module module;

            @Structure
            Qi4jSPI spi;

            SimpleDateFormat dateFormat = new SimpleDateFormat( "EEE MMM dd HH:mm:ss zzz yyyy" );

            @Override
            public void playTransaction( UnitOfWorkDomainEventsValue unitOfWorkDomainValue )
                    throws EventReplayException
            {
                UnitOfWork uow = uowf.newUnitOfWork( UsecaseBuilder.newUsecase( "Event replay" ) );
                DomainEventValue currentEventValue = null;
                try
                {
                    for (DomainEventValue domainEventValue : unitOfWorkDomainValue.events().get())
                    {
                        currentEventValue = domainEventValue;
                        // Get the entity
                        Class entityType = module.classLoader().loadClass( domainEventValue.entityType().get() );
                        String id = domainEventValue.entityId().get();
                        Object entity = null;
                        try
                        {
                            entity = uow.get( entityType, id );
                        } catch( NoSuchEntityException e )
                        {
                            // Event to play for an entity that doesn't yet exist - create a default instance
                            entity = uow.newEntity( entityType, id );
                        }

                        // check if the event has already occured
                        EntityState state = spi.entityStateOf( (EntityComposite) entity );
                        if (state.lastModified() > unitOfWorkDomainValue.timestamp().get())
                        {
                            break; // don't rerun event in this unitOfWorkDomainValue
                        }

                        playEvent( domainEventValue, entity );
                    }
                    uow.complete();
                } catch (Exception e)
                {
                    uow.discard();
                    if (e instanceof EventReplayException)
                        throw ((EventReplayException) e);
                    else
                        throw new EventReplayException( currentEventValue, e );
                }
            }

            @Override
            public void playEvent( DomainEventValue domainEventValue, Object object )
                    throws EventReplayException
            {
                UnitOfWork uow = uowf.currentUnitOfWork();
                Class entityType = object.getClass();

                // Get method
                Method eventMethod = getEventMethod( entityType, domainEventValue.name().get() );

                if (eventMethod == null)
                {
                    logger.warn( "Could not find event method " + domainEventValue.name().get() + " in entity of type " + entityType.getName() );
                    return;
                }

                // Build parameters
                try
                {
                    String jsonParameters = domainEventValue.parameters().get();
                    JSONObject parameters = (JSONObject) new JSONTokener( jsonParameters ).nextValue();
                    Object[] args = new Object[eventMethod.getParameterTypes().length];
                    for (int i = 1; i < eventMethod.getParameterTypes().length; i++)
                    {
                        Class<?> parameterType = eventMethod.getParameterTypes()[i];

                        String paramName = "param" + i;

                        Object value = parameters.get( paramName );

                        args[i] = getParameterArgument( parameterType, value, uow );
                    }

                    args[0] = domainEventValue;

                    // Invoke method
                    logger.debug( "Replay:" + domainEventValue + " on:" + object );

                    eventMethod.invoke( object, args );
                } catch (Exception e)
                {
                    throw new EventReplayException( domainEventValue, e );
                }
            }

            private Object getParameterArgument( Class<?> parameterType, Object value, UnitOfWork uow ) throws ParseException
            {
                if (value.equals( JSONObject.NULL ))
                    return null;

                if (parameterType.equals( String.class ))
                {
                    return (String) value;
                } else if (parameterType.equals( Boolean.class ) || parameterType.equals( Boolean.TYPE ))
                {
                    return (Boolean) value;
                } else if (parameterType.equals( Long.class ) || parameterType.equals( Long.TYPE ))
                {
                    return ((Number) value).longValue();
                } else if (parameterType.equals( Integer.class ) || parameterType.equals( Integer.TYPE ))
                {
                    return ((Number) value).intValue();
                } else if (parameterType.equals( Date.class ))
                {
                    return dateFormat.parse( (String) value );
                } else if (ValueComposite.class.isAssignableFrom( parameterType ))
                {
                    return module.newValueFromSerializedState( parameterType, (String) value );
                } else if (parameterType.isInterface())
                {
                    return uow.get( parameterType, (String) value );
                } else if (parameterType.isEnum())
                {
                    return Enum.valueOf( (Class<? extends Enum>) parameterType, value.toString() );
                } else
                {
                    throw new IllegalArgumentException( "Unknown parameter type:" + parameterType.getName() );
                }
            }

            private Method getEventMethod( Class<?> aClass, String eventName )
            {
                for (Method method : aClass.getMethods())
                {
                    if (method.getName().equals( eventName ))
                    {
                        Class[] parameterTypes = method.getParameterTypes();
                        if (parameterTypes.length > 0 && parameterTypes[0].equals( DomainEventValue.class ))
                            return method;
                    }
                }
                return null;
            }
        }
    }