blob: 08d58809d41783e495dbe945e25853fdb15e8b3f [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.apache.toree.magic
import org.apache.toree.plugins.{Plugin, PluginMethodResult, PluginManager}
import org.slf4j.LoggerFactory
import scala.annotation.tailrec
import scala.language.dynamics
import scala.runtime.BoxedUnit
import scala.util.{Try, Failure, Success}
class MagicManager(private val pluginManager: PluginManager) extends Dynamic {
protected val logger = LoggerFactory.getLogger(this.getClass.getName)
/**
* Checks if the provided magic is a line magic.
*
* @param magic The magic instance
* @return True if the magic is an instance of a line magic
*/
def isLineMagic(magic: Magic): Boolean =
magic.getClass.getInterfaces.contains(classOf[LineMagic])
/**
* Checks if the provided magic is a cell magic.
*
* @param magic The magic instance
* @return True if the magic is an instance of a cell magic
*/
def isCellMagic(magic: Magic): Boolean =
magic.getClass.getInterfaces.contains(classOf[CellMagic])
/**
* Finds a magic whose class name ends with a case insensitive name.
*
* @param name The name to search for
* @return The magic
* @throws MagicNotFoundException when no magics match name
*/
@throws[MagicNotFoundException]
def findMagic(name: String): Magic = {
@tailrec def inheritsMagic(klass: Class[_]): Boolean = {
if (klass == null) false
else if (klass.getInterfaces.exists(classOf[Magic].isAssignableFrom)) true
else inheritsMagic(klass.getSuperclass)
}
val magics = pluginManager.plugins
.filter(p => inheritsMagic(p.getClass))
.filter(_.simpleName.split("\\.").last.toLowerCase == name.toLowerCase)
if (magics.size <= 0){
logger.error(s"No magic found for $name!")
throw new MagicNotFoundException(name)
} else if (magics.size > 1) {
logger.warn(s"More than one magic found for $name!")
}
magics.head.asInstanceOf[Magic]
}
@throws[MagicNotFoundException]
def applyDynamic(name: String)(args: Any*): MagicOutput = {
val arg = args.headOption.map(_.toString).getOrElse("")
import org.apache.toree.plugins.Implicits._
val result = pluginManager.fireEventFirstResult(
name.toLowerCase(),
"input" -> arg
)
result match {
case Some(r: PluginMethodResult) => handleMagicResult(name, r.toTry)
case None => throw new MagicNotFoundException(name)
}
}
private def handleMagicResult(name: String, result: Try[Any]): MagicOutput = result match {
case Success(magicOutput) => magicOutput match {
case out: MagicOutput => out
case null | _: BoxedUnit => MagicOutput()
case cmo: Map[_, _]
if cmo.keys.forall(_.isInstanceOf[String]) &&
cmo.values.forall(_.isInstanceOf[String]) =>
MagicOutput(cmo.asInstanceOf[Map[String, String]].toSeq:_*)
case unknown =>
val message =
s"""Magic $name did not return proper magic output
|type. Expected ${classOf[MagicOutput].getName}, but found
|type of ${unknown.getClass.getName}.""".trim.stripMargin
logger.warn(message)
MagicOutput("text/plain" -> message)
}
case Failure(t) =>
val message = s"Magic $name failed to execute with error: \n${t.getMessage}"
logger.warn(message, t)
MagicOutput("text/plain" -> message)
}
}