Mac: JSF: why doesn't JSF web applications for development phase always catch component component changes?

Mac OS X: Yosemite 10.10.5 NetBeans8.1beta or NetBeans8.1 Glassfish4.1 or Glassfish4.1.1 Mojarra 2.2.7 or 2.2.12 [2016-08-14 EDIT: or 2.2.8-17] [EDIT: Primefaces 5.3] 

I am an experienced NetBeans + JSF developer who says that I know how it works and usually works, but for some reason it does not work properly, on one (and only one as far as I can tell) MacBook Pro [EDIT: 2016-08-14, and also on MacMini with the same version of OS X].

Brief description of the problem: A few days ago, when I was happy to develop a large JSF / Primefaces web application, I found that after several reloads of complex JSF / Primefaces pages, I was working on it and stopped updating / reflecting the changes I made (and saved ) in the constituent components. However, I found that if I wait a few minutes, I can reboot again again, several times, reflecting the CC changes until it gets stuck again.

This happens, as far as I can tell, only on my main development machine which is the MacBook Pro 15 "(macbookpro11.3 Mid2014.).

[EDIT: 2016-08-14 Now it also plays on macmini4,1 Mid2010 with the same OS X version and the (slightly) adapted version * is copied * for all the same NetBeans / Settings GlassFish NB8.1Beta / GF4.1 and with JSF 2.2.8-17]

It does not seem to matter that:

  • I am using NetBeans-8.1beta / Glassfish4.1 or NetBeans8.1 / Glassfish4.1.1 [ASIDE: the reason why I mainly use NB8.1beta / GF4.1 and not NB8.1 / GF4.1.1 at: https is explained : //stackoverflow.com/questions/35681181/jsfobjectdb-why-might-deployment-of-a-large-web-app-to-glassfish-4-1-1-take-5]

  • I am using a completely new installation of NetBeans + Glassfish or an existing one.

  • I use JDK1.7 (jdk1.7.0_51.jdk) or JDK1.8 (jdk1.8.0_60.jdk) (including for NetBeans / Glassfish and / or for compiling and executing the source code).

  • I am using a project that includes Git (the problem first occurred in a large project, but since then I reproduced it in the simplest of projects without Git, that is, it has something only with the fact that facelets changes are detected in / build / web /).

  • Whether I use Primfaces or not (I can make it happen in a very simple JSF application).

  • I am using GET reload or reloading browser command.

But this does NOT happen, as far as I can tell, with an almost identical setup on the older MacMini (macmini4.1 Mid2010).

[EDIT: 2016-08-14 Yes, this also happens on this MacMini if ​​I reload JSF pages quite often in the full, large web application that I am developing, and not just in the mini-test application]

Some other things that I think of know about this:

  • This is when the "Deploy on save" function is disabled in all cases.

  • It does not seem to affect JSF templates and does not include it, it only seems to be overwhelmed by the component components.

  • This is not a problem with javax.faces.FACELETS_REFRESH_PERIOD (which by default for mojarra is 2). If I change it to 0, the problem will disappear (no caching), but load / reload times for large complex JSF pages become painful, in some cases minutes, not seconds.

  • Just moving from one JSF page to another does not help.

  • It doesn't matter which area of ​​JSF I use.

  • This happens when deploying the application on top of / build / web.

  • The timestamps of the modified XHTML files for composite components are definitely changing as I save them in NetBeans (they are correctly copied to / build / web / resources / ...).

  • I have not done any OS software updates or installed for many days.

I made screencasts (not available here) for the whole problem, as described below.

Experience with the original very large web application

When I first encountered the problem, it was in a very large web application. I noticed this with a tiny composite component that generates some text with a style (for the icon) that was used inside p: accordionPanel and p: tab. I found that after reloading the changes a couple of times this would stop catching the changes. Only by chance did I discover that if I wait for many minutes, sometimes up to 10 minutes, he will “catch” the change.

Then I returned to my instructions for several days, until the moment when I was clearly able to develop without problems, and the problem repeated again! I tested this many times, regardless of the problem, it is not in a .git commit (which includes / nbproject / private, but not all subfolders of / nbproject / private).

Experience with the small Primefaces web application

Then I tried it with a much smaller test web application with some Primefaces test pages. I was able to reproduce the problem if I reloaded the index.xhtml page several times, modifying the tiny single-implementation component used on the index.html page. Then I found that I needed to wait about 10 seconds or sometimes a whole minute, and then the change would catch again.

Experience with the tiny JSF web application

With one index.xhtml and one compound component with one word h: outputText, I could solve this problem if I saved CC and then quickly loaded index.xhtml. I’m not saying that it hasn’t changed (because one of them “beat” javax.faces.FACELETS_REFRESH_PERIOD) I am talking about it “locking up” so that after that it doesn’t catch the changes in CC, it doesn’t matter how often it takes to reload the page, until Ghost in the car decides to "unlock" itself.

Normally, I would really provide an example or “Steps to reproduce the problem,” but that doesn't make much sense; when I transfer the test project from one computer (my MacBook Pro) to another (MacMini works with the same OS version) the problem disappears. And I can make this happen (in my main development MacBook Pro machine) with the simplest NetBeans JSF web application with the .xhtml index, which includes one CC.

[EDIT: 2016-08-14 I can really play it on this MacMini running the same OS version, but I could only play it so far with the very large web application I am developing, which is not easy to provide for others for testing (and I would need, for example, to separate the dependency of the ObjectDB database and provide dummy data)]

I understand that one question is usually asked about Stackoverflow, but the answers to any of them that can help me move forward will be appreciated:

Q0: Has anyone experienced anything like this (on a Mac)?

Q1: What else can I try to diagnose? I have no ideas.

Q2: Does anyone know something specific for the MacBook Pro that can affect the polling / detection of changes in the assembly / website folders that could explain this?

Q3: Is there anything about how Facelets and / or Glassfish work with an application deployed on / build / web that can explain this?

+2
source share
1 answer

It seems I can not properly debug the entire stack trace via com.sun.faces.facelets.impl.DefaultFaceletFactory.createFacelet(URL) , the source code is not aligned with the compiled classes for jsf-impl-2.2.12-jbossorg-2.jar .

To shorten the long story, I rewrote the cache.

With this new cache, createFacelet(URL) now called one time for facelet per request, effectively reloading component changes to composite components.

This cache implementation is not fully tested and absolutely not ready for production, but this is the beginning.

However, it must be thread safe because the internal half-case has a request scope.

Please note that I used only the import API ( javax.faces.* ) And not com.sun.faces.* , So this should work with any version of Mojarra / MyFaces 2.2.x.

 public class DebugFaceletCacheFactory extends FaceletCacheFactory { protected final FaceletCacheFactory wrapped; public DebugFaceletCacheFactory(FaceletCacheFactory wrapped) { this.wrapped = wrapped; } @Override public FaceletCacheFactory getWrapped() { return wrapped; } @Override public FaceletCache<?> getFaceletCache() { return new DebugFaceletCache(); } public static class DebugFaceletCache extends FaceletCache<Facelet> { protected static final String MEMBER_CACHE_KEY = DebugFaceletCache.class.getName() + "#MEMBER_CACHE"; protected static final String METADATA_CACHE_KEY = DebugFaceletCache.class.getName() + "#METADATA_CACHE"; protected Map<URL, Facelet> getCache(String key) { Map<String, Object> requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap(); Map<URL, Facelet> cache = (Map<URL, Facelet>) requestMap.get(key); if(cache == null) { cache = new HashMap<>(); requestMap.put(key, cache); } return cache; } protected MemberFactory<Facelet> getFactory(String key) { if(MEMBER_CACHE_KEY.equals(key)) { return getMemberFactory(); } if(METADATA_CACHE_KEY.equals(key)) { return getMetadataMemberFactory(); } throw new IllegalArgumentException(); } protected Facelet getFacelet(String key, URL url) throws IOException { Map<URL, Facelet> cache = getCache(key); Facelet facelet = cache.get(url); if(facelet == null) { MemberFactory<Facelet> factory = getFactory(key); facelet = factory.newInstance(url); cache.put(url, facelet); } return facelet; } @Override public Facelet getFacelet(URL url) throws IOException { return getFacelet(MEMBER_CACHE_KEY, url); } @Override public boolean isFaceletCached(URL url) { return getCache(MEMBER_CACHE_KEY).containsKey(url); } @Override public Facelet getViewMetadataFacelet(URL url) throws IOException { return getFacelet(METADATA_CACHE_KEY, url); } @Override public boolean isViewMetadataFaceletCached(URL url) { return getCache(METADATA_CACHE_KEY).containsKey(url); } } } 

and it is activated through faces-config.xml :

 <?xml version="1.0" encoding="utf-8"?> <faces-config version="2.2" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-facesconfig_2_2.xsd"> ... <factory> <facelet-cache-factory>it.shape.core.jsf.factory.DebugFaceletCacheFactory</facelet-cache-factory> </factory> </faces-config> 

Happy combined coding;)


UPDATE

I found that JRebel was interfering with the eclipse debugger, so I disabled it and restarted.

And I found some new interesting things:

  • A cache implementation with JRebel enabled is read as com.sun.faces.facelets.impl.DefaultFaceletCache.NoCache , but instead it is com.sun.faces.util.ExpiringConcurrentCache . This is why I had scrambled lines of source code during debugging.
  • JSF (and especially Mojarra) needs deep refactoring, seriously: at least 5 different factories and 2 different caches involved in creating / caching masks and metadata, most of which do the simple job of delegating templates.
  • com.sun.faces.facelets.impl.DefaultFaceletCache._metadataFaceletCache and com.sun.faces.application.view.FaceletViewHandlingStrategy.metadataCache poorly paired : they contain the same data and they have synchronization-dependent unidirectional processing. Conceptually wrong and consumed memory.
  • The period for updating the background in the default mode is different from what I thought: it is 2000 instead of 0.

So another workaround is to install:

 <context-param> <param-name>javax.faces.FACELETS_REFRESH_PERIOD</param-name> <param-value>0</param-value> </context-param> 

in web.xml, but to be honest, it is much less efficient than my simple cache implementation, because it creates layouts and metadata twice per instance of a composite component ...

Finally, in this debugging session, I never came across a situation where the modified dummy is not updated, and even if the implementation is monstrously inefficient and schizophrenic, this version (2.2.12) seems to work.

In my case, I think this is a JRebel problem.

However, now I can finally start by rebooting JRebel and translating the masks.

If I delete a hidden case (for example, eclipse does not copy / update the checkboxes to the target folder and / or does not set the date of the last modified file when saving from the editor), I will update this answer.


PS
In some cases, they use abstract classes because interfaces are stateless and not suitable for all conceptual templates. Unambiguous inheritance is IMO's most serious Java issue. However, with Java 8, we have default / defender methods that help mitigate the problem. However, they cannot be called by JSF ExpressionLanguage 3.0 :(


Conclusion

Ok, I found a problem. This is not easy to explain and requires special (albeit general) conditions for reproduction.

Suppose you have:

  • FACELET_REFRESH_PERIOD = 2
  • component named x:myComp
  • page where x:myComp used 100 times

Now here's what happens under the hood.

  • The first time a x:myComp is encountered during page evaluation, a Record cache is created using _creation=System.currentTimeMillis()
  • during each other time x:myComp occurs during page evaluation, Record retrieved from the cache, and DefaultFaceletCache.Record.getNextRefreshTime() is called twice (in get() and containsKey() ) to check the expiration date.
  • components are evaluated 2 times
  • Assuming that a full page score is completed in less than 2 seconds, DefaultFaceletCache.Record.getNextRefreshTime() ((100 * 2) - 1) * 2 = 398 times was called at the end
  • when DefaultFaceletCache.Record.getNextRefreshTime() is called, it increments the local atom variable _nextRefreshTime by FACELET_REFRESH_PERIOD * 1000 = 2000
  • therefore, in the end, _nextRefreshTime = initial System.currentTimeMillis() + (398 * 2000 = 796 s)

, now this facelet will expire in 796 seconds from the moment of its creation. Each access to this page before the expiration date adds another 796 seconds!

the problem is that checking the cache is (2 ^ 2 times!) related to extending the life span.

See JAVASERVERFACES-4107 and JAVASERVERFACES-4176 (and now basically JAVASERVERFACES-4178 ) for more details.


While waiting for the problem to be resolved, I use my own cache code ( Java 8 is required ), maybe it is also useful for you to use / adapt (manually condensed in one large class, maybe there is some "copy'n'paste" error):

 /** * A factory for creating ShapeFaceletCache objects. * * @author Michele Mariotti */ public class ShapeFaceletCacheFactory extends FaceletCacheFactory { protected FaceletCacheFactory wrapped; public ShapeFaceletCacheFactory(FaceletCacheFactory wrapped) { this.wrapped = wrapped; } @Override public FaceletCacheFactory getWrapped() { return wrapped; } @Override public ShapeFaceletCache getFaceletCache() { String param = FacesContext.getCurrentInstance() .getExternalContext() .getInitParameter(ViewHandler.FACELETS_REFRESH_PERIOD_PARAM_NAME); long period = NumberUtils.toLong(param, 2) * 1000; if(period < 0) { return new UnlimitedFaceletCache(); } if(period == 0) { return new DevelopmentFaceletCache(); } return new ExpiringFaceletCache(period); } public static abstract class ShapeFaceletCache extends FaceletCache<Facelet> { protected static volatile ShapeFaceletCache INSTANCE; protected Map<URL, FaceletRecord> memberCache = new ConcurrentHashMap<>(); protected Map<URL, FaceletRecord> metadataCache = new ConcurrentHashMap<>(); protected ShapeFaceletCache() { INSTANCE = this; } public static ShapeFaceletCache getInstance() { return INSTANCE; } protected Facelet getFacelet(FaceletCacheKey key, URL url) { Map<URL, FaceletRecord> cache = getLocalCache(key); FaceletRecord record = cache.compute(url, (u, r) -> computeFaceletRecord(key, u, r)); Facelet facelet = record.getFacelet(); return facelet; } protected boolean isCached(FaceletCacheKey key, URL url) { Map<URL, FaceletRecord> cache = getLocalCache(key); FaceletRecord record = cache.computeIfPresent(url, (u, r) -> checkFaceletRecord(key, u, r)); return record != null; } protected FaceletRecord computeFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record) { if(record == null || checkFaceletRecord(key, url, record) == null) { return buildFaceletRecord(key, url); } return record; } protected FaceletRecord buildFaceletRecord(FaceletCacheKey key, URL url) { try { MemberFactory<Facelet> factory = getFactory(key); Facelet facelet = factory.newInstance(url); long lastModified = URLUtils.getLastModified(url); FaceletRecord record = new FaceletRecord(facelet, lastModified); return record; } catch(IOException e) { throw new FacesException(e.getMessage(), e); } } protected FaceletRecord checkFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record) { return record; } protected Map<URL, FaceletRecord> getLocalCache(FaceletCacheKey key) { if(key == FaceletCacheKey.MEMBER) { return memberCache; } if(key == FaceletCacheKey.METADATA) { return metadataCache; } throw new IllegalArgumentException(); } protected MemberFactory<Facelet> getFactory(FaceletCacheKey key) { if(key == FaceletCacheKey.MEMBER) { return getMemberFactory(); } if(key == FaceletCacheKey.METADATA) { return getMetadataMemberFactory(); } throw new IllegalArgumentException(); } @Override public Facelet getFacelet(URL url) throws IOException { return getFacelet(FaceletCacheKey.MEMBER, url); } @Override public Facelet getViewMetadataFacelet(URL url) throws IOException { return getFacelet(FaceletCacheKey.METADATA, url); } @Override public boolean isFaceletCached(URL url) { return isCached(FaceletCacheKey.MEMBER, url); } @Override public boolean isViewMetadataFaceletCached(URL url) { return isCached(FaceletCacheKey.METADATA, url); } public void clearFacelets() { getLocalCache(FaceletCacheKey.MEMBER).clear(); } public void clearViewMetadataFacelets() { getLocalCache(FaceletCacheKey.METADATA).clear(); } public void clearAll() { clearViewMetadataFacelets(); clearFacelets(); } } public static class UnlimitedFaceletCache extends ShapeFaceletCache { public UnlimitedFaceletCache() { super(); } } public static class DevelopmentFaceletCache extends ShapeFaceletCache { public DevelopmentFaceletCache() { super(); } @Override protected FaceletRecord checkFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record) { try { Set<URL> urls = (Set<URL>) FacesContext.getCurrentInstance() .getAttributes() .computeIfAbsent(key, x -> new HashSet<>()); if(urls.add(url)) { long lastModified = URLUtils.getLastModified(url); if(lastModified != record.getLastModified()) { return null; } } return record; } catch(IOException e) { throw new FacesException(e.getMessage(), e); } } } public static class ExpiringFaceletCache extends ShapeFaceletCache { protected final long period; public ExpiringFaceletCache(long period) { super(); this.period = period; } @Override protected FaceletRecord checkFaceletRecord(FaceletCacheKey key, URL url, FaceletRecord record) { try { long now = System.currentTimeMillis(); if(now > record.getLastChecked() + period) { long lastModified = URLUtils.getLastModified(url); if(lastModified != record.getLastModified()) { return null; } record.setLastChecked(now); } return record; } catch(IOException e) { throw new FacesException(e.getMessage(), e); } } } public static class FaceletRecord { protected final Facelet facelet; protected final long lastModified; protected long lastChecked; public FaceletRecord(Facelet facelet, long lastModified) { this.facelet = facelet; this.lastModified = lastModified; lastChecked = System.currentTimeMillis(); } public long getLastModified() { return lastModified; } public Facelet getFacelet() { return facelet; } public long getLastChecked() { return lastChecked; } public void setLastChecked(long lastChecked) { this.lastChecked = lastChecked; } } public static enum FaceletCacheKey { MEMBER, METADATA; @Override public String toString() { return getClass().getName() + "." + name(); } } public static class URLUtils { public static long getLastModified(URL url) throws IOException { URLConnection urlConnection = url.openConnection(); if(urlConnection instanceof JarURLConnection) { JarURLConnection jarUrlConnection = (JarURLConnection) urlConnection; URL jarFileUrl = jarUrlConnection.getJarFileURL(); return getLastModified(jarFileUrl); } try(InputStream input = urlConnection.getInputStream()) { return urlConnection.getLastModified(); } } } } 
+2
source

Source: https://habr.com/ru/post/1245406/


All Articles