1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package io.wcm.handler.mediasource.ngdm.impl.metadata;
21
22 import java.io.IOException;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.List;
26
27 import org.apache.commons.lang3.StringUtils;
28 import org.apache.http.Header;
29 import org.apache.http.HttpHost;
30 import org.apache.http.HttpStatus;
31 import org.apache.http.client.config.RequestConfig;
32 import org.apache.http.client.methods.CloseableHttpResponse;
33 import org.apache.http.client.methods.HttpGet;
34 import org.apache.http.impl.client.CloseableHttpClient;
35 import org.apache.http.impl.client.HttpClientBuilder;
36 import org.apache.http.message.BasicHeader;
37 import org.apache.http.util.EntityUtils;
38 import org.jetbrains.annotations.NotNull;
39 import org.jetbrains.annotations.Nullable;
40 import org.osgi.service.component.annotations.Activate;
41 import org.osgi.service.component.annotations.Component;
42 import org.osgi.service.component.annotations.Deactivate;
43 import org.osgi.service.component.annotations.Reference;
44 import org.osgi.service.metatype.annotations.AttributeDefinition;
45 import org.osgi.service.metatype.annotations.Designate;
46 import org.osgi.service.metatype.annotations.ObjectClassDefinition;
47 import org.slf4j.Logger;
48 import org.slf4j.LoggerFactory;
49
50 import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaConfigService;
51 import io.wcm.handler.mediasource.ngdm.impl.NextGenDynamicMediaReference;
52
53
54
55
56 @Component(service = NextGenDynamicMediaMetadataService.class, immediate = true)
57 @Designate(ocd = NextGenDynamicMediaMetadataServiceImpl.Config.class)
58 public class NextGenDynamicMediaMetadataServiceImpl implements NextGenDynamicMediaMetadataService {
59
60 @ObjectClassDefinition(
61 name = "wcm.io Media Handler Dynamic Media with OpenAPI Metadata Service",
62 description = "Fetches metadata for Dynamic Media with OpenAPI remote assets.")
63 @interface Config {
64
65 @AttributeDefinition(
66 name = "Enabled",
67 description = "When enabled, metadata is fetched for each resolved remote asset. This checks for validity/existence of "
68 + "the asset and for the maximum supported resolution of the original image, and allows to fetch Smart Cropping information.")
69 boolean enabled() default true;
70
71 @AttributeDefinition(
72 name = "HTTP Headers",
73 description = "HTTP headers to be send with the asset metadata request. "
74 + "Format: 'header1:value1'.")
75 String[] httpHeaders();
76
77 @AttributeDefinition(
78 name = "Connect Timeout",
79 description = "HTTP Connect timeout in milliseconds.")
80 int connectTimeout() default 5000;
81
82 @AttributeDefinition(
83 name = "Connection Request Timeout",
84 description = "HTTP connection request timeout in milliseconds.")
85 int connectionRequestTimeout() default 5000;
86
87 @AttributeDefinition(
88 name = "Socket Timeout",
89 description = "HTTP socket timeout in milliseconds.")
90 int socketTimeout() default 5000;
91
92 @AttributeDefinition(
93 name = "Proxy Host",
94 description = "Proxy host name")
95 String proxyHost();
96
97 @AttributeDefinition(
98 name = "Proxy Port",
99 description = "Proxy port")
100 int proxyPort();
101
102 @AttributeDefinition(
103 name = "IMS Token API URL",
104 description = "API to obtain IMS access token for obtaining full metadata.")
105 String imsTokenApiUrl() default "https://ims-na1.adobelogin.com/ims/token/v3";
106
107 @AttributeDefinition(
108 name = "IMS OAuth Client ID",
109 description = "Optional: If you want to fetch the full metadata for assets, provide the IMS OAuth Client ID.")
110 String authenticationClientId();
111
112 @AttributeDefinition(
113 name = "IMS OAuth Client Secret",
114 description = "Optional: If you want to fetch the full metadata for assets, provide the IMS OAuth Client Secret.")
115 String authenticationClientSecret();
116
117 @AttributeDefinition(
118 name = "IMS OAuth Scope",
119 description = "OAuth Scope to use for obtaining IMS access token.")
120 String authenticationScope() default "openid,AdobeID,read_organizations,additional_info.projectedProductContext,read_pc.dma_aem_ams";
121
122 }
123
124 @Reference
125 private NextGenDynamicMediaConfigService nextGenDynamicMediaConfig;
126
127 private boolean enabled;
128 private CloseableHttpClient httpClient;
129
130 private ImsAccessTokenCache imsAccessTokenCache;
131 private String authenticationClientId;
132 private String authenticationClientSecret;
133 private String authenticationScope;
134
135 private static final Logger log = LoggerFactory.getLogger(NextGenDynamicMediaMetadataServiceImpl.class);
136
137 @Activate
138 private void activate(Config config) {
139 this.enabled = config.enabled();
140 if (enabled) {
141 httpClient = createHttpClient(config);
142
143
144 String imsTokenApiUrl = config.imsTokenApiUrl();
145 authenticationClientId = config.authenticationClientId();
146 authenticationClientSecret = config.authenticationClientSecret();
147 authenticationScope = config.authenticationScope();
148 if (log.isTraceEnabled()) {
149 log.trace("Authentication configuration: imsTokenApiUrl={}, authenticationClientId={}, authenticationClientSecret={}, authenticationScope={}",
150 StringUtils.defaultString(imsTokenApiUrl),
151 StringUtils.isNotBlank(authenticationClientId) ? "***" : "",
152 StringUtils.isNotBlank(authenticationClientSecret) ? "***" : "",
153 StringUtils.defaultString(authenticationScope));
154 }
155 if (StringUtils.isNoneBlank(imsTokenApiUrl, authenticationClientId, authenticationClientSecret, authenticationScope)) {
156 log.debug("Enable IMS access token fetching for NGDM asset metadata.");
157 imsAccessTokenCache = new ImsAccessTokenCache(httpClient, config.imsTokenApiUrl());
158 }
159 else {
160 log.debug("IMS access token fetching for NGDM asset metadata is disabled.");
161 }
162 }
163 }
164
165 private static CloseableHttpClient createHttpClient(Config config) {
166 RequestConfig requestConfig = RequestConfig.custom()
167 .setConnectTimeout(config.connectTimeout())
168 .setConnectionRequestTimeout(config.connectionRequestTimeout())
169 .setSocketTimeout(config.socketTimeout())
170 .build();
171 HttpClientBuilder builder = HttpClientBuilder.create()
172 .setDefaultRequestConfig(requestConfig)
173 .setDefaultHeaders(convertHeaders(config.httpHeaders()));
174 if (StringUtils.isNotBlank(config.proxyHost()) && config.proxyPort() > 0) {
175 builder.setProxy(new HttpHost(config.proxyHost(), config.proxyPort()));
176 }
177 return builder.build();
178 }
179
180 private static Collection<Header> convertHeaders(String[] headers) {
181 List<Header> result = new ArrayList<>();
182 for (String header : headers) {
183 String[] parts = header.split(":", 2);
184 if (parts.length == 2) {
185 result.add(new BasicHeader(parts[0], parts[1]));
186 }
187 }
188 return result;
189 }
190
191 @Deactivate
192 private void deactivate() throws IOException {
193 if (httpClient != null) {
194 httpClient.close();
195 imsAccessTokenCache = null;
196 }
197 }
198
199 @Override
200 public boolean isEnabled() {
201 return enabled;
202 }
203
204
205
206
207
208
209 @Override
210 public @Nullable NextGenDynamicMediaMetadata fetchMetadata(@NotNull NextGenDynamicMediaReference reference) {
211 if (!enabled) {
212 return null;
213 }
214 String metadataUrl = new NextGenDynamicMediaMetadataUrlBuilder(nextGenDynamicMediaConfig).build(reference);
215 if (metadataUrl == null) {
216 return null;
217 }
218
219 HttpGet httpGet = new HttpGet(metadataUrl);
220
221
222 if (imsAccessTokenCache != null) {
223 String accessToken = imsAccessTokenCache.getAccessToken(authenticationClientId, authenticationClientSecret, authenticationScope);
224 if (accessToken != null) {
225 httpGet.addHeader("Authorization", "Bearer " + accessToken);
226 }
227 }
228
229 try (CloseableHttpResponse response = httpClient.execute(httpGet)) {
230 return processResponse(response, metadataUrl);
231 }
232 catch (IOException ex) {
233 log.warn("Unable to fetch NGDM asset metadata from URL {}", metadataUrl, ex);
234 return null;
235 }
236 }
237
238 private @Nullable NextGenDynamicMediaMetadata processResponse(@NotNull CloseableHttpResponse response,
239 @NotNull String metadataUrl) throws IOException {
240 switch (response.getStatusLine().getStatusCode()) {
241 case HttpStatus.SC_OK:
242 String jsonResponse = EntityUtils.toString(response.getEntity());
243 NextGenDynamicMediaMetadata metadata = NextGenDynamicMediaMetadata.fromJson(jsonResponse);
244 log.trace("HTTP response for NGDM asset metadata {} returns: {}", metadataUrl, metadata);
245 if (metadata.isValid()) {
246 return metadata;
247 }
248 break;
249 case HttpStatus.SC_NOT_FOUND:
250 log.trace("HTTP response for NGDM asset metadata {} returns HTTP 404", metadataUrl);
251 break;
252 default:
253 log.warn("Unexpected HTTP response for NGDM asset metadata {}: {}", metadataUrl, response.getStatusLine());
254 break;
255 }
256 return null;
257 }
258
259 }