Java tutorial
/* Copyright (C) 2006-2014 Helge Hess This file is part of Go. Go is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2, or (at your option) any later version. Go is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with Go; see the file COPYING. If not, write to the Free Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.getobjects.appserver.publisher; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.getobjects.appserver.core.WOApplication; import org.getobjects.appserver.publisher.annotations.DefaultAccess; import org.getobjects.appserver.publisher.annotations.DefaultRoles; import org.getobjects.appserver.publisher.annotations.GoMethod; import org.getobjects.appserver.publisher.annotations.Private; import org.getobjects.appserver.publisher.annotations.ProtectedBy; import org.getobjects.appserver.publisher.annotations.Public; import org.getobjects.foundation.NSKeyValueCoding; import org.getobjects.foundation.NSObject; import org.getobjects.foundation.UString; /** * GoClassRegistry * <p> * Caches GoClasses. */ public class GoClassRegistry extends NSObject { //TODO: document more protected static final Log log = LogFactory.getLog("GoClassRegistry"); protected WOApplication application; protected ConcurrentHashMap<String, GoClass> nameToClass; public GoClassRegistry(final WOApplication _app) { this.application = _app; this.nameToClass = new ConcurrentHashMap<String, GoClass>(128); } /* accessors */ public WOApplication application() { return this.application; } /* exposing Java classes as So classes */ /** * First attempts to retrieve the GoClass by evaluating the 'goClass' * KVC key. If that doesn't return anything, we call goClassForJavaClass() * with the object's class. * * @param _object - Object to get the GoClass for * @param _ctx - context in which the object is active * @return the GoClass, or null if none could be found */ public GoClass goClassForJavaObject(Object _object, final IGoContext _ctx) { if (_object == null) return null; if (_object instanceof NSKeyValueCoding) { /* we assume that all relevant classes inherit from NSObject */ Object oc = ((NSKeyValueCoding) _object).valueForKey("goClass"); if (oc == null) { // deprecated oc = ((NSKeyValueCoding) _object).valueForKey("joClass"); if (oc != null) log.warn("DEPRECATED: replace 'joClass' with 'goClass'"); } if (oc != null) { if (oc instanceof GoClass) return (GoClass) oc; log.error("goClass property of object did not return a goClass: " + oc); } } return this.goClassForJavaClass(_object.getClass(), _ctx); } /** * Generates a new GoClass for an arbitrary Java class (and all its super * classes) on the fly. * This method handles the caching and the superclass traversal, the * actual mapping is done in generateGoClassFromJavaClass(). * * @param _cls - Java Class to generate a GoClass for * @param _ctx - a Go context or NULL * @return new GoClass representing the given Java class within Go */ public GoClass goClassForJavaClass(final Class _cls, final IGoContext _ctx) { if (_cls == null) return null; /* check cache */ // TODO: need to consider threading?! (multireader?) Possibly we want to // load or create classes as runtime and not just in bootstrapping GoClass goClass = this.nameToClass.get(_cls.getName()); if (goClass != null) return goClass; /* process superclass */ final Class superClass = _cls.getSuperclass(); final GoClass goSuperClass = this.goClassForJavaClass(superClass, _ctx); /* process class */ goClass = this.generateGoClassFromJavaClass(_cls, goSuperClass, _ctx); if (goClass == null) { log.error("could not create GoClass from Java class: " + _cls); return null; } /* cache result */ // THREAD this.nameToClass.put(_cls.getName(), goClass); return goClass; } /** * The primitive to generate a new GoClass for an arbitrary Java class. It * takes the GoClass of the superclass of the Java object. * <p> * Note: This is internal and does not cache, rather use * goClassForJavaClass(), which traverse superclasses and does the caching. * <p> * * @param _cls - Java Class to generate a GoClass for * @param _ctx - a Go context or NULL * @return new GoClass representing the given Java class within Go */ public GoJavaClass generateGoClassFromJavaClass(final Class _cls, final GoClass _superClass, final IGoContext _ctx) { if (_cls == null) return null; /* construct class */ final GoJavaClass clazz = new GoJavaClass(_cls.getSimpleName(), _superClass); this.processClassAnnotations(clazz, _cls); ; /* collect mapped methods */ final Map<String, Object> nameToMethod = new HashMap<String, Object>(32); for (final Method method : _cls.getDeclaredMethods()) { final GoJavaMethod goMethod = this.generateGoMethodFromJavaMethod(clazz, method, _ctx); if (goMethod == null) continue; /* not moved */ nameToMethod.put(goMethod.name(), goMethod); } clazz._setAllSlots(nameToMethod); return clazz; } protected void processClassAnnotations(final GoJavaClass _goCls, final Class _cls) { String pb = null, access = null; boolean isPrivate = false, isPublic = false; String[] anonPerms = null, authPerms = null; // We could stop if all knows have been processed, but that situation // doesn't actually happen. for (final Annotation a : _cls.getAnnotations()) { if (a instanceof ProtectedBy) pb = ((ProtectedBy) a).value(); else if (a instanceof Private) isPrivate = true; else if (a instanceof Public) isPublic = true; else if (a instanceof DefaultAccess) access = ((DefaultAccess) a).value(); else if (a instanceof DefaultRoles) { anonPerms = ((DefaultRoles) a).anonymous(); authPerms = ((DefaultRoles) a).authenticated(); } } /* object protections, only one can be set */ if (pb != null || isPrivate || isPublic) { if (isPrivate) { if (pb != null || isPublic) { log.error("declared Private on a class which already has a " + "another protection (ProtectedBy or Public)"); } _goCls.securityInfo().declareObjectPrivate(); } else if (pb != null) { if (isPublic) { log.error( "declared ProtectedBy on a class which already has a " + "another protection (Public)"); } _goCls.securityInfo().declareObjectProtected(pb); ; } else if (isPublic) _goCls.securityInfo().declareObjectPublic(); } /* roles, multiple can be set */ Map<String, ArrayList<String>> permToRoles = null; if (anonPerms != null && anonPerms.length > 0) { if (permToRoles == null) permToRoles = new HashMap<String, ArrayList<String>>(4); fillPermissionToRoleMap(permToRoles, GoRole.Anonymous, anonPerms); } if (authPerms != null && authPerms.length > 0) { if (permToRoles == null) permToRoles = new HashMap<String, ArrayList<String>>(4); fillPermissionToRoleMap(permToRoles, GoRole.Authenticated, authPerms); } if (permToRoles != null) { final GoSecurityInfo si = _goCls.securityInfo(); for (final String perm : permToRoles.keySet()) { final List<String> roles = permToRoles.get(perm); si.declareRolesAsDefaultForPermission(roles.toArray(new String[roles.size()]), perm); } } /* access */ if (access != null) { if (!access.equals("allow") && !access.equals("deny")) log.error("Invalid default-access argument: " + access); else _goCls.securityInfo().setDefaultAccess(access); } } private static void fillPermissionToRoleMap(final Map<String, ArrayList<String>> permToRoles_, final String _role, final String[] _perms) { for (final String perm : _perms) { ArrayList<String> al = permToRoles_.get(perm); if (al == null) { al = new ArrayList<String>(4); permToRoles_.put(perm, al); } al.add(_role); } } public GoJavaMethod generateGoMethodFromJavaMethod(final GoJavaClass _goCls, final Method _method, final IGoContext _ctx) { // TBD: expose methods which end in 'Action'? if (_method == null) return null; final GoMethod methodAnnotation = _method.getAnnotation(GoMethod.class); if (methodAnnotation == null) { // not exposing Java methods which are not annotated // TBD: should we auto-expose objects ending in 'Action'? return null; } // If the annotation doesn't specify a slot, use the methodName String slot = methodAnnotation.slot(); if (slot == null || slot.length() == 0) slot = _method.getName(); /* security declarations */ final GoSecurityInfo si = _goCls != null ? _goCls.securityInfo() : null; if (si != null) { if (methodAnnotation.isPrivate()) { si.declarePrivate(slot); if (methodAnnotation.protectedBy() != null || methodAnnotation.isPublic()) { log.error("declared a method private which also has a " + "another protection (ProtectedBy or Public): " + slot); } } else if (methodAnnotation.protectedBy() != null) { si.declareProtected(methodAnnotation.protectedBy(), slot); if (methodAnnotation.isPublic()) { log.error("declared a method protected which also has a " + "another protection (Public): " + slot); } } else if (methodAnnotation.isPublic()) si.declarePublic(slot); } final GoJavaMethod m = new GoJavaMethod(slot, _method); return m; } /* description */ public void appendAttributesToDescription(final StringBuilder _d) { super.appendAttributesToDescription(_d); if (this.nameToClass != null) { _d.append(" classes=" + UString.componentsJoinedByString(this.nameToClass.keySet().toArray(), ",")); } if (this.application != null) _d.append(" app=" + this.application); } }