element that will contain the consultant results using it’s element ID and use the jQuery append function to append the consultantHtml.
When the page is fully rendered, the resulting HTML will be:
<table width="100%">
<thead>
<tr>
<th>
Consultant
</th>
<th>
Blog Feed
</th>
</tr>
<tbody id="consultants">
<tr>
<td>
Jacob Orshalick
</td>
<td>
<a target="_blank"
href="http://solutionsfit.com/blog/feed/">
Blog Link
</a>
</td>
</tr>
<tr>
<td>
Nirav Assar
</td>
<td>
<a target="_blank" href=
"http://assarconsulting.blogspot.com/feeds/posts/default/">
Blog Feed
</a>
</td>
</tr>
</tbody>
</table>
Note that only JavaScript libraries and HTML have been used to consume the service. This provides the flexibility to render this content in a web application, a portal, or even a static HTML page!
That’s it for round one of enterprise mashups. Stay tuned for part 2 which will discuss how to gather data from across the enterprise with JSONP and how accessing secured services is made easy.
Enjoyed this post? Share it! |
Posted in JAX-RS, JBoss Seam, RESTEasy, jQuery
2 Comments »
April 18, 2010
If you are developing RESTful services that will be consumed by AJAX clients on different servers, you will likely need to support JSONP. JSONP allows your RESTful web services to support cross-domain communication by enabling your clients to bypass the same-origin policy browser restriction. While some JAX-RS implementations support JSONP, this article demonstrates how any JAX-RS web service can support JSONP through a servlet filter.
Why you would use JSONP
Cross-domain communication is a common problem when developing rich web clients that utilize RESTful web services. Browsers impose the same-origin policy which is described in depth in: Cross-domain communications with JSONP.
To paraphrase the problem, a script loaded from one location, say http://solutionsfit.com/blog/, could not execute an AJAX request that gets properties from a service outside of the domain solutionsfit.com. The diagram below describes this scenario.
If the AJAX request to geonames.org returned a basic JSON response, the browser would not allow access to this data. This is a problem for many AJAX applications, especially mashups, which may access a number of resources to generate content. JSONP (JSON with padding) solves this problem by wrapping the returned data with a function.
The function is invoked as a callback once the AJAX call completes with the JSON results passed as an argument. This requires that the callback function be defined in the web page. So, in the diagram above, if the response from geonames.org returns a function that takes the JSON result as an argument, we can bypass the same-origin policy. This of course requires the web service being invoked to support JSONP.
Creating a Servlet Filter to process JSONP requests
JAX-RS does not support JSONP by default. Some implementations provide an extension to produce JSONP content but some do not (see the RESTEasy JIRA issue). As a Seam user, RESTEasy is the perfect option for exposing RESTful services due to it’s tight integration. As RESTEasy does not currently support JSONP, I needed a solution. Fortunately, you can add support of JSONP using a servlet filter. The following implementation is a naive approach, but shows the general idea.
public class JSONPRequestFilter
extends org.jboss.seam.web.AbstractFilter {
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
if (!(request instanceof HttpServletRequest)) {
throw new ServletException("This filter can " +
" only process HttpServletRequest requests");
}
HttpServletRequest httpRequest = (HttpServletRequest) request;
if(isJSONPRequest(request))
{
ServletOutputStream out = response.getOutputStream();
out.println(getCallbackParameter(httpRequest) + "(");
chain.doFilter(request, response);
out.println(");");
response.setContentType("text/javascript");
}
else
{
chain.doFilter(request, response);
}
}
private String getCallbackMethod(HttpServletRequest httpRequest)
{
return httpRequest.getParameter("callback");
}
private boolean isJSONPRequest(HttpServletRequest httpRequest)
{
String callbackMethod = getCallbackMethod(httpRequest);
return (callbackMethod != null && callbackMethod.length() > 0);
}
}
This filter processes any request that provides a callback function parameter (the signature of a JSONP request). It wraps the JSON return data with a function, the value of the callback parameter. In my appication I needed to support multiple media return types so I implemented a more robust approach that checks the Accept header to verify that text/javascript or application/javascript is an accepted media type. It also wraps the HttpServletRequest to inform RESTEasy that application/json is the preferred media type. This ensures that RESTEasy provides a JSON response.
Configuring the JSONP Servlet Filter
To configure the filter to apply to RESTful web service requests, you would simply add the following to your project’s web.xml.
<filter>
<filter-class>com.solutionsfit.rest.JSONPRequestFilter</filter-class>
<filter-name>JSONPRequestFilter</filter-name>
</filter>
<filter-mapping>
<filter-name>JSONPRequestFilter</filter-name>
<url-pattern>/seam/resource/rest/*</url-pattern>
</filter-mapping>
The url-pattern should match the base URL pattern for your RESTFul web service requests. The default base URL for a Seam configuration is shown above. Once this is complete, you can bypass the same-origin policy by making JSONP requests to your JAX-RS web services.
Security Considerations
As a final note, be aware that there are security considerations associated with the use of JSONP and cross-site request forgery attacks. The same-origin policy exists to eliminate this issue, so appropriate precautions should be taken to ensure that security is enforced. Always ensure that you understand the implications of using JSONP prior to enabling it for your web services and prior to invoking a service that provides JSONP support.
Enjoyed this post? Share it! |
Posted in JAX-RS, JBoss Seam, RESTEasy
No Comments »
April 6, 2010
Even with the prominence of open source in today’s market, as a consultant, I am often asked to bolster the argument about why open source is the right option. Convincing management that open source solutions make sense is imperative. The following reasons describe why open source makes sense from a strategic business perspective.
Limit your risk
Companies fail and in today’s volatile market it is important to limit risk by not leaning too heavily on a company’s viability. Closed-source software increases this risk as the fate of the software is tied to the vendor who creates the software. With open source software, this risk is mitigated. Even if the company fails, the source is available allowing you to continue to use the product while a migration strategy is formulated.
Ensure a quality product
Closed source software is limited by the number of developers on staff that can resolve bugs while adding features to the software. Open source software is maintained by the community and end users are able to participate in that development. While there are a limited number of individuals able to alter the product itself, they are aided by community members pouring over the source identifying bugs and submitting patches. With all of these eyes on the source, this leads to a higher quality end product.
Development for free
Cost cutting is critical to the success of the business. Bug-fixes, features, and documentation all incur cost for your IT organization. With open source, these requirements are developed and maintained by the community. If the community has not yet addressed an issue, participation in the community is easy by simply getting involved.
Only pay for what you need
Developing a product using open source software is an investment in effort. A software product can be implemented rapidly and released with minimal cost. Yet, many organizations will want support for products they are releasing into a production environment. Enterprise level open source software products are backed by organizations that provide this support as well as indemnification for their products.
Attract the best talent
Have you heard of the 10x developer? These developers are 10 times as productive as their peers due to their skill and passion for what they do. The open source community is full of these talented, passionate developers ready to work on an exciting project. By using the products that interest these developers, your organization will attract these highly skilled individuals and increase the productivity of your organization.
Validated by industry use
The “free” aspect of open source software has led to rapid, widespread adoption in both small and large organizations alike. This has led to the validation of these products in far-reaching domains and production environments. Rather than relying on the promises of a vendor, you can be assured by industry use that the product will meet your expectations.
Product transparency
Being developed by a community means transparency. Bugs are found, short-comings are noted, and new capabilities are discussed and voted upon all in a public forum. This not only allows your organization to stay informed, but also provide input on product direction. Open source software evolves according to market demand rather than being tied to vendor constraints.
None of this is to say that proprietary solutions are unreasonable. But with these reasons in mind, open source software products should be considered as a viable option. As with proprietary solutions, open source software products should be evaluated according to the features they provide, market share, and cost benefit to the organization.
If you have reasons of your own, I would certainly like to hear them.
Enjoyed this post? Share it! |
Posted in Open Source
10 Comments »
March 12, 2010
In recent performance tuning of some EJBQL search queries, I’ve had a lot of discussions with other developers on database pagination. There are some definite nuances that you have to be aware of when using Hibernate’s pagination feature, so I thought I would explain them here.
Quick Introduction to Database Pagination with JPA
Database pagination allows you to step through a result set in manageable chunks (say 5 at a time). This is an important feature when a result set is large. Imagine if the user selects the first result of 1000. Essentially 999 out of 1000 results were wasted. This is wasteful in terms of CPU cycles on the database server, network usage, CPU cycles on the application server, and memory allocation. On the other hand, if we only loaded 10 results into memory, we’ve only wasted 9 results. As the result set grows, this problem becomes more important to address.
Database pagination with JPA is quite simple through the javax.persistence.Query. The following method invocations retrieve the first 10 results for the query:
javax.persistence.Query query =
em.createQuery("select order from Order as order
left join order.customer as customer
where customer.name like '%' || :name || '%'");
query.setParameter("name", name);
query.setFirstResult(0);
query.setMaxResults(10);
// returns 10 or less orders
List<Order> orders = query.getResultList();
The max number of results to retrieve at one time can be any number you choose. As the user pages through the data, we alter the setFirstResult(int) to retrieve the next set of results.
Query Tuning with Fetch Joins
When paging through a result-set, you may be interested in performing fetch joins to enhance query performance. This avoids the N+1 select problem when walking lazy relationships for displaying data. For example, let’s say we are working with an order management system. This order management system allows users to search for orders that have been placed by customers. Our domain would look something like the following, where an Order has one Customer and a Customer can be associated to many Orders.

This relationship could be described in the Order entity as:
@Entity
public class Order
{
// ... ...
@ManyToOne
private Customer customer;
// ... ...
}
In the search results, the users want to see both Order and Customer information on the page. Lazily loading the Customer results in a query being executed to retrieve the Customer for each Order displayed. To avoid this, we can perform a fetch join on the Customer when retrieving the Order results. Here is the resulting EJBQL:
select order from Order as order
left join fetch order.customer as customer
where customer.name = '%' || :name || '%'
This ensures that only a single query is executed to load both the Order and the Customer results. An example of what the SQL result set might look like in this case would be:
| order_id | cust_id | cust_name |
----------------------------------------
| 1 | 1 | Jacob Orshalick |
| 2 | 2 | Nirav Assar |
| 3 | 3 | John Doe |
As you can see, each Order is associated to a single Customer which ensures a unique result set. In this case we are guaranteed that limiting the result set to 5 will always result in 5 or less unique Order results. This is generally the right solution for a @OneToOne or a @ManyToOne relationship.
Fetching One-to-many or Many-to-many Relationships
Fetching one-to-many or many-to-many relationships gets a bit tricky. The moment you introduce a fetch join for a one-to-many or many-to-many relationship, Hibernate will load all results into memory and then only return you the max number of results you requested. This is due to the semantics of SQL queries.
Going back to our example, we will likely have a list of LineItem entries for each order that tell us what Products the Customer purchased on the Order.

And the Order entity would now look like:
@Entity
public class Order
{
// ... ...
@ManyToOne
private Customer customer;
@OneToMany
private List<LineItem> lineItems;
// Getters and Setters
}
The users request that we display the LineItem entries below each Order in the search results. So we can just do another fetch join and load this data as well right? Here is the resulting EBJQL:
select distinct order from Order as order
left join fetch order.customer as customer
left join fetch order.lineItems
where customer.name = '%' || :name || '%'
Once you introduce this additional fetch into the query, Hibernate will present the following message in the log:
[org.hibernate.hql.ast.QueryTranslatorImpl] firstResult/maxResults
specified with collection fetch; applying in memory!
This message is telling you that Hibernate is retrieving all results from the database, and then only returning the first 10 results (or the number of max results you specified). So why does Hibernate do this? Let’s have a look at an example of what the SQL result set generated from this query might look like.
| order_id | cust_id | cust_name | line_id | product_sku |
----------------------------------------------------------------
| 1 | 1 | Jacob Orshalick | 1 | 1403-1209 |
| 1 | 1 | Jacob Orshalick | 2 | 1405-1333 |
| 2 | 2 | Nirav Assar | 3 | 1300-1222 |
| 3 | 3 | John Doe | 4 | 1400-3029 |
| 3 | 3 | John Doe | 5 | 1401-1000 |
| 3 | 3 | John Doe | 6 | 1200-1000 |
Each database has it’s own SQL syntax for limiting the result set, but assuming we limit the result-set to 5 results on the database side we would only get the first 5 results. As you can see, the result set returned duplicates the Order and Customer information for each LineItem on the Order. Thanks to the way Hibernate processes these results, we would still see the 3 expected orders (order_id = 1, 2, 3), but the database would only return us 2 of the LineItem entries for John Doe’s order. This is an incorrect result from the user’s point-of-view.
Knowing this, Hibernate rightfully retrieves all results in this case and then returns you the 3 Order results with all associated LineItem entries. But, to ensure correctness, you lose the value of pagination. So will we always face the N+1 select problem when using pagination with @OneToMany or @ManyToMany relationships? Not if you consider other options from a user experience perspective.
Other Options for one-to-many Relationships
There are a number of ways to enhance performance without losing the advantages of database pagination.
Display LineItem Entries only when Requested
Technology combinations like RichFaces and Seam make this simple. Basically you can walk the lazy relationship only when the user requests this information through an AJAX request. Through use of a <rich:togglePanel> a link can be provided to expand the Order data for the user. Because Seam allows an EntityManager to span requests lazily loading this data is simple.
Another simple option is using REST and JSON to retrieve the LineItem entries through an AJAX request when accessed by the user. A simple RESTful invocation (http://my-server/order/1/lineItems) allows the LineItem entries to be retrieved for an Order and we can then parse the results and display them back to the user. RESTEasy makes this simple for any Java application.
Display the LineItem Entries on a Details Page
This is the easiest and most obvious solution. Just display high-level Order information on the search results and the user can then access a details page that provides additional details. In general, this is the solution I generally push users toward for simplicity.
Display a High-level LineItem Summary Information
Another option is to give high-level information (e.g. number of LineItem entries) on the search page, and then display all information on a detail page. With the flexibility of EJBQL, you can use aggregate functions (e.g. count(lineItem.id) ) with a group-by clause to avoid the issues with a one-to-many. But, this also generally requires introduction of DTOs to hold the query result data or additional parsing of the result set.
Performance Tuning Always has Trade-offs
As I always say when discussing performance tuning, there are always trade-offs. Whether it’s additional complexity or changes to user experience, we always have to consider the implications of tuning our applications.
Enjoyed this post? Share it! |
Posted in Hibernate, JPA, Java
3 Comments »
August 4, 2009
It seems common for developers to look for faults in established technologies when an issue is encountered. As a developer struggling to learn an unfamiliar technology, it is easy to make mistakes. These mistakes lead to frustration which, in turn, can lead the developer to blame the technology rather than taking the time to analyze the problem and identify the real issue.
Anyone can fall into this trap and I still feel myself being pulled by this temptation at times. When you are working with an established technology, it’s always necessary to realize that the problem is almost undoubtedly in your code, not the technology you are using. As we learn the technology and continue to increase our depth of knowledge, this temptation lessens with each problem encountered. Let’s take driving as an example.
When I was learning to drive, I was a poor driver (most teenagers are). To the frustration of my parents, I had several accidents, fortunately all were minor. After having an accident I was frustrated with myself for the mistakes I made while operating the vehicle. I didn’t have the gall to blame the vehicle, claiming faulty brakes or the steering went out… Sure, these were possibilities, but unlikely. It was much more likely that my inexperience in operating the vehicle led me to make poor driving decisions.
With this mind set I continued to drive and improve my skills through experience behind the wheel. Over time I built a kind of intuition while driving that helped me avoid those mistakes. At some point I may realize that a different vehicle may be easier to drive, more suited to accident avoidance, safer, etc, but I can only come to that realization as an experienced driver.
So by taking this same stance when learning an unfamiliar technology, we can become better developers. Struggling through the mistakes and the frustration while gaining experience helps build the intuition of a top-notch developer. So, the next time you find yourself blaming the unfamiliar technology you are working with out of frustration, stop and think about it. Are you really frustrated by your inexperience or is the technology really to blame?
So let’s review some techniques that can help us when attempting to solve problems encountered with an unfamiliar technology. I also covered several of the Agile practices mentioned here in my previous article, 5 steps to improving your development process.
Read the Manual
I can’t tell you how many times I have solved a problem by simply reading the portion of the manual describing the API I am attempting to use. This may seem like common sense, but unfortunately time constraints often seem to get the best of developers leading them to copy-and-paste Programming by Coincidence.
Isolate the Problem
While this may be an obvious approach to problem-solving, it may be easier said than done depending on your approach to development. TDD (Test-driven development) is a very effective technique to isolating the problem. If you know what you have already done is working as designed, you can be reasonably sure that the issue is isolated to the code just added. Now try to write a test to prove the issue with the newly added code which can drive you to the root cause of the issue.
Commit Regularly
Committing regularly to your VCS (Version Control System) is a necessity to obtain the benefits of continuous integration. This technique is also effective for allowing you to revert back to a working revision if you find yourself too far down the rabbit hole. I often use this approach to attempt various hypotheses to solving the problem. If a hypothesis proves incorrect, simply roll back and try the next hypothesis.
Logging is your Friend
I read an article recently on DZone by Bharath Ganesh that reviewed this basic principle. Look at the logs! While stack traces don’t always provide the most obvious indication of the issue, they at least give you something to search for. For an established technology, simply Google’ing the exception message will often provide at least an inkling of what the issue is related to.
Maintain a Positive Attitude
Sometimes this is easier said than done, but it is important to remain determined to solve the problem. This may require taking a break, discussing the problem with a co-worker, sleeping on it, etc.
What techniques do you recommend for problem solving with an unfamiliar technology?
Enjoyed this post? Share it! |
Posted in Agile
4 Comments »
April 6, 2009
I keep reading discussions regarding the performance of Seam applications. These discussions are generally centered around the performance overhead of the interception techniques used by Seam. While this is definitely a valid issue in certain scenarios, see this excellent forum discussion started by Tobias Hill, many tend to blame Seam too quickly for their performance issues. If it is taking many seconds or even minutes to load a page, in most cases your application is more likely to blame than Seam.
In my experience, most performance issues stem from data access. Improperly tuned queries (a common culprit) and not using the second-level cache of your ORM provider when appropriate can lead to some serious performance implications in your application. While second-level caching is nothing new, here I will describe why it is important to a Seam application and how you can improve performance using Hibernate’s second-level cache provider.
Before I go any further, note that second-level caching is not the only caching solution you have available if you are using Seam. Seam provides a multi-layer caching solution that allows you to cache page fragments and objects easily while abstracting away the details. You can read all about Seam’s multi-layer caching solution in Chapter 34 of Seam Framework: Experience the Evolution of Java EE.
Loading Reference Data
Seam provides an elegant solution to the common problem of associating entities based on a dropdown selection. Take the common booking example with Seam. We are attempting to book a Hotel and we need to input credit card information. The type of credit card is likely to be a dropdown, but that dropdown is going to need to associate to a CreditCardType entity.
@Entity
public class CreditCardType implements Serializable
{
@Id
private Long providerId;
private String description;
// ... ...
}
Our Booking class then needs a reference to the CreditCardType class.
@Entity
public class Booking implements Serializable
{
@Id
private Long id;
// ... ...
@ManyToOne
private CreditCard creditCard;
// ... ...
}
To make this task simple, Seam provides the <s:entityConverter /> component which ensures that the user selection is converted to an entity for association with your object.
<h:selectOneMenu id="creditCard" value="#{booking.creditCard}"
required="true">
<s:selectItems noSelectionLabel="" var="type"
value="#{creditCardTypes}"
itemLabel=”#{type.description}” />
<s:convertEntity />
</s:selectItems>
</h:selectOneMenu>
As you can see this is quite simple, but we need to load the creditCardTypes into the conversation context in order to associate an instance to our entity. This is because the creditCardTypes need to be managed instances in the conversation-scoped persistence context. It is quite simple to accomplish this through a @Factory method scoped to the conversation.
@Name(“bookingAction”)
@Scope(CONVERSATION)
public class BookingAction implements Serializable {
// ... ...
@In private EntityManager entityManager;
@Factory(“creditCardTypes”)
public List<creditcard> loadCreditCardTypes()
{
return entityManager.createQuery("select c from " +
"CreditCardType as c order by c.description").getResultList();
}
// ... ...
}
Great, so now we can load our entities into the context and associate them using a dropdown, so what’s the catch? The factory method only executes once, right? The problem is that the query that loads the CreditCardType instances into the conversation context executes every time a new conversation requests the dropdown list. This can cause the initial page load to lag.
This may not be a problem in this simple case as we only have this one dropdown, but what if we have many dropdowns on the screen? Even further, what if this dropdown list is used by several conversations? Doesn’t it seem wasteful to hit the database every time we need it? We can avoid the database hit and still achieve the same benefits by using second-level caching.
Second-level caching with Hibernate
Second-level caching is intended for data that is read-mostly. It allows you to store the entity and query data in-memory so that this data can be retrieved without the overhead of returning to the database. You can configure the cache expiration policy, which determines when the data will be refreshed in the cache (e.g. 1 hour, 2 hours, 1 day, etc.) according to the requirements for that entity. An entity like CreditCardType is certainly read-mostly so it is definitely a good candidate for the second-level cache.
Using Hibernate, it is quite simple to cache an entity by using the @Cache annotation.
@Entity
@Cache(usage = CacheConcurrencyStrategy.READ_ONLY)
public class CreditCardType implements Serializable {
// ... ...
}
We then need to include the jars necessary for a second-level cache provider. I tend to use Ehcache as I find it simple to use and it is fully supported by Seam’s multi-layered caching solution.
Once you include the appropriate jars, you must configure Hibernate to use second-level caching. In your persistence.xml file, add the following properties for your persistence-unit definition.
<persistence-unit name="myBookingDS">
... ...
<properties>
<property name="hibernate.cache.provider_class"
value="org.hibernate.cache.EhCacheProvider" />
<property name="hibernate.cache.use_second_level_cache"
value="true" />
<property name="hibernate.cache.use_query_cache"
value="true" />
... ...
</properties>
<persistence-unit>
The hibernate.cache.provider_class should be specific to the cache provider you are using. Hibernate supports a number of implementations as described in the reference documentation.
Notice that we also set hibernate.cache.use_query_cache to true. This allows us to take the caching a step further by caching the query itself and not just the entities. In order to cache the query, we can take two approaches: use the Hibernate Session API or the Hibernate @NamedQuery annotation. Let’s look at the Hibernate Session API approach first. Our factory method above changes to the following:
@Name(“bookingAction”)
@Scope(CONVERSATION)
public class BookingAction implements Serializable {
// ... ...
@In private EntityManager entityManager;
@Factory(“creditCardTypes”)
public List<CreditCard> loadCreditCardTypes()
{
Session session = (Session) entityManager.getDelegate();
Query query = session.createQuery("select c from " +
"CreditCard as c order by c.description");
query.setCacheable(true);
return query.list();
}
// ... ...
}
Now you will notice in the logs that once the creditCardTypes have been loaded, even a new conversation does not cause a database call the next time these entities are requested. The query and the entities are loaded directly from the second-level cache in-memory.
The other approach is to use the Hibernate @NamedQuery annotation which gives the option to cache your query.
@Entity
@NamedQuery(name="getCreditCardTypes",
query="select c from CreditCard as c " +
"order by c.description",
cacheable=true)
public class CreditCardType implements Serializable
{
@Id
private Long id;
private String description;
// ... ...
}
The @NamedQuery can then be retrieved through the createNamedQuery() method in the EntityManager API.
While we are only showing one scenario here, there are many cases where second-level caching can be applied in your application.
No silver bullet
By no means am I claiming here that second-level caching is the solution for every scenario. Performance tuning is somewhat of an art. It is definitely handy to know the various potential hot spots when tuning an application, but a solution that works in one case may not work in others. Simply read up on the various approaches and techniques to tune your application so that you can apply each technique when the time is right.
Enjoyed this post? Share it! |
Posted in JBoss Seam
10 Comments »
March 31, 2009
Michael Yuan and I will be answering questions about the book and Seam in general at JavaRanch this week in the JBoss forum. If you would like to ask us a question feel free to stop by! They will be selecting four random posters in the forum to win a free copy of the book provided by Prentice Hall. We look forward to a good week of questions and hope to see you there!
Enjoyed this post? Share it! |
Posted in JBoss Seam
No Comments »
February 23, 2009
As a follow-up to the Core Seam Refcard, DZone has now released my companion reference for using Seam with JSF. The Seam UI Refcard has now been released through the DZone Refcardz site and includes:
- Simplifying JSF
- Page Navigation
- JSF Component Annotations
- JSF Component Tags
- Hot Tips and more…
So download the Seam UI Refcard here and please send your comments and feedback to refcardz@dzone.com. For in-depth coverage of Seam 2.1, you can also purchase the just released Seam Framework: Experience the Evolution of Java EE.
In a related story, JavaLobby posted an interview with me to coincide the release of the reference card. Check it out!
Enjoyed this post? Share it! |
Posted in JBoss Seam, JBoss Seam 2E News
7 Comments »
Comments
April 26, 2010
@Tim: I will definitely be posting more on the topic of mashups in the next few...
April 26, 2010
You should give jQuery-JSONP a try, it handles timeouts transparently :)...
April 26, 2010
Very help and concise examples! Thank you for sharing. I was wondering if we’d see...
April 6, 2010
Jacob, I fully agree with you about the 7 good reasons you mentioned above for adopting...
April 6, 2010
Good article. Open source is way to go.