|
|
|
Not sure what I was thinking...
To disable caching, in CPortletAdapter.java, line 638 (in isCacheValid()), make it return false; FALSE, not true... Updated affects-version per Dave's comment to reflect afffecting existing 2.x releases. Placed on 2.5.x roadmap.
This looks like it may be a duplicate of:
Description of
Keith described the problem well, I'm going to see if I can add a bit more detail to the problem and describe what would need to be done to fix it. The source of the problem is the portlet domain object architecture of uPortal 2. To start the JSR-168 portlet specification & Pluto, the reference JSR-168 container, define the following domain object levels for portlets PortletDeployment PortletDefinition PortletEntity PortletWindow From those descriptions we can base the problem description. The way preferences are advertised to work in uPortal 2 has three layers. The base layer is defined in the portlet.xml, the next layer is defined during publishing in ChannelManager, the final layer is defined by the portlet code while the user is interacting with it. This layering is supposed to allow increasing levels of customization for a portlet. Example: A weather portlet could have a default location ZIP code in the portlet.xml. The administrator then publishes the portlet multiple times specifying different ZIP codes for each published portlet. The end user can override the published ZIP code to get weather they are interested in. None of these saved ZIP codes should interfere. With the current uPortal 2 codebase there is only one PortletDefinition per PortletDeployment instead of one PortletDefinition per published instance of the PortletDeployment. This one PortletDefinition object is where the one definition level portlet preferences object is stored. At the beginning of rendering each portlet loads its definition and entity preferences from the backing store. If there are multiple portlets based on the same deployment on a single tab they are all using the same PortletDefinition object. Each rendering portlet would load the definition preferences associated with its channel ID into the one PortletDefinition object. Now there is one portlet preferences object which contains some non-deterministic layering of preferences from all of the portlets based on that portlet deployment on that tab. Now that the shared PortletDefinition has been identified as the problem a fix needs to be determined. The correct approach to resolving this issue would be to rename the uPortal 2 PortletDefinition object to PortletDeployment. The majority of the information it provides is actually from the portlet.xml. A new PortletDefinition should be created and keyed on the channel definition ID. This object would provide the preferences for the definition level. A quick work-around that does not involve modifying uPortal code is to create a portlet deployment entry for each portlet you would like to publish. Instead of publishing 'WeatherPortletApp.3DayForcastPortlet' multiple times the portlet XML would define multiple, uniquely named portlets '3DayForcastPortlet1', '3DayForcastPortlet2', ... and each would be published just once. Raising priority of this bug. This bug is very very annoying, and is blocking interesting uses of end-user-configurable portlets, such as RSS reader portlets, weather status portlets, dashboardy widgets, and the like.
i was trying to determine a temporary work-around to this issue a few weeks ago. i did not have much time to concentrate on it, but i determined the main problem to be the preservation of the sequence of calls in the CPortletAdapter rendering pipeline. Nameley: initPortletWindow() and getMarkup()
i thought this could be fixed by making renderCharacters a synchronized method; however, initPortletWindow() is also called from getRuntimeData(), and this seems to be the method that always makes the call. the temp fix i used, is to synchronize the renderCharacters method and force initPortletWindow to be called everytime from within renderCharacters. in other words, i added the "synchronized" keyword to the renderCharacters method and commented the "if" logic that surrounds the initPortletWindow call. can anyone comment on the overall impact of this as a temp solution? could this cause serious performance problems, or have any other adverse affects? note: there may be an additional need to force initPortletWindow to be called everytime there is a multi-published portlet on the user's layout? further testing should be done to determine this. Yes synchronizing that much will have serious performance issues, and possibly could cause deadlock in the render phase.
After further investigation, coding, and testing, I can confirm Eric's analysis. However, I believe that there is a slightly less intrusive fix that would serve as a viable solution for the up2 code set.
The current architecture and database structure can adequately support the separation of portlet definition, publication, and subscription level preferences; however, the current code does not adequately manage the loading of preferences to allow preservation of this separation during the users session. Today, preferences are loaded in the following three phases: The PortletDefinition object is keyed on portletDefinitionId, which is unique to a portlet deployment descriptor specified in the portlet.xml. However, the portlet publication is keyed on chan_id, which is unique to a portlet publication descriptor specified in the chanpub.xml. There is a one (portletDefinitionId) to many (chan_id) relationship between these two keys. Therefore, in phase two of the preferences load, the publication preferences for a given chan_id are overwriting any previous publication preferences sharing that specific portletDefinitionId. At render time, PortletEntity preferences override any preferences of the same name that were previously loaded from the PortletDefinition object. Because these are stored in two separate objects at runtime, no overwrites occur. This explains why user subscription preferences have never been an issue, while publication preferences have been a problem. To correct this, we can treat the publication preferences (stored in PORTLET_DEFINITION_PREFS) as default values in the PortletEntity preferences (stored in PORTLET_ENTITY_PREFS), rather than overrides to the values in PortletDefinition preferences. In the future, preferences would be loaded in the following three phases: At render time, PortletEntity preferences would still override any preferences of the same name that were previously loaded from the PortletDefinition preferences. However, now the publication preferences would be applied to the unique instance of the subscription rather than the shared instance of the definition. The only foreseeable drawback to this solution highlights the key difference between this solution and Eric's analysis: The publication preferences would be loaded for each subscription of the portlet prior to applying user subscription preferences. This would not cause performance degradation from today's baseline, because the same preference loads occur currently (the preferences are just being loaded into different objects than before, to allow more predictable results). However, Eric's recommended solution would create an object that allowed the true separation of publication from subscription, and this may provide a slight performance enhancement over either method described herein. However, the benefit is an easily implemented fix to the bug with no anticipated impact to function or performance. A coded solution is attached as ModifiedSource.zip Tim, this approach appears very promising to me. From a conceptual angle, treating the published parameters as defaults seems very appropriate, given the Portlet preferences model.
I'm at the JA-SIG conference right now, but will try to take a closer look at your patch when I get back. I'm trying to evaluate the attached code but there is no mention of what version of uPortal the fix should be applied to and much of the spacing and line endings in the files have changes from what is in CVS. If possible please create a unified diff from the base version of uPortal you are working with and attach the diff with a comment about what version of uPortal the diff is against.
One issue with this patch is what happens when a portlet programmatically changes the PortletPreferences. The publish time preference values that are loaded into the entity preferences object would be persisted in the entity preferences table. Once this happens changing any existing preferences in the channel manage would have no affect on the portlet.
The entity level preferences will need to be physically layered instead of just loading the publish preferences and then the entity preferences into the same object. The persistence layer may also need to be updated to deal with this layered preferences system. Also a quick guide for submitting patches is available: http://www.ja-sig.org/wiki/display/UPC/Contributing+Patches Would some kind of mask for READ ONLY preferences be the answer? Something that prohibited user values for preferences the user can't modify from being persisted?
My thought being – if a user CAN change a preference, once they've been to the preferences screen I don't want to change the values without them initiating some action, seems that it breaks user expectations. And most instances where an admin wants to update preferences are preferences that I don't want the user to change, or am just providing a suggested default in which case picking up updates is perhaps not so important. A READ-ONLY marker on a preference won't help at all. The preference will still be copied into the entity preferences table marked as read-only. Even if the logic was changed to not persisted read-only preferences as entity preferences this patch as is would break the current state of functionality any existing portlet that had definition preferences and entity preferences. That option could be discussed for a MINOR release but not a PATCHES release.
The real solution, which should be possible with some work, is to re-work the entity model to store the preferences in a layered manner in memory. The definition preferences would get loaded into the base layer of the entity and then entity preferences would get loaded into the layer on top of that. Changes to preferences made by the portlet would only affect the top layer of preferences which would be persisted on a .store() call as the entity preferences. attached is an updated set of fix files that accomodates the preference storage issues raised. the full code and diffs (from 2.5.1) are included.
this could use a second or third set of eyes and some additional testing. the last attachment is not a viable fix.
i have another recommendation in the works... i will post it for review after a little more testing... i have tested this fix, and i believe it to be a viable solution to this issue.
please test and provide feedback or recommended improvements. the previous comment refers to the files in the source-and-diffs-from-up251.zip
i can't quite get used to this bug tracking tool. i don't understand why a comment made when saving an attachment, shows no association to the attachment. I'm going to review the latest patch this week.
After just a code review the latest patch looks good. I'll be doing functional testing of the patch when I get back to Wisconsin next week.
This looks like a viable patch. I've done testing with both definition and entity preferences on the 2.5-patches branch. I'll be doing testing on the trunk tomorrow and hopefully get this in for 2.6
Resolved by a slightly modified version of Tim Carrol's latest patch.
2.5-patches change set: http://developer.ja-sig.org/source/changelog/jasigsvn/?cs=41891 trunk change set: http://developer.ja-sig.org/source/changelog/jasigsvn/?cs=41892 Sweet! Jason – Jason Shao | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Each Portlet instance will call CPortletAdapter.initPortletWindow(), which in turn will get the PortletDefinitionImpl for a given portletDefinitionId. Since the two portlets have the same portletDefinitionId (say "foo.Bar"), they get the same object returned and proceed to call the PortletDefinitionImpl.setChannelDefinition() method, associating the single Portlet object with the channel instance data.
Each time that a Portlet instance does this, it then calls PortletDefinitionImpl.loadPreferences(), which appends to or overwrites a previous portlet's definition preferences.
There are a number of additional problems with this, causing it to be difficult to fix. It appears each Portlet - not each Portlet instance - has its own CPortletAdapter instance, so when the portal calls CPortletAdapter.isCacheValid(), it is also shared among each Portlet. Also, the synchronization should have some work done.
The "fix" I tested disables caching, as it appears this would be a significant amount of work to fix. The workaround that I found is as follows:
in PortletDefinitionImpl.java, on line 215 (in loadPreferences()) add:
preferences = new PreferenceSetImpl();
in CPortletAdapter.java, line 532 (after try { in getMarkup()) add:
PortletWindowImpl wnd = (PortletWindowImpl)cd.getPortletWindow();
PortletEntityImpl ent = (PortletEntityImpl)wnd.getPortletEntity();
PortletDefinitionImpl def = (PortletDefinitionImpl)ent.getPortletDefinition();
ChannelDefinition channelDefinition = ChannelRegistryStoreFactory.getChannelRegistryStoreImpl().getChannelDefinition(Integer.parseInt(sd.getChannelPublishId()));
def.setChannelDefinition(channelDefinition);
def.loadPreferences();
That will load the preferences each time a channel is rendered.
Also, disable caching in CPortletAdapter.java, line 638 (in isCacheValid()), make it return true;
I also synchronized both methods getMarkup() and initPortletWindow() with an inner synchronized(this){} block instead of function-level synchronization... I had to remove some inner synchronized() blocks to avoid deadlock. I'm not sure how the function calls are ordered, but that could pose a problem. It seems to work fine for me, however.