|
SageTV v7 Customizations This forums is for discussing and sharing user-created modifications for the SageTV version 7 application created by using the SageTV Studio or through the use of external plugins. Use this forum to discuss plugins for SageTV version 7 and newer. |
|
Thread Tools | Search this Thread | Display Modes |
#1
|
||||
|
||||
HOWTO: Using Sage Remote APIs for Java Development
This is a post on setting up the sagex-apis (Sage Remote APIs) so that you can develop the java portion of your customization, and fully test it, without having to build and deploy a jar to SageTV. Building and deploying a jar, and then restarting SageTV to pick up the changes is time consuming, and your time is better spent writing code
Overview First, just a quick overview of the sagex-apis. This is not a new plugin. This is the first plugin that I wrote when I started developing for sagetv, because I needed a way to develop for sagetv like any other desktop/web application, ie, write and test on my desktop. The sagex-apis is a thin api wrapper on top of Sage's Native API. The api can function like the normal SageAPI when deployed to a Sage Server, or it can communicate remotely with the server. You don't have to do anything special in your code to allow for this, since the API knows when it is running inside the sagetv server or remotely. The is very little overhead when running inside the sage server, since sage objects are passed around, as is. The is some overhead when running remotely, since objects have be serialized and transmitted over the network. Installation To get started, you'll need to install sagex-services - SageTV Remote API Services on the server to which you want to communicate. This plugin includes all the remote api capabilities (json/xml over http + rmi), which is why it will also depends on Jetty as well. Here's a screen shot of the Plugin Manager with sagex-services highlighted. Next, you should make sure that the RMI services is enabled, since that is how your remote Java code will communicate with the sagetv server. Go to the plugin manager, installed plugins, and select sagex-services, and then configure plugin. You should see the following screen, and just make sure that the first option, Enable RMI Remote API is True. Testing and Using So, you are now all set on the server, next go to your workstation and open up your IDE. And create short piece of test code, something like, Code:
package test; import sagex.api.Global; public class TestMisc { public static void main(String args[]) throws Exception { System.out.println("Sage OS: " + Global.GetOS()); } } Code:
INFO - Configured Root Logger LOG4J: Configured Root Logger INFO - Configured Logging for: sagex-api using file: sagex-api.log4j.properties LOG4J: Configured Logging for: sagex-api using file: sagex-api.log4j.properties Embedded SageAPI is not functional. We are most likely running remotely. Adding Remote Server: sean-desktop Adding Remote Server: mediaserver Sage OS: Linux The line about "Embedded SageAPI is NOT functional" is letting your know that you are not running this code inside the sage server, but remotely, which is what you want. Following that, the sagex apis will begin discovering remote servers to which it can communicate. On my network, I have main server (mediaserver) and my devlopment server (sean-desktop). In the event that there is more than one server returned, then sagex will use the first one, in this case, sean-deskop. And finally the last line is proof that it is working. It is the result of your System.out command, which shows the OS that the Sage Server is running on, in my case Linux. I have more than one SageTV server Sometimes, if you have more than one server, OR if the discovery code fails (which it may), then you can force a server in your test code. Code:
package test; import sagex.SageAPI; import sagex.api.Global; import sagex.remote.rmi.RMISageAPI; public class TestMisc { public static void main(String args[]) throws Exception { SageAPI.setProvider(new RMISageAPI("mediaserver")); System.out.println("Sage OS: " + Global.GetOS()); } } The output now, looks like this. Quote:
I'm Old School You might have an old library that uses the SageTV.api() and SageTV.apiUi() calls, and you don't really want to convert all that code into wrapped sagex-apis. You can still take advantage of the sagex-apis, with some minimal effort. Simply replace SageTV.api() and SageTV.apiUI() with SageAPI.call() in the old library. Recompile your library, and now your library can be used remotely. I've successfully done this will all the nielm jars, and even Greg's api wrapper. In fact, initially I wasn't going to build a set of wrappers, and all I had was the SageAPI.call() method, that proxied the SageTV.api() method. But, since autogenerating a set of wrappers is fairly easily, I extended it to make a full blown set of wrappers that also work remotely. What about Web Applications I develope and test BMT web ui, which is a fairly complex web application that depends on phoenix and bunch of other libraries, and I do it entirely outside of the sagetv server. This makes for a nice environment for developing web applications, since you fully test and develop on your local jetty/tomcat server and when you are ready, you can build a war and publish to sagetv. To do this, simply, include the sagex-api.jar in your web classpath. That's it. If you need force the sagex apis to a particular server, you can do that using the servlet's init() method, and do something like... Code:
if (SageAPI.isRemote()) { SageAPI.setProvider(new RMISageAPI("mediaserver")); } JUnit Testing The other reason I created the sagex-apis is that I needed a better way to unit test code from my eclipse development environment. I won't get into JUnit testing, only to say, that by dynamically setting a new API provider, you can unit test code, without using a real sagetv server. I provide a simple stub provider, sagex.stub.StubSageAPI, that can be be used without fear that you are affecting a live server. In your unit test code, you can add the following line.. Code:
SageAPI.setProvider(new StubSageAPI()); Another option, which I use as well, is I dynamically create a SageAPIProvider implementation using easymock. This enables me to fill the api provider with mock data so that I can do a unit test. Having options to unit test your code, is a great way easily add quality to your plugin. There is much more that I could talk about with the sagex-services/sagex-api, but this is a good start, for now Good luck.
__________________
Batch Metadata Tools (User Guides) - SageTV App (Android) - SageTV Plex Channel - My Other Android Apps - sagex-api wrappers - Google+ - Phoenix Renamer Downloads SageTV V9 | Android MiniClient |
#2
|
||||
|
||||
Thanks for the good article Sean. Once I get caught up on everything I'll write unit tests After doing that all day it's hard to come home and go through it all again.
I suppose there's a certain procedure that needs to be followed to set up Jetty? If anybody should know it would be me but I haven't tried it If you have the time would you mind sharing more about testing with mock objects? It seems some things like GetProperty would be easier than others such as GetAiringsForShow. Jason
__________________
Server: Intel Core i5 760 Quad, Gigabyte GA-H57M-USB3, 4GB RAM, Gigabyte GeForce 210, 120GB SSD (OS), 1TB SATA, HD HomeRun. Extender: STP-HD300, Harmony 550 Remote, Netgear MCA1001 Ethernet over Coax. SageTV: SageTV Server 7.1.8 on Ubuntu Linux 11.04, SageTV Placeshifter for Mac 6.6.2, SageTV Client 7.0.15 for Windows, Linux Placeshifter 7.1.8 on Server and Client, Java 1.6. Plugins: Jetty, Nielm's Web Server, Mobile Web Interface. |
#3
|
||||
|
||||
JUnit Testing using EasyMock or your own Custom Provider
I'm not much of a fan of writing unit tests myself, but with projects the size of bmt and phoenix, they need them. Without unit testing (especially phoenix), I'd be scared about making any changes to the core
Also, I'm fairly relaxed with the unit tests. ie, I create tests and will group a bunch of operations together, but some people are real sticklers about having every little test separated out into their own test case, etc. (I'm not like that ) (note, easymock can be very complicated to get your head around, but once you do, it can be a useful unit testing tool) As for easymock, here's a simple junit test that I wrote a awhile ago to test the Year value coming back from phoenix IMetadata proxy, that proxies the sagetv GetShowYear and/or Get/SetMediaFileMetadata. Code:
final Map<String, String> simpleMetadataMap = new HashMap<String, String>(); ISageAPIProvider prov = createNiceMock(ISageAPIProvider.class); expect(prov.callService(eq("GetShowYear"), (Object[]) anyObject())).andAnswer(new IAnswer<Object>() { public Object answer() throws Throwable { return simpleMetadataMap.get(((Object[])getCurrentArguments()[1])[0]); } }).anyTimes(); expect(prov.callService(eq("GetMediaFileMetadata"), (Object[]) anyObject())).andAnswer(new IAnswer<Object>() { public Object answer() throws Throwable { return simpleMetadataMap.get(((Object[])getCurrentArguments()[1])[0]); } }).anyTimes(); expect(prov.callService(eq("IsMediaFileObject"), (Object[]) anyObject())).andAnswer(new IAnswer<Object>() { public Object answer() throws Throwable { return true; } }).anyTimes(); replay(prov); SageAPI.setProvider(prov); simpleMetadataMap.put("mediafile1", "2010"); simpleMetadataMap.put("mediafile2", "ten"); simpleMetadataMap.put("mediafile3", ""); simpleMetadataMap.put("mediafile4", null); IMetadata md = AiringMetadataProxy.newInstance("mediafile1"); assertEquals(phoenix.metadata.GetYear("mediafile1"),2010); md = AiringMetadataProxy.newInstance("mediafile2"); assertEquals(phoenix.metadata.GetYear("mediafile2"),0); md = AiringMetadataProxy.newInstance("mediafile3"); assertEquals(phoenix.metadata.GetYear("mediafile3"),0); md = AiringMetadataProxy.newInstance("mediafile4"); assertEquals(phoenix.metadata.GetYear("mediafile4"),0); md = SageMediaFileMetadataProxy.newInstance("mediafile1"); assertEquals(phoenix.metadata.GetYear("mediafile1"),2010); md = SageMediaFileMetadataProxy.newInstance("mediafile2"); assertEquals(phoenix.metadata.GetYear("mediafile2"),0); md = SageMediaFileMetadataProxy.newInstance("mediafile3"); assertEquals(phoenix.metadata.GetYear("mediafile3"),0); md = SageMediaFileMetadataProxy.newInstance("mediafile4"); assertEquals(phoenix.metadata.GetYear("mediafile4"),0); } Code:
final Map<String, String> simpleMetadataMap = new HashMap<String, String>(); Code:
ISageAPIProvider prov = createNiceMock(ISageAPIProvider.class); Code:
expect(prov.callService(eq("GetShowYear"), (Object[]) anyObject())).andAnswer(new IAnswer<Object>() { public Object answer() throws Throwable { return simpleMetadataMap.get(((Object[])getCurrentArguments()[1])[0]); } }).anyTimes(); Code:
SageAPI.setProvider(prov); Code:
simpleMetadataMap.put("mediafile1", "2010"); simpleMetadataMap.put("mediafile2", "ten"); simpleMetadataMap.put("mediafile3", ""); simpleMetadataMap.put("mediafile4", null); Code:
IMetadata md = AiringMetadataProxy.newInstance("mediafile1"); assertEquals(phoenix.metadata.GetYear("mediafile1"),2010); Next I run through 4 tests, using the SageMediaFileMetadataProxy, which is Proxy that sends all Metadata requests to the GetMediaFileMetadata api for the given MediaFile. Code:
md = SageMediaFileMetadataProxy.newInstance("mediafile1"); assertEquals(phoenix.metadata.GetYear("mediafile1"),2010); This test illustrates that the entire process, while ultimately depending on SageTV, can work quite well without SageTV being present, as long as we use the sagex apis and we provider a provider to handle the communication. I use a combination of easymock and Stub Provider in my unit testing, but you could have just as easily created your own stub provider and not used easymock at all. (I used it here, because I wanted to better understand how easymock worked). Code:
ISageAPIProvider prov = new ISageAPIProvider() { @Override public Object callService(String context, String name, Object[] args) throws Exception { // don't worry about context return callService(name, args); } @Override public Object callService(String name, Object[] args) throws Exception { if ("GetShowYear".equals(name)) { return simpleMetadataMap.get(args[0]); } else if ("GetMediaFileMetadata".equals(name)) { return simpleMetadataMap.get(args[0]); } else if ("IsMediaFileObject".equals(name)) { return simpleMetadataMap.containsKey(args[0]); } else { System.out.println("Unhandled: " + name); return null; } } }; SageAPI.setProvider(prov); In some of my tests, I simply need to return some media files, based on whether the API is recievng "T" (Recordings) or "TL" (Archived Recordings). So, it looks like this. Code:
ISageAPIProvider prov = createNiceMock(ISageAPIProvider.class); expect(prov.callService(eq("GetMediaFiles"), aryEq(new Object[] {"T"}))).andAnswer(new IAnswer<Object[]>() { public Object[] answer() throws Throwable { return new Object[] {"1","2"}; } }); expect(prov.callService(eq("GetMediaFiles"), aryEq(new Object[] {"TL"}))).andAnswer(new IAnswer<Object[]>() { public Object[] answer() throws Throwable { System.out.println("GETMEDIAFILES called"); return new Object[] {"3","4"}; } }); Hope this gives better clarification on how to use easymock and sagex apis (or simply create your own provider to emulate sagetv. And one final note about unit tests. While I tend to run them from the IDE, I do have an ant task that runs through the entire set of cases as well. I'm not using a TestSuite, since I need each test in a separate JVM to ensure propper setup and tear down of the Junit tests. (I'm lazy, and I don't want to do my own setup and teardown in each test case ) Here's my ant task... Code:
<target name="unittests" depends="build"> <mkdir dir="${target}/testclasses"/> <mkdir dir="${target}/unittests"/> <javac debug="true" classpathref="project.class.path" source="1.5" target="1.5" srcdir="src/test/java" destdir="${target}/testclasses"/> <junit printsummary="yes" haltonfailure="yes" fork="true"> <classpath> <pathelement path="${target}/testclasses"/> <pathelement location="target/build/classes" /> <fileset dir="lib" includes="*.jar"/> <pathelement path="${sage.home}/Sage.jar"/> </classpath> <formatter type="xml"/> <test name="test.junit.TestMenus" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestOnlineVideos" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestMiscStuff" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestTaskManager" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestFanartLocations" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestUtilAPI" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestDateParsers" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestConfigurationMetadata" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestFormattedTitles" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestImageAPI" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestMetadata" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestKeypadRegexSearch" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestEDLCommercials" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestMediaResources" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestSkinAPI" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestTrailersVFS" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestTransformFactory" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestGetYearError" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestDynamicVariables" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestReplacements" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestRemoveAll" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestProgressMonitor" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestEvents" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestDownloadManager" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestUserProfiles" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestFanartDownloading" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestVFSBuilder" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestFilePatterns" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestSearchQuery" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestMetadataProviderFactory" haltonfailure="no" todir="${target}/unittests"/> <test name="test.junit.TestMediaBrowser" haltonfailure="no" todir="${target}/unittests"/> </junit> </target>
__________________
Batch Metadata Tools (User Guides) - SageTV App (Android) - SageTV Plex Channel - My Other Android Apps - sagex-api wrappers - Google+ - Phoenix Renamer Downloads SageTV V9 | Android MiniClient |
#4
|
||||
|
||||
Quote:
For bmt, I use GWT, so it has it's own Jetty webserver. There's nothing special that I have to do to get it working.
__________________
Batch Metadata Tools (User Guides) - SageTV App (Android) - SageTV Plex Channel - My Other Android Apps - sagex-api wrappers - Google+ - Phoenix Renamer Downloads SageTV V9 | Android MiniClient |
#5
|
||||
|
||||
Quote:
Quote:
Thanks for putting together the mock example. One thing I was hung up on was how to get a Sage Airing or MediaFile object and then pass it around to other APIs. It looks like you've covered that in your example.
__________________
Server: Intel Core i5 760 Quad, Gigabyte GA-H57M-USB3, 4GB RAM, Gigabyte GeForce 210, 120GB SSD (OS), 1TB SATA, HD HomeRun. Extender: STP-HD300, Harmony 550 Remote, Netgear MCA1001 Ethernet over Coax. SageTV: SageTV Server 7.1.8 on Ubuntu Linux 11.04, SageTV Placeshifter for Mac 6.6.2, SageTV Client 7.0.15 for Windows, Linux Placeshifter 7.1.8 on Server and Client, Java 1.6. Plugins: Jetty, Nielm's Web Server, Mobile Web Interface. |
Currently Active Users Viewing This Thread: 1 (0 members and 1 guests) | |
|
|
Similar Threads | ||||
Thread | Thread Starter | Forum | Replies | Last Post |
Library: Remote APIs for SageTV | stuckless | SageTV Studio | 421 | 07-01-2022 03:24 PM |
Howto: Install Java correctly for sage | Zervun | SageTV Linux | 6 | 05-23-2008 11:33 AM |
Sage Development Environment | stuckless | SageTV Studio | 4 | 04-05-2008 06:14 AM |
Sage Recordings Playlist Development..Any takers? | Deadbolt | SageTV Customizations | 11 | 04-14-2006 05:43 PM |
Sage SDk? howto? | oshapir | SageTV Beta Test Software | 1 | 05-07-2004 07:29 AM |