#
#    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.
#

# This task creates a JBI package based on the component/bootstrap specification.
# It extends ZipTask, and all its over lovely options.
#
# The easiest way to use this task is through the Project#package method. For example:
#   package(:jbi).tap do |jbi|
#     jbi.component :type=>:service_engine=>"MyEngine", :description=>self.comment
#     jbi.component :class_name=>"com.example.MyComponent", :delegation=>:self, :libs=>libs
#     jbi.bootstrap :class_name=>"com.example.MyBootstrap", :delegation=>:parent, :libs=>libs
#   end
class JBITask < Buildr::ZipTask

  # Specifies the name of a jbi.xml file to use, or a Proc/Method returning
  # the contents of jbi.xml. Leave empty if you want to use #component and
  # bootstrap instead.
  attr_accessor :jbi_xml

  # Component specification.
  class Component
    # Component name.
    attr_accessor :name
    # Component type, e.g. :service_engine.
    attr_accessor :type
    # Description of component.
    attr_accessor :description
    # Delegation method. Default is :parent.
    attr_accessor :delegation
    # Component class name.
    attr_accessor :class_name
    # Array of libraries used by component.
    attr_accessor :libs

    def initialize()
      @libs = []
    end
  end

  # Bootstrap specification.
  class Bootstrap
    # Delegation method. Default is :parent.
    attr_accessor :delegation
    # Bootstrap class name.
    attr_accessor :class_name
    # Array of libraries used for bootstrapping.
    attr_accessor :libs

    def initialize()
      @libs = []
    end
  end

  def initialize(*args)
    super
    prepare { path("lib").include((component.libs + bootstrap.libs).flatten.uniq) }

    enhance do
      case jbi_xml
      when String
        path("META-INF").include jbi_xml.path, :as=>"jbi.xml"
      when nil, true
        # Tempfiles gets deleted on garbage collection, so we're going to hold on to it
        # through instance variable not closure variable.
        Tempfile.open("MANIFEST.MF") { |@jbi_xml_tmp| @jbi_xml_tmp.write descriptor }
        path("META-INF").include @jbi_xml_tmp.path, :as=>"jbi.xml"
      when Proc, Method
        Tempfile.open("MANIFEST.MF") { |@jbi_xml_tmp| @jbi_xml_tmp.write jbi_xml.call.to_s }
        path("META-INF").include @jbi_xml_tmp.path, :as=>"jbi.xml"
      end
    end
  end

  def []=(key, value)
    case key.to_sym
    when :name, :description, :type
      self.component.send "#{name}=", value
    when :component, :bootstrap
      self.send key, value
    else
      super key, value
    end
    value
  end

  # Returns the component specification for this JBI package.
  # You can call accessor methods to configure the component
  # specification, you can also pass a hash of settings, for example:
  #   jbi.component :type=>:service_engine, :name=>"MyEngine"
  def component(args = nil)
    (@component ||= Component.new).tap do |component|
      args.each { |k, v| component.send "#{k}=", v } if args
    end
  end

  # Returns the bootstrap specification for this JBI package.
  # You can call accessor methods to configure the bootstrap
  # specification, you can also pass a hash of settings, for example:
  #   jbi.bootstrap :class_name=>"com.example.jbi.MyBootstrap", :libs=>libs
  def bootstrap(args = nil)
    (@bootstrap ||= Bootstrap.new).tap do |bootstrap|
      args.each { |k, v| bootstrap.send "#{k}=", v } if args
    end
  end

  # Create a JBI descriptor (jbi.xml) from the component/bootstrap specification.
  def descriptor()
    delegation = lambda { |key| "#{key || :parent}-first" }
    path_elements = lambda do |xml, libs|
      libs.each { |lib| xml.tag! "path-element", "lib/#{lib.to_s.pathmap('%f')}" }
    end
    xml = Builder::XmlMarkup.new(:indent=>2)
    xml.instruct!
    xml.jbi :xmlns=>"http://java.sun.com/xml/ns/jbi", :version=>"1.0" do
      xml.component :type=>component.type.to_s.sub("_", "-"),
        "component-class-loader-delegation"=>delegation[component.delegation],
        "bootstrap-class-loader-delegation"=>delegation[bootstrap.delegation] do
        xml.identification do
          xml.name component.name
          xml.description component.description
        end
        xml.tag!("component-class-name", component.class_name)
        xml.tag!("component-class-path") { path_elements[xml, component.libs] }
        xml.tag!("bootstrap-class-name", bootstrap.class_name)
        xml.tag!("bootstrap-class-path") { path_elements[xml, bootstrap.libs] }
      end
    end
  end

end

class Project


  def package_as_jbi(file_name) #:nodoc
    JBITask.define_task(file_name)
  end

  def package_as_jbi_spec(spec) #:nodoc
    spec.merge({ :type=>:zip })
  end

end
