Simplify Your WCS Controllers With Your Own Asset Reader

image

Oracle WebCenter Sites 12c introduced the new Asset Reader API. While you may not be ready to use it in your code - maybe your existing DAOs are working just fine or perhaps for performance reasons - you might be thinking: "Wouldn't it be nice if my controller code was this short and simple?" With a few simple code changes, you can create your own Asset Reader that wraps your existing DAOs and makes controllers simpler.

Legacy Controller

If your 11g code uses the GSF and you have a few DAOs of your own - for example, one to load Image assets and another to load Documents - your GSF controller looks something like this:

public class F1LegacyGsfController implements Action {
    @InjectForRequest
    public Model model;
    @InjectForRequest
    private ScatteredAssetAccessTemplate scatteredAssetDao;
    @InjectForRequest
    private DocumentDao documentDao;
    @InjectForRequest
    private ImageDao imageDao;

    @Override
    public void handleRequest(ICS ics) {
        // the current asset's id
        AssetId id = scatteredAssetDao.currentId();
        // load everything from the current asset
        ScatteredAsset scatteredAsset = scatteredAssetDao.read(id);
        model.add("asset", scatteredAsset);

        // Load the associated "banner" Image asset
        Map banner = imageDao.load((AssetId) scatteredAsset.get("banner"));
        model.add("banner", banner);

        // Load the associated "doc" Document asset
        Map doc = documentDao.load((AssetId) scatteredAsset.get("doc"));
        model.add("doc", doc);
    }
}

Now, there's nothing wrong with that controller, but that's a lot of code just to load one asset and two associated assets. First, you have to declare (and initialize) all your DAOs. Then, scatter the current asset. Then, for each associated Image asset, get its id and load it with the Image DAO. Why not create your own Asset Reader API to do this tedious work for you?

My Asset Reader

First, let's create our Asset Reader class, called MyAssetReader.

public class MyAssetReader {
    private ScatteredAssetAccessTemplate scatteredAssetDao;
    private DocumentDao documentDao;
    private ImageDao imageDao;
    private VideoDao videoDao;

    private AssetId id;
    private String[] attributes;
    private List<AttributeStrategy> strategies;

    public MyAssetReader(ICS ics) {
        this.scatteredAssetDao = new ScatteredAssetAccessTemplate(ics);
        this.documentDao = new DocumentDao(ics);
        this.imageDao = new ImageDao(ics);
        this.videoDao = new VideoDao(ics);
        this.strategies = new ArrayList<>();
    }

    /***
     * Specify the id of the asset to load
     *
     * @param id
     * @return
     */
    public MyAssetReader forAsset(AssetId id) {
        this.id = id;
        return this;
    }

    /**
     * Specify which attributes to load. If you don't call this method, by default all attributes will be loaded.
     *
     * @param attributes
     * @return
     */
    public MyAssetReader select(String... attributes) {
        this.attributes = attributes;
        return this;
    }

    /**
     * Specify the strategy to use to load an attribute of type asset (ATA). This creates an
     * AttributeStrategy for the given attribute name and Type (IMAGE, DOCUMENT, etc)
     *
     * @param type
     * @param name
     * @return
     */
    public MyAssetReader attr(AttributeStrategy.Type type, String name) {
        strategies.add(new AttributeStrategy(type, name));
        return this;
    }

    /**
     * Read the asset, including processing the attributes of type asset as defined in each AttributeStrategy.
     *
     * @return
     */
    public Map read() {
        if (id == null) {
            throw new IllegalStateException("You must specify an AssetId to load");
        }

        // load the current asset and put into a HashMap since ScatteredAsset implements the Map interface but doesn't
        // implement the put(key,value) method.
        Map<String, Object> assetMap;
        if (attributes != null) {
            assetMap = new HashMap<>(scatteredAssetDao.read(id, attributes));
        } else {
            assetMap = new HashMap<>(scatteredAssetDao.read(id));
        }

        for (AttributeStrategy strategy : strategies) {
            AssetId attrId = getAttrId(assetMap, strategy);
            assetMap.put(strategy.getAttribute(), readAssetWithStrategy(strategy.getType(), attrId));
        }
        return assetMap;
    }

    /**
     * Each AttributeStrategy is created for a specific attribute name. Get
     * that attribute value (an AssetId) from the current asset.
     *
     * @param assetMap Current asset
     * @param strategy
     * @return
     */
    private AssetId getAttrId(Map assetMap, AttributeStrategy strategy) {
        return (AssetId) assetMap.get(strategy.getAttribute());
    }

    private Map readAssetWithStrategy(AttributeStrategy.Type type, AssetId attrId) {
        switch (type) {
            case DOCUMENT:
                return documentDao.load(attrId);
            case IMAGE:
                return imageDao.load(attrId);
            case VIDEO:
                return videoDao.load(attrId);
            default:
                throw new RuntimeException("Unknown type AttributeStrategy Type: " + type);
        }
    }
}

Define how to load each attribute of type asset

Next, let's define the Attribute Strategies. This is where we define how to load attributes of type asset (ATAs) - i.e., what DAO to use (ImageDao, DocumentDao, etc).

public class AttributeStrategy {
    public enum Type {DOCUMENT, IMAGE, VIDEO}

    private String attribute;
    private Type type;

    public AttributeStrategy(Type type, String attribute) {
        this.attribute = attribute;
        this.type = type;
    }

    public String getAttribute() {
        return attribute;
    }

    public Type getType() {
        return type;
    }
}

My New Asset Reader Controller

Now, we can rewrite our original GSF controller F1LegacyGsfController to use our new Asset Reader, MyAssetReader.

class F1AssetReaderController extends BaseController {
    @Override
    protected void doWork(Map models) {
        Map assetMap = new F1AssetReader(ics)
                .forAsset(getAssetId())
                .attr(AttributeStrategy.Type.IMAGE, "banner")
                .attr(AttributeStrategy.Type.DOCUMENT, "doc")
                .read();
        models.put("asset", assetMap);
    }
}

And here's a simple Template to render this asset.

<h1>${asset.h1title}</h1>
<render:stream value='${asset.body}' />
<div class='banner'>
    <img src='${asset.banner.imageFile_url}' />
    <div class='caption'>${asset.banner.caption}</div> 
</div>
<div class='doc'>
    <a href='${asset.doc.file_url}'>${asset.doc.linktext}</a> 
</div>

Using our new Asset Reader, MyAssetReader, the length of our controller has been reduced by more than half. It still loads the same assets using the same DAOs as before, but now it's much easier to read and maintain.

Questions about this post? Comment below or send us an email at info@function1.com.

 

Subscribe to Our Newsletter

Stay In Touch