aboutsummaryrefslogtreecommitdiffstats
path: root/dcae_dmaapbc_webapp/dbca-common/src/main/java/org/openecomp/dcae/dmaapbc/dbcapp/controller/DbcappRestrictedBaseController.java
blob: 517af0992db2bedf38746e3b6eb0f143f6929f1c (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
package org.openecomp.dcae.dmaapbc.dbcapp.controller;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.openecomp.dcae.dmaapbc.client.DmaapBcRestClient;
import org.openecomp.dcae.dmaapbc.client.HttpStatusAndResponse;
import org.openecomp.dcae.dmaapbc.dbcapp.domain.DmaapAccess;
import org.openecomp.dcae.dmaapbc.dbcapp.rest.DbcUsvcRestClient;
import org.openecomp.dcae.dmaapbc.dbcapp.service.DmaapAccessService;
import org.openecomp.dcae.dmaapbc.dbcapp.util.DbcappProperties;
import org.openecomp.dcae.dmaapbc.model.DR_Pub;
import org.openecomp.dcae.dmaapbc.model.DR_Sub;
import org.openecomp.dcae.dmaapbc.model.DcaeLocation;
import org.openecomp.dcae.dmaapbc.model.Dmaap;
import org.openecomp.dcae.dmaapbc.model.DmaapObject;
import org.openecomp.dcae.dmaapbc.model.ErrorResponse;
import org.openecomp.dcae.dmaapbc.model.Feed;
import org.openecomp.dcae.dmaapbc.model.MR_Client;
import org.openecomp.dcae.dmaapbc.model.Topic;
import org.openecomp.portalsdk.core.controller.RestrictedBaseController;
import org.openecomp.portalsdk.core.domain.User;
import org.openecomp.portalsdk.core.logging.logic.EELFLoggerDelegate;
import org.openecomp.portalsdk.core.onboarding.util.CipherUtil;
import org.openecomp.portalsdk.core.web.support.UserUtils;
import org.springframework.beans.factory.annotation.Autowired;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;

/**
 * This base class provides utility methods to child controllers. All of the
 * requests are forwarded on to a remote REST API, so there's a large degree of
 * commonality among the implementations. Combining them kept the lines-of-code
 * count down, at the expense of some complexity.
 */
public class DbcappRestrictedBaseController extends RestrictedBaseController {

	/**
	 * Query parameter for desired page number
	 */
	protected static final String PAGE_NUM_QUERY_PARAM = "pageNum";

	/**
	 * Query parameter for desired items per page
	 */
	protected static final String VIEW_PER_PAGE_QUERY_PARAM = "viewPerPage";

	/**
	 * Tag for status code in JSON responses - ALWAYS PRESENT.
	 */
	protected static final String STATUS_RESPONSE_KEY = "status";

	/**
	 * Tag for data in JSON responses.
	 */
	protected static final String DATA_RESPONSE_KEY = "data";

	/**
	 * Tag for error message in JSON responses; absent on success.
	 */
	protected static final String ERROR_RESPONSE_KEY = "error";

	/**
	 * Tag for response integer, pages required to display complete result list
	 */
	protected static final String TOTAL_PAGES_RESPONSE_KEY = "totalPages";

	/**
	 * Tag for DMaaP name obtained from REST client.
	 */
	protected static final String PROFILE_NAME_RESPONSE_KEY = "profileName";

	/**
	 * Tag for DMaaP name obtained from REST client.
	 */
	protected static final String DMAAP_NAME_RESPONSE_KEY = "dmaapName";

	/**
	 * Tag for DCAE location name list obtained from REST client.
	 */
	protected static final String DCAE_LOCATIONS_RESPONSE_KEY = "dcaeLocations";

	/**
	 * Logger that conforms with ECOMP guidelines
	 */
	private static EELFLoggerDelegate logger = EELFLoggerDelegate.getLogger(DbcappRestrictedBaseController.class);

	/**
	 * For general use in these methods and subclasses
	 */
	protected final ObjectMapper mapper = new ObjectMapper();

	/**
	 * DAO accesses the profiles via a local database. REST accesses the
	 * profiles via a remote REST service.
	 */
	public enum AccessMethod {
		DAO, REST
	};

	/**
	 * Enum for selecting an item type.
	 */
	public enum DmaapDataItem {
		DR_FEED, DR_PUB, DR_SUB, MR_TOPIC, MR_CLIENT;
	}

	/**
	 * Application properties - NOT available to constructor.
	 */
	@Autowired
	private DbcappProperties appProperties;

	/**
	 * Database access - which might not be used.
	 */
	@Autowired
	private DmaapAccessService dmaapAccessDaoServiceAuto;

	/**
	 * Read from application properties.
	 */
	private String mechIdName, mechIdPass;

	/**
	 * This is set by {@link #getDmaapAccessService()} to the DAO or REST
	 * implementation as configured in properties.
	 */
	private DmaapAccessService dmaapAccessService;

	/**
	 * Hello Spring, here's your no-arg constructor.
	 */
	public DbcappRestrictedBaseController() {
		// Do not serialize null values
		mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
	}

	/**
	 * Access method for subclasses.
	 * 
	 * @return DbcappProperties object that was autowired by Spring.
	 */
	protected DbcappProperties getAppProperties() {
		return appProperties;
	}

	/**
	 * Lazy initialization. As a side effect, caches mech ID and password.
	 * 
	 * @return Either DAO or REST client that implements the access service
	 *         interface.
	 */
	protected DmaapAccessService getDmaapAccessService() {
		if (dmaapAccessService != null)
			return dmaapAccessService;

		// Get the application's mechid
		mechIdName = appProperties.getProperty(DbcappProperties.DMAAP_MECHID_NAME);
		// This is encrypted
		String cipher = appProperties.getProperty(DbcappProperties.DMAAP_MECHID_PASSWORD);
		if (mechIdName == null || cipher == null)
			throw new RuntimeException("Failed to get MECH_ID name and/or password from properties");
		try {
			mechIdPass = CipherUtil.decrypt(cipher);
		} catch (Exception ex) {
			throw new RuntimeException("Failed to decrypt password from config file", ex);
		}

		String accessMethod = appProperties.getProperty(DbcappProperties.PROFILE_ACCESS_METHOD);
		if (accessMethod == null)
			throw new RuntimeException("Failed to get property " + DbcappProperties.PROFILE_ACCESS_METHOD);
		AccessMethod profileAccessMethod = AccessMethod.valueOf(accessMethod.toUpperCase());
		if (AccessMethod.DAO == profileAccessMethod) {
			// Spring auto-wired this field
			dmaapAccessService = dmaapAccessDaoServiceAuto;
		} else {
			String url = appProperties.getProperty(DbcappProperties.PROFILE_USVC_URL);
			String user = appProperties.getProperty(DbcappProperties.PROFILE_USVC_USER);
			String pass = appProperties.getProperty(DbcappProperties.PROFILE_USVC_PASS);
			if (url == null || user == null || pass == null)
				throw new RuntimeException("getDmaapAccessService: missing property: one of url, user, pass");
			String clearText = null;
			try {
				clearText = CipherUtil.decrypt(pass);
			} catch (Exception ex) {
				throw new RuntimeException("getDmaapAccessService: failed to decrypt password from config");
			}
			dmaapAccessService = new DbcUsvcRestClient(url, user, clearText);
		}
		return dmaapAccessService;
	}

	/**
	 * Creates a REST client with appropriate credentials, the user/pass from
	 * the access profile if present, otherwise with the default mech ID and
	 * password.
	 * 
	 * @param dmaapAccess
	 *            Profile
	 * @return REST client.
	 */
	protected DmaapBcRestClient getDmaapBcRestClient(DmaapAccess dmaapAccess) {
		DmaapBcRestClient restClient = null;
		if (dmaapAccess.getMechId() == null || dmaapAccess.getMechId().length() == 0)
			restClient = new DmaapBcRestClient(dmaapAccess.getDmaapUrl(), mechIdName, mechIdPass);
		else
			restClient = new DmaapBcRestClient(dmaapAccess.getDmaapUrl(), dmaapAccess.getMechId(),
					dmaapAccess.getPassword());
		return restClient;
	}

	/**
	 * Pulls out of the specified list the appropriate items for the page of
	 * results specified by the page number and view-per-page parameters.
	 * 
	 * @param pageNum
	 *            Page number requested by user
	 * @param viewPerPage
	 *            Number of items per page
	 * @param itemList
	 *            List of items available
	 * @return List of items to display
	 */
	@SuppressWarnings("rawtypes")
	private static List shrinkListToPage(final int pageNum, final int viewPerPage, final List itemList) {
		// user-friendly page numbers index from 1
		int firstIndexOnThisPage = viewPerPage * (pageNum - 1);
		int firstIndexOnNextPage = viewPerPage * pageNum;
		int fromIndex = firstIndexOnThisPage < itemList.size() ? firstIndexOnThisPage : itemList.size();
		int toIndex = firstIndexOnNextPage < itemList.size() ? firstIndexOnNextPage : itemList.size();
		// answers empty list if from==to
		return itemList.subList(fromIndex, toIndex);
	}

	/**
	 * Gets the body of a HTTP request assuming UTF-8 encoding.
	 * 
	 * @param request
	 *            HttpServletRequest
	 * @return String version of request body
	 * @throws IOException
	 *             If the read fails
	 */
	protected static String getBody(HttpServletRequest request) throws IOException {
		StringBuilder stringBuilder = new StringBuilder();
		BufferedReader bufferedReader = null;
		try {
			InputStream inputStream = request.getInputStream();
			if (inputStream != null) {
				bufferedReader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
				char[] charBuffer = new char[512];
				int bytesRead = -1;
				while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
					stringBuilder.append(charBuffer, 0, bytesRead);
				}
			} else {
				stringBuilder.append("");
			}
		} finally {
			if (bufferedReader != null) {
				try {
					bufferedReader.close();
				} catch (IOException ex) {
					throw ex;
				}
			}

		}
		return stringBuilder.toString();
	}

	/**
	 * Builds a JSON success response from the specified inputs.
	 * 
	 * @param statusCode
	 *            e.g., 200 for OK
	 * @param dataPojo
	 *            Plain old Java object to serialize as JSON; ignored if null.
	 * @throws JsonProcessingException
	 *             If the POJO cannot be serialized
	 * @return JSON block with items "status" : 200 and "data" : (data..)
	 */
	protected String buildJsonSuccess(int statusCode, Object dataPojo) throws JsonProcessingException {
		Map<String, Object> model = new HashMap<String, Object>();
		model.put(STATUS_RESPONSE_KEY, statusCode);
		if (dataPojo != null)
			model.put(DATA_RESPONSE_KEY, dataPojo);
		String json = mapper.writeValueAsString(model);
		return json;
	}

	/**
	 * Builds a JSON error response from the specified inputs.
	 * 
	 * @param statusCode
	 *            e.g., 500 for internal server error
	 * @param errMsg
	 *            Information about the operation that failed
	 * @param exception
	 *            Converted to string; ignored if null.
	 * @return JSON block with tags "status" and "error".
	 */
	protected String buildJsonError(int statusCode, String errMsg, Exception exception) {
		Map<String, Object> model = new HashMap<String, Object>();
		model.put(STATUS_RESPONSE_KEY, new Integer(500));
		if (exception == null) {
			model.put(ERROR_RESPONSE_KEY, errMsg);
		} else {
			final int enough = 512;
			String exString = exception.toString();
			String exceptionMsg = exString.length() > enough ? exString.substring(0, enough) : exString;
			model.put(ERROR_RESPONSE_KEY, errMsg + ": " + exceptionMsg);
		}
		String json = null;
		try {
			json = mapper.writeValueAsString(model);
		} catch (JsonProcessingException ex) {
			// serializing the trivial map should never fail
			String err = "buildJsonError: failed to serialize";
			logger.error(EELFLoggerDelegate.errorLogger, err, ex);
			throw new RuntimeException(err, ex);
		}
		return json;
	}

	/**
	 * Gets a list of DMaaP access profiles for this user from the database. The
	 * profiles have passwords in the clear - this method decrypts the database
	 * entries.
	 * 
	 * Initializes the list for new users and/or configuration changes. Checks
	 * the database list against the configured list of URLs, and creates new
	 * rows for any configured URLs not present for the user. Most environments
	 * are expected to have exactly one valid URL, and the webapp uses a fixed
	 * MechID to authenticate itself to the DMaaP bus controller, so this
	 * approach means new users can start without any setup of URLs.
	 * 
	 * @param userId
	 *            User ID
	 * @return List of DmaapAccess objects
	 * @throws Exception
	 *             If the URL list is not available in properties
	 */
	protected List<DmaapAccess> getOrInitDmaapAccessList(String userId) throws Exception {
		String[] configUrls = getAppProperties().getCsvListProperty(DbcappProperties.DMAAP_REST_URL_LIST);
		if (configUrls == null || configUrls.length == 0)
			throw new Exception("getOrInitDmaapAccessList: Failed to get DMAAP REST URL list");
		// Update this list to track which URLs are in the database.
		List<String> configUrlList = new ArrayList<String>(configUrls.length);
		for (String c : configUrls) {
			// Validate URL to detect config botches
			URL url = new URL(c);
			configUrlList.add(url.toExternalForm());
		}

		List<DmaapAccess> dbAccessList = getDmaapAccessService().getDmaapAccessList(userId);

		// Check the database entries against the configuration. Also
		// build a list of non-DAO objects with clear-text passwords.
		List<DmaapAccess> clearList = new ArrayList<DmaapAccess>(dbAccessList.size());
		for (DmaapAccess dmaapAccess : dbAccessList) {
			// drop this URL from the list.
			// If it's not known to config, complain because that's a bogus row.
			if (!configUrlList.remove(dmaapAccess.getDmaapUrl()))
				logger.warn(EELFLoggerDelegate.errorLogger, "getOrInitDmaapAccessList: detected extra URL {}",
						dmaapAccess.getDmaapUrl());
			// Return cleartext in JSON
			DmaapAccess clone = new DmaapAccess(dmaapAccess);
			clone.setPassword(clone.decryptPassword());
			clearList.add(clone);
		}

		// Create new rows for any configured URLs not found for this user.
		for (int i = 0; i < configUrlList.size(); ++i) {
			String missUrl = configUrlList.get(i);
			logger.debug(EELFLoggerDelegate.debugLogger, "getOrInitDmaapAccessList: adding missing URL {}", missUrl);
			DmaapAccess newDmaapAccess = new DmaapAccess();
			// Create a semi-reasonable name for the table
			newDmaapAccess.setName("dmaap-" + Integer.toString(i + 1));
			newDmaapAccess.setUserId(userId);
			newDmaapAccess.setDmaapUrl(missUrl);
			// Write to db.
			getDmaapAccessService().saveDmaapAccess(newDmaapAccess);
			// Add to response, which assumes the write was successful.
			clearList.add(newDmaapAccess);
		}

		return clearList;
	}

	/**
	 * Gets the user's selected DMaaP access profile.
	 * 
	 * @param userId
	 *            User ID
	 * @return DmaapAccess object that is currently selected, or the first one
	 *         found if none are selected; null if no access profiles are
	 *         configured.
	 * @throws Exception
	 *             If the profile is not found
	 */
	protected DmaapAccess getSelectedDmaapAccess(String userId) throws Exception {
		List<DmaapAccess> profiles = getOrInitDmaapAccessList(userId);
		if (profiles.size() == 0) {
			logger.debug("getSelectedDmaapAccess: no rows found, returning null");
			return null;
		}

		// Return the first one by default if nothing is selected.
		DmaapAccess selected = profiles.get(0);
		for (DmaapAccess da : profiles)
			if (da.getSelected())
				selected = da;

		return selected;
	}

	/**
	 * Supports sorting a list of feeds by the first column displayed: ID
	 */
	private static Comparator<DmaapObject> feedComparator = new Comparator<DmaapObject>() {
		@Override
		public int compare(DmaapObject o1, DmaapObject o2) {
			Feed f1 = (Feed) o1;
			Feed f2 = (Feed) o2;
			// sort these numbers lexicographically, same as the front end
			// table.
			return f1.getFeedId().compareTo(f2.getFeedId());
		}
	};

	/**
	 * Supports sorting a list of publishers by the first column displayed: pub
	 * ID
	 */
	private static Comparator<DmaapObject> pubComparator = new Comparator<DmaapObject>() {
		@Override
		public int compare(DmaapObject o1, DmaapObject o2) {
			DR_Pub p1 = (DR_Pub) o1;
			DR_Pub p2 = (DR_Pub) o2;
			return p1.getPubId().compareTo(p2.getPubId());
		}
	};

	/**
	 * Supports sorting a list of subscribers by the first column displayed: sub
	 * ID
	 */
	private static Comparator<DmaapObject> subComparator = new Comparator<DmaapObject>() {
		@Override
		public int compare(DmaapObject o1, DmaapObject o2) {
			DR_Sub s1 = (DR_Sub) o1;
			DR_Sub s2 = (DR_Sub) o2;
			// sort these numbers lexicographically, same as the front end
			// table.
			return s1.getSubId().compareTo(s2.getSubId());
		}
	};

	/**
	 * Supports sorting a list of topics by the first column displayed: FQTN
	 */
	private static Comparator<DmaapObject> topicComparator = new Comparator<DmaapObject>() {
		@Override
		public int compare(DmaapObject o1, DmaapObject o2) {
			Topic t1 = (Topic) o1;
			Topic t2 = (Topic) o2;
			return t1.getFqtn().compareTo(t2.getFqtn());
		}
	};

	/**
	 * Supports sorting a list of clients by the first column displayed: client
	 * ID.
	 */
	private static Comparator<DmaapObject> clientComparator = new Comparator<DmaapObject>() {
		@Override
		public int compare(DmaapObject o1, DmaapObject o2) {
			MR_Client c1 = (MR_Client) o1;
			MR_Client c2 = (MR_Client) o2;
			// sort these numbers lexicographically, same as the front end
			// table.
			return c1.getMrClientId().compareTo(c2.getMrClientId());
		}
	};

	/**
	 * Gets one page of DMaaP objects and supporting information via the Bus
	 * Controller REST client. On success, returns a JSON object as String with
	 * the following tags:
	 * <UL>
	 * <LI>status: Integer; HTTP status code 200.
	 * <LI>dmaapName: String, name returned by the remote DMaaP instance.
	 * <LI>dcaeLocations: Array of string, locations returned by the remote
	 * DMaaP instance.
	 * <LI>data: Array of the desired items; e.g., data router feeds.
	 * <LI>totalPages: Integer, the number of pages required to display the
	 * complete list of items using the submitted page size
	 * </UL>
	 * 
	 * This duplicates all of {@link #buildJsonSuccess(int, Object)}.
	 * 
	 * @param dmaapAccess
	 *            Access details for the DMaaP REST API
	 * @param option
	 *            Specifies which item list type to get: data router feeds, etc.
	 * @param pageNum
	 *            Page number of results
	 * @param viewPerPage
	 *            Number of items per page
	 * @return JSON block as String, see above.
	 * @throws Exception
	 *             On any error
	 */
	private String getItemListForPage(DmaapAccess dmaapAccess, DmaapDataItem option, int pageNum, int viewPerPage)
			throws Exception {
		DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
		// Get the instance so the page can display its name
		DmaapObject dmaap = restClient.getDmaap();
		if (dmaap instanceof ErrorResponse) {
			// Bad password is caught here.
			ErrorResponse err = (ErrorResponse) dmaap;
			throw new Exception(err.getMessage());
		}
		// Get locations for editing
		List<DmaapObject> dcaeLocations = restClient.getDcaeLocations();
		if (dcaeLocations.size() == 1 && dcaeLocations.get(0) instanceof ErrorResponse) {
			// Should never happen - bad password is caught right above - but be
			// careful.
			ErrorResponse err = (ErrorResponse) dcaeLocations.get(0);
			throw new Exception(err.getMessage());
		}
		// Pass them back as String array
		String[] dcaeLocs = new String[dcaeLocations.size()];
		for (int i = 0; i < dcaeLocs.length; ++i) {
			DcaeLocation dcaeLoc = (DcaeLocation) dcaeLocations.get(i);
			dcaeLocs[i] = dcaeLoc.getDcaeLocationName();
		}
		// Get the requested item list
		List<DmaapObject> itemList = null;
		switch (option) {
		case DR_FEED:
			itemList = restClient.getFeeds();
			// size 1 may be error response
			if (itemList.size() > 1)
				Collections.sort(itemList, feedComparator);
			break;
		case DR_PUB:
			itemList = restClient.getDRPubs();
			// size 1 may be error response
			if (itemList.size() > 1)
				Collections.sort(itemList, pubComparator);
			break;
		case DR_SUB:
			itemList = restClient.getDRSubs();
			// size 1 may be error response
			if (itemList.size() > 1)
				Collections.sort(itemList, subComparator);
			break;
		case MR_TOPIC:
			itemList = restClient.getTopics();
			// size 1 may be error response
			if (itemList.size() > 1)
				Collections.sort(itemList, topicComparator);
			break;
		case MR_CLIENT:
			itemList = restClient.getMRClients();
			// size 1 may be error response
			if (itemList.size() > 1)
				Collections.sort(itemList, clientComparator);
			break;
		default:
			throw new Exception("getItemListForPage: pgmr error, unimplemented case: " + option.name());
		}

		logger.debug("getItemListForPage: list size is {}", itemList.size());
		int pageCount = (int) Math.ceil((double) itemList.size() / viewPerPage);
		@SuppressWarnings("unchecked")
		List<DmaapObject> subList = shrinkListToPage(pageNum, viewPerPage, itemList);
		itemList = subList;
		// Build response here
		Map<String, Object> model = new HashMap<String, Object>();
		model.put(STATUS_RESPONSE_KEY, new Integer(200));
		model.put(PROFILE_NAME_RESPONSE_KEY, dmaapAccess.getName());
		model.put(DMAAP_NAME_RESPONSE_KEY, ((Dmaap) dmaap).getDmaapName());
		model.put(DCAE_LOCATIONS_RESPONSE_KEY, dcaeLocs);
		model.put(DATA_RESPONSE_KEY, itemList);
		model.put(TOTAL_PAGES_RESPONSE_KEY, pageCount);

		// build the response
		String outboundJson = null;
		try {
			outboundJson = mapper.writeValueAsString(model);
		} catch (Exception ex) {
			// should never happen
			logger.error("getItemListForPage: failed to serialize model: ", ex);
			throw new Exception("sendItemListForPage", ex);
		}

		return outboundJson;
	}

	/**
	 * Gets a page of the specified DMaaP items. This method traps errors and
	 * constructs an appropriate JSON block if an error happens.
	 * 
	 * See {@link #getItemListForPage(DmaapAccess, DmaapDataItem, int, int)}.
	 * 
	 * @param request
	 *            Inbound request
	 * @param option
	 *            DMaaP item type to get
	 * @return JSON with list of serialized objects, or an error.
	 */
	protected String getItemListForPageWrapper(HttpServletRequest request, DmaapDataItem option) {
		String outboundJson = null;
		try {
			User appUser = UserUtils.getUserSession(request);
			if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
				throw new Exception("getItemListForPageWrapper: Failed to get Login UID");
			DmaapAccess selected = getSelectedDmaapAccess(appUser.getLoginId());
			if (selected == null) // leap into exception handler
				throw new Exception("No DMaaP access profiles are configured.");
			int pageNum = Integer.parseInt(request.getParameter(PAGE_NUM_QUERY_PARAM));
			int viewPerPage = Integer.parseInt(request.getParameter(VIEW_PER_PAGE_QUERY_PARAM));
			outboundJson = getItemListForPage(selected, option, pageNum, viewPerPage);
		} catch (Exception ex) {
			outboundJson = buildJsonError(500, "Failed to get DMaaP item type " + option.name(), ex);
		}
		return outboundJson;
	}

	/**
	 * Adds an item of the specified type with the specified content. Constructs
	 * an object by deserializing the JSON block, but ignores any ID field that
	 * is supplied.
	 * 
	 * On success, returns a JSON block as String with any data returned by the
	 * REST client. Throws an exception on any failure.
	 * 
	 * @param dmaapAccess
	 *            Access details for the DMaaP REST API
	 * @param userId
	 *            The login ID of the user making the request
	 * @param itemType
	 *            DMaaP item type to add
	 * @param itemContent
	 *            JSON block to deserialize as an object
	 * @param scAddlStatus
	 *            HTTP status code 200 is always accepted. If this parameter is
	 *            not null, the value is also considered a valid HTTP status
	 *            code on success; e.g., 204.
	 * @return JSON object with result of the operation
	 * @throws Exception
	 *             on any problem
	 */
	private String addDmaapItem(DmaapAccess dmaapAccess, String userId, DmaapDataItem itemType, String itemContent,
			Integer scAddlStatus) throws Exception {
		DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
		HttpStatusAndResponse<Object> hsr = null;
		switch (itemType) {
		case DR_FEED:
			Feed feed = mapper.readValue(itemContent, Feed.class);
			logger.debug("addDmaapItem: received feed: {} ", feed);
			// Null out any ID to get an auto-generated ID
			feed.setFeedId(null);
			// Assign the owner to be the webapp user
			feed.setOwner(userId);
			hsr = restClient.postFeed(feed);
			break;
		case DR_PUB:
			DR_Pub pub = mapper.readValue(itemContent, DR_Pub.class);
			logger.debug("addDmaapItem: received pub: {} ", pub);
			// Null out any ID to get an auto-generated ID
			pub.setPubId(null);
			hsr = restClient.postDRPub(pub);
			break;
		case DR_SUB:
			DR_Sub sub = mapper.readValue(itemContent, DR_Sub.class);
			logger.debug("addDmaapItem: received sub: {} ", sub);
			// Null out any ID to get an auto-generated ID
			sub.setSubId(null);
			// Assign the owner to be the webapp user
			sub.setOwner(userId);
			hsr = restClient.postDRSub(sub);
			break;
		case MR_TOPIC:
			Topic topic = mapper.readValue(itemContent, Topic.class);
			logger.debug("addDmaapItem: received topic: {} ", topic);
			// No ID on topic
			topic.setOwner(userId);
			hsr = restClient.postTopic(topic);
			break;
		case MR_CLIENT:
			MR_Client client = mapper.readValue(itemContent, MR_Client.class);
			logger.debug("addDmaapItem: received client: {} ", client);
			client.setMrClientId(null);
			hsr = restClient.postMRClient(client);
			break;
		default:
			throw new Exception("addDmaapItem: pgmr error, unimplemented case: " + itemType.name());
		}

		// Build result here
		String outboundJson = null;
		if (hsr.getStatusCode() == HttpServletResponse.SC_OK
				|| (scAddlStatus != null && hsr.getStatusCode() == scAddlStatus)) {
			outboundJson = buildJsonSuccess(hsr.getStatusCode(), hsr.getResponseString());
		} else {
			throw new Exception("Unexpected HTTP response code " + Integer.toString(hsr.getStatusCode())
					+ " with content " + hsr.getResponseString());
		}
		return outboundJson;
	}

	/**
	 * Adds the specified DMaaP item that is read from the request body. This
	 * method traps errors and constructs an appropriate JSON block if an error
	 * happens.
	 * 
	 * @param request
	 *            Used to obtain user info from the active session
	 * @param itemType
	 *            DMaaP item type to add
	 * @param scAddlStatus
	 *            HTTP status code 200 is always accepted. If this parameter is
	 *            not null, the value is also considered a valid HTTP status
	 *            code on success; e.g., 204.
	 * @return JSON block with success or failure object
	 */
	protected String addItem(HttpServletRequest request, DmaapDataItem itemType, Integer scAddlStatus) {
		String outboundJson = null;
		try {
			User appUser = UserUtils.getUserSession(request);
			if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
				throw new Exception("addDmaapItem: Failed to get Login ID");

			DmaapAccess access = getSelectedDmaapAccess(appUser.getLoginId());
			if (access == null) // leap into exception handler
				throw new Exception("No DMaaP access profiles are configured.");
			String jsonContent = getBody(request);
			outboundJson = addDmaapItem(access, appUser.getLoginId(), itemType, jsonContent, scAddlStatus);
		} catch (Exception ex) {
			outboundJson = buildJsonError(500, "Failed to add DMaaP item " + itemType.name(), ex);
		}

		return outboundJson;
	}

	/**
	 * Updates an item of the specified type with the specified content.
	 * Constructs an object by deserializing the JSON block.
	 * 
	 * On success, returns a JSON block as String with any data returned by the
	 * REST client. Throws an exception on any failure.
	 * 
	 * @param dmaapAccess
	 *            Access details for the DMaaP REST API
	 * @param userId
	 *            The Login ID of the user making the request
	 * @param itemType
	 *            DMaaP item type to update
	 * @param itemId
	 *            Item identification
	 * @param itemContent
	 *            JSON block to deserialize as an object
	 * @param scAddlStatus
	 *            HTTP status code 200 is always accepted. If this parameter is
	 *            not null, the value is also considered a valid HTTP status
	 *            code on success; e.g., 204.
	 * @return JSON object with result of the operation
	 * @throws Exception
	 *             on any problem
	 */
	private String updateDmaapItem(DmaapAccess dmaapAccess, String userId, DmaapDataItem itemType, String itemId,
			String itemContent, Integer scAddlStatus) throws Exception {
		DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
		HttpStatusAndResponse<Object> hsr = null;
		switch (itemType) {
		case DR_FEED:
			Feed feed = mapper.readValue(itemContent, Feed.class);
			logger.debug("updateDmaapItem: received feed: {} ", feed);
			// Ensure the owner is the webapp user
			feed.setOwner(userId);
			hsr = restClient.putFeed(feed);
			break;
		case DR_PUB:
			DR_Pub pub = mapper.readValue(itemContent, DR_Pub.class);
			logger.debug("updateDmaapItem: received pub: {} ", pub);
			hsr = restClient.putDRPub(pub);
			break;
		case DR_SUB:
			DR_Sub sub = mapper.readValue(itemContent, DR_Sub.class);
			logger.debug("updateDmaapItem: received sub: {} ", sub);
			// Ensure the owner is the webapp user
			sub.setOwner(userId);
			hsr = restClient.putDRSub(sub);
			break;
		case MR_TOPIC:
			Topic topic = mapper.readValue(itemContent, Topic.class);
			logger.debug("updateDmaapItem: received topic: {} ", topic);
			// Ensure the owner is the webapp user
			topic.setOwner(userId);
			// DCAE backend may implement PUT someday.
			if (true && userId != null)
				throw new UnsupportedOperationException("put topic not supported (yet)");
			break;
		case MR_CLIENT:
			MR_Client client = mapper.readValue(itemContent, MR_Client.class);
			logger.debug("updateDmaapItem: received client: {} ", client);
			hsr = restClient.putMRClient(client);
			break;
		default:
			throw new Exception("updateDmaapItem: pgmr error, unimplemented case: " + itemType.name());
		}

		// Build result here
		String outboundJson = null;
		if (hsr.getStatusCode() == HttpServletResponse.SC_OK
				|| (scAddlStatus != null && hsr.getStatusCode() == scAddlStatus)) {
			outboundJson = buildJsonSuccess(hsr.getStatusCode(), hsr.getResponseString());
		} else {
			throw new Exception("Unexpected HTTP response code " + Integer.toString(hsr.getStatusCode())
					+ " with content " + hsr.getResponseString());
		}
		return outboundJson;
	}

	/**
	 * Updates the specified DMaaP item that is read from the request body. This
	 * method traps errors and constructs an appropriate JSON block if an error
	 * happens.
	 * 
	 * @param request
	 *            Used to obtain user info from the active session
	 * @param itemType
	 *            DMaaP item type to update
	 * @param itemId
	 *            Item identification to update
	 * @param scUpdatelStatus
	 *            HTTP status code 200 is always accepted. If this parameter is
	 *            not null, the value is also considered a valid HTTP status
	 *            code on success; e.g., 204.
	 * @return JSON object with success or error information.
	 */
	protected String updateItem(HttpServletRequest request, DmaapDataItem itemType, String itemId,
			Integer scUpdatelStatus) {
		String outboundJson = null;
		try {
			User appUser = UserUtils.getUserSession(request);
			if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
				throw new Exception("updateItem: Failed to get Login ID");
			DmaapAccess access = getSelectedDmaapAccess(appUser.getLoginId());
			if (access == null) // leap into exception handler
				throw new Exception("No DMaaP access profiles are configured.");
			String jsonContent = getBody(request);
			outboundJson = updateDmaapItem(access, appUser.getLoginId(), itemType, itemId, jsonContent,
					scUpdatelStatus);
		} catch (Exception ex) {
			outboundJson = buildJsonError(500, "Failed to update DMaaP item " + itemType.name(), ex);
		}

		return outboundJson;
	}

	/**
	 * Deletes an item of the specified type with the specified ID.
	 * 
	 * @param dmaapAccess
	 *            Access details for the DMaaP REST API
	 * @param itemType
	 *            DMaaP item type to delete
	 * @param itemId
	 *            Item identification
	 * @param scAddlStatus
	 *            HTTP status code 200 is always accepted. If this parameter is
	 *            not null, the value is also considered a valid HTTP status
	 *            code on success; e.g., 204.
	 * @return On success, returns a JSON block as String with any data returned
	 *         by the REST client.
	 * @throws Exception
	 *             On any failure.
	 */
	private String deleteDmaapItem(DmaapAccess dmaapAccess, DmaapDataItem itemType, String itemId, Integer scAddlStatus)
			throws Exception {
		DmaapBcRestClient restClient = getDmaapBcRestClient(dmaapAccess);
		HttpStatusAndResponse<Object> hsr = null;
		switch (itemType) {
		case DR_FEED:
			hsr = restClient.deleteFeed(itemId);
			break;
		case DR_PUB:
			hsr = restClient.deleteDRPub(itemId);
			break;
		case DR_SUB:
			hsr = restClient.deleteDRSub(itemId);
			break;
		case MR_TOPIC:
			hsr = restClient.deleteTopic(itemId);
			break;
		case MR_CLIENT:
			hsr = restClient.deleteMRClient(itemId);
			break;
		default:
			throw new Exception("deleteDmaapItem: pgmr error, unimplemented case: " + itemType.name());
		}

		// Build result here
		String outboundJson = null;
		if (hsr.getStatusCode() == HttpServletResponse.SC_OK
				|| (scAddlStatus != null && hsr.getStatusCode() == scAddlStatus)) {
			outboundJson = buildJsonSuccess(hsr.getStatusCode(), hsr.getResponseString());
		} else {
			throw new Exception("Unexpected HTTP response code " + Integer.toString(hsr.getStatusCode())
					+ " with content " + hsr.getResponseString());
		}
		return outboundJson;
	}

	/**
	 * Deletes the specified DMaaP item. This method traps errors and constructs
	 * an appropriate JSON block if an error happens.
	 * 
	 * @param request
	 *            Used to obtain user info from the active session
	 * @param itemType
	 *            DMaaP item type to delete
	 * @param itemId
	 *            item ID to delete
	 * @param scAddlStatus
	 *            HTTP status code 200 is always accepted. If this parameter is
	 *            not null, the value is also considered a valid HTTP status
	 *            code on success; e.g., 204.
	 * @return JSON object with success or error information.
	 */
	protected String deleteItem(HttpServletRequest request, DmaapDataItem itemType, String itemId,
			Integer scAddlStatus) {
		String outboundJson = null;
		try {
			User appUser = UserUtils.getUserSession(request);
			if (appUser == null || appUser.getLoginId() == null || appUser.getLoginId().length() == 0)
				throw new Exception("deleteItem: Failed to get Login ID");
			DmaapAccess selected = getSelectedDmaapAccess(appUser.getLoginId());
			if (selected == null) // leap into exception handler
				throw new Exception("No DMaaP access profiles are configured.");
			outboundJson = deleteDmaapItem(selected, itemType, itemId, scAddlStatus);
		} catch (Exception ex) {
			outboundJson = buildJsonError(500, "Failed to delete DMaaP item " + itemType.name() + " ID " + itemId, ex);
		}
		return outboundJson;
	}

}