Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Flow initial pwa #4334

Merged
merged 50 commits into from Jul 11, 2018
Merged

Flow initial pwa #4334

merged 50 commits into from Jul 11, 2018

Conversation

mikotin
Copy link
Contributor

@mikotin mikotin commented Jun 27, 2018

Initial version of PWA.

Sets up simple PWA stack (manifest, service worker, icons, offline handling) for flow application. Configurable via PWA -annotation.

@@ -1,4 +1,4 @@
com.vaadin.flow.server.startup.ServletVerifier
com.vaadin.flow.server.startup.RouteRegistryInitializer
com.vaadin.flow.server.startup.ErrorNavigationTargetInitializer
com.vaadin.flow.server.startup.AnnotationValidator
com.vaadin.flow.server.startup.AnnotationValidator
Copy link
Contributor

Choose a reason for hiding this comment

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

Revert file. Now the only change is that we don't have a new line in the file.

* @param height height of icon
* @return
*/
public Icon size(int width, int height) {
Copy link
Contributor

Choose a reason for hiding this comment

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

setSize instead of size

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aye, there are chaining setters used in Icon with just property name for each did on purpose. Using setFoo with chaining seems not so good. I'll change those of course if needed. For instance to withFoo if it would be better? Or remove chaining totally, if it doesn't suite.

return element;
}

private Icon attr(String key, String value) {
Copy link
Contributor

Choose a reason for hiding this comment

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

For readability of code. addAttribute

*
* @return a String as [size]x[size]
*/
public String sizes() {
Copy link
Contributor

Choose a reason for hiding this comment

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

getSize

* @param href href
* @return self
*/
public Icon href(String href) {
Copy link
Contributor

Choose a reason for hiding this comment

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

setHref

* @return
*/
public List<String> getOfflineResources() {
return offlineResources.stream().collect(Collectors.toList());
Copy link
Contributor

Choose a reason for hiding this comment

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

Collections.unmodifiableList(offlineResources)

@@ -292,7 +295,10 @@ public void init() throws ServiceException {
handlers.add(new UidlRequestHandler());
handlers.add(new UnsupportedBrowserHandler());
handlers.add(new StreamRequestHandler());

PWARegistry pwaRegistry = getPwaRegistry();
Copy link
Contributor

Choose a reason for hiding this comment

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

The default implementation in VaadinServletService may return null.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Null checking added

this.pwaRegistry = PWARegistry
.initRegistry(getServlet().getServletContext());
} catch (IOException e) {
return null;
Copy link
Contributor

Choose a reason for hiding this comment

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

Instead of returning null throw a new NamedException extends RuntimeException with information on what failed and why as now things will fail with a NPE at some point.

requestHandlerMap = new HashMap<>();
this.pwaRegistry = pwaRegistry;

if (this.pwaRegistry.getPwaConfiguration().isEnabled()) {
Copy link
Contributor

Choose a reason for hiding this comment

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

if and contents could be in a init() with a direct return if not enabled for clearer reading.

@@ -1018,18 +1018,20 @@ public void theme_contents_are_appended_to_head()

Copy link
Contributor

Choose a reason for hiding this comment

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

Test changes do not add test for pwa.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, tests are missing. Need to add those.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

tests added

Added missing periods
Use defined constant
Fixed typo (extra ") from meta tag value
Changed to use unmodifiableList
Copy link
Contributor

@caalador caalador left a comment

Choose a reason for hiding this comment

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

On the fluent API I guess it's fine, but at least the getters should be in the getXYZ format.

*
* Only 1 annotation for application is supported.
*
* Annotation must be placed to master layout.
Copy link
Contributor

Choose a reason for hiding this comment

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

There's another problem also. Now if you for some reason (Forgetting a PWA when copying or something) have multiple PWA annotations then one of them will be selected and you can't really know which one, might even be a different one each time if you are unlucky.

@caalador caalador dismissed their stale review June 29, 2018 11:28

Going on vacation. Open for picks.

* Defines the developers’ preferred display mode for the website.
*
* Possible values:
* fullscreen, standalone, minimal-ui, browser
Copy link
Contributor

Choose a reason for hiding this comment

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

Have you considered making this an enum?

*
* @return Is PWA injecting enabled
*/
boolean enabled() default true;
Copy link
Contributor

Choose a reason for hiding this comment

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

Is there a use case when someone adds the @PWA annotation but with enabled=false?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No there's not. That was leftover from old code. Removed.

Copy link
Contributor

Choose a reason for hiding this comment

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

I forgot to publish my review in time. Sorry, new UI of GitHub review.

private long fileHash = 0;
private String baseName = "icons/icon.png";
private Domain domain = Domain.HEADER;
private boolean hrefOverride = false;
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need hrefOverride? It's not set anywhere.

tag is also used in one place only.

Copy link
Contributor

Choose a reason for hiding this comment

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

Also basename and domain initial values are not used ever: those are always overwritten in constructor.
Looks suspicious.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Leftovers from old code, removed

* @param outputStream output stream
* @throws IOException possible exception in stream writing
*/
public synchronized void write(OutputStream outputStream)
Copy link
Contributor

Choose a reason for hiding this comment

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

Any reason to make this synchronized?

* Exception thrown when {@link PWARegistry} initialization fails.
*
*/
public class PWAInitializationException extends RuntimeException {
Copy link
Contributor

Choose a reason for hiding this comment

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

I see it's used in one place only and it's our code.
Does it make any sense to have this exception? Can we just throw UncheckedIOException there instead?

this.offlinePath = checkPath(pwa.offlinePath());
this.display = pwa.display();
this.startUrl = getStartUrl(servletContext);
this.serviceWorkerPath = "sw.js";
Copy link
Contributor

Choose a reason for hiding this comment

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

It's the same in both cases, is it ok?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Leftover from when it was supposed to be configured, but it's a bit dangerous to let it be configured, so it was removed. I'll change it away from different blocks, for that's fixed value.

if (pwa != null) {
this.appName = pwa.name();
this.shortName = pwa.shortName().substring(0,
Math.min(pwa.shortName().length(), 12));
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we warn the user that he did not manage to fit into 12 symbols?

*
* @return Path to logo with prefix, so request matches
*/
public String relLogoPath() {
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't really understand why do we need two similar methods.
Can we leave only one and inline this instead, since it's used in a single place only?

Copy link
Contributor

Choose a reason for hiding this comment

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

Same about other rel prefixed methods here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

rel -prefix was added there to move logic of getting name in object, in stead of adding that logic (very simple, though) to place where needed. I liked the idea of leaving logic to the object. Sure it can be removed, if it doesn't feel right.

}

// Google Workbox import
stringBuilder.append("importScripts('https://storage.googleapis.com/"
Copy link
Contributor

Choose a reason for hiding this comment

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

Does this mean we depend on some external CDN resource now?
Is it safe? Afaik we did not do such thing before.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Workbox files (needed ones) are now served internally via flow

@@ -701,6 +702,42 @@ private static void setupMetaAndTitle(Element head,
});
}

private static void setupPWA(Element head) {
PWARegistry registry = VaadinService.getCurrent().getPwaRegistry();
Copy link
Contributor

Choose a reason for hiding this comment

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

VaadinService.getCurrent()
Please don't do this.
Artur made an effort to avoid getCurrent() methods call whenever it's possible.

Please pass BootstrapContext context as a method parameter and use it to get VaadinSession and a service from it. There are examples in the methods of this class.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done by Mikko.

*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface PWA {
Copy link
Contributor

Choose a reason for hiding this comment

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

There are a lot of methods (attributes) in this annotation.
So in the worst case the usage of this annotation will look like
@PWA(offlinePath="abc", manifestPath="foo", logoPath="bar", name="sdfsdf", shortName="cccc",...........................)

Looks inconvenient to me.

I'm not sure exactly how it may be improved.
But may be use PWA as an enabler and delegate all the properties to the class like it's done for @Theme annotation?

I would change in this case PWA to EnablePWA and add class or interface which contains all the methods from here (let's say its name is PWAConfig).
Then EnablePWA would be:

public @interface EnablePWA {
          Class<? extends PWAConfig> value();
}

Then there will be an additional responsibility to instantiate the provided class

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Aye, there are quite a lot of those properties. But I like the developer experience of having all that in that annotation: that way adding values and/or checking default values is supported directly with IDE.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok

private Map<String, String> attributes = new HashMap<>();
private String tag = "link";

protected PWAIcon(int width, int height, String baseName) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think SQ requires Javadocs for every protected method.

Copy link
Contributor

Choose a reason for hiding this comment

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

By the way why there is no any public CTOR?
Is it intended to be used internally only (then may be package local?).

Copy link
Contributor

Choose a reason for hiding this comment

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

All of them are used internally only, have changed to package local.
Just to note, there were arguments about using package local in our code that ended up with using protected instead. But I like package local more.

Copy link
Contributor

Choose a reason for hiding this comment

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

OK

* @param outputStream output stream
* @throws IOException possible exception in stream writing
*/
public synchronized void write(OutputStream outputStream)
Copy link
Contributor

Choose a reason for hiding this comment

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

Any specific reason why this method is synchronized ?
I don't see any reason from the code.
The instance of PWAIcon is not intended to be shared between threads (otherwise it requires special design for that which it doesn't have at the moment).

This is the only synchronized method and synchronization is done against the PWAIcon instance.
If external client of this class decides to use it in concurrent code then it's the client responsibility to make synchronization. And this synchronization will be done with totally different monitor (object to lock via synchronization) than PWAIcon instance.
So please remove it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No reason whatsovere. Was left over from some initial steps of creating PWA. Good spot. Removed.

private final boolean enabled;
private List<String> offlineResources;

protected PwaConfiguration(PWA pwa, ServletContext servletContext) {
Copy link
Contributor

Choose a reason for hiding this comment

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

So here you copy values from annotation to the PwaConfiguration instance class.
So I would say this class should be used directly as I suggested in PWA.
It also will allow to avoid big if-else since yo may provide default method implementations.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

True, initial version of this used that, but @interface is somewhat weak on what it can do (clearing up data and have logic and such). But yes, everything from PwaConfiguration could be moved directly to PwaRegistry and use just PWA internally. Configuration was done with to get data of PWA annotation nicely in an object. I liked the separation, but sure, it's not needed. But I still prefer having it separately instead of pouring everything to PwaRegirsty

Copy link
Contributor

Choose a reason for hiding this comment

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

No, I meant the other way around......
You are using PWA annotation and it has a huge number of methods. Instead you may require only value which will be PWAConfig subclass and this subclass should be implemented by the developer and set as a value.

Copy link
Contributor

Choose a reason for hiding this comment

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

Not sure what to do here. I prefer an annotation with parametetrs approach over the class one and it seems like you've also agreed with it a few notes up.

Copy link
Contributor

Choose a reason for hiding this comment

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

I just explained the original idea here to avoid misunderstanding.

I think that a lot of parameters in the annotation makes it inconvenient to use but that's an opinion which needs a DX.
Do not block this.

(session, request, response) -> {
response.setContentType("text/html");
response.getWriter().write(pwaRegistry.getOfflineHtml());
response.getWriter().close();
Copy link
Contributor

Choose a reason for hiding this comment

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

Same here (actually I think there are places above where close is also not inside finally) and about lambda.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done by Mikko.

}
OutputStream out = response.getOutputStream();
icon.write(out);
out.close();
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm not sure does icon.write declares any throws but event if it doesn't there is always a chance to get RuntimeException .
All streams should be closed anyways. So close should always be in finally block.
And may be extract the lambda as a method reference because it's hard to read lambda inside for.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done by Mikko.

response.setContentType("application/json");
PrintWriter writer = response.getWriter();
writer.write(this.pwaRegistry.getManifestJson());
writer.close();
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

Copy link
Contributor

Choose a reason for hiding this comment

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

Done by Mikko.

response.setContentType("application/javascript");
PrintWriter writer = response.getWriter();
writer.write(this.pwaRegistry.getServiceWorkerJs());
writer.close();
Copy link
Contributor

Choose a reason for hiding this comment

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

and here

Copy link
Contributor

Choose a reason for hiding this comment

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

Done by Mikko.


protected void setPwaClass(Class<?> pwaClass) {
if (pwaClass != null && pwaClass.isAnnotationPresent(PWA.class)) {
this.pwaConfigurationClass = pwaClass;
Copy link
Contributor

Choose a reason for hiding this comment

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

That needs to be atomic since the method is called from the other thread than getter.
Use AtomicReference for the pwaConfigurationClass.

Copy link
Contributor

Choose a reason for hiding this comment

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

Done by Mikko.

@SomeoneToIgnore
Copy link
Contributor

@denis-anisimov I've looked through your last comments and fixed everything that was missing here.

Copy link
Contributor

@denis-anisimov denis-anisimov left a comment

Choose a reason for hiding this comment

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

There are a couple of places where StringBuilder is used but with string concatenation.
Everything else is fine.

String workBoxAbsolutePath = servletContext.getContextPath() + "/"
+ WORKBOX_FOLDER;
// Google Workbox import
stringBuilder.append("importScripts('" + workBoxAbsolutePath
Copy link
Contributor

Choose a reason for hiding this comment

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

string concatenation within string builder append method looks weird.

Copy link
Contributor

Choose a reason for hiding this comment

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

Replaced with append calls.

@vaadin-bot
Copy link
Collaborator

SonarQube analysis reported 7 issues

Note: The following issues were found on lines that were not modified in the pull request. Because these issues can't be reported as line comments, they are summarized here:

  1. CRITICAL VaadinService.java#L2048: First sentence of Javadoc is incomplete (period is missing) or not present. rule
  2. CRITICAL VaadinService.java#L2082: First sentence of Javadoc is incomplete (period is missing) or not present. rule
  3. CRITICAL VaadinService.java#L2094: First sentence of Javadoc is incomplete (period is missing) or not present. rule
  4. CRITICAL VaadinService.java#L2106: First sentence of Javadoc is incomplete (period is missing) or not present. rule
  5. MAJOR RouteRegistry.java#L577: A "NullPointerException" could be thrown; "navigationTarget" is nullable here. rule
  6. MINOR VaadinService.java#L521: This call to "add()" may be a performance hot spot if the collection is large. rule
  7. MINOR VaadinService.java#L522: This call to "remove()" may be a performance hot spot if the collection is large. rule

@vaadin vaadin deleted a comment from denis-anisimov Jul 11, 2018
@vaadin vaadin deleted a comment from SomeoneToIgnore Jul 11, 2018
@SomeoneToIgnore SomeoneToIgnore merged commit 022dd44 into master Jul 11, 2018
@SomeoneToIgnore SomeoneToIgnore deleted the flow-initial-pwa branch July 11, 2018 08:30
@gilberto-torrezan gilberto-torrezan added this to the 1.1.0.alpha1 milestone Jul 18, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants