001 /* 002 * Copyright (c) 2007-2014 Concurrent, Inc. All Rights Reserved. 003 * 004 * Project and contact information: http://www.cascading.org/ 005 * 006 * This file is part of the Cascading project. 007 * 008 * Licensed under the Apache License, Version 2.0 (the "License"); 009 * you may not use this file except in compliance with the License. 010 * You may obtain a copy of the License at 011 * 012 * http://www.apache.org/licenses/LICENSE-2.0 013 * 014 * Unless required by applicable law or agreed to in writing, software 015 * distributed under the License is distributed on an "AS IS" BASIS, 016 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 017 * See the License for the specific language governing permissions and 018 * limitations under the License. 019 */ 020 021 package cascading.util; 022 023 import java.io.IOException; 024 import java.io.InputStream; 025 import java.io.UnsupportedEncodingException; 026 import java.net.InetAddress; 027 import java.net.MalformedURLException; 028 import java.net.URL; 029 import java.net.URLConnection; 030 import java.net.URLEncoder; 031 import java.util.Collections; 032 import java.util.Properties; 033 import java.util.Set; 034 import java.util.Timer; 035 import java.util.TimerTask; 036 import java.util.TreeSet; 037 038 import cascading.flow.planner.PlatformInfo; 039 import cascading.property.AppProps; 040 import org.slf4j.Logger; 041 import org.slf4j.LoggerFactory; 042 043 /** 044 * 045 */ 046 public class Update extends TimerTask 047 { 048 private static final Logger LOG = LoggerFactory.getLogger( Update.class ); 049 private static final String UPDATE_PROPERTIES = "latest.properties"; 050 051 public static final String UPDATE_CHECK_SKIP = "cascading.update.skip"; 052 public static final String UPDATE_URL = "cascading.update.url"; 053 054 private static final Set<PlatformInfo> platformInfoSet = Collections.synchronizedSet( new TreeSet<PlatformInfo>() ); 055 private static Timer timer; 056 057 public static synchronized void checkForUpdate( PlatformInfo platformInfo ) 058 { 059 if( Boolean.getBoolean( UPDATE_CHECK_SKIP ) ) 060 return; 061 062 if( platformInfo != null ) 063 platformInfoSet.add( platformInfo ); 064 065 if( timer != null ) 066 return; 067 068 timer = new Timer( "UpdateRequestTimer", true ); 069 timer.scheduleAtFixedRate( new Update(), 1000 * 30, 24 * 60 * 60 * 1000L ); 070 } 071 072 @Override 073 public void run() 074 { 075 checkForUpdate(); 076 } 077 078 public boolean checkForUpdate() 079 { 080 if( !Version.hasMajorMinorVersionInfo() ) 081 return true; 082 083 boolean isCurrentWip = Version.getReleaseFull() != null && Version.getReleaseFull().contains( "wip" ); 084 boolean isCurrentDev = Version.getReleaseFull() == null || Version.getReleaseFull().contains( "wip-dev" ); 085 086 URL updateCheckUrl = getUpdateCheckUrl(); 087 088 if( updateCheckUrl == null ) 089 return false; 090 091 // do this before fetching latest.properties 092 if( isCurrentDev ) 093 { 094 LOG.debug( "current release is dev build, update url: {}", updateCheckUrl.toString() ); 095 return true; 096 } 097 098 Properties latestProperties = getUpdateProperties( updateCheckUrl ); 099 100 if( latestProperties.isEmpty() ) 101 return false; 102 103 String latestMajor = latestProperties.getProperty( Version.CASCADING_RELEASE_MAJOR ); 104 String latestMinor = latestProperties.getProperty( Version.CASCADING_RELEASE_MINOR ); 105 106 boolean isSameMajorRelease = equals( Version.getReleaseMajor(), latestMajor ); 107 boolean isSameMinorRelease = equals( Version.getReleaseMinor(), latestMinor ); 108 109 if( isSameMajorRelease && isSameMinorRelease ) 110 { 111 LOG.debug( "no updates available" ); 112 return true; 113 } 114 115 String version = latestProperties.getProperty( "cascading.release.version" ); 116 117 if( version == null ) 118 LOG.debug( "release version info not found" ); 119 else 120 LOG.info( "newer Cascading release available: {}", version ); 121 122 return true; 123 } 124 125 private static Properties getUpdateProperties( URL updateUrl ) 126 { 127 try 128 { 129 URLConnection connection = updateUrl.openConnection(); 130 connection.setConnectTimeout( 3 * 1000 ); 131 132 InputStream in = connection.getInputStream(); 133 134 try 135 { 136 Properties props = new Properties(); 137 138 props.load( connection.getInputStream() ); 139 140 return props; 141 } 142 finally 143 { 144 if( in != null ) 145 close( in ); 146 } 147 } 148 catch( IOException exception ) 149 { 150 LOG.debug( "unable to fetch latest properties", exception ); 151 return new Properties(); 152 } 153 } 154 155 private static URL getUpdateCheckUrl() 156 { 157 String url = buildURL(); 158 159 String connector = url.indexOf( '?' ) > 0 ? "&" : "?"; 160 161 String spec = url + connector + buildParamsString(); 162 163 try 164 { 165 return new URL( spec ); 166 } 167 catch( MalformedURLException exception ) 168 { 169 LOG.debug( "malformed url: {}", spec, exception ); 170 return null; 171 } 172 } 173 174 private static String buildURL() 175 { 176 String baseURL = System.getProperty( UPDATE_URL, "" ); 177 178 if( baseURL.isEmpty() ) 179 { 180 String releaseBuild = Version.getReleaseBuild(); 181 182 // if wip, only test if a newer wip version is available 183 if( releaseBuild != null && releaseBuild.contains( "wip" ) ) 184 baseURL = "http://files.concurrentinc.com/cascading/"; 185 else 186 baseURL = "http://files.cascading.org/cascading/"; 187 } 188 189 if( !baseURL.endsWith( "/" ) ) 190 baseURL += "/"; 191 192 baseURL = String.format( "%s%s/%s", baseURL, Version.getReleaseMajor(), UPDATE_PROPERTIES ); 193 194 return baseURL; 195 } 196 197 private static String buildParamsString() 198 { 199 StringBuilder sb = new StringBuilder(); 200 201 sb.append( "id=" ); 202 sb.append( getClientId() ); 203 sb.append( "&instance=" ); 204 sb.append( urlEncode( AppProps.getApplicationID( null ) ) ); 205 sb.append( "&os-name=" ); 206 sb.append( urlEncode( getProperty( "os.name" ) ) ); 207 sb.append( "&jvm-name=" ); 208 sb.append( urlEncode( getProperty( "java.vm.name" ) ) ); 209 sb.append( "&jvm-version=" ); 210 sb.append( urlEncode( getProperty( "java.version" ) ) ); 211 sb.append( "&os-arch=" ); 212 sb.append( urlEncode( getProperty( "os.arch" ) ) ); 213 sb.append( "&product=" ); 214 sb.append( urlEncode( Version.CASCADING ) ); 215 sb.append( "&version=" ); 216 sb.append( urlEncode( Version.getReleaseFull() ) ); 217 sb.append( "&version-build=" ); 218 sb.append( urlEncode( Version.getReleaseBuild() ) ); 219 sb.append( "&frameworks=" ); 220 sb.append( urlEncode( getProperty( AppProps.APP_FRAMEWORKS ) ) ); 221 222 synchronized( platformInfoSet ) 223 { 224 for( PlatformInfo platformInfo : platformInfoSet ) 225 { 226 sb.append( "&platform-name=" ); 227 sb.append( urlEncode( platformInfo.name ) ); 228 sb.append( "&platform-version=" ); 229 sb.append( urlEncode( platformInfo.version ) ); 230 sb.append( "&platform-vendor=" ); 231 sb.append( urlEncode( platformInfo.vendor ) ); 232 } 233 234 platformInfoSet.clear(); 235 } 236 237 return sb.toString(); 238 } 239 240 private static boolean equals( String lhs, String rhs ) 241 { 242 return lhs != null && lhs.equals( rhs ); 243 } 244 245 private static int getClientId() 246 { 247 try 248 { 249 return Math.abs( InetAddress.getLocalHost().hashCode() ); 250 } 251 catch( Throwable t ) 252 { 253 return 0; 254 } 255 } 256 257 private static String urlEncode( String param ) 258 { 259 if( param == null ) 260 return ""; 261 262 try 263 { 264 return URLEncoder.encode( param, "UTF-8" ); 265 } 266 catch( UnsupportedEncodingException exception ) 267 { 268 LOG.debug( "unable to encode param: {}", param, exception ); 269 270 return null; 271 } 272 } 273 274 private static String getProperty( String prop ) 275 { 276 return System.getProperty( prop, "" ); 277 } 278 279 private static void close( InputStream in ) 280 { 281 try 282 { 283 in.close(); 284 } 285 catch( IOException exception ) 286 { 287 // do nothing 288 } 289 } 290 }