Case Study: Migrating Hyperic from EJB to Spring!!

Chicago, October 19 - 22, 2010 Case Study: Migrating Hyperic from EJB to Spring! ! Jennifer Hickey SpringSource SpringOne 2GX 2010. All rights reser...
Author: May McLaughlin
1 downloads 1 Views 2MB Size
Chicago, October 19 - 22, 2010

Case Study: Migrating Hyperic from EJB to Spring! ! Jennifer Hickey SpringSource

SpringOne 2GX 2010. All rights reserved. Do not distribute without permission.

SpringSource Hyperic Application and Infrastructure Management • Discover – Automatically find all resources

• Monitor – Availability, performance, capacity, history

• Track – Logs, configuration, change management

• Alert – Advanced condition definition, notification & escalation schemes

• Control – Proactive, automated actions

• Analyze – Answer questions, plan for the future

Basic HQ Architecture machine 1 items to manage

HQ Agent

machine n items to manage

HQ Agent

HQ Server

Inventory, Metric, Audit, …

HQ Web Portal HQ API

Hyperic’s Server Architecture

Why Migrate? The Obvious Answer:

But there’s a lot more to it.... 5

Today’s De Facto Standards Spring and Tomcat WebLogic JBoss WebSphere

Spring Applications

26% 38% 43%

Apache Tomcat

68%

0% 10% 20% 30% 40% 50% 60% 70% Java Application Server Usage, Source: 2008 Evans Data Survey

• Spring: Enterprise Java programming model – Centralized configuration, declarative transactions, security, messaging, remoting, Web MVC, Web Flow, persistence integration, enterprise integration, batch, …

• Tomcat: Lean and powerful Web application server

How Does Lean Help? • Installation and Provisioning – 100X Smaller Install Footprint: Less than 50MB vs. multi-Gigabytes • Hardware, software, lost productivity across Dev, QA, Staging, Production

• Simpler Configuration Improves Debugging and Quality – Impact of complexity across Dev, QA, Staging, Production adds up – “60% cite faster project completion and application quality as top reasons for using Spring”

• Upgrade and Migration – Lower complexity = faster migration / upgrade

• Developer Productivity – Fast Server Startup/Shutdown • One Developer coding/debugging an app (5 mins x 12 per day = 1 hr)

• Fluidity of Personnel – Simpler systems = faster rampup

• License and Maintenance Costs – tc Server is 33% to 75% savings vs. competitor annual maintenance; additional savings when license costs are factored in

Project Timeline

Sept 21

M1

M2

M3

Nov 20

Dec 24

Jan 15

M4

Feb 19

M5

May 28

Oct 15

Planning a Migration • Build an accurate picture of the candidate app – – – – –

Java EE APIs used 3rd party libraries Packaging Types and number of components Assess code quality (coupling, etc.)

• Analyze migration complexity • Decide partial vs full migration • Resource Estimation

9

Factors in Migration Complexity Factor Good documentation and clear understanding of existing code, database or requirements

Effect on Complexity Low complexity

Poor documentation and/or lack Low to medium complexity of clear understanding of existing code, database or requirements Well architected, layered Low complexity application Organically grown, non-layered Low to medium complexity code and architecture, combined with need to refactor

Factors in Migration Complexity Factor Effect on Complexity Organization already familiar Low complexity with and using Tomcat/tc Server and/or lightweight technologies such as Spring Framework. Strong organizational support of Medium complexity legacy EJB and full stack Java EE technologies; Tomcat/tc Server and/or lightweight technologies such as Spring Framework not yet adopted No integration with proprietary Low complexity application server frameworks or APIs

Factors in Migration Complexity Factor Integration with proprietary application server frameworks or APIs

Effect on Complexity Low to high complexity depending on extent of usage

No reliance on Session EJBs, or reliance on a straightforward use of Session EJBs (e.g. smaller quantity or delegating to plain Java business objects) server frameworks or APIs Heavy use of Session EJBs

Low to high complexity depending on extent of usage

Medium complexity

Factors in Migration Complexity Factor Reliance on stateful middle tier clustering (EJB Stateful Session Beans) True need for distributed transactions

Effect on Complexity Medium complexity Medium to high complexity

Straightforward DAO-based Low complexity database access (using either JDBC or ORM) Reliance on Entity Beans Medium to high complexity depending on amount of code

Factors in Migration Complexity Factor Servlet-spec Security usage

Effect on Complexity Low complexity

Declarative EJB (Container Managed) Security usage

Medium complexity With Spring Security: LowMedium

Using existing full stack Low to medium complexity application server's built-in JMS depending on ability to use provider external commercial or open source JMS container. Generally only licensing (no code changes) concern.

Project Evolution Complexity Analysis • • • • • •

80 Stateless Session Beans 0 Stateful Session Beans 3 MDBs 0 Entity Beans All Container-Managed Transactions, No XA JBoss Dependencies: – – – – –

JAAS Mail Service Deployment Scanner Schedulers HA

• SpringSource Migration Tool helps with analysis 15

Partial vs Full Migration • Project worked in parallel with other releases • Largest percentage of test coverage in system tests (vs standalone unit and integration tests) necessitated a partially migrated app

16

Extras • • • • • •

Switch from svn to git Modularize monolithic codebase Add Java 5 constructs Add code conventions Switch from ant to maven Introduce Eclipse Groovy plugin

17

Resources • Estimated 8 weeks for one person to do initial conversion from EAR to WAR on Tomcat • From beginning to functional complete (M5), project was staffed with 1.5 full-time people. • Initial conversion was done by 1.5 people in 12 weeks

18

Project Timeline

Sept 21

M1

M2

M3

Nov 20

Dec 24

Jan 15

M4

Feb 19

M5

May 28

Oct 15

M1 Goal A JBoss-dependent EAR with no more Stateless Session EJBs (all converted to POJOs and bootstrapped/ transaction-managed by Spring)

20

Preliminary Steps • Changed ant to compile at Java 5 compliance • Removed Xdoclet deployment descriptor and interface generation from build • Added “Local” interfaces to source control • Added deployment descriptors to source control • Added “Util” lookup classes to source control • Introduced Eclipse projects for compiling UI plugin groovy code

21

Dependency Injection • Added temporary Bootstrap class for creation of Spring ClasspathXmlApplicationContext • Enabled component scanning and autowiring to instantiate classes marked as @Service, @Repository, and @Component • Added @Repository to all DAOs and @Autowired to their constructors for injection of Hibernate SessionFactory • Added all EJBs to an app context file with factorymethod=”getOne”

22

Application Context Files dao-context.xml

Application Context Files ejb-context.xml ... for all EJBs

Converted EJB Lookup public class AgentManagerEJBImpl implements SessionBean { public static AgentManagerLocal getOne() { try { return AgentManagerUtil.getLocalHome().create(); } catch (Exception e) { throw new SystemException(e); } } }

Application Context Instantiation public class Bootstrap { private static final String[] APP_CONTEXT_FILES = new String[] { "classpath*:/META-INF/spring/dao-context.xml" }; private static final String[] EJB_APP_CONTEXT_FILES = new String[] { "classpath*:/META-INF/spring/ejb-*context.xml" }; private static ApplicationContext APP_CONTEXT; public synchronized static ApplicationContext getContext() throws Exception { boolean initialize = false; if (APP_CONTEXT == null) { initialize = true; APP_CONTEXT = new ClassPathXmlApplicationContext (APP_CONTEXT_FILES, false); } if (initialize) { ((ConfigurableApplicationContext) APP_CONTEXT).refresh(); } return APP_CONTEXT; }

Application Context Instantiation (2) public static synchronized void loadEJBApplicationContext() throws Exception { APP_CONTEXT = new ClassPathXmlApplicationContext (EJB_APP_CONTEXT_FILES, APP_CONTEXT); } public static T getBean(Class beanClass) throws Exception { Collection beans = getContext().getBeansOfType(beanClass).values(); ... lookup from parent context if not found return beans.iterator().next(); } public static Object getBean(String name) throws Exception { Object bean = getContext().getBean(name); ... lookup from parent context if not found return bean; }

Data Access and Transactions with Spring and EJB • Added Commons DBCP BasicDataSource • Added TransactionAwareDataSourceProxy to allow legacy code making direct use of DataSource to participate in Spring-managed transactions • Added a Spring JTATransactionManager and @Transactional scanning to support transactions for converted EJBs

28

Hibernate with Spring and EJB • Moved creation of Hibernate SessionFactory to Spring's LocalSessionFactoryBean • Integrated Hibernate Session management with Spring transaction management • The following 4 ways of obtaining a Hibernate session will all go through the same Spring-managed SessionFactoryUtils to ensure that each thread will share a single Hibernate session, regardless of which point was entered first 1. Hibernate Session is opened during a web request 2. Hibernate Session is opened at beginning of a CMT through the JBossInterceptor 3. Hibernate Session is opened at beginning of Spring-managed transaction (currently those converted EJBs marked @Transactional) 4. Hibernate Session is opened by call to HQ SessionManager.runInSession 29

Hibernate with Spring and EJB(2) hibernate.properties hibernate.current_session_context_class=org.springframework.orm.hibernat e3.SpringSessionContext hibernate.transaction.factory_class=org.hibernate.transaction.JTATransaction Factory jta.UserTransaction=UserTransaction

• Hibernate Session is opened during a web request -

Spring OpenSessionInViewFilter

• Hibernate Session is opened at beginning of a CMT through the JBossInterceptor -

Existing interceptor modified to obtain/start Hibernate sessions through SessionFactory.currentSession()

• Hibernate Session is opened at beginning of Springmanaged transaction (currently those converted EJBs marked @Transactional) -

SpringSessionContext

Hibernate with Spring and EJB(3) Session opened by call to HQ SessionManager private void runInSessionInternal(final SessionRunner r) throws Exception { boolean participate = false; try { ! if (TransactionSynchronizationManager.hasResource(getSessionFactory())) { ! // Do not modify the Session: just set the participate flag. ! participate = true; ! } else { ! Session session = SessionFactoryUtils.getSession(getSessionFactory(), true); ! session.setFlushMode(FlushMode.MANUAL); ! TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(session)); ! } ! HibernateTemplate template = getHibernateTemplate(); ! template.execute(new HibernateCallback() {...}); ! } finally { ! if (!participate) { ! // single session mode ! SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory()); ! SessionFactoryUtils.closeSession(sessionHolder.getSession()); ! }

EJB Conversion Checklist Convert Local Interface Remove "extends javax.ejb.EJBLocalObject" from the Local interface

-

i.e. AgentManagerLocal

-

i.e. AgentManagerLocal becomes AgentManager

-

especially in the Local interface

-

i.e. AgentManagerUtil and AgentManagerLocalHome

1. Rename the Local interface implemented by the EJB by removing "Local" from the name 2. Replace fully-qualified type names with import-based type names 3. Delete Util and LocalHome classes

32

EJB Conversion Checklist Convert Implementation Rename the EJB (using Eclipse Refactor->Rename to update dependencies) by removing "EJB" from the name

-

i.e. AgentManagerEJBImpl becomes AgentManagerImpl

1. Remove "implements SessionBean" from EJB class declaration and make class implement its corresponding Local interface 2. Move initialization logic from ejbCreate() to an init method annotated with @PostConstruct @PostConstruct public void initPager() throws Exception { valuePager = Pager.getPager(VALUE_PROCESSOR); }

33

EJB Conversion Checklist Convert Implementation (2) 4. Remove all ejb* methods (i.e. ejbCreate) and setSessionContext method 5. Convert getOne() method to the following: public static AgentManager getOne() { return Bootstrap.getBean(AgentManager.class); }

6. Mark the converted EJB with @Service 7. Remove the converted EJBʼs entry from ejb-context.xml

34

EJB Conversion Checklist Convert Implementation (3) 8. Remove all mention of the EJB from deployment descriptors in the HQ/dd or HQ-EE/dd directories 9. If the class is marked with * @ejb:transaction type="REQUIRED" (prior XDoclet markup for generating CMT), mark the class @Transactional.

-

If any methods are marked with a different ejb:transaction type, (for example, ejb:transaction type="REQUIRES_NEW), ignore them and document

10. Remove all the XDoclet markup from javadoc

35

EJB Conversion Checklist Dependency Injection 1. Remove any EJB dependencies and helper classes obtained through static lookup Example: public class SomeConvertedEJB { private void doSomething() { EscalationManagerLocal escMan = EscalationManagerEJBImpl.getOne(); escMan.escalate(); } Becomes: public class SomeConvertedEJB { private EscalationManagerLocal escalationManager; private void doSomething() { escalationManager.escalate(); } 36

EJB Conversion Checklist Dependency Injection(2) 2. Inject DAOs, EJBs and converted EJBs, and helper classes by auto-wiring the constructor @Service @Transactional public class AgentManagerImpl { private ResourceEdgeDAO resourceEdgeDAO; private ServerManagerLocal serverManager; @Autowired public AgentManagerImpl(ResourceEdgeDAO resourceEdgeDAO, ServerManagerLocal serverManager) { this.resourceEdgeDAO = resourceEdgeDAO; this.serverManager = serverManager; }

37

Struts 1.x and Spring Added Spring ContextLoader plugin to enable Spring to manage Struts actions as Beans struts-config.xml action-servlet.xml

38

Project Timeline

Sept 21

M1

M2

M3

Nov 20

Dec 24

Jan 15

M4

Feb 19

M5

May 28

Oct 15

M2 Goal Convert the JBoss-dependent HQ EAR into a single JBoss-dependent WAR

40

Message-Driven EJB Conversion • • •

Only 3 MDBs listening to a single JMS topic Applied conversion checklist to MDBs Subscribed newly converted POJOs to JMS topic using Spring JMS jms-context.xml



41

JMS Message Broker Conversion • Introduced embedded ActiveMQ broker to replace JBossMQ

- Easy to configure with Spring - Often 10x faster than JBossMQ

• Conversion not technically necessary in this project phase, but took very little time to implement

42

ActiveMQ Configuration ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! 43

JMS Producer Conversion Modified single Producer to use Spring JMSTemplate for message publishing jms-context.xml

! ! ! ! !

44

JMS Producer: Before public void publishMessage(String name, Serializable sObj) { TopicConnection conn = null; TopicSession session = null; if (_ic == null) _ic = new InitialContext(); if (_factory == null) _factory = _ic.lookup(CONN_FACTORY_JNDI); TopicConnectionFactory tFactory = (TopicConnectionFactory) _factory; Topic topic = getTopic(name); if (topic != null) { // Now create a connection to send a message if (_tConn != null) conn = _tConn; else conn = tFactory.createTopicConnection(); if (conn == null) _log.error("TopicConnection cannot be created"); if (_tSession != null) session = _tSession; else session = conn.createTopicSession(false, Session.AUTO_ACKNOWLEDGE); // Create a publisher and publish the message TopicPublisher publisher = session.createPublisher(topic); ObjectMessage msg = session.createObjectMessage(); msg.setObject(sObj); publisher.publish(msg);

45

JMS Producer: After

public void publishMessage(String name, Serializable sObj) { eventsJmsTemplate.convertAndSend(name, sObj); }

46

MBean Conversion • EAR contained several JBoss SARs (Service Archive files) registering MBeans with product functionality • Most MBeans not actually used for runtime management and monitoring • JBoss Scheduler MBeans

- A few classes registered as MBeans just to integrate with JBoss Schedulers - Registered JBoss schedulers programmatically

• JBoss Mail Service MBean - Registered JBoss mail service programmatically • JBoss Deployment Scanner - Used for hot deploy of product plugins. Temporarily disabled

47

JMX with Spring Switched to Spring JMX for exposure of actual management and monitoring interfaces !

@ManagedResource("hyperic:type=Service,name=ProductPluginDeployer") @Service public class ProductPluginDeployer { @ManagedMetric public int getProductPluginCount() {...} @ManagedAttribute public ArrayList getRegisteredPlugins(String type) {...} @ManagedOperation public void setProperty(String name, String value) {...} 48

Web Conversion • Added Spring WebApplicationContext • Kept Bootstrap class for static access to Web App Context web.xml

! contextConfigLocation ! ! ! classpath*:/META-INF/spring/*-context.xml ! ! ! org.hyperic.hq.context.BootstrapContextLoaderListener

• Auto-wired Struts actions with Service dependencies 49

Transaction Management Switched from JTA TransactionManager to Hibernate TransactionManager

! ! !

50

Project Timeline

Sept 21

M1

M2

M3

Nov 20

Dec 24

Jan 15

M4

Feb 19

M5

May 28

Oct 15

M3 Goal Run the basic HQ and HQ EE wars on Tomcat, breaking all EJB and JBoss dependencies

-Deferring some advanced functionality to a future milestone: -Unidirectional agent (JBoss Remoting) -HA -Kerberos and LDAP authentication -Plugin hot deploy

52

Security Conversion Replaced JBoss JAAS for simple JDBC login with Spring Security 3 ! ! ! ! ! ! ! ! ! ! ! ! 53

Scheduling Conversion Replaced use of JBoss Scheduler MBeans with Spring 3.0 Scheduler/TaskExecutor abstraction

@Service("availabilityCheckService") public class AvailabilityCheckServiceImpl implements AvailabilityCheckService { @Scheduled(fixedRate=120000) public void backfill() { ... } }

54

The Last of EJB and JBoss.... • Merged JBoss logging config from custom jboss-log4j.xml to single log4j.xml • Removed EJB and Remote Exceptions from all method throws clauses • Replaced references to JBoss server home directory and JBoss temp dir for File I/O

55

Final Steps Deployed the WAR on Tomcat as the ROOT webapp • When we ported the WAR from JBoss to Tomcat, we only had to fix 2 small issues before it functioned properly: - jsp-api.jar was causing conflicts and had to be removed from WAR - A few resources (such as images) were being loaded using getResourceAsStream() from the WebAppClassLoader (getClass().getClassLoader()). In JBoss, the ClassLoader could load relative to top-level WAR dir. On Tomcat, the ClassLoader is relative to WEB-INF/classes.

56

Project Timeline

Sept 21

M1

M2

M3

Nov 20

Dec 24

Jan 15

M4

Feb 19

M5

May 28

Oct 15

M4 Goal •

Fully installable server and agent distros for all supported OS/DB combos

-Still missing features left un-implemented in M3

58

M4 Tasks • • •

Removed all static "getOne" accessor methods from EJBs Optimized performance by marking some @Transactionals as "ReadOnly” Created installable servers/agents - User-configurable properties in a single properties file (aggregating several JBoss config files) - Spring PropertyPlaceholderConfigurer makes property injection easy - Bundled Tomcat (.org) and tc Server (.com) with final distros - Added custom config of Tomcat and tc Server - catalina.properties extracts most container configuration to a single file



- Modified ant-based installer program

Developed an integration test of a converted EJB as template for future testing (using a MySQL database) 59

Demo Integration Testing with Spring

SpringOne 2GX 2010. All rights reserved. Do not distribute without permission.

Project Timeline

Sept 21

M1

M2

M3

Nov 20

Dec 24

Jan 15

M4

Feb 19

M5

May 28

Oct 15

M5 Goals • Fully functional product achieving parity with previous release • Build system converted from Ant to Maven

62

M5 Tasks • • • • • • • • •

Replaced BasicDataSource with the Tomcat DataSource (high concurrency connection pool) Added LazyConnectionDataSourceProxy Added hot deploy of plugins using Roo FileWatcher Implemented LDAP authentication with Spring LDAP Re-enabled JGroups to complete HA use cases

-

Had to manually register a few JBoss MBeans as part of the web app

Implemented Kerberos AuthenticationProvider Re-enabled JBoss Remoting servlet for unidirectional agent communications Converted build system from Ant to Maven (approx 2 weeks of one person’s time) Manual merge of changes made in previous product releases 63

Project Timeline

Sept 21

M1

M2

M3

Nov 20

Dec 24

Jan 15

M4

Feb 19

M5

May 28

Oct 15

Some Final Tweaks Data Access Before public int getServicesCount(AuthzSubject subject) { Statement stmt = null; ResultSet rs = null; Integer subjectId = subject.getId(); try { Connection conn = getDBConn(); String sql = "SELECT COUNT(SVC.ID) FROM TBL_SERVICE"; stmt = conn.createStatement(); rs = stmt.executeQuery(sql); if (rs.next()) { return rs.getInt(1); } } catch (SQLException e) { log.error("Caught SQL Exception finding Services by type: " + e, e); throw new SystemException(e); } finally { DBUtil.closeJDBCObjects(LOG_CTX, null, stmt, rs); } return 0; 65

Some Final Tweaks Data Access After public int getServicesCount(AuthzSubject subject) { String sql = "SELECT COUNT(SVC.ID) FROM TBL_SERVICE"; return jdbcTemplate.queryForInt(sql); }

66

Some Final Tweaks Replaced Tapestry with Spring MVC Replaced a small number of Tapestry Components with Spring MVC @Controller and @RequestMapping @Controller public class SearchController extends BaseController { @RequestMapping(method = RequestMethod.GET, value = "/search") ! public @ResponseBody ! Map listSearchResults( ! ! ! @RequestParam(RequestParameterKeys.SEARCH_STRING) String searchString, ! ! ! HttpSession session) { ... }

67

Wrapping It Up Improved maintainability, testability, and reliability

• • • • • • •

Reduced code complexity Quicker application start time Easier to test product in isolation 18% and 12% improvement in unit/integration test code coverage for .org and .com codebases, respectively Approx 7% code reduction in .org and .com codebases Faster turnaround time for bug fixes Easily extensible architecture allows quicker development of new features

68

What’s Next? • • • • • •

Finish conversion from Struts to Spring MVC Use MVC to provide WS endpoints, eliminating the need for existing HQApi groovy controllers Eliminate remaining static lookup via the Bootstrap class Eliminate passing of auth tokens in method signatures Finish conversion of direct SQL in service layer to DAOs using JdbcTemplate Improve scalability

69

Q&A

SpringOne 2GX 2010. All rights reserved. Do not distribute without permission.

Resources •

Hyperic 4.5 Beta Release - Open Source





http://sourceforge.net/projects/hyperic-hq/files/

Enterprise Edition http://www.springsource.com/landing/vfabric-hyperic-45-beta

Hyperic Development Resources - Cloning source - Building from source - Building plugins - Accessing maven repo

http://support.hyperic.com/display/EVO/Development+Resources

tc Server Eval Download http://www.springsource.com/products/tc-server-evaluation

71