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