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 @Override
111 public void process(HttpRequest request, HttpContext context) throws HttpException, IOException {
112 Credentials credentials = (Credentials)context.getAttribute(HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS);
113 if (credentials != null) {
114
115 AuthState authState = (AuthState)context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
116 authState.update(new BasicScheme(), credentials);
117 }
118 String oauth2AccessToken = (String)context.getAttribute(HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN);
119 if (oauth2AccessToken != null) {
120
121 request.setHeader("Authorization", "Bearer " + oauth2AccessToken);
122 }
123 }
124 });
125
126
127 if (props.isRelaxedSSLCheck()) {
128 try {
129 SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustSelfSignedStrategy()).build();
130 SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslContext, new NoopHostnameVerifier());
131 httpClientBuilder.setSSLSocketFactory(sslsf);
132 }
133 catch (KeyManagementException | KeyStoreException | NoSuchAlgorithmException ex) {
134 throw new PackageManagerException("Could not set relaxedSSLCheck", ex);
135 }
136 }
137
138
139 Proxy proxy = getProxyForUrl(props.getPackageManagerUrl());
140 if (proxy != null) {
141 httpClientBuilder.setProxy(new HttpHost(proxy.getHost(), proxy.getPort(), proxy.getProtocol()));
142 }
143
144 return httpClientBuilder.build();
145 }
146
147
148
149
150
151 public @NotNull HttpClientContext getPackageManagerHttpClientContext() {
152 return getHttpClientContext(props.getPackageManagerUrl(),
153 props.getUserId(), props.getPassword(), props.getOAuth2AccessToken());
154 }
155
156
157
158
159
160 public @Nullable HttpClientContext getConsoleHttpClientContext() {
161 String bundleStatusUrl = props.getBundleStatusUrl();
162 if (bundleStatusUrl == null) {
163 return null;
164 }
165 return getHttpClientContext(bundleStatusUrl,
166 props.getConsoleUserId(), props.getConsolePassword(), props.getConsoleOAuth2AccessToken());
167 }
168
169 private @NotNull HttpClientContext getHttpClientContext(String url, String userId, String password, String oauth2AccessToken) {
170 URI uri;
171 try {
172 uri = new URI(url);
173 }
174 catch (URISyntaxException ex) {
175 throw new PackageManagerException("Invalid url: " + url, ex);
176 }
177
178 final CredentialsProvider credsProvider = new BasicCredentialsProvider();
179 HttpClientContext context = new HttpClientContext();
180 context.setCredentialsProvider(credsProvider);
181
182 if (StringUtils.isNotBlank(oauth2AccessToken)) {
183 context.setAttribute(HTTP_CONTEXT_ATTRIBUTE_OAUTH2_ACCESS_TOKEN, oauth2AccessToken);
184 }
185 else {
186
187 final AuthScope authScope = new AuthScope(uri.getHost(), uri.getPort());
188 final Credentials credentials = new UsernamePasswordCredentials(userId, password);
189 credsProvider.setCredentials(authScope, credentials);
190 context.setAttribute(HTTP_CONTEXT_ATTRIBUTE_PREEMPTIVE_AUTHENTICATION_CREDS, credentials);
191 }
192
193
194 context.setRequestConfig(HttpClientUtil.buildRequestConfig(props));
195
196
197 Proxy proxy = getProxyForUrl(url);
198 if (proxy != null && proxy.useAuthentication()) {
199 AuthScope proxyAuthScope = new AuthScope(proxy.getHost(), proxy.getPort());
200 Credentials proxyCredentials = new UsernamePasswordCredentials(proxy.getUsername(), proxy.getPassword());
201 credsProvider.setCredentials(proxyAuthScope, proxyCredentials);
202 }
203
204 return context;
205 }
206
207
208
209
210
211
212 private Proxy getProxyForUrl(String requestUrl) {
213 List<Proxy> proxies = props.getProxies();
214 if (proxies == null || proxies.isEmpty()) {
215 return null;
216 }
217 final URI uri = URI.create(requestUrl);
218 for (Proxy proxy : proxies) {
219 if (!proxy.isNonProxyHost(uri.getHost())) {
220 return proxy;
221 }
222 }
223 return null;
224 }
225
226
227
228
229
230
231
232 @SuppressWarnings("PMD.GuardLogStatement")
233 private <T> T executeHttpCallWithRetry(HttpCall<T> call, int runCount) {
234 try {
235 return call.execute();
236 }
237 catch (PackageManagerHttpActionException ex) {
238
239 if (runCount < props.getRetryCount()) {
240 log.warn("ERROR: {}", ex.getMessage());
241 log.debug("HTTP call failed.", ex);
242 log.warn("---------------");
243
244 StringBuilder msg = new StringBuilder();
245 msg.append("HTTP call failed, try again (").append(runCount + 1).append("/").append(props.getRetryCount()).append(")");
246 if (props.getRetryDelaySec() > 0) {
247 msg.append(" after ").append(props.getRetryDelaySec()).append(" second(s)");
248 }
249 msg.append("...");
250 log.warn(msg.toString());
251 if (props.getRetryDelaySec() > 0) {
252 try {
253 Thread.sleep(props.getRetryDelaySec() * DateUtils.MILLIS_PER_SECOND);
254 }
255 catch (InterruptedException ex1) {
256
257 }
258 }
259 return executeHttpCallWithRetry(call, runCount + 1);
260 }
261 else {
262 throw ex;
263 }
264 }
265 }
266
267
268
269
270
271
272
273
274 public JSONObject executePackageManagerMethodJson(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
275 PackageManagerJsonCall call = new PackageManagerJsonCall(httpClient, context, method);
276 return executeHttpCallWithRetry(call, 0);
277 }
278
279
280
281
282
283
284
285
286 public Document executePackageManagerMethodXml(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
287 PackageManagerXmlCall call = new PackageManagerXmlCall(httpClient, context, method);
288 return executeHttpCallWithRetry(call, 0);
289 }
290
291
292
293
294
295
296
297
298 public String executePackageManagerMethodHtml(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
299 PackageManagerHtmlCall call = new PackageManagerHtmlCall(httpClient, context, method);
300 return executeHttpCallWithRetry(call, 0);
301 }
302
303
304
305
306
307
308
309 public void executePackageManagerMethodHtmlOutputResponse(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
310 PackageManagerHtmlMessageCall call = new PackageManagerHtmlMessageCall(httpClient, context, method, props);
311 executeHttpCallWithRetry(call, 0);
312 }
313
314
315
316
317
318
319
320
321 public void executePackageManagerMethodStatus(CloseableHttpClient httpClient, HttpClientContext context, HttpRequestBase method) {
322 PackageManagerStatusCall call = new PackageManagerStatusCall(httpClient, context, method);
323 executeHttpCallWithRetry(call, 0);
324 }
325
326
327
328
329
330
331 @SuppressWarnings("PMD.GuardLogStatement")
332 public void waitForBundlesActivation(CloseableHttpClient httpClient, HttpClientContext context) {
333 if (StringUtils.isBlank(props.getBundleStatusUrl())) {
334 log.debug("Skipping check for bundle activation state because no bundleStatusURL is defined.");
335 return;
336 }
337
338 final int WAIT_INTERVAL_SEC = 3;
339 final long CHECK_RETRY_COUNT = props.getBundleStatusWaitLimitSec() / WAIT_INTERVAL_SEC;
340
341 log.info("Check bundle activation status...");
342 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
343 BundleStatusCall call = new BundleStatusCall(httpClient, context, props.getBundleStatusUrl(),
344 props.getBundleStatusWhitelistBundleNames());
345 BundleStatus bundleStatus = executeHttpCallWithRetry(call, 0);
346
347 boolean instanceReady = true;
348
349
350 if (!bundleStatus.isAllBundlesRunning()) {
351 log.info("Bundles starting/stopping: {} - wait {} sec (max. {} sec) ...",
352 bundleStatus.getStatusLineCompact(), WAIT_INTERVAL_SEC, props.getBundleStatusWaitLimitSec());
353 sleep(WAIT_INTERVAL_SEC);
354 instanceReady = false;
355 }
356
357
358 if (instanceReady) {
359 for (Pattern blacklistBundleNamePattern : props.getBundleStatusBlacklistBundleNames()) {
360 String bundleSymbolicName = bundleStatus.getMatchingBundle(blacklistBundleNamePattern);
361 if (bundleSymbolicName != null) {
362 log.info("Bundle '{}' is still deployed - wait {} sec (max. {} sec) ...",
363 bundleSymbolicName, WAIT_INTERVAL_SEC, props.getBundleStatusWaitLimitSec());
364 sleep(WAIT_INTERVAL_SEC);
365 instanceReady = false;
366 break;
367 }
368 }
369 }
370
371
372 if (instanceReady) {
373 break;
374 }
375 }
376 }
377
378
379
380
381
382
383 @SuppressWarnings("PMD.GuardLogStatement")
384 public void waitForSystemReady(CloseableHttpClient httpClient, HttpClientContext context) {
385 if (StringUtils.isBlank(props.getSystemReadyUrl())) {
386 log.debug("Skipping check for system ready because no systemReadyURL is defined.");
387 return;
388 }
389
390 final int WAIT_INTERVAL_SEC = 3;
391 final long CHECK_RETRY_COUNT = props.getSystemReadyWaitLimitSec() / WAIT_INTERVAL_SEC;
392
393 log.info("Check system ready status...");
394 boolean systemReady = true;
395 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
396 boolean lastTry = (i == CHECK_RETRY_COUNT);
397
398 SystemReadyStatusCall call = new SystemReadyStatusCall(httpClient, context, props.getSystemReadyUrl());
399 SystemReadyStatus systemReadyStatus = executeHttpCallWithRetry(call, 0);
400
401
402 if (!systemReadyStatus.isSystemReadyOK()) {
403 if (lastTry) {
404 throw new PackageManagerException("System is NOT ready (" + systemReadyStatus.getOverallResult() + ") - package deployment failed.\n"
405 + systemReadyStatus.getFailureInfoString());
406 }
407 log.warn("System is NOT ready ({}) - wait {} sec (max. {} sec) ...\n{}",
408 systemReadyStatus.getOverallResult(), WAIT_INTERVAL_SEC, props.getSystemReadyWaitLimitSec(),
409 systemReadyStatus.getFailureInfoString());
410 sleep(WAIT_INTERVAL_SEC);
411 systemReady = false;
412 }
413
414
415 if (systemReady) {
416 break;
417 }
418 }
419 }
420
421
422
423
424
425
426 @SuppressWarnings("PMD.GuardLogStatement")
427 public void waitForPackageManagerInstallStatusFinished(CloseableHttpClient httpClient, HttpClientContext context) {
428 if (StringUtils.isBlank(props.getPackageManagerInstallStatusURL())) {
429 log.debug("Skipping check for package manager install state because no packageManagerInstallStatusURL is defined.");
430 return;
431 }
432
433 final int WAIT_INTERVAL_SEC = 3;
434 final long CHECK_RETRY_COUNT = props.getPackageManagerInstallStatusWaitLimitSec() / WAIT_INTERVAL_SEC;
435
436 log.info("Check package manager installation status...");
437 for (int i = 1; i <= CHECK_RETRY_COUNT; i++) {
438 PackageManagerInstallStatusCall call = new PackageManagerInstallStatusCall(httpClient, context,
439 props.getPackageManagerInstallStatusURL());
440 PackageManagerInstallStatus packageManagerStatus = executeHttpCallWithRetry(call, 0);
441
442 boolean instanceReady = true;
443
444
445 if (!packageManagerStatus.isFinished()) {
446 log.info("Packager manager not ready: {} packages left for installation - wait {} sec (max. {} sec) ...",
447 packageManagerStatus.getItemCount(), WAIT_INTERVAL_SEC, props.getPackageManagerInstallStatusWaitLimitSec());
448 sleep(WAIT_INTERVAL_SEC);
449 instanceReady = false;
450 }
451
452
453 if (instanceReady) {
454 break;
455 }
456 }
457 }
458
459 private void sleep(int sec) {
460 try {
461 Thread.sleep(sec * DateUtils.MILLIS_PER_SECOND);
462 }
463 catch (InterruptedException e) {
464
465 }
466 }
467
468 }