View Javadoc
1   /*
2    * Licensed to The Apereo Foundation under one or more contributor license
3    * agreements. See the NOTICE file distributed with this work for additional
4    * information regarding copyright ownership.
5    *
6    *
7    * The Apereo Foundation licenses this file to you under the Educational
8    * Community License, Version 2.0 (the "License"); you may not use this file
9    * except in compliance with the License. You may obtain a copy of the License
10   * at:
11   *
12   *   http://opensource.org/licenses/ecl2.txt
13   *
14   * Unless required by applicable law or agreed to in writing, software
15   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
16   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
17   * License for the specific language governing permissions and limitations under
18   * the License.
19   *
20   */
21  
22  package org.opencastproject.userdirectory.moodle;
23  
24  import org.apache.http.NameValuePair;
25  import org.apache.http.client.methods.CloseableHttpResponse;
26  import org.apache.http.client.methods.HttpGet;
27  import org.apache.http.client.utils.URIBuilder;
28  import org.apache.http.impl.client.CloseableHttpClient;
29  import org.apache.http.impl.client.HttpClients;
30  import org.apache.http.message.BasicNameValuePair;
31  import org.json.simple.JSONArray;
32  import org.json.simple.JSONObject;
33  import org.json.simple.parser.JSONParser;
34  import org.json.simple.parser.ParseException;
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  import java.io.BufferedReader;
39  import java.io.IOException;
40  import java.io.InputStreamReader;
41  import java.net.URI;
42  import java.net.URISyntaxException;
43  import java.util.ArrayList;
44  import java.util.Collections;
45  import java.util.LinkedList;
46  import java.util.List;
47  
48  /**
49   * Implementation of the Moodle web service client.
50   */
51  public class MoodleWebServiceImpl implements MoodleWebService {
52    /**
53     * The logger.
54     */
55    private static final Logger logger = LoggerFactory.getLogger(MoodleUserProviderInstance.class);
56  
57    /**
58     * HTTP user agent when performing requests.
59     */
60    private static final String OC_USERAGENT = "Opencast";
61  
62    /**
63     * The URL of the Moodle instance.
64     */
65    private URI url;
66  
67    /**
68     * The token used to call Moodle REST webservices.
69     */
70    private String token;
71  
72    /**
73     * Constructs a new Moodle web service client.
74     *
75     * @param url   URL of the Moodle instance
76     * @param token Web service token
77     */
78    public MoodleWebServiceImpl(URI url, String token) {
79      this.url = url;
80      this.token = token;
81    }
82  
83    /**
84     * {@inheritDoc}
85     */
86    @Override
87    public List<MoodleUser> coreUserGetUsersByField(CoreUserGetUserByFieldFilters filter, List<String> values)
88            throws URISyntaxException, IOException, MoodleWebServiceException, ParseException {
89      logger.debug("coreUserGetUsersByField(({}, {}))", filter, values);
90  
91      List<NameValuePair> params = new ArrayList<>();
92      params.add(new BasicNameValuePair("field", filter.toString()));
93  
94      for (int i = 0; i < values.size(); ++i) {
95        params.add(new BasicNameValuePair("values[" + i + "]", values.get(i)));
96      }
97  
98      Object resp = executeMoodleRequest(MOODLE_FUNCTION_CORE_USER_GET_USERS_BY_FIELD, params);
99  
100     // Parse response
101     if (resp == null || !(resp instanceof JSONArray)) {
102       throw new MoodleWebServiceException("Moodle responded in unexpected format");
103     }
104 
105     JSONArray respArray = (JSONArray) resp;
106     List<MoodleUser> users = new ArrayList<>(respArray.size());
107 
108     for (Object userObj : respArray) {
109       if (!(userObj instanceof JSONObject)) {
110         throw new MoodleWebServiceException("Moodle responded in unexpected format");
111       }
112 
113       JSONObject userJsonObj = (JSONObject) userObj;
114       MoodleUser user = new MoodleUser();
115 
116       if (userJsonObj.containsKey("id")) {
117         user.setId(userJsonObj.get("id").toString());
118       }
119       if (userJsonObj.containsKey("username")) {
120         user.setUsername(userJsonObj.get("username").toString());
121       }
122       if (userJsonObj.containsKey("fullname")) {
123         user.setFullname(userJsonObj.get("fullname").toString());
124       }
125       if (userJsonObj.containsKey("idnumber")) {
126         user.setIdnumber(userJsonObj.get("idnumber").toString());
127       }
128       if (userJsonObj.containsKey("email")) {
129         user.setEmail(userJsonObj.get("email").toString());
130       }
131       if (userJsonObj.containsKey("auth")) {
132         user.setAuth(userJsonObj.get("auth").toString());
133       }
134 
135       users.add(user);
136     }
137 
138     return users;
139   }
140 
141   /**
142    * {@inheritDoc}
143    *
144    * @see org.opencastproject.userdirectory.moodle.MoodleWebService#toolOpencastGetCoursesForInstructor(String)
145    */
146   @Override
147   public List<String> toolOpencastGetCoursesForInstructor(String username)
148           throws URISyntaxException, IOException, MoodleWebServiceException, ParseException {
149     logger.debug("toolOpencastGetCoursesForInstructor({})", username);
150 
151     List<NameValuePair> params = Collections
152             .singletonList((NameValuePair) new BasicNameValuePair("username", username));
153 
154     return parseIdList(executeMoodleRequest(MOODLE_FUNCTION_TOOL_OPENCAST_GET_COURSES_FOR_INSTRUCTOR, params));
155   }
156 
157   /**
158    * {@inheritDoc}
159    *
160    * @see org.opencastproject.userdirectory.moodle.MoodleWebService#toolOpencastGetCoursesForLearner(String)
161    */
162   @Override
163   public List<String> toolOpencastGetCoursesForLearner(String username)
164           throws URISyntaxException, IOException, MoodleWebServiceException, ParseException {
165     logger.debug("toolOpencastGetCoursesForLearner({})", username);
166 
167     List<NameValuePair> params = Collections
168             .singletonList((NameValuePair) new BasicNameValuePair("username", username));
169 
170     return parseIdList(executeMoodleRequest(MOODLE_FUNCTION_TOOL_OPENCAST_GET_COURSES_FOR_LEARNER, params));
171   }
172 
173   @Override
174   public List<String> toolOpencastGetGroupsForLearner(String username)
175           throws URISyntaxException, IOException, MoodleWebServiceException, ParseException {
176     logger.debug("toolOpencastGetGroupsForLearner({})", username);
177 
178     List<NameValuePair> params = Collections
179             .singletonList((NameValuePair) new BasicNameValuePair("username", username));
180 
181     return parseIdList(executeMoodleRequest(MOODLE_FUNCTION_TOOL_OPENCAST_GET_GROUPS_FOR_LEARNER, params));
182   }
183 
184   /**
185    * {@inheritDoc}
186    *
187    * @see org.opencastproject.userdirectory.moodle.MoodleWebService#getURL()
188    */
189   @Override
190   public String getURL() {
191     return url.toString();
192   }
193 
194   /**
195    * Parses the returned Moodle response for a list of IDs.
196    *
197    * @param resp The Moodle response. It should be of type {@link JSONArray}.
198    * @return A list of Moodle IDs.
199    * @throws MoodleWebServiceException If the parsing failed because the response format was unexpected.
200    */
201   private List<String> parseIdList(Object resp) throws MoodleWebServiceException {
202     if (resp == null) {
203       return new LinkedList<>();
204     }
205 
206     if (!(resp instanceof JSONArray)) {
207       throw new MoodleWebServiceException("Moodle responded in unexpected format");
208     }
209 
210     JSONArray respArray = (JSONArray) resp;
211     List<String> ids = new ArrayList<>(respArray.size());
212 
213     for (Object courseObj : respArray) {
214       if (!(courseObj instanceof JSONObject) || ((JSONObject) courseObj).get("id") == null) {
215         throw new MoodleWebServiceException("Moodle responded in unexpected format");
216       }
217 
218       ids.add(((JSONObject) courseObj).get("id").toString());
219     }
220 
221     return ids;
222   }
223 
224   /**
225    * Executes a Moodle webservice request.
226    *
227    * @param function The function to execute.
228    * @param params   Additional parameters to pass.
229    * @return A JSON object, array, String, Number, Boolean, or null.
230    * @throws URISyntaxException        In case the URL cannot be constructed.
231    * @throws IOException               In case of an IO error.
232    * @throws MoodleWebServiceException In case Moodle returns an error.
233    * @throws ParseException            In case the Moodle response cannot be parsed.
234    */
235   private Object executeMoodleRequest(String function, List<NameValuePair> params)
236           throws URISyntaxException, IOException, MoodleWebServiceException, ParseException {
237     // Build URL
238     URIBuilder url = new URIBuilder(this.url);
239     url.addParameters(params);
240     url.addParameter("wstoken", token);
241     url.addParameter("wsfunction", function);
242     url.addParameter("moodlewsrestformat", "json");
243 
244     // Execute request
245     HttpGet get = new HttpGet(url.build());
246     get.setHeader("User-Agent", OC_USERAGENT);
247 
248     try (CloseableHttpClient client = HttpClients.createDefault()) {
249       try (CloseableHttpResponse resp = client.execute(get)) {
250         // Parse response
251         BufferedReader reader = new BufferedReader(new InputStreamReader(resp.getEntity().getContent()));
252         JSONParser parser = new JSONParser();
253         Object obj = parser.parse(reader);
254 
255         // Check for errors
256         if (obj instanceof JSONObject) {
257           JSONObject jObj = (JSONObject) obj;
258           if (jObj.containsKey("exception") || jObj.containsKey("errorcode")) {
259             throw new MoodleWebServiceException("Moodle returned an error: " + jObj.toJSONString());
260           }
261         }
262 
263         return obj;
264       }
265     }
266   }
267 }