blob: 8b4244fd28a0dea4fc85c0f8042c545a31601b19 [file] [log] [blame]
Jian Lic132c112016-01-28 20:27:34 -08001/*
2 * Copyright 2016 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.cpman.rest;
17
18import com.fasterxml.jackson.databind.JsonNode;
Jian Liba6b1172016-02-01 22:40:42 -080019import com.fasterxml.jackson.databind.node.ArrayNode;
Jian Lic132c112016-01-28 20:27:34 -080020import com.fasterxml.jackson.databind.node.ObjectNode;
Jian Li9f3a8852016-04-07 13:37:39 -070021import org.apache.commons.lang3.StringUtils;
Jian Li7eed4172016-04-07 22:12:03 -070022import org.onlab.metrics.MetricsService;
Jian Lic132c112016-01-28 20:27:34 -080023import org.onosproject.cpman.ControlMetric;
24import org.onosproject.cpman.ControlMetricType;
Jian Lic132c112016-01-28 20:27:34 -080025import org.onosproject.cpman.ControlPlaneMonitorService;
Jian Li9f3a8852016-04-07 13:37:39 -070026import org.onosproject.cpman.ControlResource;
Jian Lic132c112016-01-28 20:27:34 -080027import org.onosproject.cpman.MetricValue;
Jian Li1a424692016-02-03 16:21:18 -080028import org.onosproject.cpman.SystemInfo;
29import org.onosproject.cpman.impl.DefaultSystemInfo;
Jian Li7eed4172016-04-07 22:12:03 -070030import org.onosproject.cpman.impl.SystemMetricsAggregator;
Jian Li1a424692016-02-03 16:21:18 -080031import org.onosproject.cpman.impl.SystemInfoFactory;
Jian Lic132c112016-01-28 20:27:34 -080032import org.onosproject.rest.AbstractWebResource;
Jian Li9f3a8852016-04-07 13:37:39 -070033import org.slf4j.Logger;
34import org.slf4j.LoggerFactory;
Jian Lic132c112016-01-28 20:27:34 -080035
36import javax.ws.rs.Consumes;
37import javax.ws.rs.POST;
38import javax.ws.rs.Path;
39import javax.ws.rs.Produces;
40import javax.ws.rs.core.MediaType;
41import javax.ws.rs.core.Response;
42import java.io.IOException;
43import java.io.InputStream;
Jian Li9f3a8852016-04-07 13:37:39 -070044import java.util.Iterator;
Jian Lic132c112016-01-28 20:27:34 -080045import java.util.Optional;
Jian Li9f3a8852016-04-07 13:37:39 -070046import java.util.Set;
47import java.util.stream.Collectors;
Jian Lic132c112016-01-28 20:27:34 -080048
Jian Liba6b1172016-02-01 22:40:42 -080049import static org.onlab.util.Tools.nullIsIllegal;
50
Jian Lic132c112016-01-28 20:27:34 -080051/**
Jian Li80c12702016-02-20 08:58:19 +090052 * Collect system metrics.
Jian Lic132c112016-01-28 20:27:34 -080053 */
Jian Li54df73e2016-02-01 17:09:03 -080054@Path("collector")
Jian Li80c12702016-02-20 08:58:19 +090055public class SystemMetricsCollectorWebResource extends AbstractWebResource {
Jian Lic132c112016-01-28 20:27:34 -080056
Jian Li9f3a8852016-04-07 13:37:39 -070057 private final Logger log = LoggerFactory.getLogger(getClass());
Jian Li7eed4172016-04-07 22:12:03 -070058 private final ControlPlaneMonitorService monitorService = get(ControlPlaneMonitorService.class);
59 private final MetricsService metricsService = get(MetricsService.class);
60
Jian Li80c12702016-02-20 08:58:19 +090061 private static final int UPDATE_INTERVAL_IN_MINUTE = 1;
62 private static final String INVALID_SYSTEM_SPECS = "Invalid system specifications";
63 private static final String INVALID_RESOURCE_NAME = "Invalid resource name";
64 private static final String INVALID_REQUEST = "Invalid request";
Jian Li9f3a8852016-04-07 13:37:39 -070065 private static final int PERCENT_CONSTANT = 100;
66
67 private static final Set<String> MEMORY_FIELD_SET = ControlResource.MEMORY_METRICS
68 .stream().map(type -> toCamelCase(type.toString(), true))
69 .collect(Collectors.toSet());
70
71 private static final Set<String> CPU_FIELD_SET = ControlResource.CPU_METRICS
72 .stream().map(type -> toCamelCase(type.toString(), true))
73 .collect(Collectors.toSet());
Jian Lic132c112016-01-28 20:27:34 -080074
Jian Li7eed4172016-04-07 22:12:03 -070075 private SystemMetricsAggregator systemAggr =
76 new SystemMetricsAggregator(metricsService, Optional.ofNullable(null), "system");
77
Jian Lic132c112016-01-28 20:27:34 -080078 /**
79 * Collects CPU metrics.
80 *
81 * @param stream JSON stream
82 * @return 200 OK
83 * @onos.rsModel CpuMetricsPost
84 */
85 @POST
Jian Li54df73e2016-02-01 17:09:03 -080086 @Path("cpu_metrics")
Jian Lic132c112016-01-28 20:27:34 -080087 @Consumes(MediaType.APPLICATION_JSON)
88 @Produces(MediaType.APPLICATION_JSON)
89 public Response cpuMetrics(InputStream stream) {
90 ObjectNode root = mapper().createObjectNode();
91 ControlMetric cm;
92 try {
93 ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
Jian Li9f3a8852016-04-07 13:37:39 -070094
95 if (jsonTree == null || !checkFields(jsonTree, CPU_FIELD_SET)) {
96 ok(root).build();
97 }
98
99 long cpuLoad = nullIsIllegal((long) (jsonTree.get("cpuLoad").asDouble()
100 * PERCENT_CONSTANT), INVALID_REQUEST);
Jian Li1fdd2242016-02-05 10:01:19 -0800101 long totalCpuTime = nullIsIllegal(jsonTree.get("totalCpuTime").asLong(), INVALID_REQUEST);
102 long sysCpuTime = nullIsIllegal(jsonTree.get("sysCpuTime").asLong(), INVALID_REQUEST);
103 long userCpuTime = nullIsIllegal(jsonTree.get("userCpuTime").asLong(), INVALID_REQUEST);
104 long cpuIdleTime = nullIsIllegal(jsonTree.get("cpuIdleTime").asLong(), INVALID_REQUEST);
Jian Lic132c112016-01-28 20:27:34 -0800105
Jian Li1fdd2242016-02-05 10:01:19 -0800106 cm = new ControlMetric(ControlMetricType.CPU_LOAD,
107 new MetricValue.Builder().load(cpuLoad).add());
Jian Li7eed4172016-04-07 22:12:03 -0700108 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
109 systemAggr.increment(ControlMetricType.CPU_LOAD, cpuLoad);
Jian Lic132c112016-01-28 20:27:34 -0800110
Jian Li1fdd2242016-02-05 10:01:19 -0800111 cm = new ControlMetric(ControlMetricType.TOTAL_CPU_TIME,
112 new MetricValue.Builder().load(totalCpuTime).add());
Jian Li7eed4172016-04-07 22:12:03 -0700113 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
114 systemAggr.increment(ControlMetricType.TOTAL_CPU_TIME, totalCpuTime);
Jian Lic132c112016-01-28 20:27:34 -0800115
Jian Li1fdd2242016-02-05 10:01:19 -0800116 cm = new ControlMetric(ControlMetricType.SYS_CPU_TIME,
117 new MetricValue.Builder().load(sysCpuTime).add());
Jian Li7eed4172016-04-07 22:12:03 -0700118 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
119 systemAggr.increment(ControlMetricType.SYS_CPU_TIME, sysCpuTime);
Jian Lic132c112016-01-28 20:27:34 -0800120
Jian Li1fdd2242016-02-05 10:01:19 -0800121 cm = new ControlMetric(ControlMetricType.USER_CPU_TIME,
122 new MetricValue.Builder().load(userCpuTime).add());
Jian Li7eed4172016-04-07 22:12:03 -0700123 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
124 systemAggr.increment(ControlMetricType.USER_CPU_TIME, userCpuTime);
Jian Lic132c112016-01-28 20:27:34 -0800125
Jian Li1fdd2242016-02-05 10:01:19 -0800126 cm = new ControlMetric(ControlMetricType.CPU_IDLE_TIME,
127 new MetricValue.Builder().load(cpuIdleTime).add());
Jian Li7eed4172016-04-07 22:12:03 -0700128 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
129 systemAggr.increment(ControlMetricType.CPU_IDLE_TIME, cpuIdleTime);
Jian Lic132c112016-01-28 20:27:34 -0800130
131 } catch (IOException e) {
132 throw new IllegalArgumentException(e.getMessage());
133 }
134 return ok(root).build();
135 }
136
137 /**
138 * Collects memory metrics.
139 *
140 * @param stream JSON stream
141 * @return 200 OK
142 * @onos.rsModel MemoryMetricsPost
143 */
144 @POST
Jian Li54df73e2016-02-01 17:09:03 -0800145 @Path("memory_metrics")
Jian Lic132c112016-01-28 20:27:34 -0800146 @Consumes(MediaType.APPLICATION_JSON)
147 @Produces(MediaType.APPLICATION_JSON)
148 public Response memoryMetrics(InputStream stream) {
149 ObjectNode root = mapper().createObjectNode();
150 ControlMetric cm;
151 try {
152 ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
Jian Li9f3a8852016-04-07 13:37:39 -0700153
154 if (jsonTree == null || !checkFields(jsonTree, MEMORY_FIELD_SET)) {
155 ok(root).build();
156 }
157
Jian Li1fdd2242016-02-05 10:01:19 -0800158 long memUsed = nullIsIllegal(jsonTree.get("memoryUsed").asLong(), INVALID_REQUEST);
159 long memFree = nullIsIllegal(jsonTree.get("memoryFree").asLong(), INVALID_REQUEST);
Jian Li9f3a8852016-04-07 13:37:39 -0700160 long memTotal = memUsed + memFree;
161 long memUsedRatio = memTotal == 0L ? 0L : (memUsed * PERCENT_CONSTANT) / memTotal;
162 long memFreeRatio = memTotal == 0L ? 0L : (memFree * PERCENT_CONSTANT) / memTotal;
Jian Lic132c112016-01-28 20:27:34 -0800163
Jian Li1fdd2242016-02-05 10:01:19 -0800164 cm = new ControlMetric(ControlMetricType.MEMORY_USED_RATIO,
165 new MetricValue.Builder().load(memUsedRatio).add());
Jian Li7eed4172016-04-07 22:12:03 -0700166 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
167 systemAggr.increment(ControlMetricType.MEMORY_USED_RATIO, memUsedRatio);
Jian Lic132c112016-01-28 20:27:34 -0800168
Jian Li1fdd2242016-02-05 10:01:19 -0800169 cm = new ControlMetric(ControlMetricType.MEMORY_FREE_RATIO,
170 new MetricValue.Builder().load(memFreeRatio).add());
Jian Li7eed4172016-04-07 22:12:03 -0700171 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
172 systemAggr.increment(ControlMetricType.MEMORY_FREE_RATIO, memFreeRatio);
Jian Lic132c112016-01-28 20:27:34 -0800173
Jian Li1fdd2242016-02-05 10:01:19 -0800174 cm = new ControlMetric(ControlMetricType.MEMORY_USED,
175 new MetricValue.Builder().load(memUsed).add());
Jian Li7eed4172016-04-07 22:12:03 -0700176 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
177 systemAggr.increment(ControlMetricType.MEMORY_USED, memUsed);
Jian Lic132c112016-01-28 20:27:34 -0800178
Jian Li1fdd2242016-02-05 10:01:19 -0800179 cm = new ControlMetric(ControlMetricType.MEMORY_FREE,
180 new MetricValue.Builder().load(memFree).add());
Jian Li7eed4172016-04-07 22:12:03 -0700181 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, Optional.ofNullable(null));
182 systemAggr.increment(ControlMetricType.MEMORY_FREE, memFree);
Jian Lic132c112016-01-28 20:27:34 -0800183
184 } catch (IOException e) {
185 throw new IllegalArgumentException(e.getMessage());
186 }
187 return ok(root).build();
188 }
189
190 /**
191 * Collects disk metrics.
192 *
193 * @param stream JSON stream
194 * @return 200 OK
195 * @onos.rsModel DiskMetricsPost
196 */
197 @POST
Jian Li54df73e2016-02-01 17:09:03 -0800198 @Path("disk_metrics")
Jian Lic132c112016-01-28 20:27:34 -0800199 @Consumes(MediaType.APPLICATION_JSON)
200 @Produces(MediaType.APPLICATION_JSON)
201 public Response diskMetrics(InputStream stream) {
202 ObjectNode root = mapper().createObjectNode();
Jian Li1fdd2242016-02-05 10:01:19 -0800203 ControlMetric cm;
Jian Lic132c112016-01-28 20:27:34 -0800204 try {
205 ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
Jian Li9f3a8852016-04-07 13:37:39 -0700206 ArrayNode diskRes =
207 jsonTree.get("disks") == null ? mapper().createArrayNode() : (ArrayNode) jsonTree.get("disks");
208
Jian Li1fdd2242016-02-05 10:01:19 -0800209 for (JsonNode node : diskRes) {
Jian Liba6b1172016-02-01 22:40:42 -0800210 JsonNode resourceName = node.get("resourceName");
211 nullIsIllegal(resourceName, INVALID_RESOURCE_NAME);
Jian Lic132c112016-01-28 20:27:34 -0800212
Jian Li7eed4172016-04-07 22:12:03 -0700213 SystemMetricsAggregator diskAggr = new SystemMetricsAggregator(metricsService,
214 Optional.of(resourceName.asText()), "disk");
215
Jian Li1fdd2242016-02-05 10:01:19 -0800216 long readBytes = nullIsIllegal(node.get("readBytes").asLong(), INVALID_REQUEST);
217 long writeBytes = nullIsIllegal(node.get("writeBytes").asLong(), INVALID_REQUEST);
Jian Lic132c112016-01-28 20:27:34 -0800218
Jian Li1fdd2242016-02-05 10:01:19 -0800219 cm = new ControlMetric(ControlMetricType.DISK_READ_BYTES,
220 new MetricValue.Builder().load(readBytes).add());
Jian Li7eed4172016-04-07 22:12:03 -0700221 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, resourceName.asText());
222 diskAggr.increment(ControlMetricType.DISK_READ_BYTES, readBytes);
Jian Lic132c112016-01-28 20:27:34 -0800223
Jian Li1fdd2242016-02-05 10:01:19 -0800224 cm = new ControlMetric(ControlMetricType.DISK_WRITE_BYTES,
225 new MetricValue.Builder().load(writeBytes).add());
Jian Li7eed4172016-04-07 22:12:03 -0700226 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, resourceName.asText());
227 diskAggr.increment(ControlMetricType.DISK_WRITE_BYTES, writeBytes);
Jian Li1fdd2242016-02-05 10:01:19 -0800228 }
Jian Lic132c112016-01-28 20:27:34 -0800229 } catch (IOException e) {
230 throw new IllegalArgumentException(e.getMessage());
231 }
232 return ok(root).build();
233 }
234
235 /**
236 * Collects network metrics.
237 *
238 * @param stream JSON stream
239 * @return 200 OK
240 * @onos.rsModel NetworkMetricsPost
241 */
242 @POST
Jian Li54df73e2016-02-01 17:09:03 -0800243 @Path("network_metrics")
Jian Lic132c112016-01-28 20:27:34 -0800244 @Consumes(MediaType.APPLICATION_JSON)
245 @Produces(MediaType.APPLICATION_JSON)
246 public Response networkMetrics(InputStream stream) {
247 ObjectNode root = mapper().createObjectNode();
Jian Li1fdd2242016-02-05 10:01:19 -0800248 ControlMetric cm;
Jian Lic132c112016-01-28 20:27:34 -0800249 try {
250 ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
Jian Li9f3a8852016-04-07 13:37:39 -0700251
252 ArrayNode networkRes = jsonTree.get("networks") == null
253 ? mapper().createArrayNode() : (ArrayNode) jsonTree.get("networks");
254
Jian Li1fdd2242016-02-05 10:01:19 -0800255 for (JsonNode node : networkRes) {
Jian Liba6b1172016-02-01 22:40:42 -0800256 JsonNode resourceName = node.get("resourceName");
257 nullIsIllegal(resourceName, INVALID_RESOURCE_NAME);
Jian Lic132c112016-01-28 20:27:34 -0800258
Jian Li7eed4172016-04-07 22:12:03 -0700259 SystemMetricsAggregator networkAggr = new SystemMetricsAggregator(metricsService,
260 Optional.of(resourceName.asText()), "network");
261
Jian Li1fdd2242016-02-05 10:01:19 -0800262 long inBytes = nullIsIllegal(node.get("incomingBytes").asLong(), INVALID_REQUEST);
263 long outBytes = nullIsIllegal(node.get("outgoingBytes").asLong(), INVALID_REQUEST);
264 long inPackets = nullIsIllegal(node.get("incomingPackets").asLong(), INVALID_REQUEST);
265 long outPackets = nullIsIllegal(node.get("outgoingPackets").asLong(), INVALID_REQUEST);
Jian Lic132c112016-01-28 20:27:34 -0800266
Jian Li1fdd2242016-02-05 10:01:19 -0800267 cm = new ControlMetric(ControlMetricType.NW_INCOMING_BYTES,
268 new MetricValue.Builder().load(inBytes).add());
Jian Li7eed4172016-04-07 22:12:03 -0700269 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, resourceName.asText());
270 networkAggr.increment(ControlMetricType.NW_INCOMING_BYTES, inBytes);
Jian Lic132c112016-01-28 20:27:34 -0800271
Jian Li1fdd2242016-02-05 10:01:19 -0800272 cm = new ControlMetric(ControlMetricType.NW_OUTGOING_BYTES,
273 new MetricValue.Builder().load(outBytes).add());
Jian Li7eed4172016-04-07 22:12:03 -0700274 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, resourceName.asText());
275 networkAggr.increment(ControlMetricType.NW_OUTGOING_BYTES, outBytes);
Jian Lic132c112016-01-28 20:27:34 -0800276
Jian Li1fdd2242016-02-05 10:01:19 -0800277 cm = new ControlMetric(ControlMetricType.NW_INCOMING_PACKETS,
278 new MetricValue.Builder().load(inPackets).add());
Jian Li7eed4172016-04-07 22:12:03 -0700279 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, resourceName.asText());
280 networkAggr.increment(ControlMetricType.NW_INCOMING_PACKETS, inPackets);
Jian Lic132c112016-01-28 20:27:34 -0800281
Jian Li1fdd2242016-02-05 10:01:19 -0800282 cm = new ControlMetric(ControlMetricType.NW_OUTGOING_PACKETS,
283 new MetricValue.Builder().load(outPackets).add());
Jian Li7eed4172016-04-07 22:12:03 -0700284 monitorService.updateMetric(cm, UPDATE_INTERVAL_IN_MINUTE, resourceName.asText());
285 networkAggr.increment(ControlMetricType.NW_OUTGOING_PACKETS, outPackets);
Jian Li1fdd2242016-02-05 10:01:19 -0800286 }
Jian Lic132c112016-01-28 20:27:34 -0800287 } catch (IOException e) {
288 throw new IllegalArgumentException(e.getMessage());
289 }
290 return ok(root).build();
291 }
292
Jian Lic132c112016-01-28 20:27:34 -0800293 /**
Jian Li1a424692016-02-03 16:21:18 -0800294 * Collects system information.
295 * The system information includes the various control metrics
Jian Lic132c112016-01-28 20:27:34 -0800296 * which do not require aggregation.
297 *
298 * @param stream JSON stream
299 * @return 200 OK
Jian Li1a424692016-02-03 16:21:18 -0800300 * @onos.rsModel SystemInfoPost
Jian Lic132c112016-01-28 20:27:34 -0800301 */
302 @POST
Jian Li1a424692016-02-03 16:21:18 -0800303 @Path("system_info")
Jian Lic132c112016-01-28 20:27:34 -0800304 @Consumes(MediaType.APPLICATION_JSON)
305 @Produces(MediaType.APPLICATION_JSON)
Jian Li1a424692016-02-03 16:21:18 -0800306 public Response systemInfo(InputStream stream) {
Jian Lic132c112016-01-28 20:27:34 -0800307 ObjectNode root = mapper().createObjectNode();
308
309 try {
310 ObjectNode jsonTree = (ObjectNode) mapper().readTree(stream);
311 JsonNode numOfCores = jsonTree.get("numOfCores");
312 JsonNode numOfCpus = jsonTree.get("numOfCpus");
313 JsonNode cpuSpeed = jsonTree.get("cpuSpeed");
314 JsonNode totalMemory = jsonTree.get("totalMemory");
315
Jian Li1a424692016-02-03 16:21:18 -0800316 if (numOfCores != null && numOfCpus != null &&
317 cpuSpeed != null && totalMemory != null) {
318 SystemInfo systemInfo = new DefaultSystemInfo.Builder()
319 .numOfCores(numOfCores.asInt())
Jian Lic132c112016-01-28 20:27:34 -0800320 .numOfCpus(numOfCpus.asInt())
321 .cpuSpeed(cpuSpeed.asInt())
Jian Li1a424692016-02-03 16:21:18 -0800322 .totalMemory(totalMemory.asInt())
Jian Lic132c112016-01-28 20:27:34 -0800323 .build();
Jian Lic132c112016-01-28 20:27:34 -0800324
Jian Li1a424692016-02-03 16:21:18 -0800325 // try to store the system info.
326 SystemInfoFactory.getInstance().setSystemInfo(systemInfo);
Jian Lic132c112016-01-28 20:27:34 -0800327 } else {
328 throw new IllegalArgumentException(INVALID_SYSTEM_SPECS);
329 }
330
331 } catch (IOException e) {
332 throw new IllegalArgumentException(e.getMessage());
333 }
334 return ok(root).build();
335 }
Jian Li9f3a8852016-04-07 13:37:39 -0700336
337 private boolean checkFields(ObjectNode node, Set<String> original) {
338 Iterator<String> fieldNames = node.fieldNames();
339 while (fieldNames.hasNext()) {
340 String fieldName = fieldNames.next();
341 if (!original.contains(fieldName) || node.get(fieldName) == null) {
342 log.warn("Illegal field name: {}", fieldName);
343 return false;
344 }
345 }
346 return true;
347 }
348
349 private static String toCamelCase(String value, boolean startWithLowerCase) {
350 String[] strings = StringUtils.split(value.toLowerCase(), "_");
351 for (int i = startWithLowerCase ? 1 : 0; i < strings.length; i++) {
352 strings[i] = StringUtils.capitalize(strings[i]);
353 }
354 return StringUtils.join(strings);
355 }
Jian Li9d616492016-03-09 10:52:49 -0800356}