Showing posts with label flex. Show all posts
Showing posts with label flex. Show all posts

Wednesday, April 28, 2010

Flex 3 and Flex 4 - Tips

  • When manually dispatching an event, using uiComponent.dispatchEvent(your-event), you need to add listener to the same component uiComponent. So, uiComponent.addEventListener(type,handler) will work, but uiAnyOtherComponent.addEventListener(type,handler) will not work.
  • The state changes-related transition effects have to correspond to the type of changes between the corresponding states.
    Example: I had a vertical group with two components. In State 1, both components show. In State2, Comp1 is not included. I set a Resize transition for Comp1 and a Move transition for Comp2. The effect was surprising: when state changed, Comp1 disappeared and appeared at the bottom of the screen. There it was re-sized. The right fix was to change Comp1: instead of includeIn=State1, I did height.State2=0. That did the trick.
  • Constraint-based layout is supported only in a container with BasicLayout. If you want to use it inside a VGroup, for example, you can put it inside a Group for which you don't specify any layout (BasicLayout is the default).

    Monday, April 19, 2010

    Persistence Layer for Flex-and-Java Applications And The Like

    1. Introduction

    When architecting an RIA (aka Web 2.0) application, you have to decide how do you implement the persistence layer. Using the ORM is a standard nowadays, so it's given.  Question as to which ORM to use, got easier with strong acceptance of the Sun's JPA standard by the industry. But, how do you apply ORM in an RIA application is not a question with just a one obvious answer. On one side, the choice of the framework (and the architecture with it). This is presented in section 2. Section 3 presents another aspect of persistence layer: session management, that is how you actually use ORM to persist detached objects (detached, because they are received from outside of the application, namely from the client, for example a Flex client).

    2. ORM in an RIA Application

    Popular options are listed below. Each with advantages and disadvantages.

    2.1. Just-JPA Approach:
    • LCDS, BlazeDS or another Flex remoting framework
    • JPA (in general: ORM) on the server-side, with an object mapper like Dozer
    Pros and Cons:
    • Pro: Simple and a popular choice.
    • Con: Doesn't let you take advantage of ORM's lazy loading and lazy initialization.
      2.2. LCDS with Built-In Hibernate Adapter:

      This solution requires you to purchase a commercial heavy-duty Flex remoting framework Adobe LCDS (LiveCycle Data Services). If you go this way, to integrate it with persistence layer, you create a Hibernate assembler class on the server (Java) and point the LCDS destination to it, as described here.

      Pros and Cons:
      • Pro: (I believe) it takes advantage of lazy loading and initialization.
      • Cons: all of the persistence layer is implemented on the client which can lead to bigger/fatter client.
      • Cons: expense.
      • Cons: proprietary solution.
      2.3. BlazeDS with Gilead:

      Open source solution. Described in detail here.

      Pros and Cons:
      • Pro: open-source; no vendor lock-in.
      • Cons: unknown.
      2.4. GraniteDS:

      Provides a complete open-source solution. Described in a nutshell in comment to this article. More complete information, and a comparison with LCDS-with-Hibernate-assembler option, can be found in this article by the same person (as the comment), William Drai. This article has also more general, highly useful, background on the topic as a whole.

      Pros and Cons:
      • Pro: open-source; no vendor lock-in.
      • Cons: unknown
      3. Persisting Detached Objects

      The three most common applicable persistence design patterns are (as described by Hibernate documentation):
      • session per requestThe most common solution. A single Session and a single database transaction implement the processing of a particular request event. Do never use the session-per-operation anti-pattern.
      • session per conversationOnce persistent objects are considered detached during user think-time and have to be reattached to a new Session after they have been modified.
      • session-per-request-with-detached-objectRecommended. In this case a single Session has a bigger scope than a single database transaction and it might span several database transactions. Each request event is processed in a single database transaction, but flushing of the Session would be delayed until the end of the conversation and the last database transaction, to make the conversation atomic. The Session is held in disconnected state, with no open database connection, during user think-time.
      For another good article on this topic see this.

      3.1 More about Session-Per-Request Pattern

      Let's assume we have an application an RIA with stateless EJB's on the server side. Each request gets a new EntityManager injected. I suggest the following approach to implement session-per-request:
      • if we receive a new object, persist it using
            entityManager.persist(entity)
      • if we receive an object, that is already in the database, use
            entityManager.merge(entity)
      There is one narrow case, when this wouldn't work as expected: when we have a bidirectional association between Invoice and InvoiceDetail and we receive and invoiceDetail with the field invoice set to null. If we apply the method as above, the merge() will reset the invoice in invoiceDetail to null, while the field invoiceDetails in invoice object will continue pointing to invoiceDetail. However, I consider this not a practical case.

      4. Conclusion
      So, a simple starting point that I recommend for your RIA application is to use just JPA with a session-per-request persistence pattern. It can be as simple as:

      JpaDao {
        public void persist(E entity) {
          if (entity.getId() == null) {
            entityManager.persist(entity);
          } else {
            entityManager.merge(entity);
          }
        }
      }
      as suggested by Marcell Manfrin (in a comment).

      4. My Recommended Solution

      • Use JPA with an ORM provider of your own. 
      • Use only JPA annotations and avoid using native provider's annotations. If you have to use a provider's native application, mark it in the code in an easy to discover way.
      • Use session-per-request approach with object mapper like Dozer.
      • Unless domain model is very simple and used only in CRUD-like way, use DTO objects to transfer data between client and server (article).

        • Question: use simple domain objects with public fields?
      • Use service facade layer to:

        • manage session-per-request
        • map domain to/from DTO objects
        • isolate the client from the server
        • provide an lightweight API for the client rather than exposing the client to a complex domain model.

      Flex 4, Spark Architecture Overview: Notes

      Based on a great article by Deepa Subramaniam.

      Intro
      • Every Spark component consists of the skin class (defined declaratively via an MXML file) and a component class (defined via ActionScript).
      • Previous Flex component architecture and component set was MX, aka as Halo.
      • Spark components extend the MX class mx.core.UIComponent, and so Spark containers can hold MX components and vice-versa, and Spark and MX components can live side-by-side. The same component lifecycle methods, properties, and events that existed for the MX components apply to Spark components.
      Skinning

      Three key elements— data, parts, and states—define the skinning contract upon which Spark is founded:
      • Every Spark component class:

        • defines the data the component expects, 
        • defines the constituent parts that make up the component (aka skin parts), and 
        • defines the states the component can enter and exit; responsible for all of the event handling needed to identify when a state change has occurred and ensures the component is put in the right state.
      • The corresponding skin class:

        • how that data is visually displayed, 
        • how the parts are laid out and visualized (it instantiates the parts), and 
        • what the component looks like as it enters and exits different states.
      New Capabilities of Spark Architecture
      • Effects in Spark: faster and more capable; can be invoked directly within Spark skin classes through state-based transitions.
      • New layouts


        • APIs for robust 2D and 3D transformations,
        • the ability to easily implement custom layouts
        • assignable layout

        • FXG and MXML Graphics: graphics library that captures drawing primitives as simple MXML tags. FXG is a declarative XML syntax for defining vector graphics in applications built with Flex and can be created by Adobe Illustrator (or manually) and understood by other Adobe CS tools as well as by FlashPlayer (and probably created and read by Catalyst).
        What Next
        For more information, read references from the base article  by Deepa Subramaniam. For examples and details on skinning read article by Ryan Frishberg. For more information on FXG, read this Adobe well written documentation.

          Monday, December 14, 2009

          Using JSON RESTfull Web Services in A Flex Application

          Choice of JSON Library for Flex
          After a few hours of googling, I have decided that the strongest candidate is the corelib library, which for ActionScript 3 is called as3corelib. It's hosted on Google code, although there are multiple references to it on Adobe website. However, none of these references pointed me to any trace of corelib on Adobe website. I wasn't able to figure out why. Leave a comment if you know the story behind.

          It seems, that Flash Builder 4 comes with a HTTPService Wizard, which can import a JSON webservice, among other formats.

          Limitations of Flash HttpService for RESTfull Webservices
          The problem is that because FlashPlayer uses the browser for all networking operations, and Safari browser supports only Post and Get operations, HttpService doesn't support Put, Delete, Headers and Options operations. As a workaround, there is a library hosted on Google Code called RestHttpService, that can be used.

          Monday, November 16, 2009

          FlashPlayer and Flex Security Restrictions When Consuming a Webservice

          Here our system configuration (scenario):
          • Server A: hosts your Flex application
          • Server B: hosts a webservice consumed by your Flex application
           In order for your Flex application to be able to consume the webservice in above scenario, you have two options:
          • Place in the root of the application server on Server B a crossdomain.xml file specifying: allow-access-from-domain: server-A-domain, or
          • Use the Proxy service on Server A, which is part of LCDS and BlazeDS dataservices.

          Thursday, September 3, 2009

          Using FlexUnit to Unit Test Cairngorm Visual Objects

          Introduction
          In a RIA application a typical split of the source code (counted by code size, in bytes) is as follows:
          • 45-60% - client: visual objects like views and their sub-components with their logic
          • 15-20% - client: non-visual objects (primarily Cairngorm worker-to-service piping)
          • 30-35% - server: middle-tier code
          In the couple medium-size business application, I have analyzed, the middle-tier was implemented in Java and the client was implemented in Flex using the Cairngorm architecture. So, I don't have verified numbers for other RIA configurations.

          If we are determined to use TDD or simple just provide automated tests for our application and get the most coverage for the effort required to develop these tests, it seems we could use the following testing methodologies:
          • FlexUnit testing for the client, especially the client's visual objects
          • FlexMonkey tests for whole client's features
          • jUnit tests for the middle-tier code.
          I another blog, I will touch on the FlexMonkey. In this one, I'd like to present how we can unit test the visual layer of the client using the FlexUnit framework. jUnit testing is well described in many other sources.

          Testing Scenario
          In the spirit of Cairngorm, a UI user gesture is directed to a handler which creates a Cairngorm message, attaches a payload to it (any relevant information) and dispatches it (event.dispatch()). Our test will mimic a user gesture (click a button) and will try to catch the dispatched Cairngorm event and verify its payload. We're using Flex 3 (ver. 3.2) and FlexUnit 4 beta 2.

          Sample Application
          To explain the topic, I'll be using the following sample application. The view (Main.mxml adds it to the ViewStack) is (to see the code in a new window, move your mouse pointer over the code and click the left of the icons that appear in upper-right corner):
          <?xml version="1.0" encoding="utf-8"?>
          <mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml"
          width="400" height="300">
          
          <mx:Script source="MyViewScript.as" />
          
          <mx:HBox verticalAlign="middle" >
          <mx:Text id="txtValue" text="Entry:" />
          <mx:TextInput id="entryTextInput" />
          <mx:Button id="btnSave" label="Save" click="onBtnSaveClick(event)" />
          </mx:HBox>
          
          </mx:VBox>

          The script used bu the view is:
          import gov.olcc.fishbait.events.SaveEntryEvent;
          import gov.olcc.fishbait.model.ViewModelLocator;
          import gov.olcc.fishbait.vo.EntryVO;
          
          public var modelLocator:ViewModelLocator = ViewModelLocator.getInstance();
          
          public function onBtnSaveClick(event):void
          {
          trace("clickHandler detected an event of type: " + event.type);
          
          var entryVO: EntryVO = new EntryVO();
          entryVO.text = entryTextInput.text;
          
          var saveEvent: SaveEntryEvent = new SaveEntryEvent(entryVO);
          
          saveEvent.dispatch();
          }
          
          

          and the TestRunner class to run the test is
          <?xml version="1.0" encoding="utf-8"?>
          <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
          xmlns="*"
          xmlns:flexunit="http://www.adobe.com/2009/flexUnitUIRunner"
          xmlns:business="gov.olcc.fishbait.business.*"
          xmlns:control="gov.olcc.fishbait.control.*"
          xmlns:view="gov.olcc.fishbait.view.*"
          layout="vertical"
          width="100%" height="100%"
          creationComplete="runTests()">
          
          <mx:Script>
          <![CDATA[
          import com.adobe.cairngorm.control.FrontController;
          // import gov.olcc.fishbait.view.TestFishBaitView;
          import gov.olcc.fishbait.view.MyView;
          import org.flexunit.runner.FlexUnitCore;
          import org.flexunit.flexui.TestRunnerBase;
          import gov.olcc.fishbait.view.MainTest;
          // import gov.olcc.fishbait.view.TestFishBait;
          
          //Add an import statement(s) for the class(es) under test
          private var core: FlexUnitCore;
          
          private function runTests():void
          {
          core = new FlexUnitCore();
          core.addListener(testRunner);
          core.run(MainTest);
          }
          
          ]]>
          </mx:Script>
          
          <!-- Cairngorm Controller and Service Locator -->
          <control:FishBaitController id="controller" />
          <business:Services id="services" />
          
          <flexunit:TestRunnerBase id="testRunner" width="100%" height="100%" />
          </mx:Application>

          The trick is to be able to catch the Cairngorm event. What is not obvious, the event.dispatch() method uses a CairngormEventDispatcher, so we need to add our listener to it. The resulting test code is
          package gov.olcc.fishbait.view {
          import com.adobe.cairngorm.control.CairngormEventDispatcher;
          import flash.events.MouseEvent;
          import gov.olcc.fishbait.events.SaveEntryEvent;
          import gov.olcc.fishbait.model.ViewModelLocator;
          import gov.olcc.fishbait.vo.EntryVO;
          import mx.automation.codec.AssetPropertyCodec;
          import org.flexunit.Assert;
          import org.fluint.uiImpersonation.UIImpersonator;
          import org.flexunit.async.Async;
          
          public class MainTest {
          var myView: MyView;
          public var modelLocator:ViewModelLocator = ViewModelLocator.getInstance();
          
          [Before(async,ui)]
          public function setUp():void {
          myView = new MyView();
          UIImpersonator.addChild(myView);
          }
          
          [After(async,ui)]
          public function tearDown():void {
          UIImpersonator.removeChild( myView );
          }   
          
          [Test(async,timeout="3000")]
          public function testOnBtnSaveClick():void {
          CairngormEventDispatcher.getInstance().
          addEventListener(SaveEntryEvent.SAVE_TEXT,
          Async.asyncHandler( this, handleTestOnBtnSaveClick, 3000 ),
          false,0,true);
          
          myView.entryTextInput.text = "Test Text";
          var clickEvent: MouseEvent = new MouseEvent(MouseEvent.CLICK);
          myView.btnSave.dispatchEvent(clickEvent);
          }
          
          private function handleTestOnBtnSaveClick(event:SaveEntryEvent,
          passThroughData:Object):void {
          var entry:EntryVO = event.text;
          Assert.assertEquals("Test Text", entry.text);
          }
          }
          }

          Other points about this test:
          • We use FlexUnit UIImpersonator as a parent to our view. Without adding a view to a parent, the UI controlls are not initialized and can't be used.
          • To get the instance of the CairngormEventDispatcher, we use its static mathod getInstance().
          • Since the actual test happens in the event handler, we need to user an annotation [Test(async,timeout="xxx ms"] to request the Async support for the test.
          • Then, instead of providing just the handler method as an argument to the assEventListener(), we wrap this method in Async.asyncHandler(), which will wait for the handler to be called unless the handler will not be called within specified time, in which case the test will fail.
          And this is it. Enjoy testing Cairngorm visual objects.

          Tuesday, August 18, 2009

          Using FlexUnit 4 with Flex 3: Sample Application

          Introduction
          We had hard time finding on the internet a sample like this. So, here's ours. Additional steps required for this test to be run:
          • download FlexUnit 4 from their site.
          • add the FlexUnit swc files to the project's classpath
          • copy the sources below into your project and adapt them (use the view source icon in each source code frame below to view/copy the source code).
          • once everything is on place, right click on the TestRunner and select Run Flex Application.
          At the end of the article, we have added some notes about using FlexUnit with FlashBuilder 4 and Flex 4
          Enjoy! It's a great tool!

          File structure

          Tested Object

          package olcc.account
          {
          public class Account
          {
          public function Account(){
          }
          
          private var balance:Number=0;
          
          public function deposit(amount:Number): void {
          balance=balance+amount;
          }
          
          public function withdraw(amount:Number): void {
          balance=balance-amount;
          }
          
          public function getBalance():Number {
          return balance;
          }
          }
          }
          
          Test Suite (Optional)

          package olcc.account
          {
          import org.flexunit.runners.Suite;
          
          [Suite]
          [RunWith("org.flexunit.runners.Suite")]
          public class MyTestSuite
          {
          public var t1: AccountTest;
          
          }
          }
          


          Unit Test

          package olcc.account
          {
          import org.flexunit.Assert;
          
          public class AccountTest
          {
          [Test]
          public function testNew():void {
          var account:Account = new Account();
          Assert.assertEquals("Expecting zero account balance", 0, account.getBalance());
          }
          
          [Test]
          public function testDeposit():void {
          var account:Account=new Account();
          account.deposit(50);
          Assert.assertEquals("Balance on a new account after 50 deposit is 50",50,account.getBalance());
          account.deposit(25);
          Assert.assertEquals("Balance after 50 deposit and another 25 deposit is 75", 75,account.getBalance());
          
          }
          
          [Test]
          public function testWithdraw():void {
          var account:Account = new Account();
          account.deposit(100);
          account.withdraw(50);
          Assert.assertEquals("Balance should be: + $100 - $50 = $50",50,account.getBalance());
          }
          }
          }


          Test Runner
          <?xml version="1.0" encoding="utf-8"?>
          <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
          xmlns="*"
          xmlns:flexunit="http://www.adobe.com/2009/flexUnitUIRunner"
          creationComplete="onCreationComplete()">
          
          <mx:Script>
          <![CDATA[
          import org.flexunit.runner.FlexUnitCore;
          import org.flexunit.flexui.TestRunnerBase;
          //Add an import statement(s) for the class(es) under test
          import olcc.account.MyTestSuite;
          
          private var core: FlexUnitCore;
          
          private function onCreationComplete():void
          {
          core = new FlexUnitCore();
          core.addListener(testRunner);
          core.run(MyTestSuite);
          }
          ]]>
          </mx:Script>
          
          <flexunit:TestRunnerBase id="testRunner" width="100%" height="100%" />
          </mx:Application>

          Using FlexUnit 4 with Flex 4

          Flex 4 beta2 has support for FlexUnit, and the most recent version of it which is version 4.0. If you go to the New->TestCase menu, FlashBuilder can create for you a whole test class. And add to the flex build path the necessary libraries. This is done pretty well. But when I tried to apply the simple test application presented above to a Flex 4 application, I ran into multiple problems. One critical, as of today, was the issue that the AsDoc for Flex 4 is not available, nor much of the documentation for it. So, some classes from FlexUnit 4 that I needed (like TestRunnerBase), I wasn't able to find. Until these issues are resolved in Flex 4, I decided to use the FlexUnit 4 directly. To do this, I removed the Flex 4 libraries from the build path and added the FlexUnit 4 beta 2 libraries (.swc files). In general, the flex parser is not always stable, which showed up in this exercise and I had a compilation error that shows every other time I compile my TestRunner.mxml (with the same source code), but I can run the tests. Simply, I made sure that the last compilation is a clean one. Also, closing and reopening the project eliminated this issue.

          Tuesday, August 11, 2009

          FlexUnit Plugin for Eclipse

          Since we intend to do unit testing in our Flex+Java development, I have been playing with FlexUnit and have found an eclipse plugin that makes it easier to use (also, in plugin central). Here's a summary and lessons learned on how to start with the plugin

          Installation
          Follow the installation instructions from here. Once installed, open the plugin's help in Eclipse and configure it. A few points:
          • Download FlexUnit and FlexUnit extension for the plugin.
          • You will need to have Flex Builder 3.
          • You will need a debugger version of Flash Player (I used version 10). A standalone player used by the plugin comes with Flex Builder (directory: player)
          • Follows instructions from Adobe on how to enable logging and error output for Flash Player. When you follow them, after you create a mm.cfg file, you will need to restart all instances of Flash Player for the Logs folder to show up.
          Using The Plugin
          To use a plugin:
          • Create a unit test using Flex Builder's wizard. I recommend keeping them in another source folder, so they can be easily skipped when releasing the app. For example, keep the flex source in flex-src and flex tests in flex-test. The server code put in java-src and java-test, or whatever language you use for the middle-tier.
          • Create a harness. Right click in the test file in the project navigator, find FlexUnit menu and select Create Harness. This will create a small mxml file for your test. However, if you use FlexUnit 0.9, the code needs a correction. Replace:
            EclipsePluginTestRunner.runTests( new Array(AccountTest.suite()) );
            with
            EclipsePluginTestRunner.run( AccountTest.suite() );
          • Run the test. Right click on the harness file and select Run from the FlexUnit menu. Observer the test results on the nice eclipse viewer.
          I had the problem that the test launch process never finishes in eclipse. But it doesn't seem to be harmful. Just ignore it.

          Summary
          It's a great plugin. It could do quite a bit more, but it's a great start. It worked for me with a FlexUnit 4, when I used the syntax of FlexUnit 0.9. When I switched to FlexUnit 4 syntax, I didn't have to apply the correction mentioned above to the auto-generated harness to compile. However, I didn't get my tests executed. So, I guess, for now, using the FlexUnit 0.9 is a better option. However, I have heard from the plugin author that he intends to do another release of the plugin.

          Wednesday, May 20, 2009

          Using Unit Testing to Test GWT RCP Calls

          This is a short note. I'm working on a GWT application and ran into a problem with an RPC call which gets some objects from the database using Hibernate and passes them back to the client. I'm going to solve the problem using Dozer, but I needed to confirm this was the problem and simplify the testing of this in future. So, I decided I want to unit test this service from the client's perspective. Here are my notes from this exercise:
          • The easiest way to create a GWTTestCase is using the GWT's jUnitCreator. Creating it by hand is possible but tricky.
          • The asynchronous test needs to use delayTestFinish(delayMs) and finishTest(). In my case, I had to set 5000 Ms. For some reason, 500Ms, which I thought would be more than enough was timing out.
          • The good news is, that when using Eclipse plugin for GWT, starting initiating Run as JUnit Test on the GWTTestCase test starts for you also the server. And doing the same with Debug as JUnit Test allows to debug the client as well as server code.
          Note: the similar test is supported in Flex application!

          Tuesday, May 12, 2009

          Developing Flex Applications with Java Middle-Tier

          Intro
          In my job we are using Flex with Java to develop web applications that support the activity of the agency I'm working for. It's my first experience with Flex and I'd say, I'm duly impressed. I had experience of developing using GWT and I must say, that Flex is an equally good tool.

          A Simple Way
          Just follow this tutorial.

          Tools Used
          We use the following toolset:

          • eclipse with FlexBuilder plugin (plugin is not free)
          • jBoss
          • Java for back-end
          • LCDS to communicate between Flex and Java (not free, but there are free alternatives: BlazeDS and the LCDS Express is also free, I think).
          • Cairngorm pattern for Flex client structure

          Tips and Gotchas

          • A great intro to Cairngorm (and Flex itself) is one by David Tucker.
          • He is using ColdFusion for the back-end, so switch to this or this for how to do it with Java.
          The Cairngorm pattern for calling a remote service is a bit complex. Here's how it goes:
          • In the main mxml file (the one where you define mx:Application): instantiate the ServiceLocator: . The id "services" is irrelevant, it seems. And instantiate the FrontController.
          • Let say, the command LOGIN is to be handled by the back-end. Dispatch the command by initiating the LoginEvent and calling dispatch() on it.
          • Each event is mapped onto a command in the FrontController. Let say, in this case, onto a LoginCommand.
          • In loginCommand, instantiate a delegate, say LoginDelegate and call the login(event)
          • In LoginDelegate, obtain the service proxy using:
            ServiceLocator.getInstance().getRemoteObject("myRemoteObject");
            and call the login(args) on it.
          • Now, two additional files need to be setup: Services.mxml and remoting-config.xml
          • In the first one, Services.mxml, define a mx:RemoteObject with id="myRemoteObject" and the destination="myRemoteClass".
          • In the second, the remoting-config.xml, define a destination with id="myRemoteClass" and the Java class package+classname in the source tag.
          • In the Flex Server properties (access through project properties), set the Root Context as the /YourProjectName.
          If you need to pass an object between client and the server, create two corresponding value objects (usually named *VO, like LoginVO), one in ActionScript in flex branch and one in Java branch:

          • The ActionScript version, needs to have an annotation above the class definition: [RemoteClass(alias=org.sjb.LoginVO)].
          • The Java version needs to be public and have public setters.
          If you want to debug not only Flex (which is done the usual "eclipse" way), stop the JBoss server and start it in Debug mode. That's all that is needed.

          Conclusion
          Altogether, using Flex with Java is very simple. Enjoy it!