<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <title>Add orderable column to users (or: how customization degrades)</title>
  <link rel="self" href="https://liferay.dev/c/message_boards/find_thread?p_l_id=119785294&amp;threadId=111456194" />
  <subtitle>Add orderable column to users (or: how customization degrades)</subtitle>
  <id>https://liferay.dev/c/message_boards/find_thread?p_l_id=119785294&amp;threadId=111456194</id>
  <updated>2026-04-04T22:32:43Z</updated>
  <dc:date>2026-04-04T22:32:43Z</dc:date>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111862720" />
    <author>
      <name>Andrew Jardine</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111862720</id>
    <updated>2018-12-21T14:53:10Z</updated>
    <published>2018-12-21T14:53:10Z</published>
    <summary type="html">&lt;p&gt;I have really enjoyed reading this thread and I think I can
  definitely, as a third party, relate to both sides of this argument. I
  think as developers of the extensions, sometimes we forget that
  Liferay is in fact a product company and a big part of what you buy is
  just that, what you buy. As David has said, in the past it has been so
  open that some of the &amp;quot;closed&amp;quot; aspects of 7.x can sometimes
  be annoying -- no argument there. Like you I have hit a wall a few
  times and found myself doing what I needed to do to meet the
  requirements. So Tobias, I definitely see and agree, in part, with
  some of what you are saying.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;There is one part though that I think was unwarranted though.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;
  &lt;strong&gt;&amp;quot;I really hope that all my complains are not in vain and
    someone from the lead developers at Liferay is listening as well. I
    often have to create comparisons of implementation costs for
    projects and from my point of view the momentum is shifting towards
    the competitors of Liferay 7.1 now.&amp;quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;... As Milen mentioned, complaining in the forums is not the way to
  get your voice heard. This is meant for community based support and
  engagement. With that said, I do applaud the detail that you put into
  the post as it provides amazing detail for anyone else who needs to do
  this. Being an open source product though, and given that you have a
  cleaner solution, I would think a PR with the changes and a
  issues.liferay.com that references both this thread and the PR is a
  better way to ensure the complaints are not made in vain. I would
  imagine that the development team at Liferay is under plenty of
  pressure from all sides and I doubt they spend much time checking
  these threads.&lt;/p&gt;
&lt;p&gt;Now, while I am a fan of the content, there is something you said
  that I take exception with. The comment about Liferay 7.1 and the
  other competitors in the market place.The insunuation you are making
  is that -&lt;/p&gt;
&lt;p style="margin-left: 40.0px;"&gt; &lt;/p&gt;
&lt;p style="margin-left: 40.0px;"&gt;1. The quality issue you found is
  systemic in the product -- you have not surveyed 3.2 million lines of
  code to validate that you have not just found a one off&lt;/p&gt;
&lt;p style="margin-left: 40.0px;"&gt; &lt;/p&gt;
&lt;p style="margin-left: 40.0px;"&gt;2. Your code quality issue is trivial at
  best. It's annoying to see so much code but it's not as if 1000
  methods are being called in place of one -- yes it should be cleaned
  up, but really, it's a low priority. I'd rather see some of the new
  roadmap features over dedupe activities -- and I bet most paying
  customers would agree&lt;/p&gt;
&lt;p style="margin-left: 40.0px;"&gt; &lt;/p&gt;
&lt;p style="margin-left: 40.0px;"&gt;3.You are insinuating that competitors
  in the market place don't have similar issues with their code, or
  other areas where the frustration you experienced with Liferay would
  surface. I have worked with the competitors ... Adobe, Oracle, IBM,
  Vignette (back in the day), beleive me, they SUCK in comparison. My
  biggest frustration with all of them has always been how you &amp;quot;get
  what you get&amp;quot; and extending the product is an unsupported
  nightmare. &amp;quot;&lt;/p&gt;
&lt;p style="margin-left: 80.0px;"&gt;
  &lt;em&gt;Yeah, you can customize login. You just have to write your
    own&amp;quot; &lt;/em&gt;&lt;/p&gt;
&lt;p style="margin-left: 40.0px;"&gt;-- as opposed to overriding one class in
  the whole pipeline. You want to rant about duplicating code? that is
  the worst -- if you can even get your hands on it since the license
  agreement forbids you to unpack jars and look at it.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;You are of course entitled to your opinion but the problem I have
  with such a bold statement is that while you may be familiar with the
  product, others that come along here may not be. They might read your
  statement and take it to heart -- falsely assuming that Liferay is not
  a great product and can't be a great solution for them.&lt;/p&gt;
&lt;p&gt;Like you, I am a seasoned technologist and having been a consultant
  for all but 1 year of my career I have worked with dozens upon dozens
  of products. ALL of them have their points of weakness, and ALL of
  them have points of strength. I have done many many projects with
  customers where when I started there was down right hatred for the
  product (Liferay). Quite quickly though, there was the realization
  that the issue was in the implementation, not the product. Almost all
  of those same clients, by the time the engagement was done, were
  transformed into advocates for Liferay.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;If you, as you say, are responsible for evaluating competing products
  for your clients, I should hope that your criteria uses something far
  more relevant than something that 90% of clients will never see. There
  is always room for improvement -- both in Liferay, and our own code. &lt;/p&gt;</summary>
    <dc:creator>Andrew Jardine</dc:creator>
    <dc:date>2018-12-21T14:53:10Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111858570" />
    <author>
      <name>David H Nebinger</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111858570</id>
    <updated>2018-12-21T01:37:21Z</updated>
    <published>2018-12-21T01:37:21Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;p&gt;Someone who maybe knows the pros and cons of lambdas (@David:
        sorry I couldn't resist).&lt;/p&gt;
      &lt;p&gt;[snip]&lt;/p&gt;
      &lt;p&gt;p.s. @David If you still like to hear my thoughts about your
        arguments - send me a PM / mail and I will be happy to answer them.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;LOL.&lt;/p&gt;
&lt;p&gt;I know the pros and cons...  Nostalgia just gets me me sometimes for
  those good old anonymous inner classes...&lt;/p&gt;</summary>
    <dc:creator>David H Nebinger</dc:creator>
    <dc:date>2018-12-21T01:37:21Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111857874" />
    <author>
      <name>Tobias Liefke</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111857874</id>
    <updated>2018-12-20T22:54:12Z</updated>
    <published>2018-12-20T22:54:12Z</published>
    <summary type="html">&lt;p&gt;Thank you all for your affirmative and encouraging words. I'm a
  little overwhelmed now.&lt;/p&gt;
&lt;p&gt;And I really will try to give further input, but I have to admit that
  I'm not prepared to do so. And looking at the upcoming holidays it
  will be hard to me too look into all these further readings.&lt;/p&gt;
&lt;p&gt;But I'd like to repeat my points:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;If it is easy for you to add a new functionality, it is easy for
    me and others as well (&amp;quot;&lt;a
      href="https://en.wikipedia.org/wiki/Eating_your_own_dog_food"&gt;Eat
      your own dog food&lt;/a&gt;&amp;quot;).&lt;/li&gt;
  &lt;li&gt;Try to keep an eye on code quality. I mean, I would look to have
    at least one person in each development team who knows that Tiobe is
    not an asteroid. Someone who knows what premature optimization means
    and why one should try to avoid it. Someone who maybe knows the pros
    and cons of lambdas (@David: sorry I couldn't resist).&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;As much as I would like to step in here, I'm not the one that can do
  that, as I can't split myself into that many pieces. Besides that
  Liferay is not my only field of interest and my voluntary time is
  already occupied by other projects (even though I try to help Olaf
  dealing with all the stackoverflow questions from time to time).&lt;/p&gt;
&lt;p&gt;And I'd like to add another thought: I wouldn't think too much about
  all possible usecases, as I wouldn't even catch half of the use cases
  that exist out there. What I mean: Don't think too much about possible
  extension points. As long as you have a clean architecture (the OSGI
  decision was a big step in that direction and Lexicon 2.0 seems to me
  like one as well) and as long as you have an eye on clean code, there
  won't be many complaints about extensions. After all it is still Open
  Source - if I can't build a feature with the existing extension
  points, I still can create a patch and add a pull request. As I
  already said, the reason for my post was not the lack of extension
  points, but the lack of code quality.&lt;/p&gt;
&lt;p&gt;Finally I would like to add that I really would have liked to go to
  the Devcon - but the schedule was (as usually) tight at the end of the
  year. But after these encouraging words towards the upcoming version I
  will start a new try next year.&lt;/p&gt;
&lt;p&gt;p.s. @David If you still like to hear my thoughts about your
  arguments - send me a PM / mail and I will be happy to answer them.&lt;/p&gt;</summary>
    <dc:creator>Tobias Liefke</dc:creator>
    <dc:date>2018-12-20T22:54:12Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111856889" />
    <author>
      <name>Chema Balsas</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111856889</id>
    <updated>2018-12-20T19:11:49Z</updated>
    <published>2018-12-20T19:11:49Z</published>
    <summary type="html">&lt;p&gt;Hey Tobias,&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Following up Brian's comment, you can
  track https://issues.liferay.com/browse/LPS-83832 where we've already
  started the work to bring for example the new table components into DXP.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;As UI goes, we've started migrating our component strategy away from
  jsps and towards a data-oriented model, where templates don't have
  logic and all they do is render some data. That makes it easier to
  extend, since the only thing you need to change is the underlying data
  being passed down to the component. Unfortunately, the data creation
  moment might not be itself exposed if the developers didn't account
  for it, which then locks down the extension options.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;What we want to attempt, after what it's already proven to work in
  Commerce, is to provide an extensibility-first approach, so internal
  Liferay developers won't unintentionally shut extension points down.
  One option is that every major component (tag) can be fetched an osgi
  filter, or component id and that we adopt that by default. With that,
  external developers should be able to register OSGi components with
  higher ranking to extend the data that gets generated and/or passed
  down to the components.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;What do you think? Do you think this might address some of the
  existing problems you're experiencing?&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;This being said, in your posts, you clearly hint that this is more
  than a mere UI problem. I think we've had similar extension problems
  recently (ie: externally adding a new supported language) that cut
  across many different domains (database, models, localization, apps,
  themes...) which need a more holistic approach. How do you think such
  extension needs could be addressed to the developer's
  satisfaction?&lt;br /&gt;  &lt;/p&gt;
&lt;p&gt;Thanks for bearing with us. As Brian says, we'll fix this!&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;PS: Special kudos to Marco Leo for coming up and pursuing some of the
  ideas to improve our take on UI extensibility.&lt;/p&gt;</summary>
    <dc:creator>Chema Balsas</dc:creator>
    <dc:date>2018-12-20T19:11:49Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111846206" />
    <author>
      <name>Brian Chan</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111846206</id>
    <updated>2018-12-20T04:38:21Z</updated>
    <published>2018-12-20T04:38:21Z</published>
    <summary type="html">&lt;p&gt;Tobias,&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;You make very valid points. We're going to fix the 1.) code
  duplication and 2.) make it easier to customize columns like you could
  back in 6.2.x. There's no reason things simple should be complicated.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;The Commerce team already has a workaround they've been using for
  7.1.x. See this &lt;a href="https://github.com/liferay/com-liferay-commerce/pull/324/commits/56f7935122bc86301f0cfb640943621b84415d02"&gt;commit&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;I have a sprint in January where I will 1.) digest the problem as
  you've described (and which Marco Leo has brought up to me before) and
  2.) bring in Marco's solution to the portal. Then, I'm going to assign
  someone to de-duplicate and apply the pattern every where.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;We will fix this. You have my word.&lt;/p&gt;</summary>
    <dc:creator>Brian Chan</dc:creator>
    <dc:date>2018-12-20T04:38:21Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111845506" />
    <author>
      <name>André Ricardo Barreto de Oliveira</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111845506</id>
    <updated>2018-12-20T02:26:40Z</updated>
    <published>2018-12-20T02:26:40Z</published>
    <summary type="html">&lt;p&gt;Hi Tobias,&lt;/p&gt;
&lt;p&gt;I'm sorry for all the trouble you had to go through when trying to
  extend Liferay functionality. I'm Liferay's Engineering Lead of Search
  and I agree with you that sorting on a few custom fields shouldn't be
  so hard and take so much code. We've been listening to similar reports
  and in the roadmap of our next version, sort improvements were
  selected as a priority currently in active development.&lt;/p&gt;
&lt;p&gt;
  &lt;a href="https://issues.liferay.com/browse/LPS-84969"&gt;https://issues.liferay.com/browse/LPS-84969&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I wanted to say the level of detail you provided is remarkable and
  will be extremely helpful in making sure we implement this feature
  just right. This is an immensely useful, specific, end-to-end use case
  and we will make sure it gets properly covered. I would love to hear
  any other insights you may have from your experience with
  customization of Liferay Search.&lt;/p&gt;
&lt;p&gt;Thank you,&lt;/p&gt;
&lt;p&gt;André&lt;/p&gt;</summary>
    <dc:creator>André Ricardo Barreto de Oliveira</dc:creator>
    <dc:date>2018-12-20T02:26:40Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111844745" />
    <author>
      <name>Milen Dyankov</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111844745</id>
    <updated>2018-12-19T20:20:59Z</updated>
    <published>2018-12-19T20:20:59Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;p&gt; &lt;/p&gt;
      &lt;p&gt;
        &lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;/p&gt;
      &lt;p&gt;I had to write a lot of code and had to use a lot of inside
        knowledge of Liferay - which may change already by the next
        patch version. Just to add some simple sortable date columns.&lt;/p&gt;
      &lt;p&gt;...&lt;/p&gt;
      &lt;p&gt;Thus this post is more a call for higher code quality in
        Liferay than it is a tutorial on how to add sortable columns in Liferay.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;
  &lt;br /&gt; Hi Tobias,&lt;/p&gt;
&lt;p&gt;I for one tend to agree with you.  It absolutely should not require
  such big effort to achieve the goal you wanted to achieve. Period.&lt;/p&gt;
&lt;p&gt;That said, I don't disagree with David. I know there is a reason for
  everything and it is often very hard balance between innovation and
  backward compatibility and bunch of  other things. Especially for a
  product with such history and codebase. Sadly sometimes we do
  sacrifice developer experience to achieve other goals. But It's not my
  intention to make excuses or justify decisions I don't have enough
  information about. &lt;/p&gt;
&lt;p&gt;What I would like to suggest to you is to consider sharing your
  expectations and ideas of how things should work in the form of
  feature requests in Jira. I understand this is what you try to do here
  but there are 2 problems with that. First is that forums are not a
  place that all product team members monitor. Second, you did a great
  job describing the problem but it would be better if you could explain
  the expected solution. Feature requests can help with both - reach the
  right people and give them your expectations about how things should
  work instead. It also allow to start a discussion with the teams
  building the product.&lt;/p&gt;
&lt;p&gt;If you choose to do that and experience long delays before teams
  respond, please feel free to contact me directly and will do my best
  to have respective teams look at your proposals.&lt;/p&gt;
&lt;p&gt;Best,&lt;br /&gt; Milen&lt;/p&gt;
&lt;p&gt;
  &lt;br /&gt;  &lt;/p&gt;</summary>
    <dc:creator>Milen Dyankov</dc:creator>
    <dc:date>2018-12-19T20:20:59Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111843645" />
    <author>
      <name>David H Nebinger</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111843645</id>
    <updated>2018-12-19T16:53:30Z</updated>
    <published>2018-12-19T16:53:30Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;blockquote&gt;
        &lt;p&gt;Some overrides are easily implemented and still supported.
          JSP fragment bundles, for one. So there are ways to affect
          customization without forcing a rewrite of one of Liferay's modules.&lt;/p&gt;&lt;/blockquote&gt;
      &lt;p&gt;But JSP fragment bundles are an example for problems on every
        patch version - because you can't extend them. Thus you have to
        check for changes in these JSPs on every upgrade. From my point
        of view they are much worse compared to extending private
        classes - where in many cases the compiler already indicates incompatibilities.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
 There are solutions for this coming down the pike.  Liferay is building
a new method to inject into a JSP page at top and bottom (and possibly
elsewhere, I don't have all of the details yet), but this would offer
spaces that are mixed in with the Liferay core JSP and help to keep
customizations separate from core.  I don't know yet if it will be
effective for all use cases, but it should accommodate some.&lt;br /&gt;
   &lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;blockquote&gt;
        &lt;p&gt;Well, arguments about code formatting, style and
          maintainability are often somewhat subjective.&lt;/p&gt;&lt;/blockquote&gt;
      &lt;p&gt;I wonder if it is lack of knowledge or ignorance that you are
        mixing code formatting with code quality. While code formatting
        is somehow subjective and has only one rule (consistency), there
        is a common sense of code quality. In each book and on every
        page about that topic you will find nearly the same rules with
        the same argumentation. You just need to read and understand
        them to get my point.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;I don't like the tone this is taking on...&lt;/p&gt;
&lt;p&gt;Code quality can also be subjective.  Is an anonymous inner class vs
  a lambda a question of quality? Is readability a measure of quality
  and, if so, readable by whom?  A line, duplicated 3 times, may
  actually peform better than a loop with a counter and a test involved,
  but one is preferred over another from a review standpoint while the
  other is preferred from a performance standpoint. &lt;/p&gt;
&lt;p&gt;It is often easy to lob criticisms about quality, style and
  maintenance issues at someone else's code. But at the end of the day,
  you won't know what is important to them or why. The boilerplate code
  this was initially about, that stuck out for you in your review. But
  obviously for Liferay it made sense to build it that way and makes
  sense to keep it that way.&lt;/p&gt;
&lt;p&gt;The Liferay code base is a large system with teams of folks working
  on separate feature areas; all code changes get merged into a single
  git repo where CI verifies that all tests pass and there is also the
  final gatekeepers led by Brian Chan. Trust me, if they thought the
  code had quality issues they would have removed it by now and, because
  it is in there, suggests to me that there is a reason it is in there
  the way it is.&lt;/p&gt;
&lt;p&gt;While I totally agree that it appears that having the boilerplate
  code looks like a quality issue, since I don't know the reason why it
  is the way it is and don't know if it serves some special purpose for
  Liferay, I'm not going to make a big post saying there are code
  quality issues. Instead I'd try to get to one of the numerous Liferay
  events that are held each year and attended by an overwhelming number
  of Liferay staff, including Engineering, and I'd start a conversation
  to find out why the code is like the way it is. Or even post to the
  community Slack channel, where some from Engineering hang out.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;</summary>
    <dc:creator>David H Nebinger</dc:creator>
    <dc:date>2018-12-19T16:53:30Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111841061" />
    <author>
      <name>Tobias Liefke</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111841061</id>
    <updated>2018-12-19T12:57:32Z</updated>
    <published>2018-12-19T12:57:32Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;p&gt;Some overrides are easily implemented and still supported. JSP
    fragment bundles, for one. So there are ways to affect customization
    without forcing a rewrite of one of Liferay's modules.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;But JSP fragment bundles are an example for problems on every patch
  version - because you can't extend them. Thus you have to check for
  changes in these JSPs on every upgrade. From my point of view they are
  much worse compared to extending private classes - where in many cases
  the compiler already indicates incompatibilities.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Well, arguments about code formatting, style and maintainability
    are often somewhat subjective.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I wonder if it is lack of knowledge or ignorance that you are mixing
  code formatting with code quality. While code formatting is somehow
  subjective and has only one rule (consistency), there is a common
  sense of code quality. In each book and on every page about that topic
  you will find nearly the same rules with the same argumentation. You
  just need to read and understand them to get my point.&lt;/p&gt;
&lt;p&gt;Arguing about code quality is like arguing about the global warming:
  You can ignore it, you can say that it is subjective - but in the end
  it will catch you resp. your successors. And the costs will be
  exponentially higher than the costs that you would have right now.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Liferay's been doing the OSGi thing for like 3 years now, so I'm
    sure they have their own ways of dealing with code maintenance issues.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I've been doing OSGI for more than 15 years now and I don't see
  what code duplication has to do with OSGI.&lt;/p&gt;
&lt;p&gt;I really hope that all my complains are not in vain and someone from
  the lead developers at Liferay is listening as well. I often have to
  create comparisons of implementation costs for projects and from my
  point of view the momentum is shifting towards the competitors of
  Liferay 7.1 now.&lt;/p&gt;</summary>
    <dc:creator>Tobias Liefke</dc:creator>
    <dc:date>2018-12-19T12:57:32Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111835062" />
    <author>
      <name>David H Nebinger</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111835062</id>
    <updated>2018-12-18T16:53:18Z</updated>
    <published>2018-12-18T16:53:18Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;p&gt;And I don't think that one of the Liferay architects or one of
        the Liferay customers will follow your arguments: Rebuild the
        whole user administration portlet just for an additional sorting
        option? Let's rebuild the whole portal then, because I need some
        other functionalities in other areas as well which were not
        foreseen by the developers.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Well, that's a little extreme... I'm sorry if I gave the impression
  that this was the only choice.  Some overrides are easily implemented
  and still supported.  JSP fragment bundles, for one. So there are ways
  to affect customization without forcing a rewrite of one of Liferay's modules.&lt;/p&gt;
&lt;p&gt;The suggestion, though, is to stay within the lines of allowed
  customization vs ones that cross the line.  Rather than crossing the
  line, the recommended path would be to create your own.&lt;/p&gt;
&lt;p&gt;Valid paths towards customizations include:&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;JSP Fragment bundles&lt;/li&gt;
  &lt;li&gt;Service Pre/Post actions&lt;/li&gt;
  &lt;li&gt;Component overrides&lt;/li&gt;
  &lt;li&gt;Portlet filters&lt;/li&gt;
  &lt;li&gt;Service wrappers&lt;/li&gt;
  &lt;li&gt;Etc&lt;/li&gt;&lt;/ul&gt;
&lt;p&gt;The question comes down to whether your goals can be achieved by some
  other manner than extending and/or overriding Liferay internal classes.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;p&gt;I tried to show, that it is not only about &lt;em&gt;one&lt;/em&gt;
        extension point - I needed to create changes all over the place
        (which everybody would need to do for a new order criteria).
        This indicates a problem in the whole framework, because the
        Pareto case for ordering is exactly that: order by one of the
        fields of the model entity.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;This kind of use case will be helpful to Liferay to highlight
  complexities in approaching some customizations.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;p&gt;I'm not talking about what I need, I'm talking about what
        Liferay needs to do to keep their code maintainable.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Well, arguments about code formatting, style and maintainability are
  often somewhat subjective.&lt;/p&gt;
&lt;p&gt;Liferay's been doing the OSGi thing for like 3 years now, so I'm sure
  they have their own ways of dealing with code maintenance issues.&lt;/p&gt;</summary>
    <dc:creator>David H Nebinger</dc:creator>
    <dc:date>2018-12-18T16:53:18Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111832096" />
    <author>
      <name>Tobias Liefke</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111832096</id>
    <updated>2018-12-18T07:57:45Z</updated>
    <published>2018-12-18T07:57:45Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;p&gt;Just a personal thing, but I never understood why lambdas were
    supposed to be so much better than straight-up OOP...&lt;/p&gt;
  &lt;p&gt;Unfortunately the code just makes them all look boilerplate. Each
    string is used 3 times, once for the column identifier, once for the
    sort parameter value in the url, and once for the label to use for
    the value. Since Liferay uses the same value for all 3 cases, this
    appears to be boilerplate because you get the same three lines using
    the same constant string over and over and over. But that's just
    Liferay, there is the option to override and use different keys or
    identifiers or ... The fact that Liferay tends to use the same value
    is representative of the consistency they use in their code. The
    &amp;quot;buildOrderByDropdownItems(...)&amp;quot; idea would only work when
    all of the values would be the same, a case that is not always guaranteed.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I don't understand why lambdas are not straight-up OOP. And by the
  way - I even didn't say anything about lambdas, I only had to use
  them, because DropdownItemList.add expects one. So please let's focus
  on the main points of this discussion.&lt;/p&gt;
&lt;p&gt;The code doesn't only &lt;em&gt;look&lt;/em&gt; boilerplate, it &lt;em&gt;is&lt;/em&gt;
  boilerplate. Just because I want to allow different implementation
  options I don't need to duplicate code. Offering a default
  implementation with default options won't prevent someone from using
  his own implementation, if he needs another option. But this should be
  no excuse to duplicate the same code a hundred times.&lt;/p&gt;
&lt;p&gt;Consistency is not about writing the same code again and again, it is
  about using the same patterns. Right now if you check the different
  sources of &lt;em&gt;_getOrderByDropdownItems&lt;/em&gt; you will already notice a
  slight deviation in the implementations (without impact on the
  functionality) - which will eventually lead to no consistency at all.&lt;/p&gt;
&lt;p&gt;I don't want to start a whole new debate on code quality - there are
  enough websites and books out there dealing with that topic. And if
  you think that Liferay doesn't duplicate code or that this is a good
  thing to do so, you should take some time to read them.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;... But for Liferay, this was a big problem. Customers would open
    support tickets, but many times the problems ended up getting traced
    back to their custom code overrides. What was even worse was when a
    customer project was over, the dev team that knew about the
    customizations either rolled off or their contract was up, so the
    client's devops are left with a response of &amp;quot;the problem is in
    your custom code&amp;quot; and no way to fix it. It was never that
    Liferay support didn't want to help the client, they just didn't
    have the staff or the development expertise to do it. Ultimately
    though the over-customization aspects of the portal, the thing that
    we developers enjoy and use extensively, well this ended up
    impacting relationships at many levels.&lt;/p&gt;
  &lt;p&gt;OSGi, with its modularization and extensibility, well it also
    presented an opportunity to reign in the over-customization aspects.
    By creating defined extension points, the intent is that the
    platform should still be extendible, but the extensions do not
    themselves step on Liferay code; the extensions are more &amp;quot;bolt
    on&amp;quot; so the lines between Liferay code and custom code are much clearer.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;While I understand how OSGI helps extending Liferay, I don't see how
  OSGI does help the support team here - as they still don't know about
  custom code overrides?&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;So, what does this mean for your code? Well I think it means you've
    been trying to force Liferay to be just as over-customizable as it
    used to be, but much to your own chagrin. Your code is now brittle
    because it extends and overrides code which is not meant to be
    extended or overridden. The ViewUsersManagementToolbarDisplayContext
    class you're extending is not exported from the bundle; I don't know
    how you've included it in order to extend it (either by embedding
    the jar or using my blog post to force a package export), but you
    could be subject to future bugs if Liferay adjusts their version but
    yours does not get the same adjustments in a timely manner.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;A &lt;i&gt;bundle fragment&lt;/i&gt; is the key, as it has access to the private
  packages. I had to create one anyway to implement the columns in the
  JSP files. And I'm used to check all extensions for incompatibilities
  on each patch version, even if they extend code which is meant to be
  extended, because I've learned that even that is not as stable as you
  try to make me think. One of the reasons for that is the code
  duplication all over the place.&lt;/p&gt;
&lt;blockquote&gt;
  &lt;p&gt;Basically, as a Liferay developer, there's a new mindset you need
    to consider adopting. If there is an extension point, use it. If
    there is not an extension point, I think the idea is that you should
    &amp;quot;roll your own&amp;quot;. That might seem harsh, but again this
    keeps the lines clear from Liferay's user admin panel and your own
    users admin panel. Having your own admin panel isolates your code
    and protects against changes to Liferay's internal code, and you are
    free to use the Liferay APIs to provide similar functionality, but
    the difference for Liferay is that bugs in your custom admin panel
    are, well, your bugs and not Liferay's.&lt;/p&gt;
  &lt;p&gt;Me, now I personally sit on both sides of this fence... I can see
    the benefits it has for Liferay, but as a developer like yourself I
    would hate to develop and maintain my own user admin portlet just so
    I could add an email column to the table. Like you, I too want to
    extend what Liferay provides and only layer in the changes I need to
    get that column without having to create and maintain the whole
    thing myself.&lt;/p&gt;
  &lt;p&gt;But, when we choose that road, we're specifically going down a path
    that we should know is not supported and might break as early as the
    next fix pack or GA, let alone the next major version.&lt;/p&gt;
  &lt;p&gt;Is there a better way? Sure! Liferay thinks they have most of the
    necessary extension points in place, but they don't believe they
    have all of them and they are open to adding new ones. Pop over to
    issues.liferay.com and open a ticket asking for a new extension
    point and it could make it in. If you're a client, you can use the
    new help.liferay.com to open a support ticket and work the extension
    point that way.&lt;/p&gt;
  &lt;p&gt;Remember their goal is not to lock you out of customizing Liferay,
    they just want to define a controlled way to do it where you can
    customize the platform but your changes are independent and
    immunized from changes in the Liferay internals.&lt;/p&gt;&lt;/blockquote&gt;
&lt;p&gt;I think you misinterpreted one of the main points of my post. When
  talking about maintainability and readibility I meant for the
  developers at Liferay themselves, not for Liferay extension
  developers. Put it that way: The easier it is for developers at
  Liferay to implement a new field and add it as column to the UI, the
  easier it is for us extension developers to add similar functionality.&lt;/p&gt;
&lt;p&gt;And I don't think that one of the Liferay architects or one of the
  Liferay customers will follow your arguments: Rebuild the whole user
  administration portlet just for an additional sorting option? Let's
  rebuild the whole portal then, because I need some other
  functionalities in other areas as well which were not foreseen by the developers.&lt;/p&gt;
&lt;p&gt;I tried to show, that it is not only about &lt;em&gt;one&lt;/em&gt; extension
  point - I needed to create changes all over the place (which everybody
  would need to do for a new order criteria). This indicates a problem
  in the whole framework, because the Pareto case for ordering is
  exactly that: order by one of the fields of the model entity. And that
  &lt;em&gt;is&lt;/em&gt; possible with just one line of code, not with that bunch
  of duplicated code - if you do it right (I've seen enough frameworks
  which do this). Again: I'm not talking about what &lt;em&gt;I&lt;/em&gt; need, I'm
  talking about what Liferay needs to do to keep their code maintainable.&lt;/p&gt;</summary>
    <dc:creator>Tobias Liefke</dc:creator>
    <dc:date>2018-12-18T07:57:45Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111828953" />
    <author>
      <name>David H Nebinger</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111828953</id>
    <updated>2018-12-17T15:04:46Z</updated>
    <published>2018-12-17T15:04:46Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;p&gt;
        &lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;/p&gt;
      &lt;p&gt;I had to write a lot of code and had to use a lot of inside
        knowledge of Liferay - which may change already by the next
        patch version. Just to add some simple sortable date columns.&lt;/p&gt;
      &lt;p&gt;By stepping through the code I found many places where simle
        coding standards like the &lt;a&gt;DRY principle&lt;/a&gt; were violated -
        with huge drawbacks for maintainability, extensibility and
        readability. Liferay 7.1 has about 3.2 million code lines in
        Java files (not counting empty / commented lines, JSP files and
        other templates). This is an increase by  ~ 300,000 code lines
        (11%) compared to Liferay 7.0. Some might say this is an
        indicator for more functionality. But for me this is an
        indicator for more sources of errors and vulnerabilities, more
        code nobody is aware of, more code to maintain, more code you
        need to overwrite for your own extensions.&lt;/p&gt;
      &lt;p&gt;Thus this post is more a call for higher code quality in
        Liferay than it is a tutorial on how to add sortable columns in Liferay.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Without taking any sides, let me kind of explain...&lt;/p&gt;
&lt;p&gt;In Liferay 6 and earlier, all code was subject to override. Every
  JSP, every class, every resource file...  It was super easy for
  developers to get in there and override what was necessary to get
  their jobs done.&lt;/p&gt;
&lt;p&gt;But for Liferay, this was a big problem. Customers would open support
  tickets, but many times the problems ended up getting traced back to
  their custom code overrides. What was even worse was when a customer
  project was over, the dev team that knew about the customizations
  either rolled off or their contract was up, so the client's devops are
  left with a response of &amp;quot;the problem is in your custom code&amp;quot;
  and no way to fix it. It was never that Liferay support didn't want to
  help the client, they just didn't have the staff or the development
  expertise to do it. Ultimately though the over-customization aspects
  of the portal, the thing that we developers enjoy and use extensively,
  well this ended up impacting relationships at many levels.&lt;/p&gt;
&lt;p&gt;OSGi, with its modularization and extensibility, well it also
  presented an opportunity to reign in the over-customization aspects.
  By creating defined extension points, the intent is that the platform
  should still be extendible, but the extensions do not themselves step
  on Liferay code; the extensions are more &amp;quot;bolt on&amp;quot; so the
  lines between Liferay code and custom code are much clearer.&lt;/p&gt;
&lt;p&gt;When you see things such as a bundle w/o a package export for a class
  you want to override or an internal, private method, well these are
  just some of the ways in which the over-customization have been
  reigned in.&lt;/p&gt;
&lt;p&gt;With the clean lines between Liferay code and custom code, another
  correlary to this is that the Liferay code is now subject to change
  w/o necessarily broadcasting the change to you. As long as the
  extension points still exist and the contracts don't change, they may
  change up the internals at any time because those changes are internal
  and should not bleed over to the custom code via the extension points.&lt;/p&gt;
&lt;p&gt;So, what does this mean for your code? Well I think it means you've
  been trying to force Liferay to be just as over-customizable as it
  used to be, but much to your own chagrin. Your code is now brittle
  because it extends and overrides code which is not meant to be
  extended or overridden. The ViewUsersManagementToolbarDisplayContext
  class you're extending is not exported from the bundle; I don't know
  how you've included it in order to extend it (either by embedding the
  jar or using my blog post to force a package export), but you could be
  subject to future bugs if Liferay adjusts their version but yours does
  not get the same adjustments in a timely manner.&lt;/p&gt;
&lt;p&gt;Basically, as a Liferay developer, there's a new mindset you need to
  consider adopting. If there is an extension point, use it. If there is
  not an extension point, I think the idea is that you should &amp;quot;roll
  your own&amp;quot;. That might seem harsh, but again this keeps the lines
  clear from Liferay's user admin panel and your own users admin panel.
  Having your own admin panel isolates your code and protects against
  changes to Liferay's internal code, and you are free to use the
  Liferay APIs to provide similar functionality, but the difference for
  Liferay is that bugs in your custom admin panel are, well, your bugs
  and not Liferay's.&lt;/p&gt;
&lt;p&gt;Me, now I personally sit on both sides of this fence... I can see the
  benefits it has for Liferay, but as a developer like yourself I would
  hate to develop and maintain my own user admin portlet just so I could
  add an email column to the table.  Like you, I too want to extend what
  Liferay provides and only layer in the changes I need to get that
  column without having to create and maintain the whole thing myself.&lt;/p&gt;
&lt;p&gt;But, when we choose that road, we're specifically going down a path
  that we should know is not supported and might break as early as the
  next fix pack or GA, let alone the next major version.&lt;/p&gt;
&lt;p&gt;Is there a better way? Sure! Liferay thinks they have most of the
  necessary extension points in place, but they don't believe they have
  all of them and they are open to adding new ones. Pop over to
  issues.liferay.com and open a ticket asking for a new extension point
  and it could make it in. If you're a client, you can use the new
  help.liferay.com to open a support ticket and work the extension point
  that way.&lt;/p&gt;
&lt;p&gt;Remember their goal is not to lock you out of customizing Liferay,
  they just want to define a controlled way to do it where you can
  customize the platform but your changes are independent and immunized
  from changes in the Liferay internals.&lt;/p&gt;</summary>
    <dc:creator>David H Nebinger</dc:creator>
    <dc:date>2018-12-17T15:04:46Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111828459" />
    <author>
      <name>David H Nebinger</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111828459</id>
    <updated>2018-12-17T14:27:44Z</updated>
    <published>2018-12-17T14:27:44Z</published>
    <summary type="html">&lt;blockquote&gt;
  &lt;div class="quote-title"&gt;Tobias Liefke:&lt;/div&gt;
  &lt;div class="quote"&gt;
    &lt;div class="quote-content"&gt;
      &lt;p&gt;But instead of a straight forward
        buildOrderByDropdownItems(&amp;quot;first-name&amp;quot;,
        &amp;quot;last-name, &amp;quot;screen-name&amp;quot;), there is the same
        boilerplate code again and again.&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;&lt;/blockquote&gt;
&lt;p&gt;Just a personal thing, but I never understood why lambdas were
  supposed to be so much better than straight-up OOP...&lt;/p&gt;
&lt;p&gt;Unfortunately the code just makes them all look boilerplate. Each
  string is used 3 times, once for the column identifier, once for the
  sort parameter value in the url, and once for the label to use for the
  value.  Since Liferay uses the same value for all 3 cases, this
  appears to be boilerplate because you get the same three lines using
  the same constant string over and over and over.  But that's just
  Liferay, there is the option to override and use different keys or
  identifiers or ...  The fact that Liferay tends to use the same value
  is representative of the consistency they use in their code.  The
  &amp;quot;buildOrderByDropdownItems(...)&amp;quot; idea would only work when
  all of the values would be the same, a case that is not always
  guaranteed.&lt;br /&gt;  &lt;/p&gt;</summary>
    <dc:creator>David H Nebinger</dc:creator>
    <dc:date>2018-12-17T14:27:44Z</dc:date>
  </entry>
  <entry>
    <title>RE: Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111825066" />
    <author>
      <name>Tobias Liefke</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111825066</id>
    <updated>2018-12-17T10:39:57Z</updated>
    <published>2018-12-17T10:39:57Z</published>
    <summary type="html">&lt;p&gt;It turns out that this was only the tip of the iceberg:&lt;/p&gt;
&lt;p&gt;To ensure that the email address is indexed, I had to add that
  property to my portal-ext.properties:&lt;/p&gt;
&lt;pre&gt;
index.sortable.text.fields=firstName,jobTitle,lastName,name,screenName,title,emailAddress
&lt;/pre&gt;
&lt;p&gt;(which I had already done in 6.2 for one of my instances - and I
  needed some time to find out why email addresses weren't sorted on my
  other instances).&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Now I wanted to add some more columns, thus I improved my ViewUsersManagementToolbarDisplayContext.java:&lt;/p&gt;
&lt;pre&gt;
public class ViewUsersManagementToolbarDisplayContext
        extends com.liferay.users.admin.web.internal.display.context.ViewUsersManagementToolbarDisplayContext {

    private HttpServletRequest request;

    public ViewUsersManagementToolbarDisplayContext(HttpServletRequest request, RenderRequest renderRequest,
            RenderResponse renderResponse, String displayStyle, String navigation, int status) {
        super(request, renderRequest, renderResponse, displayStyle, navigation, status);
        this.request = request;
    }

    @Override
    public List&amp;lt;DropdownItem&amp;gt; getFilterDropdownItems() {
        List&amp;lt;DropdownItem&amp;gt; items = super.getFilterDropdownItems();
        addOrderColumn(items, &amp;quot;email-address&amp;quot;, &amp;quot;email-address&amp;quot;);
        addOrderColumn(items, &amp;quot;createDate&amp;quot;, &amp;quot;create-date&amp;quot;);
        addOrderColumn(items, &amp;quot;lastLoginDate&amp;quot;, &amp;quot;Login&amp;quot;);
        addOrderColumn(items, &amp;quot;lockoutDate&amp;quot;, &amp;quot;locked&amp;quot;);
        addOrderColumn(items, &amp;quot;jobTitle&amp;quot;, &amp;quot;job-title&amp;quot;);
        return items;
    }

    private void addOrderColumn(List&amp;lt;DropdownItem&amp;gt; items, final String columnKey, String columnLabel) {
        DropdownGroupItem sortGroup = (DropdownGroupItem) items.get(items.size() - 1);
        DropdownItemList itemList = (DropdownItemList) sortGroup.get(&amp;quot;items&amp;quot;);
        itemList.add(dropdownItem -&amp;gt; {
            dropdownItem.setActive(Objects.equals(getOrderByCol(), columnKey));
            dropdownItem.setHref(getPortletURL(), &amp;quot;orderByCol&amp;quot;, columnKey);
            dropdownItem.setLabel(LanguageUtil.get(this.request, columnLabel));
        });
    }

}
&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;But this isn't enough, as there is a mapper function
  &lt;em&gt;com.liferay.portlet.usersadmin.util.UsersAdminImpl.getUserOrderByComparator&lt;/em&gt; which
  maps the column names to a comparator. And that function returns the
  &lt;em&gt;UserLastNameComparator&lt;/em&gt; for every unknown column, which
  includes all my date columns. UsersAdmin is an old fashioned Liferay
  service class, which means it resides in portal-impl.jar and is
  sometimes referenced with UsersAdminUtil and sometimes injected with @Reference.&lt;/p&gt;
&lt;p&gt;In 6.2 I would have written a hook or ext-plugin to wrap that service
  in my own implementation class (and use spring-ext.xml to install it),
  but for 7.1 I decided to write a new plugin which contains my wrapper
  class, with some additional tricks from my magic bag:&lt;/p&gt;
&lt;pre&gt;
@Component(immediate = true, service = UsersAdmin.class)
public class MyUsersAdminImpl implements UsersAdmin {

    private UsersAdmin wrapped;

    @Activate
    public void activate() {
        // Install our implementation on top of the existing implementation
        if (UsersAdminUtil.getUsersAdmin() instanceof MyUsersAdminImpl) {
            this.wrapped = ((MyUsersAdminImpl) UsersAdminUtil.getUsersAdmin()).getWrapped();
        } else {
            this.wrapped = UsersAdminUtil.getUsersAdmin();
        }
        new UsersAdminUtil().setUsersAdmin(this);
    }    

    @Deactivate
    public void deactivate() {
        // Reinstall the previous implementation
        if (UsersAdminUtil.getUsersAdmin() == this) {
            new UsersAdminUtil().setUsersAdmin(this.wrapped);
        }
    }

    ... all UsersAdmin functions delegation to wrapper ...

}
&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;Now I implemented my own comparator, but instead of one per property
  (as done in Liferay) I like a more generic aproach:&lt;/p&gt;
&lt;pre&gt;
public class GenericComparator&amp;lt;T&amp;gt; extends OrderByComparator&amp;lt;T&amp;gt; {
    private static final long serialVersionUID = 1L;

    private final boolean ascending;

    private final String orderBy;

    private final String[] orderByFields;

    private final Comparator&amp;lt;T&amp;gt; comparator;

    public GenericComparator(String field, Comparator&amp;lt;T&amp;gt; comparator) {
        this(field, comparator, true);
    }

    public GenericComparator(String field, Comparator&amp;lt;T&amp;gt; comparator, boolean ascending) {
        this(field + (ascending ? &amp;quot; ASC&amp;quot; : &amp;quot; DESC&amp;quot;), new String[] { field }, comparator, ascending);
    }

    public GenericComparator(String orderBy, String[] orderByFields, Comparator&amp;lt;T&amp;gt; comparator, boolean ascending) {
        this.orderBy = orderBy;
        this.orderByFields = orderByFields;
        this.comparator = comparator;
        this.ascending = ascending;
    }

    @Override
    public int compare(T entity1, T entity2) {
        final int value = this.comparator.compare(entity1, entity2);
        return this.ascending ? value : -value;
    }

    @Override
    public String getOrderBy() {
        return this.orderBy;
    }

    @Override
    public String[] getOrderByFields() {
        return this.orderByFields;
    }

    @Override
    public boolean isAscending() {
        return this.ascending;
    }

}&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;That comparator I can use in my own &lt;em&gt;getUserOrderByComparator&lt;/em&gt;:&lt;/p&gt;
&lt;pre&gt;
@Component(immediate = true, service = UsersAdmin.class)
public class MyUsersAdminImpl implements UsersAdmin {

    ...

    @Override
    public OrderByComparator&amp;lt;User&amp;gt; getUserOrderByComparator(String orderByCol, String orderByType) {
        // Support the default comparators
        if (orderByCol.indexOf('-') &amp;gt;= 0) {
            return this.wrapped.getUserOrderByComparator(orderByCol, orderByType);
        }

        // Support additional (generic) comparators
        final boolean orderByAsc = orderByType.equals(&amp;quot;asc&amp;quot;);
        try {
            final Method getter = User.class.getMethod(&amp;quot;get&amp;quot; + StringUtil.upperCaseFirstLetter(orderByCol));
            return new GenericComparator&amp;lt;&amp;gt;(orderByCol, (a, b) -&amp;gt; {
                try {
                    return ((Comparable&amp;lt;Object&amp;gt;) getter.invoke(a)).compareTo(getter.invoke(b));
                } catch (IllegalAccessException | InvocationTargetException e) {
                    throw new IllegalArgumentException(e);
                }
            }, orderByAsc);
        } catch (NoSuchMethodException | SecurityException e) {
            return this.wrapped.getUserOrderByComparator(orderByCol, orderByType);
        }
    }

}
&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;And this isn't still enough, because all the date columns are not
  indexed. Unfortunately I can't add these to
  &lt;em&gt;index.sortable.text.fields&lt;/em&gt; from above, because they are not
  of type text.&lt;/p&gt;
&lt;p&gt;Thus I added a UserIndexPostProcessor to my plugin:&lt;/p&gt;
&lt;pre&gt;
@Component(immediate = true, service = IndexerPostProcessor.class, 
        property = { &amp;quot;indexer.class.name=com.liferay.users.admin.internal.search.UserIndexer&amp;quot; })
public class UserIndexerPostProcessor extends BaseIndexerPostProcessor {

    public void postProcessDocument(Document document, Object obj) throws Exception {
        final User user = (User) obj;
        // Add date fields for sorting to index
        document.addDateSortable(&amp;quot;createDate&amp;quot;, user.getCreateDate());
        document.addDateSortable(&amp;quot;lastLoginDate&amp;quot;, user.getLastLoginDate());
        document.addDateSortable(&amp;quot;lockoutDate&amp;quot;, user.getLockoutDate());
    }

    @Override
    public void postProcessSearchQuery(BooleanQuery searchQuery, BooleanFilter booleanFilter,
            SearchContext searchContext) throws Exception {
        // Ensure that datefields are mapped correctly
        final Sort[] sorts = searchContext.getSorts();
        if (sorts != null) {
            for (int i = 0; i &amp;lt; sorts.length; i++) {
                final Sort sort = sorts[i];
                if (sort.getType() == Sort.STRING_TYPE &amp;amp;&amp;amp; sort.getFieldName().endsWith(&amp;quot;Date&amp;quot;)) {
                    sorts[i] = new Sort(sort.getFieldName() + &amp;quot;_Number_sortable&amp;quot;, Sort.LONG_TYPE, sort.isReverse());
                }
            }
        }
    }

}
&lt;/pre&gt;
&lt;p&gt;And after reindexing the users I finally could sort by all these new columns.&lt;/p&gt;
&lt;p&gt;
  &lt;strong&gt;Conclusion:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I had to write a lot of code and had to use a lot of inside knowledge
  of Liferay - which may change already by the next patch version. Just
  to add some simple sortable date columns.&lt;/p&gt;
&lt;p&gt;By stepping through the code I found many places where simle coding
  standards like the &lt;a&gt;DRY principle&lt;/a&gt; were violated - with huge
  drawbacks for maintainability, extensibility and readability. Liferay
  7.1 has about 3.2 million code lines in Java files (not counting empty
  / commented lines, JSP files and other templates). This is an increase
  by  ~ 300,000 code lines (11%) compared to Liferay 7.0. Some might say
  this is an indicator for more functionality. But for me this is an
  indicator for more sources of errors and vulnerabilities, more code
  nobody is aware of, more code to maintain, more code you need to
  overwrite for your own extensions.&lt;/p&gt;
&lt;p&gt;Thus this post is more a call for higher code quality in Liferay than
  it is a tutorial on how to add sortable columns in Liferay.&lt;/p&gt;</summary>
    <dc:creator>Tobias Liefke</dc:creator>
    <dc:date>2018-12-17T10:39:57Z</dc:date>
  </entry>
  <entry>
    <title>Add orderable column to users (or: how customization degrades)</title>
    <link rel="alternate" href="https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111456193" />
    <author>
      <name>Tobias Liefke</name>
    </author>
    <id>https://liferay.dev/c/message_boards/find_message?p_l_id=119785294&amp;messageId=111456193</id>
    <updated>2018-11-05T22:17:21Z</updated>
    <published>2018-11-05T22:17:21Z</published>
    <summary type="html">&lt;p&gt;I am  just migrating a project from Liferay 7.0 to 7.1.&lt;/p&gt;
&lt;p&gt;That project has a fragment for com.liferay.users.admin.web to add
  some columns to the user administration.&lt;/p&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;In Liferay &lt;strong&gt;6.2&lt;/strong&gt; I had a hook where I just needed to
  add a column like this
  to /html/portlet/users_admin/user/search_columns.jspf :&lt;/p&gt;
&lt;pre&gt;
&amp;lt;liferay-ui:search-container-column-text
    href=&amp;quot;&amp;lt;%= rowURL %&amp;gt;&amp;quot;
    name=&amp;quot;email-address&amp;quot;
    orderable=&amp;quot;&amp;lt;%= true %&amp;gt;&amp;quot;
    property=&amp;quot;emailAddress&amp;quot;
/&amp;gt;&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;In Liferay &lt;strong&gt;7.0&lt;/strong&gt; I still needed to add that column,
  but the orderable flag was ignored (allthough it still exists, even
  for the predefined columns which is quite confusing).&lt;/p&gt;
&lt;p&gt;Instead I had to adjust the file view_flat_users.jspf and change the management-bar-sort:&lt;/p&gt;
&lt;pre&gt;
&amp;lt;liferay-frontend:management-bar-sort
     orderByCol=&amp;quot;&amp;lt;%= searchContainer.getOrderByCol() %&amp;gt;&amp;quot;
     orderByType=&amp;quot;&amp;lt;%= searchContainer.getOrderByType() %&amp;gt;&amp;quot;
     orderColumns='&amp;lt;%= new String[] {&amp;quot;first-name&amp;quot;, &amp;quot;last-name&amp;quot;, &amp;quot;screen-name&amp;quot;, &amp;quot;email-address&amp;quot; } %&amp;gt;'
     portletURL=&amp;quot;&amp;lt;%= portletURL %&amp;gt;&amp;quot;
/&amp;gt;
&lt;/pre&gt;
&lt;p&gt; &lt;/p&gt;
&lt;p&gt;And now in Liferay &lt;strong&gt;7.1&lt;/strong&gt; with the new &amp;quot;Clay&amp;quot;
  framework all this is still not enough.&lt;/p&gt;
&lt;p&gt;I have to override ViewUsersManagementToolbarDisplayContext.getFilterDropdownItems():&lt;/p&gt;
&lt;pre&gt;
    public List&amp;lt;DropdownItem&amp;gt; getFilterDropdownItems() {
        List&amp;lt;DropdownItem&amp;gt; items = super.getFilterDropdownItems();
        DropdownGroupItem sortGroup = (DropdownGroupItem) items.get(items.size() - 1);
        ((DropdownItemList) sortGroup.get(&amp;quot;items&amp;quot;)).add(dropdownItem -&amp;gt; {
            dropdownItem.setActive(Objects.equals(getOrderByCol(), &amp;quot;email-address&amp;quot;));
            dropdownItem.setHref(getPortletURL(), &amp;quot;orderByCol&amp;quot;, &amp;quot;email-address&amp;quot;);
            dropdownItem.setLabel(LanguageUtil.get(this.request, &amp;quot;email-address&amp;quot;));
        });
        return items;
    }
&lt;/pre&gt;
&lt;p&gt;(and I still need to modify view_flat_users.jsp or init.jsp to
  reference my class instead of the default one and I need to modify
  search_columns.jspf to include my column).&lt;/p&gt;
&lt;p&gt;To make things worse, the method
  ViewUsersManagementToolbarDisplayContext._getOrderByDropdownItems is
  private. And there are about 130
  &amp;quot;_getOrderByDropdownItems()&amp;quot; implementations and many look
  the same. But instead of a straight forward
  buildOrderByDropdownItems(&amp;quot;first-name&amp;quot;, &amp;quot;last-name,
  &amp;quot;screen-name&amp;quot;), there is the same boilerplate code again and again.&lt;/p&gt;
&lt;p&gt;I've got the feeling that even if we now are able to override
  everything with the OSGI fragments, the new frameworks make it
  difficult to customize anything, because of many implentation specific
  details, which are only accessible with much boilerplate code and are
  subject to change even on patch version upgrades. Am I the only one
  with that impression?&lt;/p&gt;</summary>
    <dc:creator>Tobias Liefke</dc:creator>
    <dc:date>2018-11-05T22:17:21Z</dc:date>
  </entry>
</feed>
