2 // (C) 2005 Mainsoft Corporation (http://www.mainsoft.com)
6 // Permission is hereby granted, free of charge, to any person obtaining
7 // a copy of this software and associated documentation files (the
8 // "Software"), to deal in the Software without restriction, including
9 // without limitation the rights to use, copy, modify, merge, publish,
10 // distribute, sublicense, and/or sell copies of the Software, and to
11 // permit persons to whom the Software is furnished to do so, subject to
12 // the following conditions:
14 // The above copyright notice and this permission notice shall be
15 // included in all copies or substantial portions of the Software.
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
18 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
19 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
20 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
21 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
22 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
23 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 using System.Collections;
30 using System.Web.Compilation;
31 using System.Collections.Specialized;
32 using System.Threading;
34 using System.Reflection;
36 namespace System.Web.J2EE
39 /// Class that allows reading assemblies.xml file for getting information about different types.
41 public class PageMapper
43 //private static readonly string _fileListName = "/filelist.xml";
44 private static readonly object LOCK_GETASSEMBLIESCACHEDDOCUMENT = new object();
45 //private static readonly object LOCK_GETFROMMAPPATHCACHE = new object();
48 static Assembly CurrentDomain_AssemblyResolve (object sender, ResolveEventArgs args)
50 Assembly resolvedAssembly = null;
53 resolvedAssembly = GetCachedAssembly (HttpContext.Current, String.Concat (HttpContext.Current.Request.ApplicationPath, "/", args.Name));
58 Console.WriteLine (ex.ToString ());
60 resolvedAssembly = null;
63 return resolvedAssembly;
68 public static string GetFromMapPathCache(string key)
70 Hashtable answer = null;
71 lock(LOCK_GETFROMMAPPATHCACHE)
73 answer = (Hashtable) AppDomain.CurrentDomain.GetData(J2EEConsts.MAP_PATH_CACHE);
76 answer = new Hashtable();
77 CachedDocumentTypeStorage storage = (CachedDocumentTypeStorage)GetAssembliesCachedDocument();
78 IDictionaryEnumerator e = storage.GetEnumerator();
82 string currentFile = (string)((DictionaryEntry)e.Current).Key;
83 answer[currentFile]= IAppDomainConfig.WAR_ROOT_SYMBOL + currentFile;
85 AppDomain.CurrentDomain.SetData(J2EEConsts.MAP_PATH_CACHE,answer);
88 return (string)answer[key];
92 //The method was used by runtime to force file names casesensitivity
93 // problem. The filelist.xml file should contain correct file names,
94 // but currently it is unused
95 public static void LoadFileList()
97 Hashtable hashTable = (Hashtable) AppDomain.CurrentDomain.GetData(J2EEConsts.FILE_LIST_FILE);
98 if (hashTable == null)
103 Stream fs = (Stream)IOUtils.getStream(_fileListName);
106 AppDomain.CurrentDomain.SetData(J2EEConsts.FILE_LIST_FILE, new Hashtable());
110 doc = new XmlDocument();
115 // Console.WriteLine("filelist.xml was not found!!!");
116 AppDomain.CurrentDomain.SetData(J2EEConsts.FILE_LIST_FILE, new Hashtable());
119 // Console.WriteLine("filelist.xml was found!!!");
120 if (doc != null && doc.DocumentElement.HasChildNodes)
122 hashTable = CollectionsUtil.CreateCaseInsensitiveHashtable();
123 XmlNodeList nodeList = doc.DocumentElement.ChildNodes;
124 for (int i = 0;i < nodeList.Count ; i++)
126 string fileName = nodeList.Item(i).InnerText;
127 hashTable.Add(fileName,fileName);
129 AppDomain.CurrentDomain.SetData(J2EEConsts.FILE_LIST_FILE, hashTable);
135 private static ICachedXmlDoc GetAssembliesCachedDocument(HttpContext context)
137 ICachedXmlDoc doc = (ICachedXmlDoc) AppDomain.CurrentDomain.GetData (J2EEConsts.ASSEMBLIES_FILE);
140 lock (LOCK_GETASSEMBLIESCACHEDDOCUMENT) {
141 doc = (ICachedXmlDoc) AppDomain.CurrentDomain.GetData (J2EEConsts.ASSEMBLIES_FILE);
143 doc = CreateDocument ();
145 AppDomain.CurrentDomain.SetData (J2EEConsts.ASSEMBLIES_FILE, doc);
147 AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler (CurrentDomain_AssemblyResolve);
149 //Try to load the global resources
150 HttpContext.AppGlobalResourcesAssembly = GetCachedAssembly (context, context.Request.ApplicationPath + "/app_globalresources");
152 catch (Exception ex) {
154 Console.WriteLine (ex.ToString ());
165 private static String NormalizeName(string url)
168 url = System.Web.Util.UrlUtils.RemoveDoubleSlashes(url);
170 if (url.StartsWith(IAppDomainConfig.WAR_ROOT_SYMBOL))
171 url = url.Substring(IAppDomainConfig.WAR_ROOT_SYMBOL.Length);
174 private static ICachedXmlDoc CreateDocument()
176 return new CachedDocumentTypeStorage();
179 public static Type GetObjectType (HttpContext context, string url)
181 return GetCachedType(context, NormalizeName(url), true);
184 public static Type GetObjectType (HttpContext context, string url, bool throwException) {
185 return GetCachedType (context, NormalizeName (url), throwException);
188 public static Assembly GetObjectAssembly (HttpContext context, string url)
190 return GetCachedAssembly (context, NormalizeName (url));
192 public static string GetAssemblyResource (HttpContext context, string url)
194 return GetCachedResource (context, NormalizeName (url));
196 private static string GetCachedResource (HttpContext context, string url)
198 ICachedXmlDoc doc = PageMapper.GetAssembliesCachedDocument(context);
199 return doc.GetAssemblyResourceName (context, url);
201 private static Assembly GetCachedAssembly (HttpContext context, string url)
203 ICachedXmlDoc doc = PageMapper.GetAssembliesCachedDocument(context);
204 return doc.GetAssembly (context, url);
206 private static Type GetCachedType (HttpContext context, string url) {
207 return GetCachedType (context, url, true);
209 private static Type GetCachedType (HttpContext context, string url, bool throwException)
211 ICachedXmlDoc doc = PageMapper.GetAssembliesCachedDocument(context);
212 Type t = doc.GetType(context, url);
213 if (t == null && throwException)
214 throw new HttpException(404,"The requested resource (" + url + ") is not available.");
219 #region ICachedXmlDoc interface
220 interface ICachedXmlDoc
222 Type GetType (HttpContext context, string key);
223 Assembly GetAssembly (HttpContext context, string key);
224 string GetAssemblyResourceName (HttpContext context, string key);
228 #region CachedDocumentTypeStorage class
229 class CachedDocumentTypeStorage : ICachedXmlDoc
231 private static readonly object _fuse = new object();
232 public static readonly ICachedXmlDoc DEFAULT_DOC =
233 new CachedDocumentTypeStorage(0);
235 private static readonly int DEFAULT_PAGES_NUMBER = 25;
237 private Hashtable _table;
239 private CachedDocumentTypeStorage(int initTableSize)
241 _table = Hashtable.Synchronized(new Hashtable(initTableSize));
244 public CachedDocumentTypeStorage() :
245 this(DEFAULT_PAGES_NUMBER)
248 string ICachedXmlDoc.GetAssemblyResourceName (HttpContext context, string o)
250 MetaProvider p = GetMetaByURL (context, o);
255 Type ICachedXmlDoc.GetType (HttpContext context, string o)
257 MetaProvider p = GetMetaByURL (context, o);
262 Assembly ICachedXmlDoc.GetAssembly (HttpContext context, string o)
264 MetaProvider p = GetMetaByURL (context, o);
270 internal IDictionaryEnumerator GetEnumerator()
272 return _table.GetEnumerator();
275 //rewamped for perfomance reasons
276 //It looks like issue is not important in development mode,
277 //but only will became important in production mode when dynamyc compilation will be enabled
278 //Anyway, locking whole table and compiling under lock looks odd
279 //spivak.December 07 2006
281 //prevent DOS attack. dont cache MetaProvider for not valid resource
283 public MetaProvider GetMetaByURL(HttpContext context, string url)
285 string lwUrl = url.ToLowerInvariant ();
286 MetaProvider retVal = (MetaProvider) _table [lwUrl];
287 if (retVal == null) {
288 retVal = PageCompiler.GetCompiler (context, url);
289 if (retVal.Type == null && retVal.Assembly == null)
291 _table [lwUrl] = retVal;
302 public interface MetaProvider
305 Assembly Assembly {get;}
306 string Resource { get;}
308 public class PageCompiler : MetaProvider
310 private static readonly string PAGE_XPATH = "preserve";
311 private static readonly string ASSEM_ATTRIB_NAME = "assem";
312 private static readonly string TYPE_ATTRIB_NAME = "type";
313 private static string _parser = null;
315 private Type _type = null;
316 private string _typeName = null;
317 private Assembly _assembly = null;
318 private string _origAssemblyName = null;
319 private string _xmlDescriptor = null;
320 private string _url = null;
321 private string _session = null;
322 readonly private HttpContext _context;
324 PageCompiler(HttpContext context, string url)
328 _xmlDescriptor = GetDescFromUrl();
329 _session = DateTime.Now.Ticks.ToString();
333 public static PageCompiler GetCompiler(HttpContext context, string url)
335 return new PageCompiler(context, url);
338 Type MetaProvider.Type
344 Assembly MetaProvider.Assembly
350 string MetaProvider.Resource
354 return _origAssemblyName != null ? _origAssemblyName + ".ghres" : "dll.ghres";
357 private void LoadTypeAndAssem()
359 if (_assembly == null)
361 string typeName = GetCachedTypeName();
363 Console.WriteLine ("Loading type:" + typeName);
365 if (typeName != null)
367 if ((_type = Type.GetType (typeName)) != null)
368 _assembly = _type.Assembly;
370 if (_origAssemblyName == null)
371 throw new TypeLoadException ("Cannot load type '" + typeName + "'");
372 _assembly = Assembly.Load (_origAssemblyName);
377 private bool InternalCompile()
379 string fileName = VirtualPathUtility.GetFileName (_url);
381 string fullFileName = (fileName.ToLower () == "global.asax") ? _url : _context.Request.MapPath (_url);
383 Console.WriteLine("fullFileName=" + fullFileName);
385 //type not found - run aspxparser
386 if (false/*File.Exists(fullFileName) || Directory.Exists(fullFileName)*/) //dynamic compilation currently is not supported
388 string[] command = GetParserCmd(fileName.ToLower() == "global.asax");
389 if (J2EEUtils.RunProc(command) != 0)
390 throw GetCompilerError();
397 //string message = "The requested resource (" + _url + ") is not available.";
398 //throw new HttpException(404, message);
401 private string GetDescriptorPath()
403 return String.Join("/", new string[] { "assemblies", _xmlDescriptor });
405 private string GetTypeNameFromAppFolder()
409 using (StreamReader sr = new StreamReader(_context.Request.MapPath("~/" + GetDescriptorPath())))
411 return GetTypeFromDescStream(sr.BaseStream);
416 Console.WriteLine(ex);
420 internal string GetTypeFromResources()
422 string typeName = null;
424 //if the desciptor exists in the war - get the type
425 string descPath = GetDescriptorPath();
430 Console.WriteLine(descPath);
432 using (Stream fs = (Stream)IOUtils.getStreamRecursive("/" + descPath))
436 return GetTypeFromDescStream(fs);
443 Console.WriteLine(ex);
448 internal string GetCachedTypeName()
450 string typeName = GetTypeFromResources();
451 if (typeName == null)
453 //spawn dynamic compilation and lookup typename from created folder
454 if (InternalCompile())
455 typeName = GetTypeNameFromAppFolder();
459 private string GetTypeName()
461 return String.Format("{0}, {1}", _typeName, _origAssemblyName);
463 private bool LoadMetaFromXmlStream(Stream fs)
469 XmlDocument descXml = new XmlDocument();
471 _origAssemblyName = descXml.SelectSingleNode(PAGE_XPATH).Attributes[ASSEM_ATTRIB_NAME].Value;
472 _typeName = descXml.SelectSingleNode(PAGE_XPATH).Attributes[TYPE_ATTRIB_NAME].Value;
478 Console.WriteLine("Failed to load typename from stream");
485 private string GetTypeFromDescStream(Stream fs)
487 if (LoadMetaFromXmlStream(fs))
488 return GetTypeName();
492 private string[] GetParserCmd(bool globalAsax)
498 cmd[3] = "/buildglobalasax";
503 cmd[3] = "/aspxFiles:" + _url;
504 cmd[4] = "/compilepages";
506 cmd[0] = GetParser();
507 cmd[1] = "/session:" + _session;
508 cmd[2] = "/appDir:" + (string)AppDomain.CurrentDomain.GetData(IAppDomainConfig.APP_PHYS_DIR);
512 private string GetParser()
517 File.OpenText (_context.Request.MapPath ("~/AspxParser.params"));
518 _parser = sr.ReadLine();
525 private string GetDescFromUrl()
527 string fileName = VirtualPathUtility.GetFileName (_url);
529 if (fileName.ToLower() == "global.asax")
530 return "global.asax.xml";
532 string id = GetIdFromUrl(_url);
533 string[] descName = new string[3] {fileName, id, ".xml"} ;
534 return string.Concat(descName).ToLowerInvariant();
537 private string GetIdFromUrl(string path)
539 string fileName = VirtualPathUtility.GetFileName(path);
540 string id = string.Empty;
542 if (VirtualPathUtility.IsAbsolute (path))
543 path = path.Substring (_context.Request.ApplicationPath.Length + 1);
545 if (path.Length > fileName.Length)
546 id = "." + path.Substring(0,path.Length - fileName.Length).Replace('/','_');
550 private Exception GetCompilerError()
552 string _errFile = _context.Request.MapPath ("~/" + _session + ".vmwerr");
554 if (!File.Exists(_errFile))
555 throw new FileNotFoundException("Internal Error",_errFile);
557 StreamReader sr = new StreamReader(_errFile);
558 string message = string.Empty, line = null, file = null, lineInFile = "0";
560 while ((line = sr.ReadLine()) != null)
562 if (line.StartsWith("Message: "))
563 message = line.Substring("Message: ".Length);
564 else if (line.StartsWith("File: "))
565 file = line.Substring("File: ".Length);
566 else if (line.StartsWith("Line: "))
567 lineInFile = line.Substring("Line: ".Length);
574 Location loc = new Location(null);
576 loc.BeginLine = int.Parse(lineInFile);
577 return new ParseException(loc,message);
580 if (message.IndexOf(typeof(FileNotFoundException).Name) != -1 &&
581 message.IndexOf(_url.Trim('\\','/').Replace('/','\\')) != -1)
582 message = "The requested resource (" + _url + ") is not available.";
583 return new HttpException(404,(message != null ? message : string.Empty));