    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      }