001 /* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 */ 019 020 021 package org.apache.geronimo.system.plugin; 022 023 import java.io.File; 024 import java.io.FileOutputStream; 025 import java.io.IOException; 026 import java.io.InputStream; 027 import java.net.HttpURLConnection; 028 import java.net.MalformedURLException; 029 import java.net.URI; 030 import java.net.URL; 031 import java.net.URLConnection; 032 import java.util.ArrayList; 033 import java.util.Arrays; 034 import java.util.Comparator; 035 import java.util.List; 036 037 import javax.security.auth.login.FailedLoginException; 038 import javax.xml.parsers.DocumentBuilder; 039 import javax.xml.parsers.ParserConfigurationException; 040 041 import org.apache.geronimo.kernel.repository.Artifact; 042 import org.apache.geronimo.kernel.repository.FileWriteMonitor; 043 import org.apache.geronimo.kernel.repository.Version; 044 import org.apache.geronimo.kernel.repository.WriteableRepository; 045 import org.apache.geronimo.kernel.util.XmlUtil; 046 import org.apache.geronimo.system.plugin.model.PluginListType; 047 import org.apache.geronimo.crypto.encoders.Base64; 048 import org.w3c.dom.Document; 049 import org.w3c.dom.Element; 050 import org.w3c.dom.Node; 051 import org.w3c.dom.NodeList; 052 import org.xml.sax.SAXException; 053 054 /** 055 * @version $Rev: 706640 $ $Date: 2008-10-21 14:44:05 +0000 (Tue, 21 Oct 2008) $ 056 */ 057 public class RemoteSourceRepository implements SourceRepository { 058 059 private final URI base; 060 private String username = null; 061 private String password = null; 062 063 public RemoteSourceRepository(URI base, String username, String password) { 064 if (!base.getPath().endsWith("/")) { 065 throw new IllegalArgumentException("base uri must end with '/', not " + base); 066 } 067 this.base = base; 068 this.username = username; 069 this.password = password; 070 } 071 072 public PluginListType getPluginList() { 073 try { 074 URL uri = base.resolve("geronimo-plugins.xml").toURL(); 075 InputStream in = openStream(null, uri); 076 if (in != null) { 077 try { 078 return PluginXmlUtil.loadPluginList(in); 079 } finally { 080 in.close(); 081 } 082 } 083 } catch (Exception e) { 084 // TODO: log it? 085 } 086 return null; 087 } 088 089 public OpenResult open(Artifact artifact, FileWriteMonitor monitor) throws IOException, FailedLoginException { 090 091 // If the artifact version is resolved then look for the artifact in the repo 092 if (artifact.isResolved()) { 093 URL location = getURL(artifact); 094 OpenResult result = open(artifact, location); 095 if (result != null) { 096 return result; 097 } 098 Version version = artifact.getVersion(); 099 // Snapshot artifacts can have a special filename in an online maven repo. 100 // The version number is replaced with a timestmap and build number. 101 // The maven-metadata file contains this extra information. 102 if (version.toString().indexOf("SNAPSHOT") >= 0 && !(version instanceof SnapshotVersion)) { 103 // base path for the artifact version in a maven repo 104 URI basePath = base.resolve(artifact.getGroupId().replace('.', '/') + "/" + artifact.getArtifactId() + "/" + version + "/"); 105 106 // get the maven-metadata file 107 Document metadata = getMavenMetadata(basePath); 108 109 // determine the snapshot qualifier from the maven-metadata file 110 if (metadata != null) { 111 NodeList snapshots = metadata.getDocumentElement().getElementsByTagName("snapshot"); 112 if (snapshots.getLength() >= 1) { 113 Element snapshot = (Element) snapshots.item(0); 114 List<String> timestamp = getChildrenText(snapshot, "timestamp"); 115 List<String> buildNumber = getChildrenText(snapshot, "buildNumber"); 116 if (timestamp.size() >= 1 && buildNumber.size() >= 1) { 117 try { 118 // recurse back into this method using a SnapshotVersion 119 SnapshotVersion snapshotVersion = new SnapshotVersion(version); 120 snapshotVersion.setBuildNumber(Integer.parseInt(buildNumber.get(0))); 121 snapshotVersion.setTimestamp(timestamp.get(0)); 122 Artifact newQuery = new Artifact(artifact.getGroupId(), artifact.getArtifactId(), snapshotVersion, artifact.getType()); 123 location = getURL(newQuery); 124 return open(artifact, location); 125 } catch (NumberFormatException nfe) { 126 // log.error("Could not create snapshot version for " + artifact, nfe); 127 } 128 } else { 129 // log.error("Could not create snapshot version for " + artifact); 130 } 131 } 132 } 133 } 134 return null; 135 } 136 137 // Version is not resolved. Look in maven-metadata.xml and maven-metadata-local.xml for 138 // the available version numbers. If found then recurse into the enclosing method with 139 // a resolved version number 140 else { 141 142 // base path for the artifact version in a maven repo 143 URI basePath = base.resolve(artifact.getGroupId().replace('.', '/') + "/" + artifact.getArtifactId() + "/"); 144 145 // get the maven-metadata file 146 Document metadata = getMavenMetadata(basePath); 147 148 // determine the available versions from the maven-metadata file 149 if (metadata != null) { 150 Element root = metadata.getDocumentElement(); 151 NodeList list = root.getElementsByTagName("versions"); 152 list = ((Element) list.item(0)).getElementsByTagName("version"); 153 Version[] available = new Version[list.getLength()]; 154 for (int i = 0; i < available.length; i++) { 155 available[i] = new Version(getText(list.item(i))); 156 } 157 // desc sort 158 Arrays.sort(available, new Comparator<Version>() { 159 public int compare(Version o1, Version o2) { 160 return o2.toString().compareTo(o1.toString()); 161 } 162 }); 163 164 for (Version version : available) { 165 URL location = getURL(new Artifact(artifact.getGroupId(), artifact.getArtifactId(), version, artifact.getType())); 166 OpenResult result = open(artifact, location); 167 if (result != null) { 168 return result; 169 } 170 } 171 } 172 } 173 return null; 174 } 175 176 private OpenResult open(Artifact artifact, URL location) throws IOException, FailedLoginException { 177 InputStream in = openStream(artifact, location); 178 return (in == null) ? null : new RemoteOpenResult(artifact, in); 179 } 180 181 private InputStream openStream(Artifact artifact, URL location) throws IOException, FailedLoginException { 182 int size = 0; 183 URLConnection con = location.openConnection(); 184 if (con instanceof HttpURLConnection) { 185 HttpURLConnection http = (HttpURLConnection) con; 186 http.connect(); 187 if (http.getResponseCode() == 401) { // need to authenticate 188 if (username == null || username.equals("")) { 189 throw new FailedLoginException("Server returned 401 " + http.getResponseMessage()); 190 } 191 //TODO is it necessary to keep getting new http's ? 192 http = (HttpURLConnection) location.openConnection(); 193 http.setRequestProperty("Authorization", 194 "Basic " + new String(Base64.encode((username + ":" + password).getBytes()))); 195 http.connect(); 196 if (http.getResponseCode() == 401) { 197 throw new FailedLoginException("Server returned 401 " + http.getResponseMessage()); 198 } else if (http.getResponseCode() == 404) { 199 return null; // Not found at this repository 200 } 201 if (http.getContentLength() > 0) { 202 size = http.getContentLength(); 203 } 204 } else if (http.getResponseCode() == 404) { 205 return null; // Not found at this repository 206 } else { 207 if (http.getContentLength() > 0) { 208 size = http.getContentLength(); 209 } 210 } 211 return http.getInputStream(); 212 } 213 return null; 214 } 215 216 217 private Document getMavenMetadata(URI base) throws IOException, FailedLoginException { 218 Document doc = null; 219 InputStream in = null; 220 221 try { 222 URL metaURL = base.resolve( "maven-metadata.xml").toURL(); 223 in = openStream(null, metaURL); 224 if (in == null) { // check for local maven metadata 225 metaURL = base.resolve("maven-metadata-local.xml").toURL(); 226 in = openStream(null, metaURL); 227 } 228 if (in != null) { 229 DocumentBuilder builder = XmlUtil.newDocumentBuilderFactory().newDocumentBuilder(); 230 doc = builder.parse(in); 231 } 232 } catch (ParserConfigurationException e) { 233 throw (IOException)new IOException().initCause(e); 234 } catch (SAXException e) { 235 throw (IOException)new IOException().initCause(e); 236 } finally { 237 if (in == null) { 238 // log.info("No maven metadata available at " + base); 239 } else { 240 in.close(); 241 } 242 } 243 return doc; 244 } 245 246 247 private URL getURL(Artifact configId) throws MalformedURLException { 248 String qualifiedVersion = configId.getVersion().toString(); 249 if (configId.getVersion() instanceof SnapshotVersion) { 250 SnapshotVersion ssVersion = (SnapshotVersion) configId.getVersion(); 251 String timestamp = ssVersion.getTimestamp(); 252 int buildNumber = ssVersion.getBuildNumber(); 253 if (timestamp != null && buildNumber != 0) { 254 qualifiedVersion = qualifiedVersion.replaceAll("SNAPSHOT", timestamp + "-" + buildNumber); 255 } 256 } 257 return base.resolve(configId.getGroupId().replace('.', '/') + "/" 258 + configId.getArtifactId() + "/" + configId.getVersion() 259 + "/" + configId.getArtifactId() + "-" 260 + qualifiedVersion + "." + configId.getType()).toURL(); 261 } 262 263 /** 264 * Gets all the text contents of the specified DOM node. 265 */ 266 private static String getText(Node target) { 267 NodeList nodes = target.getChildNodes(); 268 StringBuffer buf = null; 269 for (int j = 0; j < nodes.getLength(); j++) { 270 Node node = nodes.item(j); 271 if (node.getNodeType() == Node.TEXT_NODE) { 272 if (buf == null) { 273 buf = new StringBuffer(); 274 } 275 buf.append(node.getNodeValue()); 276 } 277 } 278 return buf == null ? null : buf.toString(); 279 } 280 281 /** 282 * Gets the text out of all the child nodes of a certain type. The result 283 * array has one element for each child of the specified DOM element that 284 * has the specified name. 285 * 286 * @param root The parent DOM element 287 * @param property The name of the child elements that hold the text 288 */ 289 private static List<String> getChildrenText(Element root, String property) { 290 NodeList children = root.getChildNodes(); 291 List<String> results = new ArrayList<String>(); 292 for (int i = 0; i < children.getLength(); i++) { 293 Node check = children.item(i); 294 if (check.getNodeType() == Node.ELEMENT_NODE && check.getNodeName().equals(property)) { 295 NodeList nodes = check.getChildNodes(); 296 StringBuffer buf = null; 297 for (int j = 0; j < nodes.getLength(); j++) { 298 Node node = nodes.item(j); 299 if (node.getNodeType() == Node.TEXT_NODE) { 300 if (buf == null) { 301 buf = new StringBuffer(); 302 } 303 buf.append(node.getNodeValue()); 304 } 305 } 306 results.add(buf == null ? null : buf.toString()); 307 } 308 } 309 return results; 310 } 311 312 private static class RemoteOpenResult implements OpenResult { 313 private final Artifact artifact; 314 private final InputStream in; 315 private File file; 316 317 private RemoteOpenResult(Artifact artifact, InputStream in) { 318 this.artifact = artifact; 319 this.in = in; 320 } 321 322 public Artifact getArtifact() { 323 return artifact; 324 } 325 326 public File getFile() throws IOException { 327 if (file == null) { 328 file = downloadFile(in); 329 } 330 return file; 331 } 332 333 public void install(WriteableRepository repo, FileWriteMonitor monitor) throws IOException { 334 File file = getFile(); 335 repo.copyToRepository(file, artifact, monitor); 336 if (!file.delete()) { 337 // log.warn("Unable to delete temporary download file " + tempFile.getAbsolutePath()); 338 file.deleteOnExit(); 339 } 340 } 341 342 public void close() { 343 if (in != null) { 344 try { 345 in.close(); 346 } catch (IOException e) { 347 //ignore 348 } 349 } 350 } 351 /** 352 * Downloads to a temporary file so we can validate the download before 353 * installing into the repository. 354 * 355 * @param in source of download 356 // * @param monitor monitor to report results of download 357 * @return downloaded file 358 * @throws IOException if input cannot be read or file cannot be written 359 */ 360 private File downloadFile(InputStream in/*, ResultsFileWriteMonitor monitor*/) throws IOException { 361 if (in == null) { 362 throw new IllegalStateException(); 363 } 364 FileOutputStream out = null; 365 byte[] buf; 366 try { 367 // monitor.writeStarted(result.getArtifact().toString(), result.getFileSize()); 368 File file = File.createTempFile("geronimo-plugin-download-", ".tmp"); 369 out = new FileOutputStream(file); 370 buf = new byte[65536]; 371 int count, total = 0; 372 while ((count = in.read(buf)) > -1) { 373 out.write(buf, 0, count); 374 // monitor.writeProgress(total += count); 375 } 376 // monitor.writeComplete(total); 377 in.close(); 378 in = null; 379 out.close(); 380 out = null; 381 return file; 382 } finally { 383 if (in != null) { 384 try { 385 in.close(); 386 } catch (IOException ignored) { 387 //ignore 388 } 389 } 390 if (out != null) { 391 try { 392 out.close(); 393 } catch (IOException ignored) { 394 //ignore 395 } 396 } 397 } 398 } 399 } 400 }