No, I do not have commit rights. I have created an issue on JIRA and attached the project with the classes added. http://wicketstuff.org/jira/browse/WSMINIS-8
Regards, Matthew. On Tue, Dec 16, 2008 at 4:08 PM, Jeremy Thomerson <jer...@wickettraining.com > wrote: > Do you have commit rights to wicketstuff? If you do, just commit it. If > not, you could ask for it or create a patch on the JIRA: > http://wicketstuff.org/jira/secure/BrowseProject.jspa?id=10020 > > If you need help, let us know. > > > > On Tue, Dec 16, 2008 at 3:37 PM, Matthew Hanlon <mrhan...@gmail.com> > wrote: > > > I just realized that gmail wasn't replying to the list. Sorry. For > > posterity... > > > > On Tue, Dec 16, 2008 at 3:34 PM, Matthew Hanlon <mrhan...@gmail.com> > > wrote: > > > > > Below is the updated CaseInsensitiveClassResolver I implemented per > your > > > suggestions. It uses MatchingResources from wicketstuff annotation. > It > > > scans packages on-demand and caches the results for future lookup. It > > seems > > > fairly fast, and due to the cache it doesn't have to re-scan the > > classpath. > > > Also below is my CaseInsensitivePackageRequestTargetUrlCodingStrategy > > > (Gasp! What an awfully long name.). > > > If you think it's worthwhile, I'd love to contribute it to > > > wicketstuff-minis. How would one got about doing that? > > > > > > public class CaseInsensitiveClassResolver implements IClassResolver { > > > > > > private static final Logger logger = > > > LoggerFactory.getLogger(CaseInsensitiveClassResolver.class); > > > private DefaultClassResolver resolver = new DefaultClassResolver(); > > > public Iterator<URL> getResources(String name) { > > > return resolver.getResources(name); > > > } > > > private Map<String, Map<String, Class<?>>> cache = new HashMap<String, > > > Map<String,Class<?>>>(); > > > public Class<?> resolveClass(String classname) throws > > > ClassNotFoundException { > > > Class<?> clazz = null; > > > try { > > > clazz = resolver.resolveClass(classname); > > > } catch (ClassNotFoundException e1) { > > > clazz = resolveClassCaseInsensitive(classname); > > > } catch (NoClassDefFoundError e2) { > > > clazz = resolveClassCaseInsensitive(classname); > > > } > > > if (clazz == null) { > > > throw new ClassNotFoundException("Unable to resolve class for name " + > > > classname); > > > } > > > return clazz; > > > } > > > public Class<?> resolveClassCaseInsensitive(String classname) { > > > if (logger.isDebugEnabled()) { > > > logger.debug("Class not found for " + classname + ". Trying to look > up > > > case-insensitive."); > > > } > > > String packageName = classname.substring(0, > classname.lastIndexOf('.')); > > > if (! cache.containsKey(packageName)) { > > > cache.put(packageName, scan(getPatternForPackage(packageName))); > > > } > > > return cache.get(packageName).get(classname.toLowerCase()); > > > } > > > /** > > > * Get the Spring search pattern given a package name or part of a > > > package name > > > * @param packageName a package name > > > * @return a Spring search pattern for the given package > > > */ > > > public String getPatternForPackage(String packageName) > > > { > > > if (packageName == null) packageName = ""; > > > packageName = packageName.replace('.', '/'); > > > if (!packageName.endsWith("/")) > > > { > > > packageName += '/'; > > > } > > > > > > return "classpath*:" + packageName + "**/*.class"; > > > } > > > > > > /** > > > * > > > * @param pattern > > > */ > > > private Map<String, Class<?>> scan(final String pattern) { > > > Map<String, Class<?>> classMap = new HashMap<String, Class<?>>(); > > > MatchingResources resources = new MatchingResources(pattern); > > > MetadataReaderFactory f = new SimpleMetadataReaderFactory(); > > > for (Resource r : resources.getAllMatches()) { > > > MetadataReader meta = null; > > > try > > > { > > > meta = f.getMetadataReader(r); > > > } > > > catch (IOException e) > > > { > > > throw new RuntimeException("Unable to get > MetadataReader > > > for " + r, e); > > > } > > > try { > > > ClassMetadata cmd = meta.getClassMetadata(); > > > String classname = cmd.getClassName(); > > > try { > > > classMap.put(classname.toLowerCase(), > > > getClass().getClassLoader().loadClass(classname)); > > > } catch (ClassNotFoundException e) { > > > logger.error("Error loading class for name " + classname); > > > } > > > } catch (Throwable e) { > > > logger.error("Unknown Error.", e); > > > } > > > } > > > return classMap; > > > } > > > > > > } > > > > > > public class CaseInsensitivePackageRequestTargetUrlCodingStrategy > extends > > > PackageRequestTargetUrlCodingStrategy { > > > > > > private static final Logger log = > > > > > > LoggerFactory.getLogger(CaseInsensitivePackageRequestTargetUrlCodingStrategy.class); > > > public CaseInsensitivePackageRequestTargetUrlCodingStrategy(final > String > > > path, PackageName packageName) { > > > super(path, packageName); > > > this.packageName = packageName; > > > } > > > > > > /** package for this mount. */ > > > private final PackageName packageName; > > > private IClassResolver resolver = new CaseInsensitiveClassResolver(); > > > /** > > > * @see > > > > > > org.apache.wicket.request.target.coding.IRequestTargetUrlCodingStrategy#decode(org.apache.wicket.request.RequestParameters) > > > */ > > > public IRequestTarget decode(RequestParameters requestParameters) > > > { > > > String remainder = > > > requestParameters.getPath().substring(getMountPath().length()); > > > final String parametersFragment; > > > int ix = remainder.indexOf('/', 1); > > > if (ix == -1) > > > { > > > ix = remainder.length(); > > > parametersFragment = ""; > > > } > > > else > > > { > > > parametersFragment = remainder.substring(ix); > > > } > > > > > > if (remainder.startsWith("/")) > > > { > > > remainder = remainder.substring(1); > > > ix--; > > > } > > > else > > > { > > > // There is nothing after the mount path! > > > return null; > > > } > > > > > > final String bookmarkablePageClassName = packageName + "." + > > > remainder.substring(0, ix); > > > Class bookmarkablePageClass; > > > try > > > { > > > bookmarkablePageClass = > > resolver.resolveClass(bookmarkablePageClassName); > > > } > > > catch (Exception e) > > > { > > > log.debug(e.getMessage()); > > > return null; > > > } > > > PageParameters parameters = new > > > PageParameters(decodeParameters(parametersFragment, > > > requestParameters.getParameters())); > > > > > > String pageMapName = > > > (String)parameters.remove(WebRequestCodingStrategy.PAGEMAP); > > > pageMapName = WebRequestCodingStrategy.decodePageMapName(pageMapName); > > > requestParameters.setPageMapName(pageMapName); > > > > > > // do some extra work for checking whether this is a normal request to > a > > > // bookmarkable page, or a request to a stateless page (in which case a > > > // wicket:interface parameter should be available > > > final String interfaceParameter = (String)parameters > > > .remove(WebRequestCodingStrategy.INTERFACE_PARAMETER_NAME); > > > > > > if (interfaceParameter != null) > > > { > > > WebRequestCodingStrategy.addInterfaceParameters(interfaceParameter, > > > requestParameters); > > > return new BookmarkableListenerInterfaceRequestTarget(pageMapName, > > > bookmarkablePageClass, parameters, > requestParameters.getComponentPath(), > > > requestParameters.getInterfaceName(), > > > requestParameters.getVersionNumber()); > > > } > > > else > > > { > > > return new BookmarkablePageRequestTarget(pageMapName, > > > bookmarkablePageClass, parameters); > > > } > > > } > > > } > > > > > > > > > > > > On Fri, Dec 12, 2008 at 10:57 AM, Jeremy Thomerson < > > > jer...@wickettraining.com> wrote: > > > > > >> One other thing - I think that the contract of IClassResolver would > mean > > >> that rather than returning null, you throw a ClassNotFoundException. > > >> > > >> > > >> On Fri, Dec 12, 2008 at 8:24 AM, Matthew Hanlon <mrhan...@gmail.com > > >wrote: > > >> > > >>> Great ideas, thanks for the input. I agree on all points. My > initial > > >>> implementation is certainly the naive approach, basically a proof of > > >>> concept. I'll look into what you mention in 4 and let you know what > I > > find. > > >>> > > >>> > > >>> > > >>> On Fri, Dec 12, 2008 at 12:44 AM, Jeremy Thomerson < > > >>> jer...@wickettraining.com> wrote: > > >>> > > >>>> Sounds like an interesting idea. Here are a few thoughts I had > after > > >>>> seeing it. Hopefully these are helpful. > > >>>> > > >>>> 1 - Say you had a page "CustomerAdminLoginPage" - this yields > > 4,194,304 combinations! > > >>>> Cache the result - either the class you found or the fact that you > > could not > > >>>> find a class. (you will have 2 to the nth power, where n equals the > > length > > >>>> of the simple name) > > >>>> 2 - DON'T use StringBuilder just to split it later - that's not what > > >>>> it's for! It's very slow and is constantly resizing it's internal > > array. > > >>>> You could use something like the code I pasted below to use a single > > array, > > >>>> initialized ahead of time to the proper size. > > >>>> > > >>>> 3 - I would suggest not even holding an array of possible combos - > > >>>> longer class names take a ton of memory because of all the millions > of > > >>>> strings created. If you must go through all combos to try to find a > > match, > > >>>> just look for the match in your loop rather than looping to create > an > > array > > >>>> of combos and then re-looping to try to find a match. > > >>>> > > >>>> 4 - Now - I would suggest seeing if you can avoid looping through > all > > >>>> possible combos altogether. Look at how Wicket Annotations (in > Wicket > > >>>> Stuff) does classpath scanning... I would think that this would be > > much > > >>>> more efficient - scan the package ahead of time and find all classes > > in the > > >>>> package and cache their names. Then just do a case-insensitive look > > into > > >>>> your cache - this saves you all the memory and processing trouble of > > ever > > >>>> computing all the combos and trying to load potentially millions of > > >>>> non-existent classes. > > >>>> > > >>>> 5 - If you get this to work and work well, add it to > wicketstuff-minis > > >>>> or a similar project where others can easily use it - let me know if > > you > > >>>> need help accomplishing that. > > >>>> > > >>>> > > >>>> Here's an example of an improved method of finding the combos - > > probably > > >>>> could still be improved considerably, but this is a significant > > improvement > > >>>> over your first rough draft. (Although see point 4 - I recommend > not > > even > > >>>> using this method at all) > > >>>> > > >>>> private static int capsCombinations(String[] combos, String > word, > > >>>> int startIndex, int arrayIndex) { > > >>>> if (arrayIndex == 0) { > > >>>> word = word.toLowerCase(); > > >>>> combos[arrayIndex++] = word; > > >>>> } > > >>>> if (arrayIndex == combos.length) { > > >>>> return arrayIndex; > > >>>> } else { > > >>>> while (startIndex < word.length()) { > > >>>> char[] chars = word.toCharArray(); > > >>>> chars[startIndex] = > > >>>> Character.toUpperCase(chars[startIndex]); > > >>>> String string = String.valueOf(chars); > > >>>> combos[arrayIndex++] = string; > > >>>> arrayIndex = capsCombinations(combos, string, > > >>>> ++startIndex, arrayIndex); > > >>>> } > > >>>> return arrayIndex; > > >>>> } > > >>>> } > > >>>> > > >>>> public static void main(String[] args) throws Exception { > > >>>> long start = System.currentTimeMillis(); > > >>>> String name = "CustomerAdminLoginPage"; > > >>>> String[] combos = new String[(int) Math.pow(2, > > name.length())]; > > >>>> capsCombinations(combos, name, 0, 0); > > >>>> System.out.print(combos.length + " combos - took "); > > >>>> System.out.print((System.currentTimeMillis() - start) + " > > millis > > >>>> - used "); > > >>>> Runtime rt = Runtime.getRuntime(); > > >>>> System.out.println((rt.totalMemory() / 1048576) + "MB of > > >>>> memory"); > > >>>> } > > >>>> > > >>>> -- > > >>>> Jeremy Thomerson > > >>>> http://www.wickettraining.com > > >>>> > > >>>> > > >>>> On Thu, Dec 11, 2008 at 2:55 PM, Matthew Hanlon <mrhan...@gmail.com > > >wrote: > > >>>> > > >>>>> I am looking for some feedback any may have on this: > > >>>>> Let's say I've mounted a package "com.company.package" using > > >>>>> PackageRequestTargetUrlCodingStrategy > > >>>>> on "/foo." So I have several pages, /foo/Bar, /foo/Baz, etc. Now, I > > >>>>> want my > > >>>>> page mounts to be case-insensitive in the case that a user has caps > > >>>>> lock on > > >>>>> or types in all lower case or whatever. For > > >>>>> PackageRequestTargetUrlCodingStrategy this works for the "/foo" > part, > > >>>>> but > > >>>>> not the classname part, obviously. > > >>>>> > > >>>>> So I implemented a CaseInsensitiveClassResolver that delegates to a > > >>>>> DefaultClassResolver. In the case that the DefaultClassResolver > > cannot > > >>>>> find > > >>>>> the class, the CaseInsensitiveClassResolver tries to load the class > > by > > >>>>> trying different combinations of upper/lower case in the classname. > > So, > > >>>>> for > > >>>>> "bar" it would try to resolve "com.company.package.Bar," " > > >>>>> com.company.package.bAr," "com.company.package.baR," etc, obviously > > >>>>> finding > > >>>>> "com.company.package.Bar" and returning that class. > > >>>>> > > >>>>> This works pretty well. Now, obviously it's not the most efficient > > >>>>> thing, > > >>>>> possibly having to catch several > > >>>>> ClassNotFoundException/NoClassDefFoundError exceptions > > >>>>> before finding the class (assuming the name exists and is spelled > > >>>>> correctly, > > >>>>> just with wrong case). But it might be better than returning a 404 > on > > a > > >>>>> page > > >>>>> simply due to improper case. I wouldn't expect it to happen often, > as > > >>>>> more > > >>>>> often than not the user will probably use a link the get to the > > pages, > > >>>>> and > > >>>>> so no typing at all. But in the rare case... > > >>>>> > > >>>>> So, any thoughts? > > >>>>> > > >>>>> Here's the code: > > >>>>> > > >>>>> public class CaseInsensitiveClassResolver implements IClassResolver > { > > >>>>> > > >>>>> private static final Logger logger = > > >>>>> LoggerFactory.getLogger(CaseInsensitiveClassResolver.class); > > >>>>> private DefaultClassResolver resolver = new > DefaultClassResolver(); > > >>>>> public Iterator<URL> getResources(String name) { > > >>>>> return resolver.getResources(name); > > >>>>> } > > >>>>> > > >>>>> public Class<?> resolveClass(String classname) { > > >>>>> Class<?> clazz = null; > > >>>>> try { > > >>>>> clazz = resolver.resolveClass(classname); > > >>>>> } catch (ClassNotFoundException e1) { > > >>>>> clazz = resolveClassCaseInsensitive(classname); > > >>>>> } catch (NoClassDefFoundError e2) { > > >>>>> clazz = resolveClassCaseInsensitive(classname); > > >>>>> } > > >>>>> return clazz; > > >>>>> } > > >>>>> public Class<?> resolveClassCaseInsensitive(String classname) > throws > > >>>>> ClassNotFoundException { > > >>>>> if (logger.isDebugEnabled()) { > > >>>>> logger.debug("Class not found for " + classname + ". Trying to > look > > up > > >>>>> case-insensitive."); > > >>>>> } > > >>>>> String packageName = classname.substring(0, > > >>>>> classname.lastIndexOf('.')); > > >>>>> String simpleName = classname.substring(classname.lastIndexOf('.') > + > > >>>>> 1); > > >>>>> String combos = capsCombinations(simpleName.toLowerCase(), 0); > > >>>>> Class<?> cls = null; > > >>>>> for (String combo : combos.split(",")) { > > >>>>> try { > > >>>>> cls = resolver.resolveClass(packageName + "." + combo); > > >>>>> } catch (ClassNotFoundException e1) { > > >>>>> if (logger.isDebugEnabled()) { > > >>>>> logger.debug("Class not found for " + packageName + "." + combo + > > "."); > > >>>>> } > > >>>>> } catch (NoClassDefFoundError e2) { > > >>>>> if (logger.isDebugEnabled()) { > > >>>>> logger.debug("Class not found for " + packageName + "." + combo + > > "."); > > >>>>> } > > >>>>> } > > >>>>> if (cls != null) { > > >>>>> if (logger.isDebugEnabled()) { > > >>>>> logger.debug("Class found for " + packageName + "." + combo + "."); > > >>>>> } > > >>>>> return cls; > > >>>>> } > > >>>>> } > > >>>>> return null; > > >>>>> } > > >>>>> > > >>>>> private String capsCombinations(String word, int startIndex) { > > >>>>> StringBuilder sb = new StringBuilder(word); > > >>>>> if (word.equals(word.toUpperCase())) { > > >>>>> return sb.toString(); > > >>>>> } else { > > >>>>> for (; startIndex < word.length();) { > > >>>>> char[] chars = word.toCharArray(); > > >>>>> chars[startIndex] = Character.toUpperCase(chars[startIndex]); > > >>>>> sb.append(","); > > >>>>> sb.append(capsCombinations(new String(chars), ++startIndex)); > > >>>>> } > > >>>>> return sb.toString(); > > >>>>> } > > >>>>> } > > >>>>> } > > >>>>> -- > > >>>>> Matthew Rollins Hanlon > > >>>>> http://squareoftwo.org > > >>>>> _____________________ > > >>>>> Hanlon's Razor: > > >>>>> "Never attribute to malice that which can be adequately explained > by > > >>>>> stupidity." > > >>>>> http://wikipedia.org/wiki/Hanlon's_razor > > >>>>> > > >>>> > > >>>> > > >>>> > > >>>> > > >>> > > >>> > > >>> -- > > >>> Matthew Rollins Hanlon > > >>> http://squareoftwo.org > > >>> _____________________ > > >>> Hanlon's Razor: > > >>> "Never attribute to malice that which can be adequately explained by > > >>> stupidity." > > >>> http://wikipedia.org/wiki/Hanlon's_razor > > >>> > > >> > > >> > > >> > > >> -- > > >> Jeremy Thomerson > > >> http://www.wickettraining.com > > >> > > > > > > > > > > > > -- > > > Matthew Rollins Hanlon > > > http://squareoftwo.org > > > _____________________ > > > Hanlon's Razor: > > > "Never attribute to malice that which can be adequately explained by > > > stupidity." > > > http://wikipedia.org/wiki/Hanlon's_razor > > > > > > > > > > > -- > > Matthew Rollins Hanlon > > http://squareoftwo.org > > _____________________ > > Hanlon's Razor: > > "Never attribute to malice that which can be adequately explained by > > stupidity." > > http://wikipedia.org/wiki/Hanlon's_razor > > > > > > -- > Jeremy Thomerson > http://www.wickettraining.com > -- Matthew Rollins Hanlon http://squareoftwo.org _____________________ Hanlon's Razor: "Never attribute to malice that which can be adequately explained by stupidity." http://wikipedia.org/wiki/Hanlon's_razor