/* JFox, the OpenSource J2EE Application Server
 *
 * Copyright (C) 2002 huihoo.com
 * Distributable under GNU LGPL license
 * See the GNU Lesser General Public License for more details.
 */

package org.huihoo.jfox.xmlet;

import java.io.File;
import java.io.InputStream;
import java.util.List;
import java.util.Set;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ArrayList;
import java.net.URLClassLoader;
import java.net.URL;
import java.net.URLStreamHandlerFactory;
import java.net.MalformedURLException;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.JAXBException;
import javax.management.loading.ClassLoaderRepository;
import javax.management.MBeanRegistration;
import javax.management.MBeanServer;
import javax.management.ObjectName;
import javax.management.MBeanServerFactory;
import javax.management.ServiceNotFoundException;
import javax.management.JMRuntimeException;
import javax.management.ObjectInstance;
import javax.management.Attribute;

import org.huihoo.jfox.jmx.loading.PrimitiveClassLoader;
import org.huihoo.jfox.jmx.loading.MLetException;
import org.huihoo.jfox.jmx.loading.ObjectInputStreamLoader;
import org.huihoo.jfox.xmlet.jaxb.XMLetType;
import org.huihoo.jfox.xmlet.jaxb.XMLetsType;
import org.huihoo.jfox.xmlet.jaxb.ArgType;
import org.huihoo.jfox.xmlet.jaxb.AttrType;

/**
 * 
 * @author <a href="mailto:young_yy@hotmail.com">Young Yang</a>
 */

public class XMLet extends URLClassLoader implements XMLetMBean, MBeanRegistration {

  private String liberaryDirectory = System.getProperty("user.dir");

  protected MBeanServer server = null;
  private ObjectName objectName = null;
  private ClassLoaderRepository clrepo = null;

  protected PrimitiveClassLoader paramLoader = PrimitiveClassLoader.getInstance();

    public XMLet(){
    this(new URL[0]);
  }

  public XMLet(URL[] urls) {
    super(urls);
  }

  public XMLet(URL[] urls, ClassLoader parent) {
    super(urls, parent);
  }

  public XMLet(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) {
    super(urls, parent, factory);
  }
  public ObjectName preRegister(MBeanServer server, ObjectName objName) throws Exception {
    this.server = server;
    if(objName == null) objName = new ObjectName(server.getDefaultDomain() + ":" + "service=" + this.toString());
    this.objectName = objName;
    clrepo = MBeanServerFactory.getClassLoaderRepository(server);
    return objectName;
  }

  public void postRegister(Boolean success) {
    if(!success.booleanValue()) server = null;
  }

  public void preDeregister() throws Exception {

  }

  public void postDeregister() {

  }

  public Set getMBeansFromURL(URL url) throws ServiceNotFoundException {
    if(server == null) throw new IllegalStateException("This MLet MBean is not registered with an MBeanServer.");
 		if(url == null) throw new ServiceNotFoundException("Can not load MBeans from null URL");
    Set mbeanInstances = new HashSet();
    String urlString = url.toString();
    String defaultCodeBase = urlString.substring(0, urlString.lastIndexOf('/') + 1);
    try {
      Iterator mlets = this.parse(url);
      while(mlets.hasNext()){
        XMLetType mlet = (XMLetType)mlets.next();
        URL[] urls = this.getMLetURLs(mlet,defaultCodeBase);
        for(int i=0;i<urls.length;i++){
          this.addURL(urls[i]);
        }
        ObjectInstance oi = createMBean(mlet);

        setAttributes(mlet);

// not support operation now
//        invokeOperations(mlet);

        mbeanInstances.add(oi);


      }

    }
    catch(MLetException e){
      e.printStackTrace();
      throw new JMRuntimeException(e.getMessage());
    }
    return mbeanInstances;
  }

  public Set getMBeansFromURL(String url) throws ServiceNotFoundException {
    try {
      URL urlObj = new URL(url);
      return this.getMBeansFromURL(urlObj);
    }
    catch(MalformedURLException e) {
      throw new ServiceNotFoundException("The specified URL is malformed");
    }

  }

  public void addURL(URL url) {
    if(!Arrays.asList(getURLs()).contains(url)) super.addURL(url);
  }

  public void addURL(String url) throws ServiceNotFoundException {
    try {
      URL urlObj = new URL(url);
      this.addURL(urlObj);
    }
    catch(MalformedURLException e) {
      throw new ServiceNotFoundException("The specified URL is malformed");
    }

  }

  public String getLibraryDirectory() {
    return liberaryDirectory;
  }

  public void setLibraryDirectory(String dir) {
    liberaryDirectory = dir;
  }

  public Class loadClass(String name) throws ClassNotFoundException {
    Class cla = null;
    try{
      cla = super.loadClass(name);
    }
    catch(ClassNotFoundException e){
      try{
        clrepo.loadClassWithout(this,name);
      }
      catch(ClassNotFoundException ex){
        throw ex;
      }
    }
    return cla;
  }


  /**
   * 用 jaxb 把 XMLet Config 文件解析成 Java object
   * @param url
   * @return
   * @throws MLetException
   */
  private Iterator parse(URL url) throws MLetException {
    try {
      JAXBContext jcontect = JAXBContext.newInstance(getClass().getPackage().getName() + ".jaxb");
      Unmarshaller unmarshaller = jcontect.createUnmarshaller();
      unmarshaller.setValidating(true);
      XMLetsType _mlets = (XMLetsType)unmarshaller.unmarshal(url);
      List mlets =  _mlets.getXMLET();
      for(int i=0; i< mlets.size(); i++){
        XMLetType mlet = (XMLetType)mlets.get(i);
        checkMLet(mlet);
      }
      return mlets.iterator();
    }
    catch(JAXBException e){
      e.printStackTrace();
      throw new MLetException(e.getMessage());
    }
  }

  /**
   * 必须要设置 NAME, ARCHIVE 可以不设
   * @param mlet
   * @throws MLetException
   */
  private static void checkMLet(XMLetType mlet) throws MLetException {
    if(mlet.getNAME() == null){
      throw new MLetException("The MLet NAME attribute must be set");
    }

    if(mlet.getCODE() == null && mlet.getOBJECT() == null) {
      throw new MLetException("The MLet CODE attribute and OBJECT must be set either");
    }

    if(mlet.getCODE() != null && mlet.getOBJECT() != null) {
      throw new MLetException("The MLet CODE attribute and OBJECT must only be set one");
    }
  }

  /**
   * get the jar urls, so can add it the the classloader path
   */
  private URL[] getMLetURLs(XMLetType mlet,String defaultCodeBase){
    List urls = new ArrayList();
    String codeBase = mlet.getCODEBASE();
    codeBase = codeBase == null ? defaultCodeBase : defaultCodeBase + codeBase;
    if(!codeBase.endsWith("/")) codeBase += "/";
    String archive = mlet.getARCHIVE(); // maybe "a.jar,b.jar"

    // xmlet 中，ARCHIVE 可以不设置
    if(archive == null) return new URL[0];

    String[] archives = decompoundArchive(archive);

    for(int i = 0;i<archives.length;i++){
      String arch = archives[i];
      if(!arch.startsWith("/")) arch = codeBase + arch;
      URL url = null;
      try{
        url = new URL(arch);
      }
      catch(MalformedURLException e){
        // maybe cause by not specified the file:/ protocal
        File file = new File(arch);
        try{
          if(file.exists()) url = file.toURL();
        }
        catch(Exception ignore){
          ignore.printStackTrace();
        }
      }
      if(url != null) urls.add(url);
    }
    return (URL[])urls.toArray(new URL[0]);
  }

  // decompound multi archives "a.jar,b.jar" to array
  private static String[] decompoundArchive(String archive){
    List archives = new ArrayList();
    int start = 0;
    int commaIndex = -1;
    int pointer = -1;

    while((pointer = archive.indexOf(",",start)) > 0){ // find comma
      archives.add(archive.substring(start,pointer).trim());
      commaIndex = pointer;
      start = pointer + 1;
    }
    if(commaIndex == -1){ // not found comma
      archives.add(archive);
    }
    else{
      archives.add(archive.substring(commaIndex+1).trim());
    }
    return (String[])archives.toArray(new String[0]);
  }

  /**
   * doCreate a MBean by the offered mlet
   *
   * @param mlet
   * @return ObjectInstance of the created MBean
   */
  protected ObjectInstance createMBean(XMLetType mlet) {
    ObjectInstance instance = null;
    Object mbean = null;
    String className = mlet.getCODE();
    try{
      if(className != null){ // specified classname

        XMLetType.ARGUMENTSType argsType = mlet.getARGUMENTS();
//        System.out.println("Arguments: " + argsType);
        if(argsType == null){
          mbean = server.instantiate(className,objectName);
        }
        else{
          List args = argsType.getARG();
          Object[] params = new Object[args.size()];
          String[] signatures = new String[args.size()];
          for(int i=0;i<args.size();i++){
            ArgType arg = (ArgType)args.get(i);
            // doCreate param object
            signatures[i] = arg.getTYPE();
            params[i] = paramLoader.createObject(arg.getVALUE(),signatures[i]);
          }

          mbean = server.instantiate(className,objectName,params,signatures);
        }
      }
      else{ // must specified the serialized object
        String serFile = mlet.getOBJECT();
        InputStream is = getResourceAsStream(serFile);
        if (is == null) throw new ServiceNotFoundException("Cannot find MBean " + serFile + " in this ClassLoader classpath");
        ObjectInputStreamLoader loader = new ObjectInputStreamLoader(is,this);
        mbean = loader.readObject();

      }
      ObjectName objName = null;
  // if objName is null,the mbean must implements MBeanRegistry,provide the DEFAULT_OBJECTNAME bye preRegister
      String oname = mlet.getNAME();
      if(oname != null && oname.trim().length()!=0) objName = new ObjectName(oname);
      instance = server.registerMBean(mbean,objName);
    }
    catch(Exception e){
      e.printStackTrace();
    }

    return instance;
  }

  private void setAttributes(XMLetType mlet) {
    String _objectName = mlet.getNAME();
    XMLetType.ATTRIBUTESType attrsType = mlet.getATTRIBUTES();
    if(attrsType == null) return;

    List attrs = attrsType.getATTR();
    for(int i = 0; i<attrs.size(); i++){
      AttrType attr = (AttrType)attrs.get(i);
      setAttribute(_objectName,attr);
    }

  }

  private void setAttribute(String on, AttrType attr) {
    try {
      ObjectName objectName = new ObjectName(on);
      String attributeName = attr.getNAME();
      String attributeType = attr.getTYPE();
      String attributeValue = attr.getVALUE();

      Class attrClass = null;
      attrClass = Class.forName(attributeType);
      Object value = attrClass.getConstructor(new Class[]{java.lang.String.class}).newInstance(new Object[]{attributeValue});
      Attribute attribute = new Attribute(attributeName,value);
      server.setAttribute(objectName,attribute);
    }
    catch(Exception e){
      e.printStackTrace();
    }
  }

  public static void main(String[] args) throws Exception{
    JAXBContext jcontect = JAXBContext.newInstance(XMLet.class.getPackage().getName() + ".jaxb");
    Unmarshaller unmarshaller = jcontect.createUnmarshaller();
    unmarshaller.setValidating(true);
    XMLetsType _mlets = (XMLetsType)unmarshaller.unmarshal(new File("conf/xmlet.xml"));
    List mlets =  _mlets.getXMLET();
    for(int i=0; i< mlets.size(); i++){
      XMLetType mlet = (XMLetType)mlets.get(i);
      System.out.println(mlet.getNAME());

      XMLetType.ARGUMENTSType arggs = mlet.getARGUMENTS();

      if(arggs != null) {
        List li = arggs.getARG();
        for(int j=0; j<li.size(); j++){
          ArgType arg = (ArgType)li.get(j);
          System.out.print(arg.getTYPE() + ": ");
          System.out.println(arg.getVALUE());
        }
      }
    }
  }
}
