Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.
Sign upIssue #17 Contribute BYU Employee Time Reporting and Staff Leave Reporting portlets #18
Conversation
|
Would it be better to take this opportunity to go to an |
|
That's probably a good idea. Should it be org.apereo or org.jasig since everything else is org.jasig at this point (for consistency)? |
|
This is all new code. If it can't be packaged as For that matter, maybe the boilerplate license headers should reference Apereo rather than Jasig at this point. |
| public interface StaffTimePunchDao { | ||
|
|
||
| /** | ||
| * Returns a list of <code>TimePunchEntry</code> items for the employee. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
The order to display on the UI. Good point to clarify in the docs.
| /** | ||
| * Returns a list of <code>TimePunchEntry</code> items for the employee. | ||
| * @param emplId Employee ID | ||
| * @return List of <code>TimePUnchEntry</code> items |
This comment has been minimized.
This comment has been minimized.
| * @param emplId | ||
| * @param jobCode | ||
| */ | ||
| void punchInTimeClock (String emplId, int jobCode, String clientIP); |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Worth specifying in the interface the behavior in the case where that employee is already punched into that job code? Is already punched in to another job code?
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
Good point that I should enhance the documentation. The intention is to leave that to the dao implementation because the business rules may vary at different institutions. For BYU for instance, they want to allow that situation but return a message to the user because their business process allows a user to self-correct (externally) when they punch into a job code twice or punch into two different job codes. However if the user punches out to punch in again then it goes through a manager review process that they want to minimize.
And to your point, I need to document the mechanism for returning a message to the user (specific run-time exception).
| * @param jobCode | ||
| * @param clientIP | ||
| */ | ||
| void punchOutTimeClock (String emplId, int jobCode, String clientIP); |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Worth specifying on the interface the behavior if that employee is not punched in to that job code?
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
Similar point to above; leaving the handling to the dao implementation because the business rules may vary at different institutions. General expectation is doing nothing and returning a message to the user (via a specific run-time exception that I need to document).
| package edu.byu.hr.model.timereporting; | ||
|
|
||
| /** | ||
| * Description |
This comment has been minimized.
This comment has been minimized.
| * List of leave information for each day in the pay period. This list may not be fully populated for all days in | ||
| * the pay period. | ||
| */ | ||
| List<TimePeriodEntry> timePeriodEntries; |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 19, 2014
Author
In this case order is not important. It could be a set but I'll leave as a list with a comment.
| List<JobDescription> jobDescriptions, Set<Integer> displayOnlyJobCodes, | ||
| List<TimePeriodEntry> timePeriodEntries) { | ||
| this.payPeriodStart = payPeriodStart; | ||
| this.payPeriodEnd = payPeriodEnd; |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Worth doing some validation in the constructor? That payPeriodStart is not after payPeriodEnd?
This comment has been minimized.
This comment has been minimized.
| } | ||
|
|
||
| public PayPeriodDailyLeaveTimeSummary(LocalDate payPeriodStart, LocalDate payPeriodEnd, | ||
| List<JobDescription> jobDescriptions, Set<Integer> displayOnlyJobCodes, |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Order of jobDescriptions List? Or is that an orderless collection better modeled as a Set?
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
It drives the order of display on the UI allowing the dao impls to put read-only summary of time worked on the top, holidays on the bottom, or whatever the institution wants for its ordering. Worthy of a note. Thanks.
|
|
||
| <dependencyManagement> | ||
| <dependencies> | ||
| <!-- For sl4j/logback logging, see https://wiki.jasig.org/display/PLT/Logging+Best+Practices --> |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Approach 2 at https://wiki.jasig.org/display/PLT/Logging+Best+Practices and thereby excising dependency on commons-logging and log4j is probably worth getting to.
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
Not sure I am following you. This is approach 1 which is easiest and most maintainable IMHO and the approach I use when upgrading portlets from log4j to logback.
| <dependency> | ||
| <groupId>org.codehaus.jackson</groupId> | ||
| <artifactId>jackson-core-asl</artifactId> | ||
| <version>1.9.13</version> |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| import org.springframework.stereotype.Repository; | ||
|
|
||
| /** | ||
| * Description |
This comment has been minimized.
This comment has been minimized.
| * @param emplId Employee ID | ||
| * @return List of <code>TimePUnchEntry</code> items | ||
| */ | ||
| List<TimePunchEntry> getTimePunchEntries(PortletRequest request, String emplId, boolean refresh); |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
Presented on the UI in that order allowing the DAO implementation to order however it sees fit. Worthy of improving the docs. Thanks again for such attention to detail and great questions.
| * @param emplId | ||
| * @param jobCode | ||
| */ | ||
| void punchInTimeClock (PortletRequest request, String emplId, int jobCode, String clientIP); |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Why is the PortletRequest making its way past the controller abstraction and into the service layer?
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
This is a strategy that Drew suggested to me in other situations to reduce maintenance and make the APIs less brittle to signature changes which would potentially impact other institutions that have custom implementations. Though I was initially resistant to the idea because of separation of concerns, I've found that his suggestion has a lot of merit at the service layer (less so at a dao layer) and I've seen its beneficial use in several of the portlet projects. Though it's not as useful in this particular case because these service layers are pretty much a wrapper for the dao, I had chosen to follow it as a convention.
| import org.springframework.stereotype.Service; | ||
|
|
||
| /** | ||
| * Description |
This comment has been minimized.
This comment has been minimized.
| import org.joda.time.LocalDate; | ||
|
|
||
| /** | ||
| * Description |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
| @Override | ||
| @Cacheable(value="leaveSummary", key="#emplId + #dateInPayPeriod.toString()") | ||
| public PayPeriodDailyLeaveTimeSummary getLeaveHoursReported(PortletRequest request, String emplId, LocalDate dateInPayPeriod) { | ||
| log.debug("Invoking dao to get leave summary for employee ID {}, date {}", emplId, dateInPayPeriod); |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
This kind of thing might want to be a log.trace since it's just tracing path through the call stack.
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 20, 2014
Author
I can see trace level, but it is at debug to allow reporting on or troubleshooting caching.
| service.punchOutTimeClock(request, emplId, jobCode, request.getProperty("REMOTE_ADDR")); | ||
| response.setRenderParameter("message", messageSource.getMessage(PUNCHED_OUT_MESSAGE, null, "You have been punched out", request.getLocale())); | ||
| } catch (HrPortletRuntimeException e) { | ||
| log.debug("Punch Out action failed for employee {} job code {}", emplId, jobCode, e); |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Failing to punch out is more serious than a debug, no? This want to be a warn?
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
Intent is to display a message to the user on the UI, so I think debug is adequate.
| // fall through to error. | ||
| } | ||
| } | ||
| throw new HrPortletRuntimeException("Invalid Time entry not HH:MM, was '" + time); |
This comment has been minimized.
This comment has been minimized.
apetro
Mar 18, 2014
Member
Is it anticipated that a caller will want to differentially catch and handle HrPortletRuntimeException, or would this be just as well implemented as IllegalArgumentException?
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Mar 18, 2014
Author
I am generically using HrPortletRuntimeException to allow the DAO implementations (and other methods as well) to pass a message to display to the user on the page; e.g. a potentially user-recoverable error or at least alert message. I did that here as well for consistency though I see merit to using IllegalArgumentException as more descriptive.
This comment has been minimized.
This comment has been minimized.
|
Updated pull request with feedback, plus adding PortletRequest to dao to allow BYU to use arbitrary user attributes or portlet preferences in their dao implementation. Please review. |
This comment has been minimized.
This comment has been minimized.
apetro
commented on ffa9ef5
Apr 21, 2014
|
I'm really struggling with the abstraction violation of Got an example of what something is doing with a |
|
I also agree that the |
| <groupId>org.jvnet.jaxb2.maven2</groupId> | ||
| <artifactId>maven-jaxb2-plugin</artifactId> | ||
| <configuration> | ||
| <schemaDirectory>src/main/resources/xsd/hrs</schemaDirectory> |
This comment has been minimized.
This comment has been minimized.
apetro
Apr 21, 2014
Member
I don't think hrs-portlets-employee-time-reporting has a src/main/resources/xsd/hrs directory, does it? Not using JAXB to generate domain model / DTO classes? So can drop this <plugin/> ?
| <scope>provided</scope> | ||
| </dependency> | ||
|
|
||
| <!-- ===== Test Dependencies ====================================== --> |
This comment has been minimized.
This comment has been minimized.
apetro
Apr 21, 2014
Member
hrs-portlets-employee-time-reporting has no unit tests, so it has no test dependencies? Can drop these three dependencies?
| package org.apereo.portlet.hr.model.timereporting; | ||
|
|
||
| /** | ||
| * Model of a brief job description (not a full-HR job description with responsibilities). |
This comment has been minimized.
This comment has been minimized.
apetro
Apr 21, 2014
Member
How does this JobDescription relate to edu.wisc.hr.dm.person.Job (generated from hrs-portlet-person-1.0.xsd ) ?
| package org.apereo.portlet.hr.model.timereporting; | ||
|
|
||
| /** | ||
| * Model object representing Leave time balance. |
This comment has been minimized.
This comment has been minimized.
apetro
Apr 21, 2014
Member
How does this LeaveTimeBalance relate to edu.wisc.hr.dm.absbal.AbsenceBalance (generated from hrs-portlet-absbal-1.0.xsd )?
This comment has been minimized.
This comment has been minimized.
jameswennmacher
Apr 21, 2014
Author
Related. I am using the approach that notifications, Courses, and other portlets use to have a java data model. The specific data sources would pull in their data however they do and incorporate it into the java data model, thus providing isolation and reuse. The front end controller, service, and view tiers are thus isolated from details of a Peoplesoft v3, highly customized Peoplesoft v2, or other data source.
I think this type of strategy is required to maximize re-use and simplify integrations. Note I did not go to the effort of insuring the java data model has all elements of the existing PS data model. I was hoping that would happen as Wisconsin evolved their portlets.
|
EDITED: TL;DR
Summary of Hangups:Would be better factored as a separate projectThese new portlets bring with them new APIs such that they do not meaningfully intersect with the existing DAO API should not include PortletRequest
Even when, exceptionally, in practice some When it is necessary, access to the So, big picture: Thanks @jameswennmacher for all the work on this, I think this is great functionality to come available and it's been made better through the improvements through this pull request. What we've got here are lovely entirely new portlets. Whereas the existing hrs-portlets use XSD and JAXB and really ambitious occult technologies involving goat sacrifices to generate the domain model, these new portlets adopt the entirely sensible approach of a lovingly handcrafted Java domain model. These new portlets don't make use of any of the existing domain model or API in hrs-portlets. Now, I'm not saying these new portlets are wrong to avoid using what's here in But it seems to me that packaging these portlets into the same web application, the same So here's my suggestion: the world needs a And then both There's some duplication here. Let all the flowers bloom. Maybe we come to find that the APIs defined by As you might guess, I'm no fan of the But I'm not there yet. Looking at this, I don't think -0.9 for merge of this pull request as it stands. (Previously, -1). |
|
I think having these portlets in another portlet project is not the way to go. I think the better, longer-term strategy is to combine efforts. One sure way to have the portlets atrophy is to have a lot of fragmentation and little re-use, If everyone has their own portlet implementation, what are we gaining? The HRS portlets are very useful, but the portlet project will not be a heavily-used project and will not have a lot of adopters. By keeping the related portlets together, they have the greatest opportunity of code re-use and other's contributions helping out current adopters. In the case of BYU, they are writing the back end (using the JAXB/XSD approach) to create their specific DAO implementations to map to the common Java object model. They will be able to (and are planning on) re-using some of that infrastructure with the Payroll Information and other HRS portlets that they are rolling out in phase 2 and 3. TBD is to find out if I have to create a Java Object Model for the Payroll Information implementation or if they can use their JAXB-generated objects with the current Wisconsin controller and JSP. I suggest including these portlets into the HRS suite and (over time) adjust the current Wisconsin implementation to map the JAXB-generated objects to Java objects. If we focus our efforts on a good Java object model, a clean and flexible UI, and improve the mechanism of allowing adopters to select the portlets they want and provide their own DAO implementations for those portlets (ideally leveraging as much of the existing as possible by copy/paste example if nothing else), I think the project will be much farther along in the mid-term and adopted more widely. |
|
RE PortletRequest going into the DAO layer, as I mentioned in a previous comment I too was initially resistant to the abstraction boundary transgression, but at least in this case I think there is merit to allowing the PortletRequest into the DAO layer. The specific use case was that we are passing through the employeeID to the dao to access the data. It came up that they also need userID (don't recall exact reasons if for audit logging or security). Though I am OK with changing the API to pass in two strings; userId and employeeId as I can see those two potentially being required, I think we have an expectation in this case for different dao implementations that may have additional needs and it may be helpful to be able to leverage portlet preferences or different request attributes in the dao implementations, say a portlet preference for a template URL to access Peoplesoft or other HR system, or a need to get the Client IP (which I'm specifically passing in but wouldn't need to with PortletRequest) or potentially other user attributes for auditing purposes. I agree there is not a strong compelling case requiring PortletRequest in the dao layer. It is a convenience (suggested by Drew based on his experiences) to make the API less brittle in certain use cases where the API's definition is likely to need to be more flexible. I felt in this case the benefit of flexibility out weighted the negatives of a strong, rigid boundary. |
I too am hopeful for collaboration opportunities over the long term. We're disagreeing about whether this is the juncture at which the long term has arrived.
The same thing we're gaining when portlets that don't technically benefit from being co-located are smashed into the same portlet application, and each adopter then teases them back apart to implement nonintersecting sets of portlets using nonintersecting portions of the APIs and implementations? Which is, to be sure, greater than zero gain, in that at least some of the code is publicly available and transparent. Much to be said for sharing code publicly. But that public availability and transparency doesn't require sharing a Were this pull request merged, everyone would still have their own portlets, and their own backing APIs and implementations, it's just some of those are in the same repo rather than in separate repos. We're not so much collaborating here yet -- we're sharing parallel code. A would-be adopter of the (have I mentioned, really quite attractive?) new time entry portlet is going to have a cleaner, clearer, better starting point if that portlet is factored as its own project carrying only the dependencies and complexity that it's using. That would-be adopter doesn't need to know anything about existing Existing By all means, let's revisit this when the codebases mature to the point where either or both benefit from the other. Future BYU phases in which existing |
|
wrt making context about the currently authenticated user available through the whole stack request-to-response:
Consider implementing Spring Security for this kind of thing. Components can get the currently applicable |
|
A few (important!) words on passing a PortletRequest beyond a controller class... This is a strategy Jen & I had developed over several quarters and many conversations. It has payed off hugely, and I consider it a keystone design principal. This idea doesn't, however, mean passing a PortletRequest to every type imaginable, every method. In open source portlet development, we often found we needed pluggable strategies for things like...
We don't have the resources to build and maintain separate Blackboard, Moodle, and Sakai portlets, so we focused on a single Courses portlet with pluggable data sources. (Even if we had the resources, separate portlets would be wasting them... there are better uses for those resources.) These "pluggable" data sources are of course based on Java interfaces and basic polymorphism. Initially, we took the approach of putting method signatures on these interfaces based on simple types -- int, String, Date, etc. But we found that -- using this approach -- we were creating interfaces that looked too much like the first (initial) implementation, and that forced us to revise those interfaces constantly as we added support for new data sources. This is not a good situation to be in. Many adopters of Apereo open source portlets have custom data sources that pull data (e.g.) from custom database tables or a custom RESTful JSON API. When you make changes to the interfaces for data sources, now newer versions of the portlet are not drop-in replacements... they're projects. We encourage folks to stay current. One way we make that more feasible for them is by making newer builds of portlets backwards-compatible with the work they did previously developing custom data sources. So we developed a design pattern focused on making these interfaces as non-brittle as possible. The NotificationPortlet is a decent example. It's interface for data sources is called INotificationService: https://github.com/Jasig/NotificationPortlet/blob/master/notification-portlet-webapp/src/main/java/org/jasig/portlet/notice/INotificationService.java. We have several implementations of INotificationService that variously rely (iirc) on...
Passing the PortletRequest to data sources keeps the world of possibilities open to them, as well as quite a bit of clutter out of the controllers. It's cleaner for the controllers not to know how the data sources go about gathering data. It's less clean for implementation details of data sources to "spill out" into the controllers... particularly when you need to support very different strategies simultaneously. The best reason I can think of not to pass a PortletRequest to a service interface would be if I thought it would need to be called outside a portlet lifecycle phase -- such as in a ServletRequest or in a Quartz job. (Though the first of those is easily handled with a method overload.) As long as a Java interface of class is compiled and packaged as a portlet project war file, chances are code that its methods will be invoked within portlet lifecycle phases. So this pattern/recommendation would not apply (necessarily) to utility jars that are pulled into a portlet project as a <dependency>. But in the paragraph above I used the expression service interface. I prefer this term or the term "data source" for where this pattern/advice applies. Often this structure might go something like...
Where the Java types with an asterisk (*) would get a reference to the PortletRequest. In this pattern...
I consider all 3 of these points to be advantages. AFAIK most of the portlet code since uMobile has been developed with these principals. |
|
So, without including the
Right? |
|
That works, in the majority of cases, but it strikes me more as a workaround for an uncommon need (and a very handy) than a mainstream way of modeling a core interaction between 2 layers or components. If my SpecialFooImpl class gets the request from the RequestContextHolder, how does client code know I need or use one? What if I want to narrow my domain behavior implementing method to an ActionRequest? |
|
Changed |
|
Thanks for the clarification @drewwills . That seems to make sense to me. I think that we could go either way with separate portlets for everything or a bundle. As long as we can choose not to implement specific portlets I think we are okay with having them bundled. +1 for merge. |
HRSPLT-191 add Year End Leave Balance link.
|
@jameswennmacher Merge conflicts have cropped up, would you be able to resolve them? |
jameswennmacher commentedMar 18, 2014
Submitting HRS portlets from BYU:
The two portlets are by default wired to an in-memory demo dao data source to allow them to be registered and added to a page for demo purposes. For actual use the dao would need to be replaced with an implementation that calls to Peoplesoft and populates the model, and sends model updates to Peoplesoft.
Notes:
To test: