Adding ability to project device, link and host model as alternate entities.

Change-Id: If23c018b024a3bbe693f0e66888c5f1707e3f66d
diff --git a/core/api/src/main/java/org/onosproject/net/AbstractProjectableModel.java b/core/api/src/main/java/org/onosproject/net/AbstractProjectableModel.java
new file mode 100644
index 0000000..6fee974
--- /dev/null
+++ b/core/api/src/main/java/org/onosproject/net/AbstractProjectableModel.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2014-2016 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.net;
+
+import com.google.common.annotations.Beta;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.driver.Projectable;
+import org.onosproject.net.provider.ProviderId;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Set;
+
+import static com.google.common.base.Preconditions.checkState;
+
+/**
+ * Base model entity, capable of being extended via projection mechanism.
+ */
+@Beta
+public abstract class AbstractProjectableModel extends AbstractModel implements Projectable {
+
+    private static Logger log = LoggerFactory.getLogger(AbstractProjectableModel.class);
+
+    protected static final String NO_DRIVER_SERVICE = "Driver service not bound yet";
+    protected static final String NO_DRIVER = "Driver has not been bound to %s";
+
+    // Static reference to the driver service; injected via setDriverService
+    private static DriverService driverService;
+
+    private Driver driver;
+
+    // For serialization
+    public AbstractProjectableModel() {
+    }
+
+    /**
+     * Creates a model entity attributed to the specified provider and
+     * optionally annotated.
+     *
+     * @param providerId  identity of the provider
+     * @param annotations optional key/value annotations
+     */
+    public AbstractProjectableModel(ProviderId providerId, Annotations[] annotations) {
+        super(providerId, annotations);
+    }
+
+    /**
+     * Injects the driver service reference for use during projections into
+     * various behaviours.
+     * <p>
+     * This is a privileged call; unauthorized invocations will result in
+     * illegal state exception
+     *
+     * @param key           opaque admin key object
+     * @param driverService injected driver service
+     * @throws IllegalStateException when invoked sans authorization
+     */
+    public static void setDriverService(Object key, DriverService driverService) {
+        // TODO: Rework this once we have means to enforce access to admin services in general
+        checkState(AbstractProjectableModel.driverService == key, "Unauthorized invocation");
+        AbstractProjectableModel.driverService = driverService;
+    }
+
+    /**
+     * Returns the currently bound driver service reference.
+     *
+     * @return driver service
+     */
+    protected static DriverService driverService() {
+        return driverService;
+    }
+
+    /**
+     * Returns the currently bound driver or null of no driver is bound.
+     *
+     * @return bound driver; null if none
+     */
+    protected Driver driver() {
+        return driver;
+    }
+
+    @Override
+    public <B extends Behaviour> B as(Class<B> projectionClass) {
+        checkState(driverService != null, NO_DRIVER_SERVICE);
+        if (driver == null) {
+            driver = locateDriver();
+        }
+        checkState(driver != null, NO_DRIVER, this);
+        return driver.createBehaviour(asData(), projectionClass);
+    }
+
+    @Override
+    public <B extends Behaviour> boolean is(Class<B> projectionClass) {
+        checkState(driverService != null, NO_DRIVER_SERVICE);
+        if (driver == null) {
+            driver = locateDriver();
+        }
+        checkState(driver != null, "Driver has not been bound to %s", this);
+        return driver.hasBehaviour(projectionClass);
+    }
+
+    /**
+     * Locates the driver to be used by this entity.
+     * <p>
+     * The default implementation derives the driver based on the {@code driver}
+     * annotation value.
+     *
+     * @return driver for alternate projections of this model entity or null
+     * if no driver is expected or driver is not found
+     */
+    protected Driver locateDriver() {
+        String driverName = annotations().value(AnnotationKeys.DRIVER);
+        if (driverName != null) {
+            try {
+                return driverService.getDriver(driverName);
+            } catch (ItemNotFoundException e) {
+                log.warn("Driver {} not found.", driverName);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns self as an immutable driver data instance.
+     *
+     * @return self as driver data
+     */
+    protected DriverData asData() {
+        return new AnnotationDriverData();
+    }
+
+
+    /**
+     * Projection of the parent entity as a driver data entity.
+     */
+    protected class AnnotationDriverData implements DriverData {
+        @Override
+        public Driver driver() {
+            return driver;
+        }
+
+        @Override
+        public DeviceId deviceId() {
+            throw new UnsupportedOperationException("Entity not a device");
+        }
+
+        @Override
+        public MutableAnnotations set(String key, String value) {
+            throw new UnsupportedOperationException("Entity is immutable");
+        }
+
+        @Override
+        public MutableAnnotations clear(String... keys) {
+            throw new UnsupportedOperationException("Entity is immutable");
+        }
+
+        @Override
+        public Set<String> keys() {
+            return annotations().keys();
+        }
+
+        @Override
+        public String value(String key) {
+            return annotations().value(key);
+        }
+    }
+
+}