Programmers
Guide to the
Observation Manager Java API 1.0
This document should be read by programmers who want to use or extend
the Observation Manager Java API. The latest version of the API can
always be downloaded at the projects main page.
Please also see the JavaDoc
pages which should be used as additional
information source about the Java API.
Also please see the XML Standard documentation, as the standard is the
basis on
which the API is constructed. Latest version and HTML documentation for
the XML Standard can be accessed here.
1.0 Introduction
Prerequisite for using the Java API:
- Java 1.4
Platform, Standard Edition. Download here.
- Apache
Xerces 2 Parser. Download here.
- Apache
Ant 1.6 (optional: needed for building jars, javadoc...) Download here.
I won't give a
"howto install" here for the above named software, therefore please see
the
corresponding documentation of the referred software.
Please use no prior version before Java 1.4 as the required java xml
interfaces
were introduced with 1.4. For the Apache Xerces you'll need to place
these two jars: xercesImpl.jar,
xml-apis.jar
into your java classpath.
Apache Ant can be used to build the APIs jars or javadocs. The
corresponding build.xml
descriptions can be found in the Observation
Manager Java API source distribution (see
/build folder). Please use at
least version 1.6 of Apache Ant.
License:
The whole Observation Manager Project (means all its distributions and
sources) are copyrighted by the corresponding author and distributed
under the Apache
License 2.0.
2.0 Coding standards and styles
These
general naming conventions and method behaviors can be find across
almost all classes of the Java API.
Setter methods that throw java.lang.IllegalArgumentException
Some
setter methods declare to throw a java.lang.IllegalArgumentException, even if this is not
necessary as this exception is handled by java.
Then, why does the method tell you that it might throw the exception?
The answer is easy: All mandatory fields that can be set over a setter
method throw this exception. First this should help the programmer to
directly see that the field is mandatory and also it ensures that only
valid values can be set. If you try to set a non valid value (like NULL) to a field that is
mandatory, you end up with a java.lang.IllegalArgumentException. Please mind that
the java.lang.IllegalArgumentException is an unchecked
exception, means the compiler doesn't take care about it. If the value
is somehow not OK, you'll find out during runtime!
Getter methods that return Float.NaN or Double.NaN
If a
getter methods returns you a Float.NaN (or Double.NaN) (NaN = Not a
number) it just indicates that the field the getter belongs to is
optional and wasn't set so far. So a unset optional field has a default
value of NULL, Float.NaN or Double.NaN. Please be aware
that you can't check for NaN values with the normal equals operator
like:
float
a = Float.NaN;
if( a == Float.Nan ) {
}
this won't work, as a is "not a number".
Use this instead:
float
a = Float.NaN;
if( Float.isNaN(a) ) {
}
The method: addAsLinkToXmlElement(org.w3c.Element)
In many
classes of the Java API you'll find a method like: addAsLinkToXmlElement(org.w3c.Element). So what is it used
for? In most cases you shouldn't care about this method, as it is
called automatically while serializing a whole de.lehmannet.om.Observations document.
Nevertheless here's what this method does in an example:
Lets say you call this method on an instance of the class de.lehmannet.om.Scope and pass the xml
element <observation> as parameter.
What the method does is: It creates a new element <scope> underneath <observation> with objects ID as
value. (the ID is mostly some unique GUID).
Then it gets the parent document of <observation> (which is normally: <fgca:observations>) and searches for
the <scope> container element
(which is called: <scopes>). If it doesn't find
the container element, it creates it. Then it creates a new <scope> element underneath
the container and writes the objects data inside the new element.
Seen from the XML document point of view the method call would look
like this:
Before:
<fgca:observations>
<observation>
Some more stuff that belongs to the observation,
like observer, target....
<observation>
More observations and container elements
</fgca:observations>
After:
<fgca:observations>
<observation>
<scope>fabe9:fda997a012:-7fee</scope>
Some more stuff that belongs to the observation,
like observer, target....
<observation>
<scopes>
<scope id="fabe9:fda997a012:-7fee">
<model>Nikon 10x50 CF</model>
<aperture>50.0</aperture>
<magnification>10.0</magnification>
</scope>
</scopes>
More observations and container elements
</fgca:observations>
3.0 The Structure
of
the java aPI
The
structure of the API derives from the XML Schema definition. The start
element of the Java API is de.lehmannet.om.Observations,
which
corresponds with the root element in the XML Schema <fgca:observations>. The class
de.lehmannet.om.Observations itself holds no data. It is
more a
container for all de.lehmannet.om.Observations classes
which have
been entered. Also it
provides some XML Constants needed for serializing a document. The most
interesting part of the class is the method:
serializeAsXml(java.io.File
xmlFile) which
writes all de.lehmannet.om.Observations (and their
subelements) in the given file as
xml. Another interesting class is de.lehmannet.om.Observation it is the main class
for data storage and corresponds with <observation> in the xml.
The class
contains directly or indirectly all data which describes an
astronomical observation.
If two instances of de.lehmannet.om.Observation where made by the
same observer (which is represented by de.lehmannet.om.Observer) both instances
share the same class! This corresponds with the XML Schema definition,
as both classes "link" to one and the same observer. The same applies
to all other data that is stored in de.lehmannet.om.Observation (except for the
"native" types like the begin date (= the start date of the
observation). All other elements of the package de.lehmannet.om represent more or
less all the elements described in the XML Standard definition.
The package de.lehmannet.om.util contains helper and
utilities classes, which are needed to load, format or convert data.
One example is the class
de.lehmannet.om.util.DateConverter which is needed to
transform a java.util.Date object into a
ISO8601 string, that represents a date within the XML Standard.
Additionally the class contains a parser, which can take a ISO8601
formated string and return a java.util.Date object. The
conversion of a Gregorian date into a Julian date is also offered by
the class. The class de.lehmannet.om.util.UIDGenerator is a simple global
unique ID generator based on the java.rmi.server.UID class. As almost
every class representing an xml element in the Java API needs a unique
ID (e.g. for "linking" the elements. see above example with observer),
this class generates the IDs. (As it's not sure if the IDs should be
more self explaining in the future, the class de.lehmannet.om.util.UIDGenerator itself should always
be used over its interface de.lehmannet.om.util.IIDGenerator).
The classes de.lehmannet.om.util.ConfigLoader
and de.lehmannet.om.util.SchemaLoader are needed for
reading/parsing a XML File containing a valid XML Standard description
(for
more please see Reading XML
Schema Files).
For the
implementation of the abstract elements <result> and <target>, extension packages
are defined. Currently the only extension is the DeepSky
Extension (de.lehmannet.om.extension.deepSky). The extensions are
specialized classes for a specific observation targets (like DeepSky
Objects) and observations results. (Simple Example: A observation
target inside the solar system, has to have other attributes as a
observation target that is a DeepSky Object (e.g. a Galaxy). So are the
observation results))
4.0 Writing XML
Schema Files
This
paragraph should show you in an short example how to create a valid
observation and how to serialize it.
Creating an observation is pretty straight forward. You instantiate a
class of de.lehmannet.om.Observation and meanly thats it.
OK, for instantiating you need some more instances of other API
classes. Lets look at this:
Say you observed M45
(Messier 45, The Pleiads). As they're an open
star cluster, which is a DeepSky object, you need some class of the
DeepSky extension. de.lehmannet.om.extension.deepSky.DeepSkyTargetOC is specialized for
open clusters so:
ITarget
myObservedTarget = new DeepSkyTargetOC("M45", "Messier Catalogue");
thats
about all. You can set some more parameters, but we skip this for now.
Next we need an de.lehmannet.om.Observer
object. This can
easily be done like this:
IObserver thatsMe =
new Observer("John", "Doe");
Next, we need a observation site. Somewhere the observation took place.
So here we go:
ISite
myObservingSite = new Site("Wehrheim, Germany",
new Angle(8.567, Angle.DEGREE),
new Angle(50.3, Angle.DEGREE),
2
);
This is it. The first parameter is the name of the site, the second one
is the longitude (where positive values are east of Greenwich,
England).
The third parameter this the latitude, where positive value indicate
north of the equator. The last parameter this the timezone. Now you
also used one of the most famous helper classes of the API: de.lehmannet.om.Angle. The class is always
used to represent some sort of an angle, whatever unit it may have.
Position angle, latitude, longitude... everything that can be packed
into
an angle is represented by de.lehmannet.om.Angle.
The next thing we need to represent is the telescope we made the
observation with. Therefore we use de.lehmannet.om.Scope.
IScope myTelescope =
new Scope("Starfinder 10",
254
1140
);
The first parameter is a name for the telescope (can be vendor name and
model or the name you personally call the scope....doesn't matter
here). Second is the aperture
given in mm, and third is the focal length also in mm.
Next, if we used a telescope, we also used a eyepiece. So lets build up
an eyepiece object with: de.lehmannet.om.Eyepiece
IEyepiece uncleAlsBestOne = new
Eyepiece("Nagler",
31
);
This is the easiest and cheapest way to get a 31 Nagler. :-)
Next, is the object de.lehmannet.om.Session. A session describes
an observing night or a group of observations, that have been all made
at the same observation session/night. This is not necessary here, but
we create one nevertheless.
ISession theNight =
new Session(new java.util.Date(2004, 01, 01, 22, 00),
new java.util.Date(2004, 01, 01, 23, 30));
So the observing night, took place at the 1 of January 2004, and
started at 22:00 local time and lasted until 23:30 local time.
Last object we need is a de.lehmannet.om.IFinding instance. As we
observed a DeepSky target, the class de.lehmannet.om.extension.deepSky.DeepSkyFinding is specialized for
this kind of observation results.
IFinding myFindings
= new DeepSkyFinding("Wow! There're more than 7 stars!",
1
);
The first parameter is a description of what the observer saw. This can
be only a short "Wow!" up to the whole life story of the observer that
takes pages.
The second parameter is special for de.lehmannet.om.extension.deepSky.DeepSkyFinding. It describes
something like the "quality" the object could be seen. 1 stands
for "Simple conspicuous object in the eyepiece". (For a complete
list of possible rates, please see the JavaDocs of de.lehmannet.om.extension.deepSky.DeepSkyFinding at our online
JavaDoc section.
As we got everything we need, lets put it all together to one
observation:
IObservation
observationOfM45 = new Observation(new java.util.Date(2004, 01, 01, 22,
15),
new java.util.Date(2004, 01, 01, 22, 30),
5.0f,
2,
150.0f,
myObservedTarget,
thatsMe,
myObservingSite,
myTelescope,
uncleAlsBestOne,
theNight,
myFindings
);
That's it. We got our first observation! Something more on the
parameters: The first one is the start date of the observation and the
second one is the end date. Please mind: these dates refer to the
times, the observer looked on the given target. The start and end time
of
the whole observing night, are captured in the session object! Also
please mind, that the observations start and end time, have to be
inside the sessions start and end time (Yes, the API is
restrictive..:-)
)
The third value is the magnitude of the faintest star that could be
seen with the unaided eye during the observation. Fourth is the seeing
(1=best, 5 worst) (see JavaDocs
for more), fifth is the magnification (can be calculated by telescopes
focal length and eyepiece length). All other parameters you should know
now.
So now we got everything as java objects but how to persist them as a
valid xml?
Therefore look at a sample XML. The xml standard always starts with a <fgca:observations> element. This is
represented in the Java API by the class
de.lehmannet.om.Observations (don't mess this
with
de.lehmannet.om.Observation the first one is a
container, while the second is a "real" observation). So lets get the
observations container:
Observations observationsContainer = new
Observations();
try {
observationsContainer.addObservation(observationOfM45);
} catch(SchemaException se) {
// Thrown e.g. if passed object as NULL
}
After we got the
container we can use its serializeAsXml(java.io.File) method, to write the
observation in a file:
try {
observations.serializeAsXml(new
java.io.File(thePathToTheXmlFile));
} catch(SchemaException se) {
// Something went wrong. Maybe IO error? See
exceptions message...
}
That's it. Now you've seen how to write valid xml files that fit the
xml standard!
5.0 Reading XML
Schema Files
This
paragraph should explain how you could use the Java API for parsing the
xml standard. As a result of the parsing of a xml standard file you
should get a instance of de.lehmannet.om.Observations that contains
the xml data as Java objects.
Also this paragraph will explain how the Java API parses the xml data,
and what does it do in the background.
Loading a xml file
The parsing of a xml file starts with
de.lehmannet.om.util.SchemaLoader. This class
offers two mean methods: load(java.io.File) and load(org.w3c.dom.Document). Both methods do the
same only the parameter is different.
If you got a java.io.File object that
represents a xml file containing valid xml standard data, all you got
to do is the following to parse the file:
File myXmlFile = new
File("/home/john/observations.xml");
SchemaLoader parser = null;
try {
parser = new SchemaLoader();
Observations observationsContainer = parser.load(myXmlFile);
} catch(FGCAException fgca) {
// Problem while parsing the xml file
}
The returned object is a instance of
de.lehmannet.om.Observations, that you already
know from the above chapter.
The object contains all data of the xml file, represented as Java
Objects.
As a user of the JavaAPI this is all you got to know about how xml
files can be transformed to Java API objects.
For expanding and understanding the JavaAPI, you should know what's
behind the scenes of
de.lehmannet.om.util.SchemaLoader. What is class does
on a first glance is pretty simple. It scans the xml file for the known
container elements and if it finds a container it parses all the
contained elements. Almost all Java API elements have a constructor
like this Scope(org.w3c.dom.Node). It enables the
corresponding class to create itself out of a org.w3c.dom.Node element, where the
given node is always the root of the corresponding element, e.g. <scope>.
This is all more or less straight forward. The trouble begins with the
xml schema elements <result> (as subelement of <observation>) and <target>.
These two elements are defined abstract by the standard. Means their
real implementation are represented by the optional extensions.
Currently we offer only the DeepSky extension, that implements the
abstract classes, but hopefully we can offer more extensions in the
future (e.g. SolarSystem, VariableStars....).
But how do we know while parsing a xml file, which implementation of
e.g. a target we've found? This we can tell by a xml element attribute
called xsi:type
that must be set at all <result> and <target> subclasses (or
subelements). For example the open cluster deep sky targets (which are
represented in the Java API as de.lehmannet.om.extension.deepSky.DeepSkyTargetOC) have the xsi:type
value fgca:deepSkyOC.
So if the parser finds a <result> or a <target> element, it searches
for the xsi:type attribute as well.
But knowing the xsi:type is only the half way to get the corresponding
Java API class for the element. Therefore a mapping is defined that
links a xsi:type with a java class. This mapping must be stored in a file
called:
SCHEMATYPE which has to be
located in the META-INF
path of the jar that contains the Java API extension classes.
If you open for example the ext_DeepSky.zip
file, you see the SCHEMATYPE file, under META-INF. This file defines
the mapping between a xsi:type value that can be found at the xml
elements and the corresponding Java API class.
The parser resolves the xsi:type with the help of the class de.lehmannet.om.util.ConfigLoader , which tells him
the java class which he has to load via javas reflection mechanism.
With the above mentioned procedure, the Java API is flexible enough to
handle all future xml standard extensions which may come. Another main
advantage of this procedure is that it's easy to handle by the user.
All he needs to do is putting the extension jar into his java
classpath. The
de.lehmannet.om.util.SchemaLoader (better the de.lehmannet.om.util.ConfigLoader class) will find it
there.
The
format of the SCHEMATYPE
file
To ensure that the SCHEMATYPE file can be loaded,
it has to have a simple format, that will be explained here.
An entry for a single mapping (xsi:type to java class) has to have two lines.
Example:
DS_TargetDN_XSI_Relation_Class:
de.lehmannet.om.extension.deepSky.DeepSkyTargetDN
DS_TargetDN_XSI_Relation_Type:
fgca:deepSkyDN
The first part of a line is the key which ends with a colon (:). The
last part is the value.
The key itself is build up of two parts. The identifier and the type.
The type can only have two values: XSI_Relation_Class
and XSI_Relation_Type.
In the example above DS_TargetDN_ is the identifier
followed by its type XSI_Relation_Class, the key is
separated
at the end with a colon.
For a valid entry both types (XSI_Relation_Class and XSI_Relation_Type) have to appear in
the file, both with the same key identifier. The identifier can be
chosen by the developer of the extension. The value of the type XSI_Relation_Class must contain the
java class, while the value of the type XSI_Relation_Type must be the xsi:type.
Please mind, that the file must be placed inside the META-INF folder of the jar
that contains the extensions classes.
|