View Javadoc
1   /*
2    * Copyright (c) 2002-2019, Mairie de Paris
3    * All rights reserved.
4    *
5    * Redistribution and use in source and binary forms, with or without
6    * modification, are permitted provided that the following conditions
7    * are met:
8    *
9    *  1. Redistributions of source code must retain the above copyright notice
10   *     and the following disclaimer.
11   *
12   *  2. Redistributions in binary form must reproduce the above copyright notice
13   *     and the following disclaimer in the documentation and/or other materials
14   *     provided with the distribution.
15   *
16   *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
17   *     contributors may be used to endorse or promote products derived from
18   *     this software without specific prior written permission.
19   *
20   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
21   * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
22   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
23   * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
24   * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
25   * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
26   * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
27   * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
28   * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
30   * POSSIBILITY OF SUCH DAMAGE.
31   *
32   * License 1.0
33   */
34  package fr.paris.lutece.plugins.botpress.service;
35  
36  import fr.paris.lutece.plugins.botpress.service.renderers.BotMessageRenderer;
37  import com.fasterxml.jackson.databind.JsonNode;
38  import com.fasterxml.jackson.databind.ObjectMapper;
39  import com.fasterxml.jackson.databind.node.MissingNode;
40  import fr.paris.lutece.plugins.botpress.business.RequestMessage;
41  import fr.paris.lutece.plugins.chatbot.business.BotPost;
42  import fr.paris.lutece.portal.service.util.AppLogService;
43  import fr.paris.lutece.util.ReferenceList;
44  import fr.paris.lutece.util.httpaccess.HttpAccess;
45  import fr.paris.lutece.util.httpaccess.HttpAccessException;
46  import java.io.IOException;
47  import java.util.ArrayList;
48  import java.util.HashMap;
49  import java.util.Iterator;
50  import java.util.List;
51  import java.util.Locale;
52  import java.util.Map;
53  import org.apache.log4j.Logger;
54  
55  /**
56   * ConverseService
57   */
58  public final class ConverseService
59  {
60  
61      private static final String CONTENT_TYPE = "Content-Type";
62      private static final String CONTENT_TYPE_JSON = "application/json";
63      private static final String NODE_RESPONSES = "responses";
64      private static final String NODE_PAYLOAD = "payload";
65      private static final int VERSION_1 = 1;
66  
67      private static final String LOGGER_NAME = "botpress";
68      private static final Logger LOGGER = Logger.getLogger( LOGGER_NAME );
69      private static ObjectMapper _objectMapper = new ObjectMapper( );
70  
71      /** Private constructor */
72      private ConverseService( )
73      {
74      }
75  
76      /**
77       * Get responses from the bot
78       * 
79       * @param strMessage
80       *            The message from the User
81       * @param strConversationId
82       *            The conversation ID (or UserID)
83       * @param strBotApiEntryPointUrl
84       *            The URL of the Bot API
85       * @param strErrorMessage
86       *            Error message sent by the bot if the connection fails
87       * @param locale
88       *            The locale The locale
89       * @return A list of bot responses
90       */
91      public static List<BotPost> getBotResponse( String strMessage, String strConversationId, String strBotApiEntryPointUrl, String strErrorMessage,
92              Locale locale )
93      {
94          List<BotPost> listPosts = new ArrayList<>( );
95          RequestMessages/business/RequestMessage.html#RequestMessage">RequestMessage message = new RequestMessage( strMessage );
96          String strUrl = null;
97          String strJsonResponsePretty = null;
98          try
99          {
100             String strJsonMessage = _objectMapper.writeValueAsString( message );
101             HttpAccess client = new HttpAccess( );
102             HashMap<String, String> mapRequestHeaders = new HashMap<>( );
103             mapRequestHeaders.put( CONTENT_TYPE, CONTENT_TYPE_JSON );
104             HashMap<String, String> mapResponseHeaders = new HashMap<>( );
105             strUrl = strBotApiEntryPointUrl + strConversationId;
106             String strJsonResponse = client.doPostJSON( strUrl, strJsonMessage, mapRequestHeaders, mapResponseHeaders );
107             Object jsonResponse = _objectMapper.readTree( strJsonResponse );
108             strJsonResponsePretty = _objectMapper.writerWithDefaultPrettyPrinter( ).writeValueAsString( jsonResponse );
109             if ( LOGGER.isDebugEnabled( ) )
110             {
111                 LOGGER.debug( "Message : " + strMessage + "\nResponse : \n" + strJsonResponsePretty );
112             }
113             parseJsonResponse( strJsonResponse, listPosts );
114             return listPosts;
115         }
116         catch( HttpAccessException | IOException ex )
117         {
118             StringBuilder sbError = new StringBuilder( );
119             sbError.append( "Error getting response from Botpress API : " ).append( ex.getMessage( ) );
120             if ( strUrl != null )
121             {
122                 sbError.append( "\n - POST URL : " ).append( strUrl );
123             }
124             if ( strJsonResponsePretty != null )
125             {
126                 sbError.append( "\n - JSON response : " ).append( strJsonResponsePretty );
127             }
128             AppLogService.error( sbError.toString( ), ex );
129 
130             BotPost post = new BotPost( strErrorMessage );
131             listPosts.add( post );
132 
133             return listPosts;
134         }
135 
136     }
137 
138     /**
139      * Parse the JSON response from the bot
140      * 
141      * @param strJsonResponse
142      *            The JSON response
143      * @param listPosts
144      *            The list of bot posts
145      * @throws IOException
146      *             if an error occurs
147      */
148     static void parseJsonResponse( String strJsonResponse, List<BotPost> listPosts ) throws IOException
149     {
150         JsonNode rootNode = _objectMapper.readTree( strJsonResponse );
151         JsonNode responsesNode = rootNode.path( NODE_RESPONSES );
152         if(  ! (responsesNode instanceof MissingNode ) )
153         {
154         Iterator<JsonNode> elements = responsesNode.elements( );
155         while ( elements.hasNext( ) )
156         {
157             JsonNode response = elements.next( );
158             BotMessageRenderer renderer = RendererService.getRenderer( response );
159             if ( renderer != null )
160             {
161                 BotPost post = new BotPost( renderer.render( _objectMapper.convertValue( response, Map.class ) ), renderer.getPostContentType( ) );
162                 listPosts.add( post );
163             }
164         }
165         }
166         else
167         {
168             JsonNode payloadNode = rootNode.path( NODE_PAYLOAD );
169             if(  ! ( payloadNode instanceof MissingNode ))
170             {
171                 String strText = payloadNode.get( "text" ).asText();
172                 BotPost post = new BotPost( strText , BotPost.CONTENT_TYPE_TEXT );
173                 listPosts.add( post );
174             }
175         }
176 
177     }
178 
179     /**
180      * Returns the list of managed API versions
181      * 
182      * @return A list of versions
183      */
184     public static ReferenceList getApiVersions( )
185     {
186         ReferenceList list = new ReferenceList( );
187         list.addItem( VERSION_1, "BotPress Converse API Version 1" );
188         return list;
189 
190     }
191 
192     /**
193      * Build the entry point URL
194      * 
195      * @param strBotKey
196      *            The bot key
197      * @param strServerUrl
198      *            The server URL
199      * @param nApiVersion
200      *            The API version number
201      * @return The entry point URL
202      */
203     public static String getBotApiEntryPointUrl( String strBotKey, String strServerUrl, int nApiVersion )
204     {
205         StringBuilder sbEntryPointUrl = new StringBuilder( );
206 
207         switch( nApiVersion )
208         {
209             case VERSION_1:
210                 sbEntryPointUrl.append( ( strServerUrl.endsWith( "/" ) ) ? strServerUrl : strServerUrl + "/" ).append( "api/v1/bots/" ).append( strBotKey )
211                         .append( "/converse/" );
212                 break;
213 
214             default:
215                 AppLogService.error( "Invalid Bot Press API version number : " + nApiVersion );
216                 break;
217 
218         }
219         return sbEntryPointUrl.toString( );
220 
221     }
222 
223 }