7964e45c20d133e0679ef1896c85cce9e4d8e7e7
[ui/dmaapbc.git] /
1 package org.onap.dcae.dmaapbc.dbcapp.controller;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.net.URL;
8 import java.util.ArrayList;
9 import java.util.Collections;
10 import java.util.Comparator;
11 import java.util.HashMap;
12 import java.util.List;
13 import java.util.Map;
14
15 import javax.servlet.http.HttpServletRequest;
16 import javax.servlet.http.HttpServletResponse;
17
18 import org.onap.dcae.dmaapbc.client.DmaapBcRestClient;
19 import org.onap.dcae.dmaapbc.client.HttpStatusAndResponse;
20 import org.onap.dcae.dmaapbc.dbcapp.domain.DmaapAccess;
21 import org.onap.dcae.dmaapbc.dbcapp.rest.DbcUsvcRestClient;
22 import org.onap.dcae.dmaapbc.dbcapp.service.DmaapAccessService;
23 import org.onap.dcae.dmaapbc.dbcapp.util.DbcappProperties;
24 import org.onap.dcae.dmaapbc.model.DR_Pub;
25 import org.onap.dcae.dmaapbc.model.DR_Sub;
26 import org.onap.dcae.dmaapbc.model.DcaeLocation;
27 import org.onap.dcae.dmaapbc.model.Dmaap;
28 import org.onap.dcae.dmaapbc.model.DmaapObject;
29 import org.onap.dcae.dmaapbc.model.ErrorResponse;
30 import org.onap.dcae.dmaapbc.model.Feed;
31 import org.onap.dcae.dmaapbc.model.MR_Client;
32 import org.onap.dcae.dmaapbc.model.Topic;
33 import org.openecomp.portalsdk.core.controller.RestrictedBaseController;
34 import org.openecomp.portalsdk.core.domain.User;
35 import org.openecomp.portalsdk.core.logging.logic.EELFLoggerDelegate;
36 import org.openecomp.portalsdk.core.onboarding.util.CipherUtil;
37 import org.openecomp.portalsdk.core.web.support.UserUtils;
38 import org.springframework.beans.factory.annotation.Autowired;
39
40 import com.fasterxml.jackson.annotation.JsonInclude;
41 import com.fasterxml.jackson.core.JsonProcessingException;
42 import com.fasterxml.jackson.databind.ObjectMapper;
43
44 /**
45  * This base class provides utility methods to child controllers. All of the
46  * requests are forwarded on to a remote REST API, so there's a large degree of
47  * commonality among the implementations. Combining them kept the lines-of-code
48  * count down, at the expense of some complexity.
49  */
50 public class DbcappRestrictedBaseController extends RestrictedBaseController {
51
52         /**
53          * Query parameter for desired page number
54          */
55         protected static final String PAGE_NUM_QUERY_PARAM = "pageNum";
56
57         /**
58          * Query parameter for desired items per page
59          */
60         protected static final String VIEW_PER_PAGE_QUERY_PARAM = "viewPerPage";
61
62         /**
63          * Tag for status code in JSON responses - ALWAYS PRESENT.
64          */
65         protected static final String STATUS_RESPONSE_KEY = "status";
66
67         /**
68          * Tag for data in JSON responses.
69          */
70         protected static final String DATA_RESPONSE_KEY = "data";
71
72         /**
73          * Tag for error message in JSON responses; absent on success.
74          */
75         protected static final String ERROR_RESPONSE_KEY = "error";
76
77         /**
78          * Tag for response integer, pages required to display complete result list
79          */
80         protected static final String TOTAL_PAGES_RESPONSE_KEY = "totalPages";
81
82         /**
83          * Tag for DMaaP name obtained from REST client.
84          */
85         protected static final String PROFILE_NAME_RESPONSE_KEY = "profileName";
86
87         /**
88          * Tag for DMaaP name obtained from REST client.
89          */
90         protected static final String DMAAP_NAME_RESPONSE_KEY = "dmaapName";
91
92         /**
93          * Tag for DCAE location name list obtained from REST client.
94          */
95         protected static final String DCAE_LOCATIONS_RESPONSE_KEY = "dcaeLocations";
96
97         /**
98          * Logger that conforms with ECOMP guidelines
99          */
100         private static EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(DbcappRestrictedBaseController.class);
101
102         /**
103          * For general use in these methods and subclasses
104          */
105         protected final ObjectMapper mapper = new ObjectMapper();
106
107         /**
108          * DAO accesses the profiles via a local database. REST accesses the
109          * profiles via a remote REST service.
110          */
111         public enum AccessMethod {
112                 DAO, REST
113         };
114
115         /**
116          * Enum for selecting an item type.
117          */
118         public enum DmaapDataItem {
119                 DR_FEED, DR_PUB, DR_SUB, MR_TOPIC, MR_CLIENT;
120         }
121
122         /**
123          * Application properties - NOT available to constructor.
124          */
125         @Autowired
126         private DbcappProperties appProperties;
127
128         /**
129          * Database access - which might not be used.
130          */
131         @Autowired
132         private DmaapAccessService dmaapAccessDaoServiceAuto;
133
134         /**
135          * Read from application properties.
136          */
137         private String mechIdName, mechIdPass;
138
139         /**
140          * This is set by {@link #getDmaapAccessService()} to the DAO or REST
141          * implementation as configured in properties.
142          */
143         private DmaapAccessService dmaapAccessService;
144
145         /**
146          * Hello Spring, here's your no-arg constructor.
147          */
148         public DbcappRestrictedBaseController() {
149                 // Do not serialize null values
150                 mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
151         }
152
153         /**
154          * Access method for subclasses.
155          * 
156          * @return DbcappProperties object that was autowired by Spring.
157          */
158         protected DbcappProperties getAppProperties() {
159                 return appProperties;
160         }
161
162         /**
163          * Lazy initialization. As a side effect, caches mech ID and password.
164          * 
165          * @return Either DAO or REST client that implements the access service
166          *         interface.
167          */
168         protected DmaapAccessService getDmaapAccessService() {
169                 if (dmaapAccessService != null)
170                         return dmaapAccessService;
171
172                 // Get the application's mechid
173                 mechIdName = appProperties.getProperty(DbcappProperties.DMAAP_MECHID_NAME);
174                 // This is encrypted
175                 String cipher = appProperties.getProperty(DbcappProperties.DMAAP_MECHID_PASSWORD);
176                 if (mechIdName == null || cipher == null)
177                         throw new RuntimeException("Failed to get MECH_ID name and/or password from properties");
178                 try {
179                         mechIdPass = CipherUtil.decrypt(cipher);
180                 } catch (Exception ex) {
181                         throw new RuntimeException("Failed to decrypt password from config file", ex);
182                 }
183
184                 String accessMethod = appProperties.getProperty(DbcappProperties.PROFILE_ACCESS_METHOD);
185                 if (accessMethod == null)
186                         throw new RuntimeException("Failed to get property " + DbcappProperties.PROFILE_ACCESS_METHOD);
187                 AccessMethod profileAccessMethod = AccessMethod.valueOf(accessMethod.toUpperCase());
188                 if (AccessMethod.DAO == profileAccessMethod) {
189                         // Spring auto-wired this field
190                         dmaapAccessService = dmaapAccessDaoServiceAuto;
191                 } else {
192                         String url = appProperties.getProperty(DbcappProperties.PROFILE_USVC_URL);
193                         String user = appProperties.getProperty(DbcappProperties.PROFILE_USVC_USER);
194                         String pass = appProperties.getProperty(DbcappProperties.PROFILE_USVC_PASS);
195                         if (url == null || user == null || pass == null)
196                                 throw new RuntimeException("getDmaapAccessService: missing property: one of url, user, pass");
197                         String clearText = null;
198                         try {
199                                 clearText = CipherUtil.decrypt(pass);
200                         } catch (Exception ex) {
201                                 throw new RuntimeException("getDmaapAccessService: failed to decrypt password from config");
202                         }
203                         dmaapAccessService = new DbcUsvcRestClient(url, user, clearText);
204                 }
205                 return dmaapAccessService;
206         }
207
208         /**
209          * Creates a REST client with appropriate credentials, the user/pass from
210          * the access profile if present, otherwise with the default mech ID and
211          * password.
212          * 
213          * @param dmaapAccess
214          *            Profile
215          * @return REST client.
216          */
217         protected DmaapBcRestClient getDmaapBcRestClient(DmaapAccess dmaapAccess) {
218                 DmaapBcRestClient restClient = null;
219                 if (dmaapAccess.getMechId() == null || dmaapAccess.getMechId().length() == 0)
220                         restClient = new DmaapBcRestClient(dmaapAccess.getDmaapUrl(), mechIdName, mechIdPass);
221                 else
222                         restClient = new DmaapBcRestClient(dmaapAccess.getDmaapUrl(), dmaapAccess.getMechId(),
223                                         dmaapAccess.getPassword());
224                 return restClient;
225         }
226
227         /**
228          * Pulls out of the specified list the appropriate items for the page of
229          * results specified by the page number and view-per-page parameters.
230          * 
231          * @param pageNum
232          *            Page number requested by user
233          * @param viewPerPage
234          *            Number of items per page
235          * @param itemList
236          *            List of items available
237          * @return List of items to display
238          */
239         @SuppressWarnings("rawtypes")
240         private static List shrinkListToPage(final int pageNum, final int viewPerPage, final List itemList) {
241                 // user-friendly page numbers index from 1
242                 int firstIndexOnThisPage = viewPerPage * (pageNum - 1);
243                 int firstIndexOnNextPage = viewPerPage * pageNum;
244                 int fromIndex = firstIndexOnThisPage < itemList.size() ? firstIndexOnThisPage : itemList.size();
245                 int toIndex = firstIndexOnNextPage < itemList.size() ? firstIndexOnNextPage : itemList.size();
246                 // answers empty list if from==to
247                 return itemList.subList(fromIndex, toIndex);
248         }
249
250         /**
251          * Gets the body of a HTTP request assuming UTF-8 encoding.
252          * 
253          * @param request
254          *            HttpServletRequest
255          * @return String version of request body
256          * @throws IOException
257          *             If the read fails
258          */
259         protected static String getBody(HttpServletRequest request) throws IOException {
260                 StringBuilder stringBuilder = new StringBuilder();
261                 BufferedReader bufferedReader = null;
262                 try {
263                         InputStream inputStream = request.getInputStream();
264                         if (inputStream != null) {
265                                 bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
266                                 char[] charBuffer = new char[512];
267                                 int bytesRead = -1;
268                                 while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
269                                         stringBuilder.append(charBuffer, 0, bytesRead);
270                                 }
271                         } else {
272                                 stringBuilder.append("");
273                         }
274                 } finally {
275                         if (bufferedReader != null) {
276                                 try {
277                                         bufferedReader.close();
278                                 } catch (IOException ex) {
279                                         throw ex;
280                                 }
281                         }
282
283                 }
284                 return stringBuilder.toString();
285         }
286
287         /**
288          * Builds a JSON success response from the specified inputs.
289          * 
290          * @param statusCode
291          *            e.g., 200 for OK
292          * @param dataPojo
293          *            Plain old Java object to serialize as JSON; ignored if null.
294          * @throws JsonProcessingException
295          *             If the POJO cannot be serialized
296          * @return JSON block with items "status" : 200 and "data" : (data..)
297          */
298         protected String buildJsonSuccess(int statusCode, Object dataPojo) throws JsonProcessingException {
299                 Map<String, Object> model = new HashMap<String, Object>();
300                 model.put(STATUS_RESPONSE_KEY, statusCode);
301                 if (dataPojo != null)
302                         model.put(DATA_RESPONSE_KEY, dataPojo);
303                 String json = mapper.writeValueAsString(model);
304                 return json;
305         }
306
307         /**
308          * Builds a JSON error response from the specified inputs.
309          * 
310          * @param statusCode
311          *            e.g., 500 for internal server error
312          * @param errMsg
313          *            Information about the operation that failed
314          * @param exception
315          *            Converted to string; ignored if null.
316          * @return JSON block with tags "status" and "error".
317          */
318         protected String buildJsonError(int statusCode, String errMsg, Exception exception) {
319                 Map<String, Object> model = new HashMap<String, Object>();
320                 model.put(STATUS_RESPONSE_KEY, new Integer(500));
321                 if (exception == null) {
322                         model.put(ERROR_RESPONSE_KEY, errMsg);
323                 } else {
324                         final int enough = 512;
325                         String exString = exception.toString();
326                         String exceptionMsg = exString.length() > enough ? exString.substring(0, enough) : exString;
327                         model.put(ERROR_RESPONSE_KEY, errMsg + ": " + exceptionMsg);
328                 }
329                 String json = null;
330                 try {
331                         json = mapper.writeValueAsString(model);
332                 } catch (JsonProcessingException ex) {
333                         // serializing the trivial map should never fail
334                         String err = "buildJsonError: failed to serialize";
335                         logger.error(EELFLoggerDelegate.errorLogger, err, ex);
336                         throw new RuntimeException(err, ex);
337                 }
338                 return json;
339         }
340
341         /**
342          * Gets a list of DMaaP access profiles for this user from the database. The
343          * profiles have passwords in the clear - this method decrypts the database
344          * entries.
345          * 
346          * Initializes the list for new users and/or configuration changes. Checks
347          * the database list against the configured list of URLs, and creates new
348          * rows for any configured URLs not present for the user. Most environments
349          * are expected to have exactly one valid URL, and the webapp uses a fixed
350          * MechID to authenticate itself to the DMaaP bus controller, so this
351          * approach means new users can start without any setup of URLs.
352          * 
353          * @param userId
354          *            User ID
355          * @return List of DmaapAccess objects
356          * @throws Exception
357          *             If the URL list is not available in properties
358          */
359         protected List<DmaapAccess> getOrInitDmaapAccessList(String userId) throws Exception {
360                 String[] configUrls = getAppProperties().getCsvListProperty(DbcappProperties.DMAAP_REST_URL_LIST);
361                 if (configUrls == null || configUrls.length == 0)
362                         throw new Exception("getOrInitDmaapAccessList: Failed to get DMAAP REST URL list");
363                 // Update this list to track which URLs are in the database.
364                 List<String> configUrlList = new ArrayList<String>(configUrls.length);
365                 for (String c : configUrls) {
366                         // Validate URL to detect config botches
367                         URL url = new URL(c);
368                         configUrlList.add(url.toExternalForm());
369                 }
370
371                 List<DmaapAccess> dbAccessList = getDmaapAccessService().getDmaapAccessList(userId);
372
373                 // Check the database entries against the configuration. Also
374                 // build a list of non-DAO objects with clear-text passwords.
375                 List<DmaapAccess> clearList = new ArrayList<DmaapAccess>(dbAccessList.size());
376                 for (DmaapAccess dmaapAccess : dbAccessList) {
377                         // drop this URL from the list.
378                         // If it's not known to config, complain because that's a bogus row.
379                         if (!configUrlList.remove(dmaapAccess.getDmaapUrl()))
380                                 logger.warn(EELFLoggerDelegate.errorLogger, "getOrInitDmaapAccessList: detected extra URL {}",
381                                                 dmaapAccess.getDmaapUrl());
382                         // Return cleartext in JSON
383                         DmaapAccess clone = new DmaapAccess(dmaapAccess);
384                         clone.setPassword(clone.decryptPassword());
385                         clearList.add(clone);
386                 }
387
388                 // Create new rows for any configured URLs not found for this user.
389                 for (int i = 0; i < configUrlList.size(); ++i) {
390                         String missUrl = configUrlList.get(i);
391                         logger.debug(EELFLoggerDelegate.debugLogger, "getOrInitDmaapAccessList: adding missing URL {}", missUrl);
392                         DmaapAccess newDmaapAccess = new DmaapAccess();
393                         // Create a semi-reasonable name for the table
394                         newDmaapAccess.setName("dmaap-" + Integer.toString(i + 1));
395                         newDmaapAccess.setUserId(userId);
396                         newDmaapAccess.setDmaapUrl(missUrl);
397                         // Write to db.
398                         getDmaapAccessService().saveDmaapAccess(newDmaapAccess);
399                         // Add to response, which assumes the write was successful.
400                         clearList.add(newDmaapAccess);
401                 }
402
403                 return clearList;
404         }
405
406         /**
407          * Gets the user's selected DMaaP access profile.
408          * 
409          * @param userId
410          *            User ID
411          * @return DmaapAccess object that is currently selected, or the first one
412          *         found if none are selected; null if no access profiles are
413          *         configured.
414          * @throws Exception
415          *             If the profile is not found
416          */
417         protected DmaapAccess getSelectedDmaapAccess(String userId) throws Exception {
418                 List<DmaapAccess> profiles = getOrInitDmaapAccessList(userId);
419                 if (profiles.size() == 0) {
420                         logger.debug("getSelectedDmaapAccess: no rows found, returning null");
421                         return null;
422                 }
423
424                 // Return the first one by default if nothing is selected.
425                 DmaapAccess selected = profiles.get(0);
426                 for (DmaapAccess da : profiles)
427                         if (da.getSelected())
428                                 selected = da;
429
430                 return selected;
431         }
432
433         /**
434          * Supports sorting a list of feeds by the first column displayed: ID
435          */
436         private static Comparator<DmaapObject> feedComparator = new Comparator<DmaapObject>() {
437                 @Override
438                 public int compare(DmaapObject o1, DmaapObject o2) {
439                         Feed f1 = (Feed) o1;
440                         Feed f2 = (Feed) o2;
441                         // sort these numbers lexicographically, same as the front end
442                         // table.
443                         return f1.getFeedId().compareTo(f2.getFeedId());
444                 }
445         };
446
447         /**
448          * Supports sorting a list of publishers by the first column displayed: pub
449          * ID
450          */
451         private static Comparator<DmaapObject> pubComparator = new Comparator<DmaapObject>() {
452                 @Override
453                 public int compare(DmaapObject o1, DmaapObject o2) {
454                         DR_Pub p1 = (DR_Pub) o1;
455                         DR_Pub p2 = (DR_Pub) o2;
456                         return p1.getPubId().compareTo(p2.getPubId());
457                 }
458         };
459
460         /**
461          * Supports sorting a list of subscribers by the first column displayed: sub
462          * ID
463          */
464         private static Comparator<DmaapObject> subComparator = new Comparator<DmaapObject>() {
465                 @Override
466                 public int compare(DmaapObject o1, DmaapObject o2) {
467                         DR_Sub s1 = (DR_Sub) o1;
468                         DR_Sub s2 = (DR_Sub) o2;
469                         // sort these numbers lexicographically, same as the front end
470                         // table.
471                         return s1.getSubId().compareTo(s2.getSubId());
472                 }
473         };
474
475         /**
476          * Supports sorting a list of topics by the first column displayed: FQTN
477          */
478         private static Comparator<DmaapObject> topicComparator = new Comparator<DmaapObject>() {
479                 @Override
480                 public int compare(DmaapObject o1, DmaapObject o2) {
481                         Topic t1 = (Topic) o1;
482                         Topic t2 = (Topic) o2;
483                         return t1.getFqtn().compareTo(t2.getFqtn());
484                 }
485         };
486
487         /**
488          * Supports sorting a list of clients by the first column displayed: client
489          * ID.
490          */
491         private static Comparator<DmaapObject> clientComparator = new Comparator<DmaapObject>() {
492                 @Override
493                 public int compare(DmaapObject o1, DmaapObject o2) {
494                         MR_Client c1 = (MR_Client) o1;
495                         MR_Client c2 = (MR_Client) o2;
496                         // sort these numbers lexicographically, same as the front end
497                         // table.
498                         return c1.getMrClientId().compareTo(c2.getMrClientId());
499                 }
500         };
501
502         /**
503          * Gets one page of DMaaP objects and supporting information via the Bus
504          * Controller REST client. On success, returns a JSON object as String with
505          * the following tags:
506          * <UL>
507          * <LI>status: Integer; HTTP status code 200.
508          * <LI>dmaapName: String, name returned by the remote DMaaP instance.
509          * <LI>dcaeLocations: Array of string, locations returned by the remote
510          * DMaaP instance.
511          * <LI>data: Array of the desired items; e.g., data router feeds.
512          * <LI>totalPages: Integer, the number of pages required to display the
513          * complete list of items using the submitted page size
514          * </UL>
515          * 
516          * This duplicates all of {@link #buildJsonSuccess(int, Object)}.
517          * 
518          * @param dmaapAccess
519          *            Access details for the DMaaP REST API
520          * @param option
521          *            Specifies which item list type to get: data router feeds, etc.
522          * @param pageNum
523          *            Page number of results
524          * @param viewPerPage
525          *            Number of items per page
526          * @return JSON block as String, see above.
527          * @throws Exception
528          *             On any error
529          */
530         private String getItemListForPage(DmaapAccess dmaapAccess, DmaapDataItem option, int pageNum, int viewPerPage)
531                         throws Exception {
532                 DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
533                 // Get the instance so the page can display its name
534                 DmaapObject dmaap = restClient.getDmaap();
535                 if (dmaap instanceof ErrorResponse) {
536                         // Bad password is caught here.
537                         ErrorResponse err = (ErrorResponse) dmaap;
538                         throw new Exception(err.getMessage());
539                 }
540                 // Get locations for editing
541                 List<DmaapObject> dcaeLocations = restClient.getDcaeLocations();
542                 if (dcaeLocations.size() == 1 && dcaeLocations.get(0) instanceof ErrorResponse) {
543                         // Should never happen - bad password is caught right above - but be
544                         // careful.
545                         ErrorResponse err = (ErrorResponse) dcaeLocations.get(0);
546                         throw new Exception(err.getMessage());
547                 }
548                 // Pass them back as String array
549                 String[] dcaeLocs = new String[dcaeLocations.size()];
550                 for (int i = 0; i < dcaeLocs.length; ++i) {
551                         DcaeLocation dcaeLoc = (DcaeLocation) dcaeLocations.get(i);
552                         dcaeLocs[i] = dcaeLoc.getDcaeLocationName();
553                 }
554                 // Get the requested item list
555                 List<DmaapObject> itemList = null;
556                 switch (option) {
557                 case DR_FEED:
558                         itemList = restClient.getFeeds();
559                         // size 1 may be error response
560                         if (itemList.size() > 1)
561                                 Collections.sort(itemList, feedComparator);
562                         break;
563                 case DR_PUB:
564                         itemList = restClient.getDRPubs();
565                         // size 1 may be error response
566                         if (itemList.size() > 1)
567                                 Collections.sort(itemList, pubComparator);
568                         break;
569                 case DR_SUB:
570                         itemList = restClient.getDRSubs();
571                         // size 1 may be error response
572                         if (itemList.size() > 1)
573                                 Collections.sort(itemList, subComparator);
574                         break;
575                 case MR_TOPIC:
576                         itemList = restClient.getTopics();
577                         // size 1 may be error response
578                         if (itemList.size() > 1)
579                                 Collections.sort(itemList, topicComparator);
580                         break;
581                 case MR_CLIENT:
582                         itemList = restClient.getMRClients();
583                         // size 1 may be error response
584                         if (itemList.size() > 1)
585                                 Collections.sort(itemList, clientComparator);
586                         break;
587                 default:
588                         throw new Exception("getItemListForPage: pgmr error, unimplemented case: " + option.name());
589                 }
590
591                 logger.debug("getItemListForPage: list size is {}", itemList.size());
592                 int pageCount = (int) Math.ceil((double) itemList.size() / viewPerPage);
593                 @SuppressWarnings("unchecked")
594                 List<DmaapObject> subList = shrinkListToPage(pageNum, viewPerPage, itemList);
595                 itemList = subList;
596                 // Build response here
597                 Map<String, Object> model = new HashMap<String, Object>();
598                 model.put(STATUS_RESPONSE_KEY, new Integer(200));
599                 model.put(PROFILE_NAME_RESPONSE_KEY, dmaapAccess.getName());
600                 model.put(DMAAP_NAME_RESPONSE_KEY, ((Dmaap) dmaap).getDmaapName());
601                 model.put(DCAE_LOCATIONS_RESPONSE_KEY, dcaeLocs);
602                 model.put(DATA_RESPONSE_KEY, itemList);
603                 model.put(TOTAL_PAGES_RESPONSE_KEY, pageCount);
604
605                 // build the response
606                 String outboundJson = null;
607                 try {
608                         outboundJson = mapper.writeValueAsString(model);
609                 } catch (Exception ex) {
610                         // should never happen
611                         logger.error("getItemListForPage: failed to serialize model: ", ex);
612                         throw new Exception("sendItemListForPage", ex);
613                 }
614
615                 return outboundJson;
616         }
617
618         /**
619          * Gets a page of the specified DMaaP items. This method traps errors and
620          * constructs an appropriate JSON block if an error happens.
621          * 
622          * See {@link #getItemListForPage(DmaapAccess, DmaapDataItem, int, int)}.
623          * 
624          * @param request
625          *            Inbound request
626          * @param option
627          *            DMaaP item type to get
628          * @return JSON with list of serialized objects, or an error.
629          */
630         protected String getItemListForPageWrapper(HttpServletRequest request, DmaapDataItem option) {
631                 String outboundJson = null;
632                 try {
633                         User appUser = UserUtils.getUserSession(request);
634                         if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
635                                 throw new Exception("getItemListForPageWrapper: Failed to get Login UID");
636                         DmaapAccess selected = getSelectedDmaapAccess(appUser.getLoginId());
637                         if (selected == null) // leap into exception handler
638                                 throw new Exception("No DMaaP access profiles are configured.");
639                         int pageNum = Integer.parseInt(request.getParameter(PAGE_NUM_QUERY_PARAM));
640                         int viewPerPage = Integer.parseInt(request.getParameter(VIEW_PER_PAGE_QUERY_PARAM));
641                         outboundJson = getItemListForPage(selected, option, pageNum, viewPerPage);
642                 } catch (Exception ex) {
643                         outboundJson = buildJsonError(500, "Failed to get DMaaP item type " + option.name(), ex);
644                 }
645                 return outboundJson;
646         }
647
648         /**
649          * Adds an item of the specified type with the specified content. Constructs
650          * an object by deserializing the JSON block, but ignores any ID field that
651          * is supplied.
652          * 
653          * On success, returns a JSON block as String with any data returned by the
654          * REST client. Throws an exception on any failure.
655          * 
656          * @param dmaapAccess
657          *            Access details for the DMaaP REST API
658          * @param userId
659          *            The login ID of the user making the request
660          * @param itemType
661          *            DMaaP item type to add
662          * @param itemContent
663          *            JSON block to deserialize as an object
664          * @param scAddlStatus
665          *            HTTP status code 200 is always accepted. If this parameter is
666          *            not null, the value is also considered a valid HTTP status
667          *            code on success; e.g., 204.
668          * @return JSON object with result of the operation
669          * @throws Exception
670          *             on any problem
671          */
672         private String addDmaapItem(DmaapAccess dmaapAccess, String userId, DmaapDataItem itemType, String itemContent,
673                         Integer scAddlStatus) throws Exception {
674                 DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
675                 HttpStatusAndResponse<Object> hsr = null;
676                 switch (itemType) {
677                 case DR_FEED:
678                         Feed feed = mapper.readValue(itemContent, Feed.class);
679                         logger.debug("addDmaapItem: received feed: {} ", feed);
680                         // Null out any ID to get an auto-generated ID
681                         feed.setFeedId(null);
682                         // Assign the owner to be the webapp user
683                         feed.setOwner(userId);
684                         hsr = restClient.postFeed(feed);
685                         break;
686                 case DR_PUB:
687                         DR_Pub pub = mapper.readValue(itemContent, DR_Pub.class);
688                         logger.debug("addDmaapItem: received pub: {} ", pub);
689                         // Null out any ID to get an auto-generated ID
690                         pub.setPubId(null);
691                         hsr = restClient.postDRPub(pub);
692                         break;
693                 case DR_SUB:
694                         DR_Sub sub = mapper.readValue(itemContent, DR_Sub.class);
695                         logger.debug("addDmaapItem: received sub: {} ", sub);
696                         // Null out any ID to get an auto-generated ID
697                         sub.setSubId(null);
698                         // Assign the owner to be the webapp user
699                         sub.setOwner(userId);
700                         hsr = restClient.postDRSub(sub);
701                         break;
702                 case MR_TOPIC:
703                         Topic topic = mapper.readValue(itemContent, Topic.class);
704                         logger.debug("addDmaapItem: received topic: {} ", topic);
705                         // No ID on topic
706                         topic.setOwner(userId);
707                         hsr = restClient.postTopic(topic);
708                         break;
709                 case MR_CLIENT:
710                         MR_Client client = mapper.readValue(itemContent, MR_Client.class);
711                         logger.debug("addDmaapItem: received client: {} ", client);
712                         client.setMrClientId(null);
713                         hsr = restClient.postMRClient(client);
714                         break;
715                 default:
716                         throw new Exception("addDmaapItem: pgmr error, unimplemented case: " + itemType.name());
717                 }
718
719                 // Build result here
720                 String outboundJson = null;
721                 if (hsr.getStatusCode() == HttpServletResponse.SC_OK
722                                 || (scAddlStatus != null && hsr.getStatusCode() == scAddlStatus)) {
723                         outboundJson = buildJsonSuccess(hsr.getStatusCode(), hsr.getResponseString());
724                 } else {
725                         throw new Exception("Unexpected HTTP response code " + Integer.toString(hsr.getStatusCode())
726                                         + " with content " + hsr.getResponseString());
727                 }
728                 return outboundJson;
729         }
730
731         /**
732          * Adds the specified DMaaP item that is read from the request body. This
733          * method traps errors and constructs an appropriate JSON block if an error
734          * happens.
735          * 
736          * @param request
737          *            Used to obtain user info from the active session
738          * @param itemType
739          *            DMaaP item type to add
740          * @param scAddlStatus
741          *            HTTP status code 200 is always accepted. If this parameter is
742          *            not null, the value is also considered a valid HTTP status
743          *            code on success; e.g., 204.
744          * @return JSON block with success or failure object
745          */
746         protected String addItem(HttpServletRequest request, DmaapDataItem itemType, Integer scAddlStatus) {
747                 String outboundJson = null;
748                 try {
749                         User appUser = UserUtils.getUserSession(request);
750                         if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
751                                 throw new Exception("addDmaapItem: Failed to get Login ID");
752
753                         DmaapAccess access = getSelectedDmaapAccess(appUser.getLoginId());
754                         if (access == null) // leap into exception handler
755                                 throw new Exception("No DMaaP access profiles are configured.");
756                         String jsonContent = getBody(request);
757                         outboundJson = addDmaapItem(access, appUser.getLoginId(), itemType, jsonContent, scAddlStatus);
758                 } catch (Exception ex) {
759                         outboundJson = buildJsonError(500, "Failed to add DMaaP item " + itemType.name(), ex);
760                 }
761
762                 return outboundJson;
763         }
764
765         /**
766          * Updates an item of the specified type with the specified content.
767          * Constructs an object by deserializing the JSON block.
768          * 
769          * On success, returns a JSON block as String with any data returned by the
770          * REST client. Throws an exception on any failure.
771          * 
772          * @param dmaapAccess
773          *            Access details for the DMaaP REST API
774          * @param userId
775          *            The Login ID of the user making the request
776          * @param itemType
777          *            DMaaP item type to update
778          * @param itemId
779          *            Item identification
780          * @param itemContent
781          *            JSON block to deserialize as an object
782          * @param scAddlStatus
783          *            HTTP status code 200 is always accepted. If this parameter is
784          *            not null, the value is also considered a valid HTTP status
785          *            code on success; e.g., 204.
786          * @return JSON object with result of the operation
787          * @throws Exception
788          *             on any problem
789          */
790         private String updateDmaapItem(DmaapAccess dmaapAccess, String userId, DmaapDataItem itemType, String itemId,
791                         String itemContent, Integer scAddlStatus) throws Exception {
792                 DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
793                 HttpStatusAndResponse<Object> hsr = null;
794                 switch (itemType) {
795                 case DR_FEED:
796                         Feed feed = mapper.readValue(itemContent, Feed.class);
797                         logger.debug("updateDmaapItem: received feed: {} ", feed);
798                         // Ensure the owner is the webapp user
799                         feed.setOwner(userId);
800                         hsr = restClient.putFeed(feed);
801                         break;
802                 case DR_PUB:
803                         DR_Pub pub = mapper.readValue(itemContent, DR_Pub.class);
804                         logger.debug("updateDmaapItem: received pub: {} ", pub);
805                         hsr = restClient.putDRPub(pub);
806                         break;
807                 case DR_SUB:
808                         DR_Sub sub = mapper.readValue(itemContent, DR_Sub.class);
809                         logger.debug("updateDmaapItem: received sub: {} ", sub);
810                         // Ensure the owner is the webapp user
811                         sub.setOwner(userId);
812                         hsr = restClient.putDRSub(sub);
813                         break;
814                 case MR_TOPIC:
815                         Topic topic = mapper.readValue(itemContent, Topic.class);
816                         logger.debug("updateDmaapItem: received topic: {} ", topic);
817                         // Ensure the owner is the webapp user
818                         topic.setOwner(userId);
819                         // DCAE backend may implement PUT someday.
820                         if (true && userId != null)
821                                 throw new UnsupportedOperationException("put topic not supported (yet)");
822                         break;
823                 case MR_CLIENT:
824                         MR_Client client = mapper.readValue(itemContent, MR_Client.class);
825                         logger.debug("updateDmaapItem: received client: {} ", client);
826                         hsr = restClient.putMRClient(client);
827                         break;
828                 default:
829                         throw new Exception("updateDmaapItem: pgmr error, unimplemented case: " + itemType.name());
830                 }
831
832                 // Build result here
833                 String outboundJson = null;
834                 if (hsr.getStatusCode() == HttpServletResponse.SC_OK
835                                 || (scAddlStatus != null && hsr.getStatusCode() == scAddlStatus)) {
836                         outboundJson = buildJsonSuccess(hsr.getStatusCode(), hsr.getResponseString());
837                 } else {
838                         throw new Exception("Unexpected HTTP response code " + Integer.toString(hsr.getStatusCode())
839                                         + " with content " + hsr.getResponseString());
840                 }
841                 return outboundJson;
842         }
843
844         /**
845          * Updates the specified DMaaP item that is read from the request body. This
846          * method traps errors and constructs an appropriate JSON block if an error
847          * happens.
848          * 
849          * @param request
850          *            Used to obtain user info from the active session
851          * @param itemType
852          *            DMaaP item type to update
853          * @param itemId
854          *            Item identification to update
855          * @param scUpdatelStatus
856          *            HTTP status code 200 is always accepted. If this parameter is
857          *            not null, the value is also considered a valid HTTP status
858          *            code on success; e.g., 204.
859          * @return JSON object with success or error information.
860          */
861         protected String updateItem(HttpServletRequest request, DmaapDataItem itemType, String itemId,
862                         Integer scUpdatelStatus) {
863                 String outboundJson = null;
864                 try {
865                         User appUser = UserUtils.getUserSession(request);
866                         if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
867                                 throw new Exception("updateItem: Failed to get Login ID");
868                         DmaapAccess access = getSelectedDmaapAccess(appUser.getLoginId());
869                         if (access == null) // leap into exception handler
870                                 throw new Exception("No DMaaP access profiles are configured.");
871                         String jsonContent = getBody(request);
872                         outboundJson = updateDmaapItem(access, appUser.getLoginId(), itemType, itemId, jsonContent,
873                                         scUpdatelStatus);
874                 } catch (Exception ex) {
875                         outboundJson = buildJsonError(500, "Failed to update DMaaP item " + itemType.name(), ex);
876                 }
877
878                 return outboundJson;
879         }
880
881         /**
882          * Deletes an item of the specified type with the specified ID.
883          * 
884          * @param dmaapAccess
885          *            Access details for the DMaaP REST API
886          * @param itemType
887          *            DMaaP item type to delete
888          * @param itemId
889          *            Item identification
890          * @param scAddlStatus
891          *            HTTP status code 200 is always accepted. If this parameter is
892          *            not null, the value is also considered a valid HTTP status
893          *            code on success; e.g., 204.
894          * @return On success, returns a JSON block as String with any data returned
895          *         by the REST client.
896          * @throws Exception
897          *             On any failure.
898          */
899         private String deleteDmaapItem(DmaapAccess dmaapAccess, DmaapDataItem itemType, String itemId, Integer scAddlStatus)
900                         throws Exception {
901                 DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
902                 HttpStatusAndResponse<Object> hsr = null;
903                 switch (itemType) {
904                 case DR_FEED:
905                         hsr = restClient.deleteFeed(itemId);
906                         break;
907                 case DR_PUB:
908                         hsr = restClient.deleteDRPub(itemId);
909                         break;
910                 case DR_SUB:
911                         hsr = restClient.deleteDRSub(itemId);
912                         break;
913                 case MR_TOPIC:
914                         hsr = restClient.deleteTopic(itemId);
915                         break;
916                 case MR_CLIENT:
917                         hsr = restClient.deleteMRClient(itemId);
918                         break;
919                 default:
920                         throw new Exception("deleteDmaapItem: pgmr error, unimplemented case: " + itemType.name());
921                 }
922
923                 // Build result here
924                 String outboundJson = null;
925                 if (hsr.getStatusCode() == HttpServletResponse.SC_OK
926                                 || (scAddlStatus != null && hsr.getStatusCode() == scAddlStatus)) {
927                         outboundJson = buildJsonSuccess(hsr.getStatusCode(), hsr.getResponseString());
928                 } else {
929                         throw new Exception("Unexpected HTTP response code " + Integer.toString(hsr.getStatusCode())
930                                         + " with content " + hsr.getResponseString());
931                 }
932                 return outboundJson;
933         }
934
935         /**
936          * Deletes the specified DMaaP item. This method traps errors and constructs
937          * an appropriate JSON block if an error happens.
938          * 
939          * @param request
940          *            Used to obtain user info from the active session
941          * @param itemType
942          *            DMaaP item type to delete
943          * @param itemId
944          *            item ID to delete
945          * @param scAddlStatus
946          *            HTTP status code 200 is always accepted. If this parameter is
947          *            not null, the value is also considered a valid HTTP status
948          *            code on success; e.g., 204.
949          * @return JSON object with success or error information.
950          */
951         protected String deleteItem(HttpServletRequest request, DmaapDataItem itemType, String itemId,
952                         Integer scAddlStatus) {
953                 String outboundJson = null;
954                 try {
955                         User appUser = UserUtils.getUserSession(request);
956                         if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
957                                 throw new Exception("deleteItem: Failed to get Login ID");
958                         DmaapAccess selected = getSelectedDmaapAccess(appUser.getLoginId());
959                         if (selected == null) // leap into exception handler
960                                 throw new Exception("No DMaaP access profiles are configured.");
961                         outboundJson = deleteDmaapItem(selected, itemType, itemId, scAddlStatus);
962                 } catch (Exception ex) {
963                         outboundJson = buildJsonError(500, "Failed to delete DMaaP item " + itemType.name() + " ID " + itemId, ex);
964                 }
965                 return outboundJson;
966         }
967
968 }