Skip to content

Fixes #1439 - Verify if we need to handle proxy settings in com.google.cloud.tools.eclipse.projectselector.GoogleApiFactory#1513

Merged
akerekes merged 13 commits intomasterfrom
i1439
Mar 7, 2017
Merged

Fixes #1439 - Verify if we need to handle proxy settings in com.google.cloud.tools.eclipse.projectselector.GoogleApiFactory#1513
akerekes merged 13 commits intomasterfrom
i1439

Conversation

@akerekes
Copy link
Copy Markdown
Contributor

@akerekes akerekes commented Mar 2, 2017

I've turned GoogleApiFactory into a service.
A proxy settings change listener is used to ensure we respect those changes.

A test for proxy setting changes is still pending, but the rest is ready for review.

Copy link
Copy Markdown
Contributor

@chanseokoh chanseokoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just some quick comments.

@Component
public class GoogleApiFactory implements IGoogleApiFactory {

private static final String GOOGLEAPIS_URL = "https://appengine.googleapis.com";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It doesn't feel right that this one contains appengine.

import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson.JacksonFactory;
import com.google.api.client.repackaged.com.google.common.base.Preconditions;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is from a wrong dependency.

Preconditions.checkNotNull(proxyService, "proxyService is null");
jsonFactory = new JacksonFactory();
buildTransport();
proxyService.addProxyChangeListener(new IProxyChangeListener() {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm concerned about thread-safety here. It's not a UI widget, so I think this can be executed in any thread.

Copy link
Copy Markdown
Contributor

@elharo elharo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good after Chanseok's comments are addressed

@codecov-io
Copy link
Copy Markdown

codecov-io commented Mar 2, 2017

Codecov Report

Merging #1513 into master will decrease coverage by -0.03%.
The diff coverage is 65.21%.

@@             Coverage Diff              @@
##             master    #1513      +/-   ##
============================================
- Coverage      70.3%   70.28%   -0.03%     
- Complexity     1299     1300       +1     
============================================
  Files           232      232              
  Lines          8935     8942       +7     
  Branches        762      761       -1     
============================================
+ Hits           6282     6285       +3     
- Misses         2335     2339       +4     
  Partials        318      318
Impacted Files Coverage Δ Complexity Δ
...gine/deploy/ui/StandardDeployPreferencesPanel.java 85.14% <ø> (ø) 22 <0> (ø)
...ploy/ui/standard/StandardDeployCommandHandler.java 1.38% <0%> (-0.04%) 1 <0> (ø)
...e/appengine/deploy/ui/DeployPreferencesDialog.java 0% <0%> (ø) 0 <0> (ø)
...d/tools/eclipse/test/util/http/TestHttpServer.java 91.83% <100%> (+0.34%) 12 <2> (+2)
...clipse/appengine/deploy/ui/DeployPropertyPage.java 52.74% <100%> (+1.06%) 13 <0> (ø)
...ols/eclipse/projectselector/ProjectRepository.java 94.59% <81.81%> (-0.15%) 14 <1> (-1)

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 39c0969...5ac0152. Read the comment docs.

buildCloudResourceManagerTransport();
} catch (URISyntaxException ex) {
logger.log(Level.SEVERE, "Could not create transport using the proxy settings", ex);
transports = null;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this will cause NPE in many places onward. Even if you initialize it with an empty map, it will cause trouble in places where you do transports.get(...). Probably it's better to leave transport as-is? At least it will keep previous, working transports.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Speaking of which, should we be prepared for when transports.get(...) returns null too?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

CloudResourceManager resourceManager =
new CloudResourceManager.Builder(transports.get(googleApiUrls.cloudResourceManagerUrl()),
jsonFactory, credential)
.setApplicationName(CloudToolsInfo.USER_AGENT).build();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: indent +4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Appengine appengine =
new Appengine.Builder(transports.get(googleApiUrls.appEngineAdminUrl()), jsonFactory,
credential)
.setApplicationName(CloudToolsInfo.USER_AGENT).build();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: indent +4

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

private static final Logger logger = Logger.getLogger(GoogleApiFactory.class.getName());

private JsonFactory jsonFactory;
private Map<String, HttpTransport> transports = new HashMap<>();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are these HttpTransports expensive? Do we really want them living forever? Would we be better off making this a Guava Cache instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason to keep them around is because of the Javadoc of NetHttpTransport:

Implementation is thread-safe. For maximum efficiency, applications should use a single globally-shared instance of the HTTP transport.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's your call, but I'd prefer to have them GC'd when they're unused. Guava Caches are thread-safe, so we can probably get rid of synchronization here, and the CacheLoader will simplify some of the logic here. And using weakValues() means the instances will stick around as long as they're being used.


IProxyData[] proxyData = proxyService.select(new URI(url));
for (final IProxyData iProxyData : proxyData) {
if (IProxyData.HTTPS_PROXY_TYPE.equals(iProxyData.getType())) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not supporting SOCKS proxies? Hmm, we should log something if the IProxyService returned some proxy data (i.e., we should use a proxy) and we're not supporting it — otherwise we silently fail.

private Proxy createProxy(String url) throws URISyntaxException {
Preconditions.checkNotNull(proxyService, "proxyService is null");
Preconditions.checkNotNull(googleApiUrls, "googleApiUrl is null");

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since none of the Google APIs support HTTP anymore (right?) then we could assert here that the URL does not start with 'http://', so we should never see a proxy-data of type HTTP.

Preconditions.checkNotNull(proxyService, "proxyService is null");
jsonFactory = new JacksonFactory();
buildTransports();
proxyService.addProxyChangeListener(new IProxyChangeListener() {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be better if we made this service optionally dependent on the IProxyService (i.e., ReferenceCardinality.OPTIONAL: 0..1). If there is no proxy service, we just treat everything like DIRECT. (I believe the IProxyService is actually registered programmatically in org.eclipse.core.net's activator and so if that hasn't been activated — which is unlikely, but possible — then our service won't be available.)

case IProxyData.SOCKS_PROXY_TYPE:
return new Proxy(Type.SOCKS, new InetSocketAddress(iProxyData.getHost(),
iProxyData.getPort()));
default:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code isn't handling a proxy directive, and we should at least log something.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, in the case of the unhandled, we should continue looping through any other proxyData items (e.g., IProxyService returns { HTTP_PROXY_TYPE, HTTPS_PROXY_TYPE } — we don't do the first, but we will do the second). The default case should happen outside of the for loop.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

}

IProxyData[] proxyData = proxyService.select(new URI(url));
for (final IProxyData iProxyData : proxyData) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

plural: proxyDatum? Can we come up with a better name than iProxyData?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

Copy link
Copy Markdown
Contributor

@chanseokoh chanseokoh Mar 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think @briandealwis was suggesting iProxyData --> proxyDatum. (Technically Datum is the singular form, but I guess nowadays people just use data for anything.)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was referring to iProxyData. And oops re: datum being singular!

plugin.properties,\
lib/
lib/,\
OSGI-INF/com.google.cloud.tools.eclipse.googleapis.internal.GoogleApiFactory.xml
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might just want to make this OSGI-INF/ rather than the specific file.

Preconditions.checkNotNull(googleApiUrls, "googleApiUrl is null");
Preconditions.checkArgument(!url.startsWith("http://"), "http is not supported schema");

if (proxyService == null) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sorry for suggesting you make this dynamic. You should operate on a copy of the proxyService reference as it may be unbound before the call to select() below.

@Reference(policy=ReferencePolicy.DYNAMIC, cardinality=ReferenceCardinality.OPTIONAL)
public void setProxyService(IProxyService proxyService) {
this.proxyService = proxyService;
this.proxyService.addProxyChangeListener(proxyChangeListener);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure OSGi ensures you won't be unbound while being bound, so this field access is safe.

@akerekes
Copy link
Copy Markdown
Contributor Author

akerekes commented Mar 3, 2017

@briandealwis @elharo @chanseokoh PTAL
FYI tests are coming, but functionality-wise it's ready for another round of review.

Copy link
Copy Markdown
Member

@briandealwis briandealwis left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good!


package com.google.cloud.tools.eclipse.googleapis;

public class GoogleApiException extends Exception {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc would be good.

this.proxyService = proxyService;
this.proxyService.addProxyChangeListener(proxyChangeListener);
buildTransports();
if (proxyFactory != null) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is proxyFactory ever null? Would it make sense to make it final and set in the constructor?

Copy link
Copy Markdown
Contributor

@chanseokoh chanseokoh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some quick readability suggestions.

}

private void connectReadAndDisconnect(HttpURLConnection connection) throws IOException {
connection.connect();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's concise and also better to just check

assertThat(connection.getResponseCode(), is(200));

and remove the try block altogether (while leaving the finally block.)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My intent was to check the response and fail if we accidentally connected to an existing site. I've changed the code to make this more explicit.

};

public GoogleApiFactory() {
proxyFactory = new ProxyFactory();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Personally, I prefer this(new ProxyFactory()). This way, it's easy to tell if there are difference between the two constructors, and if so, how they differ.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


class TransportCacheLoader extends CacheLoader<GoogleApiUrl, HttpTransport> {

private ProxyFactory proxyFactory;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could be final.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

// used to indicate a missing application
return AppEngine.withId(application.getId());
} catch (IOException ex) {
} catch (IOException | GoogleApiException ex) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we're at it, I think it's better to

} catch (GoogleJsonResponseException ex) {
  ...
} catch (IOException | GoogleApiException ex) {
  ...
}

and remove the if (ex instanceof GoogleJsonResponseException) {.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

APPENGINE_ADMIN_API("https://appengine.googleapis.com"),
CLOUDRESOURCE_MANAGER_API("https://cloudresourcemanager.googleapis.com");

private String url;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


public class GoogleApiFactoryTest {
/**
*
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be filled in

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done


@Activate
public void init() {
jsonFactory = new JacksonFactory();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason you're not initializing this and making it final? You set mocks in the tests, but don't seem to do anything with the mocks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done

*/
class TimeoutAwareConnectionFactory extends DefaultConnectionFactory {

private static final int DEFAULT_TIMEOUT_MS = 1000;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if this should be a tuneable parameter.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can change it when it becomes necessary.

@akerekes akerekes merged commit 78772ab into master Mar 7, 2017
@akerekes akerekes deleted the i1439 branch March 7, 2017 18:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants