View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.report.projectinfo.avatars;
20  
21  import javax.inject.Named;
22  
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.OutputStream;
28  import java.net.URI;
29  import java.net.URISyntaxException;
30  import java.net.URL;
31  import java.nio.file.Files;
32  import java.nio.file.Path;
33  import java.nio.file.Paths;
34  import java.security.MessageDigest;
35  import java.security.NoSuchAlgorithmException;
36  import java.util.Locale;
37  
38  import org.codehaus.plexus.util.IOUtil;
39  import org.slf4j.Logger;
40  import org.slf4j.LoggerFactory;
41  
42  /**
43   * Provider for user avatar from gravatar.com
44   * <p>
45   * <a href="https://docs.gravatar.com/api/avatars/images/">Gravatar API</a>
46   */
47  @Named("gravatar")
48  class GravatarProvider implements AvatarsProvider {
49  
50      private static final Logger LOGGER = LoggerFactory.getLogger(GravatarProvider.class);
51  
52      private static final String AVATAR_SIZE = "s=60";
53  
54      private static final String AVATAR_DIRECTORY = "avatars";
55  
56      private static final String AVATAR_DEFAULT_FILE_NAME = "00000000000000000000000000000000.jpg";
57  
58      private String baseUrl = "https://www.gravatar.com/avatar/";
59  
60      private Path outputDirectory;
61  
62      @Override
63      public void setBaseUrl(String baseUrl) {
64          this.baseUrl = baseUrl.endsWith("/") ? baseUrl : baseUrl + "/";
65      }
66  
67      @Override
68      public void setOutputDirectory(File outputDirectory) {
69          this.outputDirectory = outputDirectory.toPath();
70      }
71  
72      public String getAvatarUrl(String email) {
73          return getAvatarUrl(email, "blank");
74      }
75  
76      private String getAvatarUrl(String email, String defaultAvatar) {
77          if (email == null || email.isEmpty()) {
78              return getSpacerGravatarUrl();
79          }
80  
81          try {
82              email = email.trim().toLowerCase(Locale.ROOT);
83              MessageDigest md = MessageDigest.getInstance("MD5");
84              md.update(email.getBytes());
85              byte[] byteData = md.digest();
86              StringBuilder sb = new StringBuilder();
87              final int lowerEightBitsOnly = 0xff;
88              for (byte aByteData : byteData) {
89                  sb.append(Integer.toString((aByteData & lowerEightBitsOnly) + 0x100, 16)
90                          .substring(1));
91              }
92              return baseUrl + sb + ".jpg?d=" + defaultAvatar + "&" + AVATAR_SIZE;
93          } catch (NoSuchAlgorithmException e) {
94              LOGGER.warn("Error while getting MD5 hash, use default image: {}", e.getMessage());
95              return getSpacerGravatarUrl();
96          }
97      }
98  
99      @Override
100     public String getLocalAvatarPath(String email) throws IOException {
101         // use 404 http status for not existing avatars
102         String avatarUrl = getAvatarUrl(email, "404");
103         try {
104             URL url = new URI(avatarUrl).toURL();
105             Path name = Paths.get(url.getPath()).getFileName();
106             if (AVATAR_DEFAULT_FILE_NAME.equals(name.toString())) {
107                 copyDefault();
108             } else {
109                 copyUrl(url, outputDirectory.resolve(AVATAR_DIRECTORY).resolve(name));
110             }
111             return AVATAR_DIRECTORY + "/" + name;
112         } catch (URISyntaxException | IOException e) {
113             if (e instanceof FileNotFoundException) {
114                 LOGGER.debug(
115                         "Error while getting external avatar url for: {}, use default image: {}:{}",
116                         email,
117                         e.getClass().getName(),
118                         e.getMessage());
119             } else {
120                 LOGGER.warn(
121                         "Error while getting external avatar url for: {}, use default image: {}:{}",
122                         email,
123                         e.getClass().getName(),
124                         e.getMessage());
125             }
126             copyDefault();
127             return AVATAR_DIRECTORY + "/" + AVATAR_DEFAULT_FILE_NAME;
128         }
129     }
130 
131     private String getSpacerGravatarUrl() {
132         return baseUrl + AVATAR_DEFAULT_FILE_NAME + "?d=blank&f=y&" + AVATAR_SIZE;
133     }
134 
135     private void copyUrl(URL url, Path outputPath) throws IOException {
136         if (!Files.exists(outputPath)) {
137             Files.createDirectories(outputPath.getParent());
138             try (InputStream in = url.openStream();
139                     OutputStream out = Files.newOutputStream(outputPath)) {
140                 LOGGER.debug("Copying URL {} to {}", url, outputPath);
141                 IOUtil.copy(in, out);
142             }
143         }
144     }
145 
146     private void copyDefault() throws IOException {
147         Path outputPath = outputDirectory.resolve(AVATAR_DIRECTORY).resolve(AVATAR_DEFAULT_FILE_NAME);
148         if (!Files.exists(outputPath)) {
149             Files.createDirectories(outputPath.getParent());
150             try (InputStream in = getClass().getResourceAsStream("default-avatar.jpg");
151                     OutputStream out = Files.newOutputStream(outputPath)) {
152                 IOUtil.copy(in, out);
153             }
154         }
155     }
156 }