1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.tooling.commons.packmgr;
21
22 import java.io.IOException;
23 import java.net.URI;
24 import java.net.URISyntaxException;
25 import java.security.KeyManagementException;
26 import java.security.KeyStoreException;
27 import java.security.NoSuchAlgorithmException;
28 import java.util.List;
29 import java.util.regex.Pattern;
30
31 import javax.net.ssl.SSLContext;
32
33 import org.apache.commons.lang3.StringUtils;
34 import org.apache.commons.lang3.time.DateUtils;
35 import org.apache.http.HttpException;
36 import org.apache.http.HttpHost;
37 import org.apache.http.HttpRequest;
38 import org.apache.http.HttpRequestInterceptor;
39 import org.apache.http.auth.AuthScope;
40 import org.apache.http.auth.AuthState;
41 import org.apache.http.auth.Credentials;
42 import org.apache.http.auth.UsernamePasswordCredentials;
43 import org.apache.http.client.CredentialsProvider;
44 import org.apache.http.client.methods.HttpRequestBase;
45 import org.apache.http.client.protocol.HttpClientContext;
46 import org.apache.http.conn.ssl.NoopHostnameVerifier;
47 import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
48 import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
49 import org.apache.http.impl.auth.BasicScheme;
50 import org.apache.http.impl.client.BasicCredentialsProvider;
51 import org.apache.http.impl.client.CloseableHttpClient;
52 import org.apache.http.impl.client.HttpClientBuilder;
53 import org.apache.http.impl.client.HttpClients;
54 import org.apache.http.protocol.HttpContext;
55 import org.apache.http.ssl.SSLContextBuilder;
56 import org.jdom2.Document;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59 import org.json.JSONObject;
60 import org.slf4j.Logger;
61 import org.slf4j.LoggerFactory;
62
63 import io.wcm.tooling.commons.packmgr.httpaction.BundleStatus;
64 import io.wcm.tooling.commons.packmgr.httpaction.BundleStatusCall;
65 import io.wcm.tooling.commons.packmgr.httpaction.HttpCall;
66 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerHtmlCall;
67 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerHtmlMessageCall;
68 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerInstallStatus;
69 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerInstallStatusCall;
70 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerJsonCall;
71 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerStatusCall;
72 import io.wcm.tooling.commons.packmgr.httpaction.PackageManagerXmlCall;
73 import io.wcm.tooling.commons.packmgr.httpaction.SystemReadyStatus;
74 import io.wcm.tooling.commons.packmgr.httpaction.SystemReadyStatusCall;
75 import io.wcm.tooling.commons.packmgr.util.HttpClientUtil;
76
77
78
79
80 public final class PackageManagerHelper {
81
82
83
84
85 public static final String CRX_PACKAGE_EXISTS_ERROR_MESSAGE_PREFIX = "Package already exists: ";
86
87 private static final String HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS = PackageManagerHelper.class.getName() + "_PreemptiveAuthenticationCreds";
88 private static final String HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN = PackageManagerHelper.class.getName() + "_oauth2AccessToken";
89
90 private final PackageManagerProperties props;
91
92 private static final Logger log = LoggerFactory.getLogger(PackageManagerHelper.class);
93
94
95
96
97 public PackageManagerHelper(PackageManagerProperties props) {
98 this.props = props;
99 }
100
101
102
103
104
105 public @NotNull CloseableHttpClient getHttpClient() {
106 HttpClientBuilder httpClientBuilder = HttpClients.custom()
107
108 .setKeepAliveStrategy((response, context) -> 1)
109 .addInterceptorFirst(new HttpRequestInterceptor() {
110
111 @Override
112 public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
113 Credentials credentials = (Credentials)context.getAttribute(HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS);
114 if (credentials != null) {
115
116 AuthState authState = (AuthState)context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
117 authState.update(new BasicScheme(), credentials);
118 }
119 String oauth2AccessToken = (String)context.getAttribute(HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN);
120 if (oauth2AccessToken != null) {
121
122 request.setHeader("Authorization", "Bearer " + oauth2AccessToken);
123 }
124 }
125 });
126
127
128 if (props.isRelaxedSSLCheck()) {
129 try {
130 SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
131 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
132 httpClientBuilder.setSSLSocketFactory(sslsf);
133 }
134 catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException ex) {
135 throw new PackageManagerException("Could not set relaxedSSLCheck", ex);
136 }
137 }
138
139
140 Proxy proxy = getProxyForUrl(props.getPackageManagerUrl());
141 if (proxy != null) {
142 httpClientBuilder.setProxy(new HttpHost(proxy.host(), proxy.port(), proxy.protocol()));
143 }
144
145 return httpClientBuilder.build();
146 }
147
148
149
150
151
152 public @NotNull HttpClientContext getPackageManagerHttpClientContext() {
153 return getHttpClientContext(props.getPackageManagerUrl(),
154 props.getUserId(), props.getPassword(), props.getOAuth2AccessToken());
155 }
156
157
158
159
160
161 public @Nullable HttpClientContext getConsoleHttpClientContext() {
162 String bundleStatusUrl = props.getBundleStatusUrl();
163 if (bundleStatusUrl == null) {
164 return null;
165 }
166 return getHttpClientContext(bundleStatusUrl,
167 props.getConsoleUserId(), props.getConsolePassword(), props.getConsoleOAuth2AccessToken());
168 }
169
170 private @NotNull HttpClientContext getHttpClientContext(String url, String userId, String password, String oauth2AccessToken) {
171 URI uri;
172 try {
173 uri = new URI(url);
174 }
175 catch (URISyntaxException ex) {
176 throw new PackageManagerException("Invalid url: " + url, ex);
177 }
178
179 final CredentialsProvider credsProvider = new BasicCredentialsProvider();
180 HttpClientContext context = new HttpClientContext();
181 context.setCredentialsProvider(credsProvider);
182
183 if (StringUtils.isNotBlank(oauth2AccessToken)) {
184 context.setAttribute(HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN, oauth2AccessToken);
185 }
186 else {
187
188 final AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort());
189 final Credentials credentials = new UsernamePasswordCredentials(userId, password);
190 credsProvider.setCredentials(authScope, credentials);
191 context.setAttribute(HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS, credentials);
192 }
193
194
195 context.setRequestConfig(HttpClientUtil.buildRequestConfig(props));
196
197
198 Proxy proxy = getProxyForUrl(url);
199 if (proxy != null && proxy.useAuthentication()) {
200 AuthScope proxyAuthScope = new AuthScope(proxy.host(), proxy.port());
201 Credentials proxyCredentials = new UsernamePasswordCredentials(proxy.username(), proxy.password());
202 credsProvider.setCredentials(proxyAuthScope, proxyCredentials);
203 }
204
205 return context;
206 }
207
208
209
210
211
212
213 private Proxy getProxyForUrl(String requestUrl) {
214 List<Proxy> proxies = props.getProxies();
215 if (proxies == null || proxies.isEmpty()) {
216 return null;
217 }
218 final URI uri = URI.create(requestUrl);
219 for (Proxy proxy : proxies) {
220 if (!proxy.isNonProxyHost(uri.getHost())) {
221 return proxy;
222 }
223 }
224 return null;
225 }
226
227
228
229
230
231
232
233 @SuppressWarnings("PMD.GuardLogStatement")
234 private <T> T executeHttpCallWithRetry(HttpCall<T> call, int runCount) {
235 try {
236 return call.execute();
237 }
238 catch (PackageManagerHttpActionException ex) {
239
240 if (runCount < props.getRetryCount()) {
241 log.warn("ERROR: {}", ex.getMessage());
242 log.debug("HTTP call failed.", ex);
243 log.warn("---------------");
244
245 StringBuilder msg = new StringBuilder();
246 msg.append("HTTP call failed, try again (").append(runCount + 1).append("/").append(props.getRetryCount()).append(")");
247 if (props.getRetryDelaySec() > 0) {
248 msg.append(" after ").append(props.getRetryDelaySec()).append(" second(s)");
249 }
250 msg.append("...");
251 log.warn(msg.toString());
252 if (props.getRetryDelaySec() > 0) {
253 try {
254 Thread.sleep(props.getRetryDelaySec() * DateUtils.MILLIS_PER_SECOND);
255 }
256 catch (InterruptedException ex1) {
257 Thread.currentThread().interrupt();
258 }
259 }
260 return executeHttpCallWithRetry(call, runCount + 1);
261 }
262 else {
263 throw ex;
264 }
265 }
266 }
267
268
269
270
271
272
273
274
275 public JSONObject executePackageManagerMethodJson(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
276 PackageManagerJsonCall call = new PackageManagerJsonCall(httpClient, context, method);
277 return executeHttpCallWithRetry(call, 0);
278 }
279
280
281
282
283
284
285
286
287 public Document executePackageManagerMethodXml(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
288 PackageManagerXmlCall call = new PackageManagerXmlCall(httpClient, context, method);
289 return executeHttpCallWithRetry(call, 0);
290 }
291
292
293
294
295
296
297
298
299 public String executePackageManagerMethodHtml(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
300 PackageManagerHtmlCall call = new PackageManagerHtmlCall(httpClient, context, method);
301 return executeHttpCallWithRetry(call, 0);
302 }
303
304
305
306
307
308
309
310 public void executePackageManagerMethodHtmlOutputResponse(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
311 PackageManagerHtmlMessageCall call = new PackageManagerHtmlMessageCall(httpClient, context, method, props);
312 executeHttpCallWithRetry(call, 0);
313 }
314
315
316
317
318
319
320
321
322 public void executePackageManagerMethodStatus(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
323 PackageManagerStatusCall call = new PackageManagerStatusCall(httpClient, context, method);
324 executeHttpCallWithRetry(call, 0);
325 }
326
327
328
329
330
331
332 @SuppressWarnings("PMD.GuardLogStatement")
333 public void waitForBundlesActivation(CloseableHttpClient httpClient, HttpClientContext context) {
334 if (StringUtils.isBlank(props.getBundleStatusUrl())) {
335 log.debug("Skipping check for bundle activation state because no bundleStatusURL is defined.");
336 return;
337 }
338
339 final int WAIT_INTERVAL_SEC = 3;
340 final long CHECK_RETRY_COUNT = props.getBundleStatusWaitLimitSec() / WAIT_INTERVAL_SEC;
341
342 log.info("Check bundle activation status...");
343 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
344 BundleStatusCall call = new BundleStatusCall(httpClient, context, props.getBundleStatusUrl(),
345 props.getBundleStatusWhitelistBundleNames());
346 BundleStatus bundleStatus = executeHttpCallWithRetry(call, 0);
347
348 boolean instanceReady = true;
349
350
351 if (!bundleStatus.isAllBundlesRunning()) {
352 log.info("Bundles starting/stopping: {} - wait {} sec (max. {} sec) ...",
353 bundleStatus.getStatusLineCompact(), WAIT_INTERVAL_SEC, props.getBundleStatusWaitLimitSec());
354 sleep(WAIT_INTERVAL_SEC);
355 instanceReady = false;
356 }
357
358
359 if (instanceReady) {
360 for (Pattern blacklistBundleNamePattern : props.getBundleStatusBlacklistBundleNames()) {
361 String bundleSymbolicName = bundleStatus.getMatchingBundle(blacklistBundleNamePattern);
362 if (bundleSymbolicName != null) {
363 log.info("Bundle '{}' is still deployed - wait {} sec (max. {} sec) ...",
364 bundleSymbolicName, WAIT_INTERVAL_SEC, props.getBundleStatusWaitLimitSec());
365 sleep(WAIT_INTERVAL_SEC);
366 instanceReady = false;
367 break;
368 }
369 }
370 }
371
372
373 if (instanceReady) {
374 break;
375 }
376 }
377 }
378
379
380
381
382
383
384 @SuppressWarnings("PMD.GuardLogStatement")
385 public void waitForSystemReady(CloseableHttpClient httpClient, HttpClientContext context) {
386 if (StringUtils.isBlank(props.getSystemReadyUrl())) {
387 log.debug("Skipping check for system ready because no systemReadyURL is defined.");
388 return;
389 }
390
391 final int WAIT_INTERVAL_SEC = 3;
392 final long CHECK_RETRY_COUNT = props.getSystemReadyWaitLimitSec() / WAIT_INTERVAL_SEC;
393
394 log.info("Check system ready status...");
395 boolean systemReady = true;
396 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
397 boolean lastTry = (i == CHECK_RETRY_COUNT);
398
399 SystemReadyStatusCall call = new SystemReadyStatusCall(httpClient, context, props.getSystemReadyUrl());
400 SystemReadyStatus systemReadyStatus = executeHttpCallWithRetry(call, 0);
401
402
403 if (!systemReadyStatus.isSystemReadyOK()) {
404 if (lastTry) {
405 throw new PackageManagerException("System is NOT ready (" + systemReadyStatus.overallResult() + ") - package deployment failed.\n"
406 + systemReadyStatus.getFailureInfoString());
407 }
408 log.warn("System is NOT ready ({}) - wait {} sec (max. {} sec) ...\n{}",
409 systemReadyStatus.overallResult(), WAIT_INTERVAL_SEC, props.getSystemReadyWaitLimitSec(),
410 systemReadyStatus.getFailureInfoString());
411 sleep(WAIT_INTERVAL_SEC);
412 systemReady = false;
413 }
414
415
416 if (systemReady) {
417 break;
418 }
419 }
420 }
421
422
423
424
425
426
427 @SuppressWarnings("PMD.GuardLogStatement")
428 public void waitForPackageManagerInstallStatusFinished(CloseableHttpClient httpClient, HttpClientContext context) {
429 if (StringUtils.isBlank(props.getPackageManagerInstallStatusURL())) {
430 log.debug("Skipping check for package manager install state because no packageManagerInstallStatusURL is defined.");
431 return;
432 }
433
434 final int WAIT_INTERVAL_SEC = 3;
435 final long CHECK_RETRY_COUNT = props.getPackageManagerInstallStatusWaitLimitSec() / WAIT_INTERVAL_SEC;
436
437 log.info("Check package manager installation status...");
438 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
439 PackageManagerInstallStatusCall call = new PackageManagerInstallStatusCall(httpClient, context,
440 props.getPackageManagerInstallStatusURL());
441 PackageManagerInstallStatus packageManagerStatus = executeHttpCallWithRetry(call, 0);
442
443 boolean instanceReady = true;
444
445
446 if (!packageManagerStatus.isFinished()) {
447 log.info("Packager manager not ready: {} packages left for installation - wait {} sec (max. {} sec) ...",
448 packageManagerStatus.getItemCount(), WAIT_INTERVAL_SEC, props.getPackageManagerInstallStatusWaitLimitSec());
449 sleep(WAIT_INTERVAL_SEC);
450 instanceReady = false;
451 }
452
453
454 if (instanceReady) {
455 break;
456 }
457 }
458 }
459
460 private void sleep(int sec) {
461 try {
462 Thread.sleep(sec * DateUtils.MILLIS_PER_SECOND);
463 }
464 catch (InterruptedException e) {
465 Thread.currentThread().interrupt();
466 }
467 }
468
469 }