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

io.klerch.alexa.state.handler.AlexaSessionStateHandler Maven / Gradle / Ivy

Go to download

This SDK is an extension to the Alexa Skills SDK for Java. It provides a framework for managing state of POJO models in a variety of persistence stores in an Alexa skill.

There is a newer version: 1.1.0
Show newest version
/**
 * Made by Kay Lerch (https://twitter.com/KayLerch)
 *
 * Attached license applies.
 * This library is licensed under GNU GENERAL PUBLIC LICENSE Version 3 as of 29 June 2007
 */
package io.klerch.alexa.state.handler;

import com.amazon.speech.speechlet.Session;
import io.klerch.alexa.state.model.AlexaScope;
import io.klerch.alexa.state.model.AlexaStateModel;
import io.klerch.alexa.state.model.AlexaStateModelFactory;
import io.klerch.alexa.state.model.AlexaStateObject;
import io.klerch.alexa.state.utils.AlexaStateException;
import org.apache.commons.lang3.Validate;
import org.apache.log4j.Logger;

import java.util.*;
import java.util.stream.Collectors;

/**
 * As this handler works in the session scope it persists all models to the attributes of an associate Alexa Session object.
 * This handler reads and writes state for AlexaStateModels and considers all its fields annotated with AlexaSaveState-tags.
 */
public class AlexaSessionStateHandler implements AlexaStateHandler {
    private final Logger log = Logger.getLogger(AlexaSessionStateHandler.class);
    final Session session;

    /**
     * Initializes a handler and applies an Alexa Session to persist models.
     *  @param session The Alexa session of your current skill invocation.
     */
    public AlexaSessionStateHandler(final Session session) {
        this.session = session;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Session getSession() {
        return this.session;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  TModel createModel(final Class modelClass) {
        // acts just like a factory for custom models
        return AlexaStateModelFactory.createModel(modelClass, this, null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  TModel createModel(final Class modelClass, final String id) {
        // acts just like a factory for custom models
        return AlexaStateModelFactory.createModel(modelClass, this, id);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeModel(final AlexaStateModel model) throws AlexaStateException {
        Validate.notNull(model, "Model to write must not be null.");
        writeModels(Collections.singletonList(model));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeModels(final Collection models) throws AlexaStateException {
        Validate.notNull(models, "Collection of models to write must not be null.");
        models.forEach(model -> {
            try {
                // scope annotations will be ignored as there is only one context you can saveState attributes
                // thus scope will always be session
                session.setAttribute(model.getAttributeKey(), model.toMap(AlexaScope.SESSION));
                log.debug(String.format("Wrote state to session attributes for '%1$s'.", model));
            } catch (final AlexaStateException e) {
                log.error(e);
            }
        });
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeValue(final String id, final Object value) throws AlexaStateException {
        writeValue(id, value, AlexaScope.SESSION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeValue(final String id, final Object value, final AlexaScope scope) throws AlexaStateException {
        Validate.notBlank(id, "Id of single state object must not be blank.");
        Validate.notNull(scope, "Scope of single state object must not be null.");
        writeValue(new AlexaStateObject(id, value, scope));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeValue(final AlexaStateObject stateObject) throws AlexaStateException {
        Validate.notNull(stateObject, "State object must not be null.");
        writeValues(Collections.singleton(stateObject));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void writeValues(final Collection stateObjects) throws AlexaStateException {
        Validate.notNull(stateObjects, "List of state objects to write to persistence store must not be null.");
        stateObjects.stream()
                .filter(o -> AlexaScope.SESSION.includes(o.getScope()))
                .forEach(stateObject -> session.setAttribute(stateObject.getId(), stateObject.getValue()));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeModel(final AlexaStateModel model) throws AlexaStateException {
        Validate.notNull(model, "Model to be removed must not be null.");
        removeModels(Collections.singletonList(model));
    }

    @Override
    public void removeModels(Collection models) throws AlexaStateException {
        Validate.notNull(models, "Collection of models to be removed must not be null.");
        final List ids = models.stream().map(AlexaStateModel::getAttributeKey).collect(Collectors.toList());
        removeValues(ids);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void removeValue(final String id) throws AlexaStateException {
        removeValues(Collections.singletonList(id));
    }

    @Override
    public void removeValues(final Collection ids) throws AlexaStateException {
        Validate.notNull(ids, "Collection of ids whose values to be removed must not be null.");
        ids.forEach(session::removeAttribute);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  Optional readModel(final Class modelClass) throws AlexaStateException {
        return readModel(modelClass, null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @SuppressWarnings("unchecked")
    public  Optional readModel(final Class modelClass, final String id) throws AlexaStateException {
        final Object o = session.getAttribute(TModel.getAttributeKey(modelClass, id));
        if (o == null) {
            log.info(String.format("Could not find state for '%1$s' in session attributes.", TModel.getAttributeKey(modelClass, id)));
            return Optional.empty();
        }

        if (o instanceof Map) {
            final Map childAttributes = (Map) o;
            final TModel model = AlexaStateModelFactory.createModel(modelClass, this, id);

            if (model != null) {
                model.getSaveStateFields(AlexaScope.SESSION).parallelStream()
                        .filter(field -> childAttributes.containsKey(field.getName()))
                        .forEach(field -> {
                            try {
                                model.set(field, childAttributes.get(field.getName()));
                            } catch (AlexaStateException e) {
                                log.error(String.format("Could not set value for '%1$s' of model '%2$s'", field.getName(), model), e);
                            }
                        });
                log.debug(String.format("Read state for '%1$s' in session attributes.", model));
            }
            return model != null ? Optional.of(model) : Optional.empty();
        }
        else if (o instanceof String) {
            final TModel model = AlexaStateModelFactory.createModel(modelClass, this, id);
            model.fromJSON((String)o);
            log.debug(String.format("Read state for '%1$s' in session attributes.", model));
            return Optional.of(model);
        }
        else {
            // if not a map than expect it to be the model
            // this only happens if a model was added to the session before its json-serialization
            final TModel model = (TModel)o;
            log.debug(String.format("Read state for '%1$s' in session attributes.", model));
            return Optional.of(model);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  boolean exists(final Class modelClass) throws AlexaStateException {
        return exists(TModel.getAttributeKey(modelClass));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  boolean exists(final Class modelClass, final String id) throws AlexaStateException {
        return exists(TModel.getAttributeKey(modelClass, id));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  boolean exists(final Class modelClass, final AlexaScope scope) throws AlexaStateException {
        return exists(TModel.getAttributeKey(modelClass), scope);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public  boolean exists(final Class modelClass, final String id, final AlexaScope scope) throws AlexaStateException {
        return exists(TModel.getAttributeKey(modelClass, id), scope);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean exists(final String id) throws AlexaStateException {
        return exists(id, AlexaScope.SESSION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean exists(final String id, final AlexaScope scope) throws AlexaStateException {
        return readValue(id, scope).isPresent();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional readValue(final String id) throws AlexaStateException {
        return readValue(id, AlexaScope.SESSION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Optional readValue(final String id, final AlexaScope scope) throws AlexaStateException {
        final Map result = readValues(Collections.singletonList(id), scope);
        return result.isEmpty() ? Optional.empty() : Optional.of(result.get(id));
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map readValues(final Collection ids) throws AlexaStateException {
        return readValues(ids, AlexaScope.SESSION);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map readValues(final Collection ids, final AlexaScope scope) throws AlexaStateException {
        final Map idsInScope = new HashMap<>();
        ids.forEach(id -> idsInScope.putIfAbsent(id, scope));
        return readValues(idsInScope);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Map readValues(Map idsInScope) throws AlexaStateException {
        final Map stateObjectMap = new HashMap<>();
        idsInScope.forEach((k,v) -> {
            // do for session-scoped keys only
            if (existsInSession(k, v)) {
                // read from session and wrap value in state object
                stateObjectMap.putIfAbsent(k, new AlexaStateObject(k, session.getAttribute(k), v));
            }
        });
        return stateObjectMap;
    }

    private boolean existsInSession(final String id, final AlexaScope scope) {
        return AlexaScope.SESSION.includes(scope) && session.getAttributes().containsKey(id);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy