1   /*
2    * Copyright 2003 - 2013 The eFaps Team
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   *
16   * Revision:        $Rev$
17   * Last Changed:    $Date$
18   * Last Changed By: $Author$
19   */
20  
21  package org.efaps.admin.program.esjp;
22  
23  import java.io.File;
24  import java.io.IOException;
25  import java.io.InputStream;
26  import java.net.URL;
27  
28  import org.apache.commons.io.FileUtils;
29  import org.efaps.admin.AppConfigHandler;
30  import org.efaps.ci.CIAdminProgram;
31  import org.efaps.ci.CIType;
32  import org.efaps.db.Checkout;
33  import org.efaps.db.InstanceQuery;
34  import org.efaps.db.QueryBuilder;
35  import org.efaps.util.EFapsException;
36  import org.joda.time.DateTime;
37  import org.slf4j.Logger;
38  import org.slf4j.LoggerFactory;
39  
40  /**
41   * This Class extends the ClassLoader of java, to be able to load Classes on
42   * demand from the eFaps Database.
43   *
44   * @author The eFaps Team
45   * @version $Id$
46   */
47  public final class EFapsClassLoader
48      extends ClassLoader
49  {
50  
51      /**
52       * Classloader to be used for singleton.
53       */
54      private static EFapsClassLoader CLASSLOADER;
55  
56      /**
57       * Logger for this class.
58       */
59      private static final Logger LOG = LoggerFactory.getLogger(EFapsClassLoader.class);
60  
61      /**
62       * Temporary folder.
63       */
64      private static File TMPFOLDER;
65  
66      /**
67       * Type instance of compile EJSP program.
68       */
69      private final CIType classType;
70  
71      /**
72       * Is the classloader permitted to load from the eFaps DataBase.
73       */
74      private final boolean offline;
75  
76      /**
77       * Constructor setting the Parent of the EFapsClassLoader in ClassLoader.
78       *
79       * @param _parentClassLoader the Parent of the this EFapsClassLoader
80       * @param _offline has the classloader tehr rigth to access the dataBase
81       */
82      private EFapsClassLoader(final ClassLoader _parentClassLoader,
83                               final boolean _offline)
84      {
85          super(_parentClassLoader);
86          this.offline = _offline;
87          this.classType = CIAdminProgram.JavaClass;
88      }
89  
90      /**
91       * @see java.lang.ClassLoader#findClass(java.lang.String)
92       * @param _name name of the class
93       * @return Class
94       * @throws ClassNotFoundException if class was not found
95       */
96      @Override
97      public Class<?> findClass(final String _name)
98          throws ClassNotFoundException
99      {
100         Class<?> ret = null;
101         if (this.offline) {
102             throw new ClassNotFoundException(_name);
103         } else {
104             final byte[] b = loadClassData(_name);
105             if (b != null) {
106                 ret = defineClass(_name, b, 0, b.length);
107             } else {
108                 throw new ClassNotFoundException(_name);
109             }
110         }
111         return ret;
112     }
113 
114     /**
115      * In case of jbpm this is necessary for compiling,
116      * because they search the classes with URL.
117      * @param _name filename as url
118      * @return URL if found
119      */
120     @Override
121     public URL findResource(final String _name)
122     {
123         URL ret = null;
124         final String name = _name.replaceAll(System.getProperty("file.separator"), ".").replaceAll(".class", "");
125         final byte[] data = loadClassData(name);
126         if (data != null && data.length > 0) {
127             final File file = FileUtils.getFile(EFapsClassLoader.getTempFolder(), name);
128             try {
129                 if (!file.exists() || FileUtils.isFileOlder(file, new DateTime().minusHours(1).toDate())) {
130                     FileUtils.writeByteArrayToFile(file, data);
131                 }
132                 ret = file.toURI().toURL();
133             } catch (final IOException e) {
134                 LOG.error("Could not geneate File for reading from URL: {}", name);
135             }
136         }
137         return ret;
138     }
139 
140     /**
141      * Loads the wanted Resource with the EFapsResourceStore into a byte-Array
142      * to pass it on to findClass.
143      *
144      * @param _resourceName name of the Resource to load
145      * @return byte[] containing the compiled javaclass
146      */
147     protected byte[] loadClassData(final String _resourceName)
148     {
149         EFapsClassLoader.LOG.debug("Loading Class '{}' from Database.", _resourceName);
150         final byte[] x = read(_resourceName);
151         return x;
152     }
153 
154     /**
155      * The compiled class is received from the eFaps database (checked out)
156      * using the name <code>_resourceName</code>.
157      *
158      * @param _resourceName     name of the resource to be received (ESJP class
159      *                          name)
160      * @return byte array containing the compiled ESJP class
161      */
162     public byte[] read(final String _resourceName)
163     {
164         byte[] ret = null;
165         EFapsClassLoader.LOG.debug("read ''", _resourceName);
166         try {
167             final QueryBuilder queryBuilder = new QueryBuilder(this.classType);
168             queryBuilder.addWhereAttrEqValue("Name", _resourceName);
169             final InstanceQuery query = queryBuilder.getCachedQuery("esjp");
170             query.execute();
171             if (query.next()) {
172                 final Checkout checkout = new Checkout(query.getCurrentValue());
173                 final InputStream is = checkout.executeWithoutAccessCheck();
174 
175                 ret = new byte[is.available()];
176                 is.read(ret);
177                 is.close();
178             }
179         } catch (final EFapsException e) {
180             EFapsClassLoader.LOG.error("could not access the Database for reading '{}'", e , _resourceName);
181         } catch (final IOException e) {
182             EFapsClassLoader.LOG.error("could not read the Javaclass '{}'", e, _resourceName);
183         }
184         return ret;
185     }
186 
187     /**
188      * @return the tmpfolder
189      */
190     private static File getTempFolder()
191     {
192         if (EFapsClassLoader.TMPFOLDER == null || !EFapsClassLoader.TMPFOLDER.exists()) {
193             File tmpfld = AppConfigHandler.get().getTempFolder();
194             if (tmpfld == null) {
195                 File temp;
196                 try {
197                     temp = File.createTempFile("eFaps", ".tmp");
198                     tmpfld = temp.getParentFile();
199                     temp.delete();
200                 } catch (final IOException e) {
201                     LOG.error("Cannot create temp file", e);
202                 }
203             }
204             EFapsClassLoader.TMPFOLDER = new File(tmpfld, "eFaps-ClassFiles");
205             if (!EFapsClassLoader.TMPFOLDER.exists()) {
206                 final boolean mkdir = EFapsClassLoader.TMPFOLDER.mkdir();
207                 if (!mkdir) {
208                     LOG.error("Temp folder was not created");
209                 }
210             }
211         }
212         return EFapsClassLoader.TMPFOLDER;
213     }
214 
215     /**
216      * Get the current EFapsClassLoader.
217      * This static method is used to provide a way to use the same classloader
218      * in different threads, due to the reason that using different classloader
219      * instances might bring the problem of "instanceof" return unexpected results.
220      *
221      * @return the current EFapsClassLoader
222      */
223     public static synchronized EFapsClassLoader getInstance()
224     {
225         if (EFapsClassLoader.CLASSLOADER == null) {
226             EFapsClassLoader.CLASSLOADER = new EFapsClassLoader(EFapsClassLoader.class.getClassLoader(), false);
227         }
228         return EFapsClassLoader.CLASSLOADER;
229     }
230 
231 
232     /**
233      * Get the current EFapsClassLoader.
234      * This static method is used to provide a way to use the same classloader
235      * in different threads, due to the reason that using different classloader
236      * instances might bring the problem of "instanceof" return unexpected results.
237      * @param _parent parent classloader
238      * @return the current EFapsClassLoader
239      */
240     public static synchronized EFapsClassLoader getOfflineInstance(final ClassLoader _parent)
241     {
242         if (EFapsClassLoader.CLASSLOADER == null) {
243             EFapsClassLoader.CLASSLOADER = new EFapsClassLoader(_parent, true);
244         }
245         return EFapsClassLoader.CLASSLOADER;
246     }
247 
248     /**
249      * To be able to know if it is the first time the Classloader is wanted.
250      * @return is the static Class loader initialized.
251      */
252     public static boolean isInitialized()
253     {
254         return EFapsClassLoader.CLASSLOADER != null;
255     }
256 }