Wednesday, May 07, 2008

MathML vs LaTeX


So I was trying out some stuff in Sakai, pretending to write an assessment (quiz, to the uninitiated) with complicated equations. I ran into a few problems like you can't do fractions, there is no square root symbol, no multiplication symbol, etc. Perhaps I will write a full review once I figure out if these limitations are real, or if I'm just missing something.

Anyway, I remember seeing complicated equations on Wikipedia, so I decided to investigate how they do it. It looks like the equations on there were generated by LaTeX, but they are just ugly png files with a lot of jaggies. I was thinking it would have been cool if they had used SVG. But, they had some info on possible future direction, which led me to the MathML entry, which is supposedly going to be implemented in next gen web browsers.

One of the more annoying aspects of what you can do with Sakai (and Microsoft's Equation Editor I might add) is that you have to do tons of highlighting and clicking to format things. Like, if I want a chemical equation like HC2H3O2, it requires highlighting 3 things and clicking 3 buttons. That back and forth of the mouse gets quite frustrating. In LaTeX, things are much simpler. You would just write:
HC_2H_3O_2

and you would be done. MathML, on the other hand, is a terribly verbose XML format (check out the quadratic equation example in the Wikipedia entry), so it's not much better than the point and click method. I realize that LaTeX is a little intimidating to write by hand, and a program could easily generate either LaTeX or MathML, but I have yet to see a program that is as efficient at building equations as hand-coding LaTeX markup is. With that in mind, I think allowing a subset of LaTeX is probably the best way to allow people to enter equations. There is some interesting work out there, like this JavaScript library. It would be cool to see something like that integrated into one of Sakai's rich text editors.

Tuesday, May 06, 2008

Sakai is a monster

I wanted to test out some functionality in the assessment tool in Sakai, so I just ran svn up on my dusty 2-5-x sources, built, and started up tomcat. This was at the bottom of catalina.out:

INFO: Server startup in 144105 ms (2008-05-06 18:00:38,367 main_org.apache.catalina.startup.Catalina)


It takes two minutes and 24 seconds to start! And I have a recent MacBook Pro with 4 gigs of ram. Madness, I say.

Getting tomcat to stop deleting your context xml files

I've been plagued with this annoying problem in tomcat for about a year, and it has been driving me crazy. I've looked through the code, joined the mailing list, etc. etc. The problem is when you have context xml files, such as /usr/local/tomcat/conf/Catalina/localhost/app.xml. Generally, you would use such files to set up JNDI data sources for database connections, but I also use them to set up configuration beans in order to keep environment-specific data outside of your application code (which I consider the whole war file to be). Anyway, tomcat seems to randomly delete those files when I change anything in the application files. I tried various configurations using war files, no war files, and war files in funny locations, and it happened to all of them.

I never did find out why it deletes the files, but I did find a solution, thanks to a user on the tomcat mailing list. All you have to do is turn off "autoDeploy" on the "Host". Basically, you need to edit /usr/local/tomcat/conf/server.xml and change the xml element, so that it says autoDeploy="false" instead of autoDeploy="true". And with that, all my problems are over.

Dropping tables in MySQL

Say you have access to a MySQL database, and you need to restore it from a previous copy. You could just run the SQL file from the dump, but that is slightly unclean, since it won't delete tables that it doesn't know about. Now, let's assume you don't have full admin access to the server, so you can't drop and recreate the db. You have to drop all the tables. Here's what happens:
mysql> show tables;
+------------------------------+
| Tables_in_cem |
+------------------------------+
| academic_periods |
| admin_leases |
| admin_services |
| admins |
| course_adds_kills |
| course_gateways |
| course_organizations |
| course_services |
| course_types |
| courses |
| instructors |
| organization_course_gateways |
| organization_gateways |
| organizations |
| roles |
| section_params |
| sections |
| services |
| transactions |
+------------------------------+
19 rows in set (0.00 sec)

mysql> drop table academic_periods, admin_leases, admin_services, admins, course_adds_kills, course_gateways, course_organizations, course_services, course_types, courses, instructors, organization_course_gateways, organization_gateways, organizations, roles, section_params, sections, services, transactions;
ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails

mysql> show tables;
+------------------+
| Tables_in_cem |
+------------------+
| academic_periods |
| course_gateways |
| course_types |
| courses |
| organizations |
| services |
+------------------+
6 rows in set (0.00 sec)

mysql> drop table academic_periods, admin_leases, admin_services, admins, course_adds_kills, course_gateways, course_organizations, course_services, course_types, courses, instructors, organization_course_gateways, organization_gateways, organizations, roles, section_params, sections, services, transactions;
ERROR 1217 (23000): Cannot delete or update a parent row: a foreign key constraint fails

mysql> show tables;
+------------------+
| Tables_in_cem |
+------------------+
| academic_periods |
| course_types |
+------------------+
2 rows in set (0.00 sec)

mysql> drop table academic_periods, admin_leases, admin_services, admins, course_adds_kills, course_gateways, course_organizations, course_services, course_types, courses, instructors, organization_course_gateways, organization_gateways, organizations, roles, section_params, sections, services, transactions;
ERROR 1051 (42S02): Unknown table 'admin_leases,admin_services,admins,course_adds_kills,course_gateways,course_organizations,course_ser'

mysql> show tables;
Empty set (0.00 sec)
Mission accomplished. You have to run the command n number of times, where n is the depth of the foreign key constraints, but keep feeding mysql the command and the tables will eventually be gone. I would contend that a "real" database would work out the dependencies and drop them all in one feel swoop.

Thursday, May 01, 2008

Hibernate error reporting

Sometimes, Hibernate is really bad at error reporting. Today I added a NOT NULL constraint to a column in my database and ran my unit tests. Several failed with errors like this:



testAddInstructor(edu.asu.cem.test.dao.CourseDAOTest) Time elapsed: 0.009 sec <<<>
org.hibernate.exception.GenericJDBCException: Could not execute JDBC batch update
at org.hibernate.exception.SQLStateConverter.handledNonSpecificException(SQLStateConverter.java:103)
at org.hibernate.exception.SQLStateConverter.convert(SQLStateConverter.java:91)
at org.hibernate.exception.JDBCExceptionHelper.convert(JDBCExceptionHelper.java:43)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:253)
at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:92)
at org.hibernate.jdbc.AbstractBatcher.prepareStatement(AbstractBatcher.java:87)
at org.hibernate.jdbc.AbstractBatcher.prepareBatchStatement(AbstractBatcher.java:222)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2229)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2665)
at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:60)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:279)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:263)
at org.hibernate.engine.ActionQueue.executeInserts(ActionQueue.java:158)
at org.hibernate.event.def.AbstractSaveEventListener.performSaveOrReplicate(AbstractSaveEventListener.java:245)
at org.hibernate.event.def.AbstractSaveEventListener.performSave(AbstractSaveEventListener.java:181)
at org.hibernate.event.def.AbstractSaveEventListener.saveWithGeneratedId(AbstractSaveEventListener.java:107)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.saveWithGeneratedOrRequestedId(DefaultSaveOrUpdateEventListener.java:187)
at org.hibernate.event.def.DefaultSaveEventListener.saveWithGeneratedOrRequestedId(DefaultSaveEventListener.java:33)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.entityIsTransient(DefaultSaveOrUpdateEventListener.java:172)
at org.hibernate.event.def.DefaultSaveEventListener.performSaveOrUpdate(DefaultSaveEventListener.java:27)
at org.hibernate.event.def.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:70)
at org.hibernate.impl.SessionImpl.fireSave(SessionImpl.java:535)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:523)
at org.hibernate.impl.SessionImpl.save(SessionImpl.java:519)
at edu.asu.cem.test.dao.CourseDAOTest.setUp(CourseDAOTest.java:103)
at sun.reflect.GeneratedMethodAccessor6.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.junit.internal.runners.MethodRoadie.runBefores(MethodRoadie.java:122)
at org.junit.internal.runners.MethodRoadie.runBeforesThenTestThenAfters(MethodRoadie.java:86)
at org.junit.internal.runners.MethodRoadie.runTest(MethodRoadie.java:77)
at org.junit.internal.runners.MethodRoadie.run(MethodRoadie.java:42)
at org.junit.internal.runners.JUnit4ClassRunner.invokeTestMethod(JUnit4ClassRunner.java:88)
at org.junit.internal.runners.JUnit4ClassRunner.runMethods(JUnit4ClassRunner.java:51)
at org.junit.internal.runners.JUnit4ClassRunner$1.run(JUnit4ClassRunner.java:44)
at org.junit.internal.runners.ClassRoadie.runUnprotected(ClassRoadie.java:27)
at org.junit.internal.runners.ClassRoadie.runProtected(ClassRoadie.java:37)
at org.junit.internal.runners.JUnit4ClassRunner.run(JUnit4ClassRunner.java:42)
at org.apache.maven.surefire.junit4.JUnit4TestSet.execute(JUnit4TestSet.java:62)
at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.executeTestSet(AbstractDirectoryTestSuite.java:138)
at org.apache.maven.surefire.suite.AbstractDirectoryTestSuite.execute(AbstractDirectoryTestSuite.java:125)
at org.apache.maven.surefire.Surefire.run(Surefire.java:132)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:585)
at org.apache.maven.surefire.booter.SurefireBooter.runSuitesInProcess(SurefireBooter.java:308)
at org.apache.maven.surefire.booter.SurefireBooter.main(SurefireBooter.java:879)
Caused by: java.sql.BatchUpdateException: failed batch
at org.hsqldb.jdbc.jdbcStatement.executeBatch(Unknown Source)
at org.hsqldb.jdbc.jdbcPreparedStatement.executeBatch(Unknown Source)
at org.hibernate.jdbc.BatchingBatcher.doExecuteBatch(BatchingBatcher.java:48)
at org.hibernate.jdbc.AbstractBatcher.executeBatch(AbstractBatcher.java:246)
... 44 more
I had made several changes since running the tests, so I had to think about what might be making a database call crash. Luckily, I was able to remember, but I have spent hours debugging these before. I wish the JDBC exception was floated to a higher point in the exception chain so it would be displayed. In this case, all I had to do was set a value for the fields that were no longer nullable.