Newsgroup: it.comp.java
Titolo: ClassLoader

Ho fatto un applet che scarica un jar su disco. Subito dopo
quest'applet richiama una certa classe che a sua volta importa classi
da questo jar. Naturalmente la prima volta che faccio girare l'applet,
quando carico la classe in questione, ottengo un'eccezione di tipo
ClassNotFound perchè il jar che ho scaricato non era presente nel
classpath al momento nel quale il classloader ha caricato l'applet
stessa, mentre la seconda volta funziona tutto regolarmente. C'è un
modo di "forzare" il classloader a riconsiderare della classi che sono
state inserite "a runtime", e che al momento del suo caricamento non
c'erano?
//In pratica: applet "madre" (o "padre"?)
/*
...
*/
scaricaJar(); // nel jar è presente il package XYZ
// Devo fare qualcosa con il ClassLoader qui? Ma cosa?
miaClasse mc = new miaClasse(); // Qui mi da errore!
/*
...
*/
//miaClasse.java
import XYZ.altraClasse;
public class miaClasse() {
/*
...
*/
}
Grazie per l'attenzione, ciao!
GePs

 c'erano?
http://www.google.com/search?sourceid=navclient&ie=UTF-8&oe=UTF-8
&q=sadun+DynamicClassLoader
Occhio che con un'applet pero' il classloading e' soggetto a certi limiti 
di sicurezza. Del resto se scarichi un jar su disco hai gia' superato il 
problema..
-- 
You dont know what to do when you dont know what you're doing.
http://space.tin.it/computer/csadun

 problema..
Sě, infatti non ho problemi con la sicurezza: la mia applet č firmata
e ha tutti i permessi. Stavo pensando di farmi un classloader "in
casa", ma sapevo che con un'applet potrei avere qualche problema,
quindi cercavo una soluzione piů di comodo...
Grazie per il link, ho scaricato tutto il materiale. Gli do
un'occhiata e ti faccio sapere. Ciao!

 &q=sadun+DynamicClassLoader
Ho dato una letta ma non ho capito molto...
Immaginiamo che io frughi in un certo albero di directory in cerca di jar
files. Questi non sono nel classpath quindi i package che contengono sono
inaccessibili alla reflection. Come faccio ad aggiungere quei jar al
classpath (a runtime, senza modificare la linea di comando o le variabili
d'ambiente) in modo da poter utilizzare le classi che contengono?
Grazie!
Matteo
-- 
chi sono e soprattutto chi non sono:
http://www.ilnomedeldominio.it
un piccolo programma per studenti universitari:
http://inutility.ilnomedeldominio.it

 d'ambiente) in modo da poter utilizzare le classi che contengono?
DynamicClassLoader ha un metodo setClassPath() e un addClassPathEntry().
Trovi i jar.
Usi addClassPathEntry per ogni jar
Come per ogni ClassLoader, Usi findClass per caricare la classe che vuoi.


 Come per ogni ClassLoader, Usi findClass per caricare la classe che vuoi.
Uhmm... ma findClass è protected... devo passare un'istanza di
DynamicClassLoader a Class.forName() per ottenere l'oggetto Class?
Scusa ma evidentemente sono un po' tordo ;)


 DynamicClassLoader a Class.forName() per ottenere l'oggetto Class?
Pardon, intendevo loadClass(name) - in altre parole, lo stesso metodo che 
useresti con qualunque classloader, compreso quello di sistema.
Internamente poi loadClass chiama findClass (la cosa e' 
nell'implementazione di base in java.lang.ClassLoader)


 useresti con qualunque classloader, compreso quello di sistema.
Ciao! Scusa se ti disturbo ancora, ma dopo avere letto questi altri
due messaggi non posso farne a meno. Io ho fatto come hai spiegato tu
(piů sotto allego il codice), ma non sono riuscito a cavare un ragno
dal buco!
Ti ricapitolo la situazione: ho un'applet firmata che scarica dalla
rete al disco del client uno (al limite anche piů) file jar. Questi
contengono delle classi necessarie al proseguimento del programma:
diciamo che non appena copio questi file, istanzio una classe che
carica una o piů classi dal jar. Non sono ancora riuscito ad
effettuare questo ultimo passaggio, forse non ho ancora capito bene
come usare il tuo DynamicClassLoader. Il codice che allego č quello
della classe che viene richiamata dall'applet, e che quindi dovrebbe
caricare il jar. Prima di tutto imposto un nuovo classpath con il
percorso sul quale ho scaricato i file dalla rete, poi provo a
caricare la classe con loadClass. Stampando il vecchio e il nuovo
classpath vedo che l'istruzione setClassPath funziona, ma quello che
ottengo da loadClass č sempre e solo una ClassNotFoundException. Dove
sto sbagliando? Spero che il problema non siano le restrizioni di
sicurezza delle applet, perchč vorrebbe dire che ho perso un sacco di
tempo sbattendo contro un muro di gomma. Ma le applet firmate
dovrebbero avere tutti i permessi, giusto?
import java.io.File;
import org.sadun.util.DynamicClassLoader;
import com.deltax.util.DynamicClassFileFinder;
public class Loader {
  public Loader() {
    DynamicClassLoader dcl = new DynamicClassLoader();
    DynamicClassFileFinder dcff = dcl.getClassFileFinder();
    String oldCp = dcff.getClassPath();
    System.out.println(oldCp);
    String jar = System.getProperty("user.home") + File.separator +
      "lib" + File.separator + "library.jar";
    dcff.setClassPath(oldCp + ";" + jar);
    System.out.println(dcff.getClassPath());
    try {
      Class nc = dcl.loadClass("packageNelJar.ClasseDaCaricare");
      packageNelJar.ClasseDaCaricare n =
(packageNelJar.ClasseDaCaricare)nc.newInstance();
    }
    catch (Exception e) {
      e.printStackTrace();
    }
  }
 
  public static void main(String[] args) {
    new Loader();
  }
}

spamma...@email.it
 (GePs) wrote in
news:286cf7e1.04032...@posting.google.com
: 
Nessun problema. Il tuo codice ha il problema che nella classe Loader 
riferisci *esplicitamente* il tipo "packageNelJar.ClasseDaCaricare", che 
crea problemi quando la JVM tenta di risolvere la classe. Se provi 
"Object" e usi la reflection per invocare i metodi, funziona senza 
problemi:
     dcff.setClassPath(oldCp + ";" + jar);
     System.out.println(dcff.getClassPath());
     try {
       Class nc = dcl.loadClass("packageNelJar.ClasseDaCaricare");
       Object n = nc.newInstance();
    	 .. invoca i metodi via reflection..
     }
     catch (Exception e) {
       e.printStackTrace();
     }
Se vuoi avere un tipo statico da usare per evitare la reflection, devi 
definire un'interfaccia (visibile all'applet al momento del *suo* 
caricamento) e far si' che le classi nel plugin implementino 
quell'interfaccia. Ad esempio:
  Class nc = dcl.loadClass("org.T2");
  TInterface n = (TInterface)nc.newInstance();
  n.doSome();
dove: packageNelJar.ClasseDaCaricare implements TInterface
(e interface definisce "doSome()").


 (e interface definisce "doSome()").
Questo sarebbe ottimo, se il "plugin" lo avessi scritto io.
A questo punto credo che proverò ad arrangiarmi con la reflection,
prima di pensare ad altre strade. In ogno caso dovrò riscrivere un bel
po' di codice!

 po' di codice!
-- 


 loro interno far quello che ti serve.
Sarà il mio prossimo tentativo. Per ora ho incontrato un altro
problema.
Intanto ricapitoliamo: uso il tuo ClassLoader per caricare le classi
prese da dei file jar che copio in una directory del disco del
computer sul quale gira la mia applet.
Ho provato ad usare la reflection per istanziare gli oggetti che mi
servono in questo modo:
package mioPack;
public Class MiaClasse() {
  public MiaClasse() {
    //...
    dcl = new DynamicClassLoader();
    DynamicClassFileFinder dcff = dcl.getClassFileFinder();
    String altriCp = ...;
    dcff.setClassPath(dcff.getClassPath() + altriCp);
    System.out.println(dcff.getClassPath());
 
    // Questo oggetto mi serve per inizializzare l'oggetto che voglio
istanziare
    Properties providerProperties = new Properties();
    providerProperties.put("PKCS11_NATIVE_MODULE", "gclib.dll");
    // La classe da caricare
    Class pkcs11ProviderClass =
      dcl.loadClass("iaik.pkcs.pkcs11.provider.IAIKPkcs11");
    Constructor pkcs11ProviderCtor =
      pkcs11ProviderClass.getConstructor(new Class[]
{Properties.class});
    Object pkcs11Provider =
    // QUI sotto ho l'eccezione!
      pkcs11ProviderCtor.newInstance(new Object[]
{providerProperties});
    //...
    }
}
Con classi più semplici la cosa funziona, e invoco anche i metodi, ma
il costruttore di IAIKPkcs11 va a leggere dei file di configurazione
da qualche parte, e credo che per questo qualcosa vada storto.
L'output del programma infatti presenta queste righe:
// INIZIO OUTPUT
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
D:\Lavoro\Tesi\WORKSPACES\alfa\bin
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
C:\Programmi\Java\org.sadun.util_1.8\org.sadun.util.jar
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
C:\Documents and Settings\Utente\librerie
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
C:\Documents and Settings\Utente\librerie\iaikPkcs11Provider_signed.jar
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
D:\Lavoro\Tesi\WORKSPACES\alfa\bin
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
C:\Programmi\Java\org.sadun.util_1.8\org.sadun.util.jar
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
C:\Documents and Settings\Utente\librerie
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties in
C:\Documents and Settings\Utente\librerie\iaikPkcs11Provider_signed.jar
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/IAIKPkcs11Global.properties in
D:\Lavoro\Tesi\WORKSPACES\alfa\bin
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/IAIKPkcs11Global.properties in
C:\Programmi\Java\org.sadun.util_1.8\org.sadun.util.jar
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/IAIKPkcs11Global.properties in C:\Documents
and Settings\Utente\librerie
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/IAIKPkcs11Global.properties in C:\Documents
and Settings\Utente\librerie\iaikPkcs11Provider_signed.jar
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/IAIKPkcs11Global.properties in C:\Documents
and Settings\Utente\librerie\iaik_jce_full_signed.jar
JDK12CFF: looking for resource
iaik/pkcs/pkcs11/provider/IAIKPkcs11Global.properties in C:\Documents
and Settings\Utente\librerie\iaikPkcs11Wrapper_signed.jar
// FINE OUTPUT
IAIKPKCS11Global.properties è il file di configurazione, i percorsi
fanno tutti parte del classpath e le directory
iaik/pkcs/pkcs11/provider/default/ e iaik/pkcs/pkcs11/provider/ sono
nel file jar iaikPkcs11Provider_signed.jar.
Nota che il file IAIKPKCS11Global.properties è effettivamente presente
nella directory iaik/pkcs/pkcs11/provider/default/ nel jar.
Il CLASSPATH è questo:
D:\Lavoro\Tesi\WORKSPACES\alfa\bin;C:\Programmi\Java\org.sadun.util_1.8\org.sadun.util.jar;C:\Documents
and Settings\Utente\librerie;C:\Documents and
Settings\Utente\librerie\iaikPkcs11Provider_signed.jar;C:\Documents
and Settings\Utente\librerie\iaik_jce_full_signed.jar;C:\Documents and
Settings\Utente\librerie\iaikPkcs11Wrapper_signed.jar
Subito dopo viene sollevata l'eccezione seguente, corrispondente alla
riga che ti ho indicato sul codice:
java.lang.ExceptionInInitializerError
  at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native
Method)
  at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:39)
  at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:27)
  at java.lang.reflect.Constructor.newInstance(Constructor.java:274)
  at mioPack.MiaClasse.<init>(MiaClasse.java:43)
(...
continua per molte righe
...)
Caused by: java.lang.NullPointerException
  at com.deltax.util.JDK12ClassFileFinder.findResourceFile(Unknown
Source)
  at org.sadun.util.DynamicClassLoader.findResource(Unknown Source)
  at java.lang.ClassLoader.getResource(ClassLoader.java:787)
  at iaik.pkcs.pkcs11.provider.IAIKPkcs11.<clinit>(IAIKPkcs11.java:187)
... 28 more
Sembrerebbe che il file non venga caricato: infatti se invece
dell'invocazione newIstance() faccio
InputStream is = dcl.getResourceAsStream("iaik/pkcs/pkcs11/provider/default/IAIKPkcs11Global.properties");
Non ottengo eccezioni, quindi il file viene caricato! La stessa cosa
non avviene però con newIstance()...
Scusate se mi sono dilungato, ma non ho trovato un modo migliore di
esporre il problema, e ho esaurito tutte le idee.

 problema.
E' un bug che accade quando la risorsa non viene trovata: in 
JDK12ClassFileFinder il codice di 
public File findResourceFile(String resource) throws IOException 
e' 
{
  return findResourceFile0(resource).f;
}
mentre dovrebbe essere
{
 ResourceSearchResult r = findResourceFile0(resource);
 return (r!=null) ? r.f : null;
}
Mettero' un patch, grazie! :)
Per il resto: 
1) come carichi la tua risorsa dal plugin? 
Ho provato con una directory (non un jar), caricando una classe che usa 
getClass().getClassLoader().getResourceAsStream() e funziona senza 
problemi.
La mia classe di test e'
import java.util.Properties;
public class T {
  public T(Properties p) {
  	java.io.InputStream is = getClass().getClassLoader
().getResourceAsStream("test.properties");
  	if (is==null) throw new RuntimeException("Resource not found");
  }
}
e "test.properties" e' in una directory che non e' nel classpath di sistema 
ma e' in quello dinamico (dove si trova anche T).
2) Nel tuo codice, mi pare che tu aggiunga la classpath entry solo al 
ClassFileFinder, dove dici
 dcff.setClassPath(dcff.getClassPath() + altriCp);
Invece, devi assegnarlo all'intero classloader:
 dcl.setClassPath


  dcl.setClassPath
Questo non l'ho fatto, ora corro a provare!
Naturalmente ti farò sapere!

 [CUT]
Tutto ok! La modifica che mi hai detto di fare ha aggiustato tutto! Il
tuo codice e il tuo aiuto mi ha fatto capire il caricamento delle
classi in Java molto meglio di libri e siti vari!
Ho esteso DynamicClassLoader per implementare il metodo
findLibrary(String libname), che ho sovrascritto per fargli cercare
nel classpath librerie dinamiche da caricare e linkare (per farlo ho
dovuto impostare il modificatore di rff a protected). Eccolo qui:
/** 
 * Cerca il nome di una libreria nel classpath e se la trova la carica
 * @see java.lang.ClassLoader#findLibrary(java.lang.String)
 * @author GePs
 */
protected String findLibrary(String libname) {
    String cp = rff.getClassPath();
    String[] paths = cp.split(";");
    for (int i = 0; i < paths.length; i++) {
        if (!paths[i].endsWith(".jar")) {
            java.io.File dir = new File(paths[i]);
            if (dir.isDirectory()) {
                java.io.File lib = new java.io.File(dir, libname);
                if (lib.exists()) 
                    return lib.getAbsolutePath();
                lib = new File(dir, libname + ".dll");
                if (lib.exists())
                    return lib.getAbsolutePath();
                lib = new File(dir, libname + ".so");
                if (lib.exists())
                    return lib.getAbsolutePath();
                // Si può continuare e provare con altre estensioni...
            }
        }
    }
    return null;
}
Per finire il mio lavoro mi manca di ottenere i permessi di esecuzione
per la classe caricata, in quanto se carico l'applet principale da
browser ottengo una
java.security.AccessControlException:
    access denied (java.util.PropertyPermission * read,write)
Che dipende dalle varie elaborazioni delle classi che vado a gestire
con il "plugin". Sto studiando il meccanismo di concessione dei
permessi, sai, tutto quello che ha a che vedere con il CodeSource, le
Policy, le Permission e i domini di protezione. Spero di venirne a
capo entro breve, altrimenti metterò un post ad-hoc sul gruppo!
Comunque se hai qualche dritta fammi sapere!
Grazie di tutto! Ciao!
GePs

 Tutto ok! 
Ottimo. Grazie per il metodo, lo incorporero' nel codice.
-cs


 Ottimo. Grazie per il metodo, lo incorporero' nel codice.
Onorato! :)
Ho scoperto anche un'altra cosa: la tecnica dell'implementazione
dell'inteface nota da parte della classe caricata a runtime vale solo
in contesti differenti dal browser. In una application lanciata in
locale e nell'appletviewer la cosa funziona egregiamente, ma se provo
a fare il cast in questo modo
public class Caricatore extends JApplet {
//...
CustomClassLoader ccl = new CustomCLassLoader();
// NB: MiaClass implementa MiaInterface, e MiaInterface č nel jar
dell'applet
Class cls = ccl.loadClass("MiaClass");
Object o = cls.newInstance();
/* Questa istruzione ritorna bene in appletviewer, ma genera una
 * ClassCastException nei browser
 */
MiaInterface mi = (MiaInterface)o;
//...
in un jar firmato dentro un browser (ho provato si Mozila che IE)
ottengo una ClassCastException dove ho indicato. La cosa avviene sia
con il tuo classloader che con un altro che sto scrivendo io. Non č un
problema che non si possa risolvere con la reflection, ma sto cercando
di capire se č possibile evitarlo. All'inizio ho pensato che fosse un
problema di permission, come ho scritto nel post precedente riguardo
al caricamento della classe che mi da AccessControlException, ma sto
iniziando a pensare che le due cose siano separate.
Che ne dici?

 Che ne dici?
Ci possono essere problemi nel mescolare classi (intese qui come classi 
java o interfacce java) caricate da classloader diversi, specie in contesti 
di sicurezza. Ricordo che ne ebbi uno con JDCB DriverManager - il quale 
testa esplicitamente il ClassLoader usato per un caricare driver, e quindi 
preclude tecniche dinamiche. In genere pero' questo richiede codice tipo 
"if (cls.getClass().getClassLoader()!=getClass().getClassLoader()) throw 
...".
Pero' una ClassCastException e' strana. Se stampi o.getClass() e o.getClass
().getInterfaces(), che ti dice?


 ().getInterfaces(), che ti dice?
Da Eclipse:
o.getClass(): class it.GePs.Adapter
o.getClass().getInterfaces(): [Ljava.lang.Class;@1fbfd6
da browser è uguale:
o.getClass(): class class it.GePs.Adapter
o.getClass().getInterfaces(): [Ljava.lang.Class;@13e15f7
Ma perchè poi dovrebbe fare confusione, e a causa di quale 
"mescolamento"? Ho pensato anch'io che ci potesse essere confuzione tra 
i namespace dell'interface, e allora ho provato tutte le combinazioni 
possibili: jar dell'applet con e senza interface, jar delle classi 
esterne firmato, non fimato, con e senza intarface, classi esterne in 
delle directory anzichè nel jar... niente.
Comunque sia dal browser che da Eclipse, se stampo la lista dei metodi
della classe mi da quelli che effettivamente ci devono essere, e questo
vuol dire che li posso invocare, giusto?

 delle directory anzichè nel jar... niente.
Dipende dal tipo di controllo che la JVM fa sul codice. L'interfaccia e 
la classe caricata hanno ClassLoader diversi - che normalmente non e' un 
problema ma potrebbe esserlo in un contesto sicuro.
Nel commento a URLClassLoader, per esempio, dice:
"The classes that are loaded are by default granted permission only to 
access the URLs specified when the URLClassLoader was created"


 appare tra le interfacce implementate, c'e' qualcosa che non va.
Beh, comunque non è grave se posso aggirare il tutto con la
reflection. La cosa probabilmente dipende appunto dal contesto di
sicurezza del browser, nel quale sto operando.
Comunque per ora la lascio in sospeso e continuo il lavoro, alla fine,
con più tranquillità, mi ci rimetterò e ti farò sapere sicuramente.
Ancora un grazie di cuore. A risentirci!

Che cos'è un clash?
Un conflitto.
Ciao
Marco
xprofx(AT)katamail_punto_com