DUE TO SPAM, SIGN-UP IS DISABLED. Goto Selfserve wiki signup and request an account.
In an attempt to help determine the cause of the intermittent REST call failures in the juneau-microservice-test project, I've made some debugging enhancements to the microservice.
The test project will now generate 4 log files...
The jetty-thread-dump.log file is generated by Jetty on the event of a failed request and shows a snapshot of the current state of the threads in Jetty. The retry handler in TestMicroservice is shown below and will stop the tests as soon as the first error occurs...
new HttpRequestRetryHandler() {
@Override
public boolean retryRequest(IOException exception, int executionCount, HttpContext context) {
System.err.println("*** ERROR ***");
TestMicroservice.jettyDump();
microservice.stop();
System.exit(2);
return true;
}
}
public static void jettyDump() {
try {
String dump = microservice.getServer().dump();
IOUtils.pipe(dump, new FileWriter(microservice.getConfig().getString("Logging/logDir") + "/jetty-thread-dump.log"));
} catch (Exception e) {
e.printStackTrace();
}
}
The jetty-requests.log is generated by adding this to the jetty.xml file...
<Set name="requestLog">
<New id="RequestLogImpl" class="org.eclipse.jetty.server.NCSARequestLog">
<Set name="filename"><Property name="jetty.logs" default="$C{Logging/logDir,logs}"/>/jetty-requests.log</Set>
<Set name="filenameDateFormat">yyyy_MM_dd</Set>
<Set name="LogTimeZone">GMT</Set>
<Set name="retainDays">90</Set>
<Set name="append">false</Set>
<Set name="LogLatency">true</Set>
</New>
</Set>
This just shows if Jetty actually got the request from the last call.
Note that the jetty.xml file now supports embedded Juneau variables (e.g. $C{Logging/logDir}).
I also created a separate Jetty section in the config file...
#=======================================================================================================================
# Jetty settings
#=======================================================================================================================
[Jetty]
# Path of the jetty.xml file used to configure the Jetty server.
config = jetty.xml
# Resolve Juneau variables in the jetty.xml file.
resolveVars = true
# Port to use for the jetty server.
# You can specify multiple ports. The first available will be used. '0' indicates to try a random port.
# The resulting available port gets set as the system property "availablePort" which can be referenced in the
# jetty.xml file as "$S{availablePort}" (assuming resolveVars is enabled).
port = 10000,0,0,0
Note that we now support dynamically specifying the port again through the use of the $S{availablePort} variable.
The test.0.log file now contains the entire debug logging generated by Jetty (yea...it's big...7.8MB). I had to create a new org.apache.juneau.microservice.JettyLogger class to serve as a simple bridge between Jetty logging and java-util-logging, but it now allows all the logging to be directed to a single log file. The configuration for doing this is shown below:
[Logging]
logDir = $S{user.dir}/target/logs
logFile = test.%g.log
levels =
{
'':'WARNING',
org.apache.juneau: 'WARNING',
org.eclipse.jetty: 'FINEST'
}
consoleLevel = WARNING
fileLevel = FINEST
[SystemProperties]
org.eclipse.jetty.util.log.class = org.apache.juneau.microservice.JettyLogger
org.eclipse.jetty.LEVEL = ALL
If an error is occurring somewhere in Jetty, it should show up here.
The third-party-proxy-resource.txt file contains log entries from RestHooks added to the ThirdPartyProxyResource class (which seems to be a common place for failure). This makes sure that the failure isn't occurring within our logic. If we see START/PRE/POST/END messages for our calls, then we know we're not causing a problem.
public static FileWriter logFile;
static {
try {
logFile = new FileWriter("./target/logs/third-party-proxy-resource.txt", false);
} catch (IOException e) {
e.printStackTrace();
}
}
@RestHook(HookEvent.START_CALL)
public static void startCall(HttpServletRequest req) {
try {
logFile.append("START["+new Date()+"]-").append(req.getQueryString()).append("\n");
logFile.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@RestHook(HookEvent.PRE_CALL)
public static void preCall(HttpServletRequest req) {
try {
logFile.append("PRE["+new Date()+"]-").append(req.getQueryString()).append("\n");
logFile.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@RestHook(HookEvent.POST_CALL)
public static void postCall(HttpServletRequest req) {
try {
logFile.append("POST["+new Date()+"]-").append(req.getQueryString()).append("\n");
logFile.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
@RestHook(HookEvent.END_CALL)
public static void endCall(HttpServletRequest req) {
try {
Exception e = (Exception)req.getAttribute("Exception");
Long execTime = (Long)req.getAttribute("ExecTime");
logFile.append("END["+new Date()+"]-").append(req.getQueryString()).append(", time=").append(""+execTime).append(", exception=").append(e == null ? null : e.toString()).append("\n");
logFile.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
If you do encounter testcase failure due to HTTP request errors, I recommend using WireShark listening on port 10001:
This should help show whether the requests appear to be failing on the server or client side...
FYI...
I've made some significant modifications to the Javadoc overview document so that it mirrors the new Maven artifacts...
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html
The Introduction has been rewritten:
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html#Intro
I've also added a new section describing how to use Juneau REST with Spring:
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html#juneau-rest-server.Injection
The ObjectMap section contains new information:
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html#juneau-marshall.ObjectMap
There's a new section on using HTTP/2 features, but it's just a placeholder for now:
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html#juneau-rest-server.HTTP2
The packaging has been updated for the template and examples projects so that they're contained within self-encapsulated zip files that can be easily imported into a stand-alone Eclipse workspace. The docs have been updated to reflect these changes:
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html#juneau-microservice-template
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html#juneau-examples-core
http://juneau.incubator.apache.org/site/apidocs/overview-summary.html#juneau-examples-rest
I've created an updated "about" page on the website. It's currently not linked, but can be viewed via this URL...
http://juneau.incubator.apache.org/index2.html
I've tried to break up the information based on the Maven artifacts.
Let me know your thoughts....
FYI.....I plan on reworking the Juneau projects to use nested feature group projects. The groups will be as follows...
- juneau-core - Core projects.
- juneau-rest - REST and Microservice projects.
- juneau-examples - Example projects.
- juneau-releng - Release engineering projects.
The layout will be as follows....
Note: The juneau-rest-test project prereqs the juneau-microservice project which is why the REST and microservice projects are in the same group.
Let me know if anyone has any concerns with this change.
The Juneau serializers will now serialize the contents of Readers and InputStreams directly to the output stream. They used to just call call the toString() method on these objects (which wasn't particularly useful).
When used in conjunction with swaps, you can now provide low-level serialization of POJOs for any of the non-RDF languages:
@Pojo(swap=MyBeanSwap.class)
public class MyBean {...}
public class MyBeanSwap extends PojoSwap<MyBean,Object> {
public Object swap(BeanSession session, MyPojo o) throws Exception {
MediaType mt = session.getMediaType();
if (mt.hasSubType("json"))
return new StringReader("{foo:'bar'}"); // Custom JSON output
return o; // Otherwise treat as normal bean
}
}
// Produces "{foo:'bar'}"
String json = JsonSerializer.DEFAULT_LAX.toString(new MyBean());
* This doesn't work for RDF because we don't have access to the underlying stream. Instead, we use an intermediate Jena model. In this case, the contents of the Reader/InputStream are converted to a literal.
This is a stepping stone to supporting FreeMarker template support for POJOs. Instead of a solution for just HTML, I'm interested in finding a generalized template solution for any language that builds upon the existing Swap API.
The general idea is that we can define template strings against POJOs using an annotation such as the following:
@Pojo(templates={"freemarker:/MemberAddressRender.div.ftl"})
public class MemberAddress {
}
Or they can be defined on @RestResource and @RestMethod annotations...
@RestMethod(
templates={
"MemberAddress=freemarker:/MemberAddressRender.div.ftl"
},
templateEngines={
FreeMarkerTemplateEngine.class
}
)
Engines would be tied to media types. So for example, the FreeMarkerTemplateEngine would be mapped to the "text/html" media type and thus only be invoked by HtmlSerializer. The engine would swap out POJOs with either Readers or HTML5 beans.
I've added a new annotation that resolves the feature request shown below.
It also adds the capability for performing child resource initialization before parent resource initialization.
The change replaces the various servlet and REST call lifecycle methods with a single unifying method annotation:
http://juneau.incubator.apache.org/site/apidocs/org/apache/juneau/rest/annotation/RestHook.html
One of the nice features is that you can add as many of these annotated methods as you like to your class. For example, instead of a single init(RestConfig) method, just add your own init methods and never have to worry about calling super.init(RestConfig).
In order to get this to work, I did have to make the RestServlet.init(ServletConfig) final so that it can no longer be extended. The normal HttpServlet.init() method can still be overridden, however it's probably best-practice to just use @RestHook(INIT).
I debated whether to keep these, but decided to remove them from the RestServlet class to keep things simple:
init(RestConfig)- UseHookEvent.INITinstead.onSuccess(RestRequest, RestResponse, long)- UseHookEvent.END_CALLinstead.onPreCall(RestRequest)- UseHookEvent.PRE_CALLinstead.onPostCall(RestRequest, RestResponse)- UseHookEvent.POST_CALLinstead.
Some updates have been made around the use of Jetty in the microservice API:
- The version of Jetty has been upgraded to v9.4.6.v20170531
- The microservice now ships with a default jetty.xml file that can be used to configure Jetty. Settings related to authentication and SSL have been removed from the cfg file. You can still define resource mappings and used ports in the cfg file, but the default behavior is to specify these in the jetty.xml file.
- The introduction of the new Jetty version required me to bump up the javax.servlet version from 3.0.1 to 3.1.0 and add some minor code changes. This is going to require at least a minor version bump.
Some improvements I've just added surrounding the menu-item widgets:
- Widget classes can now be defined as inner-classes of resources.
- New Tooltip template class has been added that allows you to programmatically specify a tooltip. It can be mixed with arbitrary HTML5 beans.
I've added a new menu-item example to the Pet Store resource complete with tooltips:
The menu item is defined as an inner class on the PetStoreResource class and gets added via a new addPet() method...
/**
* Sample REST resource that renders summary and detail views of the same bean.
*/
@RestResource(
...
)
public class PetStoreResource extends ResourceJena {
@RestMethod(name="POST", path="/")
public Redirect addPet(@Body Pet pet) throws Exception {
this.petDB.put(pet.id, pet);
return new Redirect("servlet:/");
}
/**
* Renders the "ADD" menu item.
*/
public class AddPet extends MenuItemWidget {
@Override
public String getLabel(RestRequest req) throws Exception {
return "add";
}
@Override
public Object getContent(RestRequest req) throws Exception {
return div(
form().id("form").action("servlet:/").method("POST").children(
table(
tr(
th("ID:"),
td(input().name("id").type("number").value(getNextAvailableId())),
td(new Tooltip("(?)", "A unique identifer for the pet.", br(), "Must not conflict with existing IDs"))
),
tr(
th("Name:"),
td(input().name("name").type("text")),
td(new Tooltip("(?)", "The name of the pet.", br(), "e.g. 'Fluffy'"))
),
tr(
th("Kind:"),
td(
select().name("kind").children(
option("CAT"), option("DOG"), option("BIRD"), option("FISH"), option("MOUSE"), option("RABBIT"), option("SNAKE")
)
),
td(new Tooltip("(?)", "The kind of animal."))
),
tr(
th("Breed:"),
td(input().name("breed").type("text")),
td(new Tooltip("(?)", "The breed of animal.", br(), "Can be any arbitrary text"))
),
tr(
th("Gets along with:"),
td(input().name("getsAlongWith").type("text")),
td(new Tooltip("(?)", "A comma-delimited list of other animal types that this animal gets along with."))
),
tr(
th("Price:"),
td(input().name("price").type("number").placeholder("1.0").step("0.01").min(1).max(100)),
td(new Tooltip("(?)", "The price to charge for this pet."))
),
tr(
th("Birthdate:"),
td(input().name("birthDate").type("date")),
td(new Tooltip("(?)", "The pets birthday."))
),
tr(
td().colspan(2).style("text-align:right").children(
button("reset", "Reset"),
button("button","Cancel").onclick("window.location.href='/'"),
button("submit", "Submit")
)
)
).style("white-space:nowrap")
)
);
}
}
// Poormans method for finding an available ID.
private int getNextAvailableId() {
int i = 100;
for (Integer k : petDB.keySet())
i = Math.max(i, k);
return i+1;
}
}
The Tooltip class is a simple POJO with a swap method that wraps the contents in a Div tag....
public class Tooltip {
private final String display;
private final List<Object> content;
/**
* Constructor.
*
* @param display
* The normal display text.
* This is what gets rendered normally.
* @param content
* The hover contents.
* Typically a list of strings, but can also include any HTML5 beans as well.
*/
public Tooltip(String display, Object...content) {
this.display = display;
this.content = new ArrayList<Object>(Arrays.asList(content));
}
/**
* The swap method.
*
* <p>
* Converts this bean into a div tag with contents.
*
* @param session The bean session.
* @return The swapped contents of this bean.
*/
public Div swap(BeanSession session) {
return div(
small(display),
span()._class("tooltiptext").children(content)
)._class("tooltip");
}
}
This is a notification of changes being made internally in the serializers and parsers. Existing APIs and behavior are unchanged.
The major change is that SerializerSession and ParserSession objects are now reusable as long as they're reused within the same thread. They used to be throw-away objects that would only exist for the duration of a single serialization/parse.
// Old way (still works)
|
The original driver behind this change was so that the existing HtmlSerializerSession can be used in the MenuItemWidget (and other widgets) to serialize POJOs to match the formatting rules on the rest of the page.
Internally, the actual serialization and parsing code has been moved from the Serializer/Parser subclasses into the Session classes. This had the nice side effect of allowing me to eliminate many of the getter methods on the session objects and replace them with references to private final fields which cleaned up the code quite a bit. There might be some slight performance improvements, but the GIT compiler was probably already inlining much of this code.
Session objects are rather cheap to create, but they do still involve setting 20+ private final fields. So I can envision the sessions being reused when ultra-efficiency is needed. But again the usecase would be limited since they're not designed to be thread safe.
Objective:
Bind a specific POJO to a freemarker template and render the object as an html element.
Steps:
First, your project should include freemarker:
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
<version>2.3.26-incubating</version>
</dependency>
Next, you need a proper pojo containing multiple fields. Here's the pojo I'm trying to customize:
import java.io.Serializable;
import javax.annotation.Generated;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.juneau.annotation.BeanProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
"addr1",
"addr2",
"country",
"city",
"state",
"zip"
})
public class Adr implements Serializable
{
@JsonProperty("addr1")
@BeanProperty("addr1")
private String addr1;
@JsonProperty("addr2")
@BeanProperty("addr2")
private String addr2;
@JsonProperty("country")
@BeanProperty("country")
private String country;
@JsonProperty("city")
@BeanProperty("city")
private String city;
@JsonProperty("state")
@BeanProperty("state")
private String state;
@JsonProperty("zip")
@BeanProperty("zip")
private String zip;
/**
*
* @return
* The addr1
*/
@JsonProperty("addr1")
@BeanProperty("addr1")
public String getAddr1() {
return addr1;
}
/**
*
* @param addr1
* The addr1
*/
@JsonProperty("addr1")
@BeanProperty("addr1")
public void setAddr1(String addr1) {
this.addr1 = addr1;
}
public Adr withAddr1(String addr1) {
this.addr1 = addr1;
return this;
}
/**
*
* @return
* The addr2
*/
@JsonProperty("addr2")
@BeanProperty("addr2")
public String getAddr2() {
return addr2;
}
/**
*
* @param addr2
* The addr2
*/
@JsonProperty("addr2")
@BeanProperty("addr2")
public void setAddr2(String addr2) {
this.addr2 = addr2;
}
public Adr withAddr2(String addr2) {
this.addr2 = addr2;
return this;
}
/**
*
* @return
* The country
*/
@JsonProperty("country")
@BeanProperty("country")
public String getCountry() {
return country;
}
/**
*
* @param country
* The country
*/
@JsonProperty("country")
@BeanProperty("country")
public void setCountry(String country) {
this.country = country;
}
public Adr withCountry(String country) {
this.country = country;
return this;
}
/**
*
* @return
* The city
*/
@JsonProperty("city")
@BeanProperty("city")
public String getCity() {
return city;
}
/**
*
* @param city
* The city
*/
@JsonProperty("city")
@BeanProperty("city")
public void setCity(String city) {
this.city = city;
}
public Adr withCity(String city) {
this.city = city;
return this;
}
/**
*
* @return
* The state
*/
@JsonProperty("state")
@BeanProperty("state")
public String getState() {
return state;
}
/**
*
* @param state
* The state
*/
@JsonProperty("state")
@BeanProperty("state")
public void setState(String state) {
this.state = state;
}
public Adr withState(String state) {
this.state = state;
return this;
}
/**
*
* @return
* The zip
*/
@JsonProperty("zip")
@BeanProperty("zip")
public String getZip() {
return zip;
}
/**
*
* @param zip
* The zip
*/
@JsonProperty("zip")
@BeanProperty("zip")
public void setZip(String zip) {
this.zip = zip;
}
public Adr withZip(String zip) {
this.zip = zip;
return this;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(addr1).append(addr2).append(country).append(city).append(state).append(zip).toHashCode();
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if ((other instanceof Adr) == false) {
return false;
}
Adr rhs = ((Adr) other);
return new EqualsBuilder().append(addr1, rhs.addr1).append(addr2, rhs.addr2).append(country, rhs.country).append(city, rhs.city).append(state, rhs.state).append(zip, rhs.zip).isEquals();
}
}
Next, create a freemarker template to translate an instance of this bean type into a specific HTML element.
Here's an example that renders the address class as a DIV, using a conditional on the addr2 field, and combining city state and zip into a single row.
<div>
<table>
<tr>
<td>
${addr1}
</td>
</tr>
<#if addr2?has_content>
<tr>
<td>
${addr2}
</td>
</tr>
</#if>
<tr>
<td>
${city}, ${state} ${zip}
</td>
</tr>
</table>
</div>
Next, you need to create an HtmlRender class for this type of bean. For example:
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import org.apache.juneau.dto.html5.Div;
import org.apache.juneau.dto.html5.Form;
import org.apache.juneau.html.HtmlParser;
import org.apache.juneau.html.HtmlRender;
import org.apache.juneau.serializer.SerializerSession;
import Adr;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Locale;
import static org.apache.juneau.dto.html5.HtmlBuilder.div;
import static org.apache.juneau.dto.html5.HtmlBuilder.h2;
/**
* Created by sblackmon on 7/17/17.
*/
public class MemberAddressRender extends HtmlRender<Adr> {
static Configuration cfg = new Configuration(Configuration.VERSION_2_3_26);
static {
cfg.setClassLoaderForTemplateLoading(MemberAddressRender.class.getClassLoader());
cfg.setEncoding(Locale.US, Charset.defaultCharset().name());
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.HTML_DEBUG_HANDLER);
}
@Override
public Div getContent(SerializerSession session, Adr value) {
StringWriter stringWriter = new StringWriter();
try {
Template myTemplate = cfg.getTemplate("MemberAddressRender.div.ftl");
myTemplate.process(value, stringWriter);
} catch( Exception ex ) {
ex.printStackTrace();
}
String divString = stringWriter.toString();
Div div = div();
try {
div = HtmlParser.DEFAULT.parse(divString, Div.class);
} catch( Exception ex ) {
ex.printStackTrace();
}
return div;
}
}
Finally, you need to bind the custom Render class onto the class(es) which have the class you are customizing as a child.
import org.apache.juneau.html.annotation.Html;
import Adr;
import Member;
/**
* Created by sblackmon on 7/17/17.
*/
public class MemberHtml extends Member {
@Override
@Html(link = "servlet:/{username}")
public String getUsername() {
return super.getUsername();
}
@Override
@Html(render=MemberAddressRender.class)
public Adr getAdr() {
return super.getAdr();
}
@Override
@Html(render=MemberImageRender.class)
public String getPicture() {
return super.getPicture();
}
}
In case you are interested, here's the auto-generated Member class whose presentation is being customized.
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.annotation.Generated;
import javax.validation.Valid;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyOrder;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.juneau.annotation.BeanProperty;
@JsonInclude(JsonInclude.Include.NON_NULL)
@Generated("org.jsonschema2pojo")
@JsonPropertyOrder({
"username",
"email",
"picture",
"adr",
"bday",
"tel",
"title",
"url",
"skills",
"profiles"
})
public class Member implements Serializable
{
@JsonProperty("username")
@BeanProperty("username")
private String username;
@JsonProperty("email")
@BeanProperty("email")
private String email;
@JsonProperty("picture")
@BeanProperty("picture")
private String picture;
@JsonProperty("adr")
@BeanProperty("adr")
@Valid
private Adr adr;
@JsonProperty("bday")
@BeanProperty("bday")
private String bday;
@JsonProperty("tel")
@BeanProperty("tel")
private String tel;
@JsonProperty("title")
@BeanProperty("title")
private String title;
@JsonProperty("url")
@BeanProperty("url")
private String url;
@JsonProperty("skills")
@BeanProperty("skills")
@Valid
private List<Skill> skills = new ArrayList<Skill>();
@JsonProperty("profiles")
@BeanProperty("profiles")
@Valid
private Profiles profiles;
/**
*
* @return
* The username
*/
@JsonProperty("username")
@BeanProperty("username")
public String getUsername() {
return username;
}
/**
*
* @param username
* The username
*/
@JsonProperty("username")
@BeanProperty("username")
public void setUsername(String username) {
this.username = username;
}
public Member withUsername(String username) {
this.username = username;
return this;
}
/**
*
* @return
* The email
*/
@JsonProperty("email")
@BeanProperty("email")
public String getEmail() {
return email;
}
/**
*
* @param email
* The email
*/
@JsonProperty("email")
@BeanProperty("email")
public void setEmail(String email) {
this.email = email;
}
public Member withEmail(String email) {
this.email = email;
return this;
}
/**
*
* @return
* The picture
*/
@JsonProperty("picture")
@BeanProperty("picture")
public String getPicture() {
return picture;
}
/**
*
* @param picture
* The picture
*/
@JsonProperty("picture")
@BeanProperty("picture")
public void setPicture(String picture) {
this.picture = picture;
}
public Member withPicture(String picture) {
this.picture = picture;
return this;
}
/**
*
* @return
* The adr
*/
@JsonProperty("adr")
@BeanProperty("adr")
public Adr getAdr() {
return adr;
}
/**
*
* @param adr
* The adr
*/
@JsonProperty("adr")
@BeanProperty("adr")
public void setAdr(Adr adr) {
this.adr = adr;
}
public Member withAdr(Adr adr) {
this.adr = adr;
return this;
}
/**
*
* @return
* The bday
*/
@JsonProperty("bday")
@BeanProperty("bday")
public String getBday() {
return bday;
}
/**
*
* @param bday
* The bday
*/
@JsonProperty("bday")
@BeanProperty("bday")
public void setBday(String bday) {
this.bday = bday;
}
public Member withBday(String bday) {
this.bday = bday;
return this;
}
/**
*
* @return
* The tel
*/
@JsonProperty("tel")
@BeanProperty("tel")
public String getTel() {
return tel;
}
/**
*
* @param tel
* The tel
*/
@JsonProperty("tel")
@BeanProperty("tel")
public void setTel(String tel) {
this.tel = tel;
}
public Member withTel(String tel) {
this.tel = tel;
return this;
}
/**
*
* @return
* The title
*/
@JsonProperty("title")
@BeanProperty("title")
public String getTitle() {
return title;
}
/**
*
* @param title
* The title
*/
@JsonProperty("title")
@BeanProperty("title")
public void setTitle(String title) {
this.title = title;
}
public Member withTitle(String title) {
this.title = title;
return this;
}
/**
*
* @return
* The url
*/
@JsonProperty("url")
@BeanProperty("url")
public String getUrl() {
return url;
}
/**
*
* @param url
* The url
*/
@JsonProperty("url")
@BeanProperty("url")
public void setUrl(String url) {
this.url = url;
}
public Member withUrl(String url) {
this.url = url;
return this;
}
/**
*
* @return
* The skills
*/
@JsonProperty("skills")
@BeanProperty("skills")
public List<Skill> getSkills() {
return skills;
}
/**
*
* @param skills
* The skills
*/
@JsonProperty("skills")
@BeanProperty("skills")
public void setSkills(List<Skill> skills) {
this.skills = skills;
}
public Member withSkills(List<Skill> skills) {
this.skills = skills;
return this;
}
/**
*
* @return
* The profiles
*/
@JsonProperty("profiles")
@BeanProperty("profiles")
public Profiles getProfiles() {
return profiles;
}
/**
*
* @param profiles
* The profiles
*/
@JsonProperty("profiles")
@BeanProperty("profiles")
public void setProfiles(Profiles profiles) {
this.profiles = profiles;
}
public Member withProfiles(Profiles profiles) {
this.profiles = profiles;
return this;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this);
}
@Override
public int hashCode() {
return new HashCodeBuilder().append(username).append(email).append(picture).append(adr).append(bday).append(tel).append(title).append(url).append(skills).append(profiles).toHashCode();
}
@Override
public boolean equals(Object other) {
if (other == this) {
return true;
}
if ((other instanceof Member) == false) {
return false;
}
Member rhs = ((Member) other);
return new EqualsBuilder().append(username, rhs.username).append(email, rhs.email).append(picture, rhs.picture).append(adr, rhs.adr).append(bday, rhs.bday).append(tel, rhs.tel).append(title, rhs.title).append(url, rhs.url).append(skills, rhs.skills).append(profiles, rhs.profiles).isEquals();
}
}
Great. Now any resource which returns a MemberHtml or a List<MemberHtml> to the Juneau HTML Serializer will see the freemarker template applied to any Adr instances within the appropriate cell.
BEFORE:
AFTER:
The @RestMethod.bpIncludes()/bpExcludes() annotations have been replaced simplified syntax.
Instead of LAX JSON, it's now an array of simple key/value pairs and the names have been shortened:
// Old:
@RestMethod(
bpExcludes="{Bean1: 'foo,bar', '*': 'baz,qux'}"
)
// New:
@RestMethod(
bpx={"Bean1: foo,bar", "*: baz,qux"}
)
See:
http://juneau.incubator.apache.org/site/apidocs/org/apache/juneau/rest/annotation/RestMethod.html#bpi--
http://juneau.incubator.apache.org/site/apidocs/org/apache/juneau/rest/annotation/RestMethod.html#bpx--
I've simplified the way the headers are defined on the HTML pages.
I've eliminated the @HtmlDoc.title()/description()/branding() annotations (and related methods and properties). Instead, you just use the @HtmlDoc.header() with variables to define the header.
The default header value is defined in RestServletDefault as shown:
You can easily override this value to specify any value you wish. This makes the header much more flexible and easier-to-understand.
The $R variable was changed to a DefaultingVar so that you can chain possible values to find a first-match (as shown above).
I've also added a new $F variable for pulling in values directly from localized files if you don't want to embed a bunch of HTML inside your annotations:
It's similar to widgets except it resolves to just arbitrary text (and doesn't support javascript and css like widgets do).
It's also similar to Class.getResourceAsStream(), but searches up the servlet class hierarchy for the file. It can even pull the file from the JVM working directory as last resort.
It also supports localized names based on the request Accept-Language. So in the example above, if the request language is Japanese, we'll also search for DockerRegistryResourceAside_ja_JP.html and DockerRegistryResourceAside_ja.html.
Like widgets, it also strips comments from HTML files so you can add Apache license headers to your files.
Javadoc is here: http://juneau.incubator.apache.org/site/apidocs/org/apache/juneau/rest/vars/FileVar.html
Just like widgets, the $F variable file contents can contain other variables. (Heck...it can even contain other $F variables if you want to do fancy recursive nesting of files)
The full list of variables has been update here and linked to from various locations where they can be used:
http://juneau.incubator.apache.org/site/apidocs/org/apache/juneau/rest/RestContext.html#getVarResolver--
I've added an "INHERIT" literal string that can be used in the @HtmlDoc annotation to inherit and extend values from the servlet class.
Where this is useful is when you want to define a a set of links on a class, but want to add an additional link on a particular method. Previously this meant you had to duplicate all the links on the method annotation such as shown below from the PetStore example:
Links defined on class:
Links overridden on method to add a single QUERY menu item:
With the "INHERIT" literal, you can include the parent links and augment them with more links like so:
The parent links are included in the position specified in the array, so you can include links before or after the list parent links, or you can optionally use square brackets to specify an index position if you want to insert your link in the middle of the parent links.
The "INHERIT" literal can be used in any of the following @HtmlDoc values: header, script, style, links, aside, footer.
When used on the @HtmlDoc in the @RestResource annotation, it inherits the value from the parent class.
I've made several overall improvements to the stylesheet support in the HTML views.
- I've simplified the CSS stylesheets. It should be easier to create new stylesheets.
- The nav links are now a <OL> instead of a span of hardcoded links. Makes it easier to customize the LAF of the nav section through CSS.
- New "light" stylesheet...
For reference, this is the "devops" style...
And this is the "original" style... - I've added a menu-time to the examples that allow you to view the different styles...















