Wowza Community

AMF Data Handling Problem?...

…or am I missing something?

This code worked in 1.3.3, as I recall, for sending data to the server for storage in arrays, and subsequent retrieval. Now, I can’t get it to work in 1.5.2…

[B]public void storeAmfData(IClient client, RequestFunction function, AMFDataList params) {
		// Send a string from the client, and store it in an object...
		String testName_str = getParamString(params, PARAM1);
		getLogger().info("storeAmfData called with name: " + testName_str);
		
		// Create an array to hold a group of AMFDataObj's
		AMFDataArray testInfo_array = new AMFDataArray();
		
		// Create a test AMFDataObj to hold data...
		AMFDataObj test_obj = new AMFDataObj();
		test_obj.put("testName_str", new AMFDataItem(testName_str));
		test_obj.put("testHobby_str", new AMFDataItem("no Hobby"));
		test_obj.put("testID_int", new AMFDataItem(14)); 
		
		// Does the loginSO already exist?
		boolean testSoExists_bool = client.getAppInstance().getSharedObjects(true).exists("TestDataStorage/testDataStorage");
		
		// SO handle...
		SharedObject user_so;
		
		// Get the SO, if it exists...
		if(testSoExists_bool) {
			user_so = new SharedObject("TestDataStorage/testDataStorage", true,
					client.getAppInstance().getSharedObjects(true).getStorageDir());
			getLogger().info("SO obtained from storeAmfData()");
		} else {
			// ...and register it if it doesn't yet exist.
			user_so = new SharedObject("TestDataStorage/testDataStorage", true,
					client.getAppInstance().getSharedObjects(true).getStorageDir());
			client.getAppInstance().getSharedObjects(true).put("TestDataStorage/testDataStorage", user_so);
			getLogger().info("New SO created from storeAmfData()");
		}
		if(user_so.containsProperty("testInfo_array")) {
			testInfo_array = (AMFDataArray)user_so.getProperty("testInfo_array");
			getLogger().info("SO contains... testInfo_array");
		}
		
		getLogger().info("DEBUG::testInfo_array.size(): " + testInfo_array.size());
		testInfo_array.add(test_obj);
		getLogger().info("DEBUG::testInfo_array.size(): " + testInfo_array.size());
		
		getLogger().info("DEBUG::*** ARRAY...");
		for(int i = 0; i < testInfo_array.size(); i++){
			getLogger().info("DEBUG::index: " + i);
			getLogger().info("DEBUG::testName_str: " + testInfo_array.getObject(i).getString("testName_str"));
			getLogger().info("DEBUG::testHobby_str: " + testInfo_array.getObject(i).getString("testHobby_str"));
			getLogger().info("DEBUG::testID_int: " + testInfo_array.getObject(i).getInt("testID_int"));
		}
		
		//user_so.lock();
		synchronized(user_so) {
			user_so.setProperty("testInfo_array", testInfo_array);
			user_so.flush();
		}
		//user_so.unlock();
		user_so = null;
		sendResult(client, params, "Hello.  Stored data.");
	}[/B]

I used synchronized() for 1.3.3 and lock() for 1.5.2

First call of the method works as expected. Second call results in this error…

ERROR server comment - invoke(storeAmfData): java.lang.ClassCastException: com.wowza.wms.amf.AMFDataMixedArray cannot be cast to com.wowza.wms.amf.AMFDataArray: accWowzaDataExample.AccDataExample.storeAmfData(AccDataExample.java:84)

java.lang.ClassCastException: com.wowza.wms.amf.AMFDataMixedArray cannot be cast to com.wowza.wms.amf.AMFDataArray

So, it seems to be storing my AMFDataArray as an AMFDataMixedArray, which I don’t want, because the AMFDataMixedArray doesn’t have the methods I need.

I also tried AMFDataList, instead of AMFDataArray, but that didn’t work with either 1.3.3 or 1.5.2, even though it seems as though it should have.

Suggestions? My app depends on being able to store, retrieve and delete clients’ data and info, and the arrays of objects is the handiest way to do that.

Actually even the new code is not quite right. There is a strange side effect in 1.5.2 that I really can’t get rid of. You can’t use AMFDataArray objects with SharedObjects. The following code works around the issue and properly persists the shared object. See if this code works for you:

public void storeAmfData(IClient client, RequestFunction function, AMFDataList params)
{
	// Send a string from the client, and store it in an object...
	String testName_str = getParamString(params, PARAM1);
	getLogger().info("storeAmfData called with name: " + testName_str);
	// Create a test AMFDataObj to hold data...
	AMFDataObj test_obj = new AMFDataObj();
	test_obj.put("testName_str", new AMFDataItem(testName_str));
	test_obj.put("testHobby_str", new AMFDataItem("no Hobby"));
	test_obj.put("testID_int", new AMFDataItem(14));
	// Does the loginSO already exist?
	String soName = "TestDataStorage/testDataStorage";
	ISharedObjects sharedObjects = client.getAppInstance().getSharedObjects(true);
	ISharedObject user_so = sharedObjects.getOrCreate(soName);
	user_so.lock();
	try
	{
		String propName = "testInfo_array";
		AMFDataArray testInfo_array = new AMFDataArray();
		if (user_so.containsProperty(propName))
		{
			getLogger().info("SO contains... testInfo_array");
			AMFData propData = user_so.getProperty(propName);
			if (propData instanceof AMFDataMixedArray)
			{
				AMFDataMixedArray mixedArray = (AMFDataMixedArray)propData;
				int msize = mixedArray.size();
				for(int i=0;i<msize;i++)
					testInfo_array.add(mixedArray.get(i));
			}
			else
				testInfo_array = (AMFDataArray)propData;
		}
		getLogger().info("DEBUG::testInfo_array.size(): " + testInfo_array.size());
		testInfo_array.add(test_obj);
		getLogger().info("DEBUG::testInfo_array.size(): " + testInfo_array.size());
		getLogger().info("DEBUG::*** ARRAY...");
		for (int i = 0; i < testInfo_array.size(); i++)
		{
			getLogger().info("DEBUG::index: " + i);
			getLogger().info("DEBUG::testName_str: " + testInfo_array.getObject(i).getString("testName_str"));
			getLogger().info("DEBUG::testHobby_str: " + testInfo_array.getObject(i).getString("testHobby_str"));
			getLogger().info("DEBUG::testID_int: " + testInfo_array.getObject(i).getInt("testID_int"));
		}
		user_so.setProperty("testInfo_array", testInfo_array);
		user_so.flush();
	}
	catch(Exception e)
	{
		getLogger().error("storeAmfData: "+e.toString());
		e.printStackTrace();
	}
	finally
	{
		user_so.unlock();
	}
	sendResult(client, params, "Hello.  Stored data.");
}

Quick explanation. AMFDataArray will work for non-persistent shared objects. The problem is when you persist the shared object. All shared object data gets persisted to AMF3 regardless of the level of its AMF origin. This is so we can normalize what we store. AMF3 does not contain a AMFDataArray object match. All array data in AMF3 is stored as the AMFMixedArray type. So you can store an AMFDataArray object but when it gets persisted it gets stored as an AMFDataMixedArray. So when it get deserialized it is now an AMFDataMixedArray. The above code will convert it back into the AMFDataArray format when needed.

Charlie

Could you indicate which is line 84?

Also, did anything change client side? Is this all AMF3 or AMF0? Is it AS3 or AS2? Can you post the part of your code that is interacting with this shared object?

Also, what is missing from the AMFMixedArray API?

Charlie

BTW, AMFDataArray now have the remove method (Wowza Pro 1.5.2). It was an oversight that it did not.

Charlie

Sorry for the line 84 omission. In this block…

if(user_so.containsProperty(“testInfo_array”)) {

line 84–> testInfo_array = (AMFDataArray)user_so.getProperty(“testInfo_array”);

getLogger().info(“SO contains… testInfo_array”);

}

So, the error happens when the server attempts to retrieve the stored array. It believes the stored array should be a DataMixedArray, instead of a DataArray. I suspect this is some internal error with the data handling. I haven’t yet tested my code with simple data like strings on Wowza, instead of the AMFDataObject that I need, but I’ll guess that the problem might go away in that case.

No changes to my client code. I use Flash 8 for developing, so I guess it’s AMF0? I use AS2. The code is very simple. The client does not know about the SO…

// Handle the callback from the server call, below.
var resultStoreData_obj:Object = new Object();
resultStoreData_obj.onResult = function(_str:String) {
	msg_txt.text += newline + newline + _str;
}
storeData_btn.onRelease = function() {
	the_nc.call("storeAmfData", resultStoreData_obj, "Freddy");
}

The AMFDataMixedArray does not have an add() method, to simply append an object. Since AMFDataMixedArray is extended from the AMFDataObject, it does have the put() method, but this requires a property label. I just want to add to an array and access using indices, as normal arrays do. I would really prefer to use the AMFDataList, which has the remove() method, which the AMFDataArray does not, but I can’t get that to work with either version.

I cut and pasted wrong from somewhere and I accessed the SO incorrectly. I changed these 2 lines…

if(testSoExists_bool) {

user_so = new SharedObject(“TestDataStorage/testDataStorage”, true,

client.getAppInstance().getSharedObjects(true).getStorageDir());

to this…

if(testSoExists_bool) {

user_so =

(SharedObject)(client.getAppInstance().getSharedObjects(true).get(“TestDataStorage/testDataStorage”));

Now, it seems to work. I’ll test it further, though.

Sorry about that. Thanks for the replies.

Yes, that workaround is doing the job, thanks.

So, somehow the AMFDataArray is being converted and stored as AMFMixedDataArray after the app stops, because as long as the client is still connected the data type retrieved from the SO is AMFDataArray, with each method call.

I hadn’t noticed the change in the API for 1.5.2 that AMFDataArray now has remove(), so that helps, too.

Thanks for the solutions!