io.klerch.alexa.tellask.model.wrapper.AlexaSpeechletResponse.java Source code

Java tutorial

Introduction

Here is the source code for io.klerch.alexa.tellask.model.wrapper.AlexaSpeechletResponse.java

Source

/**
 * Created by Kay Lerch (https://twitter.com/KayLerch)
 *
 * Contribute to https://github.com/KayLerch/alexa-skills-kit-tellask-java
 *
 * Attached license applies.
 * This source is licensed under GNU GENERAL PUBLIC LICENSE Version 3 as of 29 June 2007
 */
package io.klerch.alexa.tellask.model.wrapper;

import com.amazon.speech.speechlet.SpeechletResponse;
import com.amazon.speech.ui.OutputSpeech;
import com.amazon.speech.ui.Reprompt;
import com.amazon.speech.ui.SsmlOutputSpeech;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
import io.klerch.alexa.tellask.model.AlexaOutput;
import io.klerch.alexa.tellask.model.AlexaOutputSlot;
import io.klerch.alexa.tellask.schema.UtteranceReader;
import io.klerch.alexa.tellask.util.resource.YamlReader;
import org.apache.commons.lang3.Validate;
import org.apache.log4j.Logger;

import java.io.IOException;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * An extended version of the orginial speechlet response which makes it
 * compatible with TellAsk SDK and the speechlet handler it provides. This
 * object is capable of turning your AlexaOutput into a valid speechlet response.
 */
public class AlexaSpeechletResponse extends SpeechletResponse {
    private static final Logger LOG = Logger.getLogger(AlexaSpeechletResponse.class);

    @JsonIgnore
    private final AlexaOutput output;
    @JsonIgnore
    private final YamlReader yamlReader;
    private final OutputSpeech outputSpeech;
    private final Reprompt reprompt;

    /**
     * A speechlet response is generated from an AlexaOutput object which should
     * contain all the information necessary to get access to a set of utterances
     * (over the utterance reader) from which it picks randomly according to the
     * intent name also given by the AlexaOutput.
     * @param output the AlexaOutput
     * @param utteranceReader the reader to use when reading utterances
     * @param locale the locale to use if output does not provide one
     */
    public AlexaSpeechletResponse(final AlexaOutput output, final UtteranceReader utteranceReader,
            final String locale) {
        // an utterance reader is picked (either from the output or the one given to this constructor)
        final UtteranceReader utteranceReaderToUse = output.getUtteranceReader() != null
                ? output.getUtteranceReader()
                : utteranceReader;
        this.output = output;
        this.yamlReader = new YamlReader(utteranceReaderToUse,
                output.getLocale() != null ? output.getLocale() : locale);
        this.setShouldEndSession(output.shouldEndSession());
        this.setCard(output.getCard());
        this.outputSpeech = getOutputSpeech();
        setOutputSpeech(outputSpeech);

        if (output.shouldReprompt()) {
            this.reprompt = getReprompt();
            // a reprompt is optional
            if (this.reprompt != null) {
                setReprompt(reprompt);
            } else {
                LOG.warn("Reprompt was desired but could not be generated from contents out of YAML file.");
            }
        } else {
            LOG.debug("No reprompt is desired. Skip looking for reprompt speech in YAML file.");
            this.reprompt = null;
        }
    }

    @Override
    @JsonInclude // works around a bug in Skills Kit SDK
    public boolean getShouldEndSession() {
        return output.shouldEndSession();
    }

    /**
     * The AlexaOutput used to generate the speechlet response
     * @return The AlexaOutput used to generate the speechlet response
     */
    public AlexaOutput getOutput() {
        return output;
    }

    /**
     * Gets the generated output speech.
     * @return the generated output speech.
     */
    @Override
    public OutputSpeech getOutputSpeech() {
        if (outputSpeech != null) {
            return outputSpeech;
        }

        final String utterance;

        try {
            utterance = yamlReader.getRandomUtterance(output).orElseThrow(IOException::new);
            LOG.debug("Random utterance read out from YAML file: " + utterance);
        } catch (IOException e) {
            LOG.error("Error while generating response utterance.", e);
            return null;
        }

        final String utteranceSsml = resolveSlotsInUtterance(utterance);

        final SsmlOutputSpeech ssmlOutputSpeech = new SsmlOutputSpeech();
        ssmlOutputSpeech.setSsml(utteranceSsml);
        return ssmlOutputSpeech;
    }

    /**
     * Gets the generated reprompt.
     * @return the generated reprompt
     */
    @Override
    public Reprompt getReprompt() {
        if (reprompt != null || !output.shouldReprompt()) {
            return reprompt;
        }

        final String repromptSpeech = yamlReader.getRandomReprompt(output).orElse(null);

        if (repromptSpeech != null) {
            final String utteranceSsml = resolveSlotsInUtterance(repromptSpeech);
            final SsmlOutputSpeech ssmlOutputSpeech = new SsmlOutputSpeech();
            ssmlOutputSpeech.setSsml(utteranceSsml);
            final Reprompt reprompt2 = new Reprompt();
            reprompt2.setOutputSpeech(ssmlOutputSpeech);
            return reprompt2;
        }
        return null;
    }

    private String resolveSlotsInUtterance(final String utterance) {
        final StringBuffer buffer = new StringBuffer();
        // extract all the placeholders found in the utterance
        final Matcher slotsInUtterance = Pattern.compile("\\{(.*?)\\}").matcher(utterance);
        // for any of the placeholders ...
        while (slotsInUtterance.find()) {
            // ... placeholder-name is the slotName to look after in two places of the output
            final String slotName = slotsInUtterance.group(1);
            final AlexaOutputSlot outputSlot = output
                    // prefer directly set output slots
                    .getSlots().stream()
                    // which do have the same name as what is found in the utterance
                    .filter(slot -> slot.getName().equals(slotName)).findFirst()
                    // if not directly applied look in provided models for AlexaSlotSave fields
                    .orElse(getSavedSlot(slotName));

            Validate.notNull(outputSlot, "Could not replace placeholder with name {" + slotName
                    + "} because no corresponding slot was set in the output.");
            // RJH - FEB 2017 - Matcher.quoteReplacement on slot input to fix bug
            // ~ https://github.com/KayLerch/alexa-skills-kit-tellask-java/issues/1
            slotsInUtterance.appendReplacement(buffer, Matcher.quoteReplacement(outputSlot.getSsml()));
        }
        slotsInUtterance.appendTail(buffer);
        return "<speak>" + buffer.toString() + "</speak>";
    }

    private AlexaOutputSlot getSavedSlot(String slotName) {
        return output.getModels().stream()
                // for those having that AlexaSlotSave field
                .filter(model -> model.hasOutputSlot(slotName))
                // create a AlexaOutputSlot from attributes in annotation + the field value itself
                .map(model -> model.getOutputSlot(slotName).orElse(null)).findFirst().orElse(null);
    }
}