• Views
  • Iteration Report
  • My Iteration Report
  •  
OMERO.server
  • Login
  • Help/Guide
  • About Trac
  • Preferences
  • Wiki
  • Timeline
  • Roadmap
  • Browse Source
  • View Tickets
  • Search

Context Navigation

  • Last Change
  • Annotate
  • Revision Log

root/trunk/components/blitz/src/omero/util/IceMapper.java

Revision 3006, 30.7 kB (checked in by jmoore, 2 months ago)

ticket:1106 - More "protected" work

  • Added object factories for Details and Permissions
  • Added accessors for Permissions.perm1
  • Propagated to tests and other code
Line 
1/*
2 *   $Id$
3 *
4 *   Copyright 2007 Glencoe Software, Inc. All rights reserved.
5 *   Use is subject to license terms supplied in LICENSE.txt
6 *
7 */
8
9package omero.util;
10
11import java.io.PrintWriter;
12import java.io.StringWriter;
13import java.lang.reflect.Array;
14import java.lang.reflect.Field;
15import java.lang.reflect.Method;
16import java.sql.Timestamp;
17import java.util.ArrayList;
18import java.util.Collection;
19import java.util.Collections;
20import java.util.Date;
21import java.util.HashMap;
22import java.util.HashSet;
23import java.util.IdentityHashMap;
24import java.util.List;
25import java.util.Map;
26import java.util.Set;
27
28import static omero.rtypes.*;
29import ome.api.IPojos;
30import ome.conditions.InternalException;
31import ome.model.IObject;
32import ome.model.ModelBased;
33import ome.system.Principal;
34import ome.system.Roles;
35import ome.util.Filterable;
36import ome.util.ModelMapper;
37import ome.util.ReverseModelMapper;
38import ome.util.Utils;
39import omeis.providers.re.RGBBuffer;
40import omeis.providers.re.data.PlaneDef;
41import omero.ApiUsageException;
42import omero.RArray;
43import omero.RBool;
44import omero.RClass;
45import omero.RDouble;
46import omero.RFloat;
47import omero.RInt;
48import omero.RInternal;
49import omero.RList;
50import omero.RLong;
51import omero.RObject;
52import omero.RSet;
53import omero.RString;
54import omero.RTime;
55import omero.RType;
56import omero.ServerError;
57import omero.romio.BlueBand;
58import omero.romio.GreenBand;
59import omero.romio.RedBand;
60import omero.romio.XY;
61import omero.romio.XZ;
62import omero.romio.ZY;
63import omero.sys.EventContext;
64import omero.sys.Filter;
65import omero.sys.Parameters;
66
67import org.apache.commons.logging.Log;
68import org.apache.commons.logging.LogFactory;
69
70import Ice.UserException;
71
72/**
73 * Responsible for the mapping of ome.* types to omero.* types and back again.
74 * Not all types are bidirectional, rather only those mappings are needed that
75 * actually appear in the blitz API.
76 *
77 * As of Beta3.1, an {@link IceMapper} instance can also be configured to handle
78 * return value mapping, though by default an exception will be thrown if
79 * {@link #mapReturnValue(Object)} is called.
80 */
81public class IceMapper extends ome.util.ModelMapper implements
82        ReverseModelMapper {
83
84    private static Log log = LogFactory.getLog(IceMapper.class);
85
86    // Return value mapping
87    // =========================================================================
88
89    private final ReturnMapping mapping;
90
91    public IceMapper() {
92        this.mapping = null;
93    }
94
95    public IceMapper(ReturnMapping mapping) {
96        this.mapping = mapping;
97    }
98
99    public interface ReturnMapping {
100
101        public Object mapReturnValue(IceMapper mapper, Object value)
102                throws Ice.UserException;
103
104    }
105
106    public final static ReturnMapping VOID = new ReturnMapping() {
107
108        public Object mapReturnValue(IceMapper mapper, Object value)
109                throws UserException {
110            if (value != null) {
111                throw new IllegalArgumentException("Method is void");
112            }
113            return null;
114        }
115
116    };
117
118    public final static ReturnMapping FILTERABLE = new ReturnMapping() {
119
120        public Object mapReturnValue(IceMapper mapper, Object value)
121                throws UserException {
122            return mapper.map((Filterable) value);
123        }
124
125    };
126
127    public final static ReturnMapping FILTERABLE_ARRAY = new ReturnMapping() {
128
129        public Object mapReturnValue(IceMapper mapper, Object value)
130                throws UserException {
131            Filterable[] array = (Filterable[]) value;
132            if (array == null) {
133                return null;
134            } else {
135                List rv = new ArrayList(array.length);
136                for (int i = 0; i < array.length; i++) {
137                    rv.add(mapper.map(array[i]));
138                }
139                return rv;
140            }
141        }
142
143    };
144
145    public final static ReturnMapping FILTERABLE_COLLECTION = new ReturnMapping() {
146
147        public Object mapReturnValue(IceMapper mapper, Object value)
148                throws UserException {
149            Collection<Filterable> coll = (Collection<Filterable>) value;
150            if (coll == null) {
151                return null;
152            } else {
153                List rv = new ArrayList();
154                for (Filterable f : coll) {
155                    rv.add(mapper.map(f));
156                }
157                return rv;
158            }
159        }
160
161    };
162
163    public final static ReturnMapping OBJECT_TO_RTYPE = new ReturnMapping() {
164        public Object mapReturnValue(IceMapper mapper, Object value)
165                throws Ice.UserException {
166            return mapper.toRType(value);
167        }
168    };
169
170    public final static ReturnMapping STRING_TO_RSTRING = new ReturnMapping() {
171
172        public Object mapReturnValue(IceMapper mapper, Object value)
173                throws Ice.UserException {
174            String str = (String) value;
175            return omero.rtypes.rstring(str);
176        }
177    };
178
179    public final static ReturnMapping PRIMITIVE = new ReturnMapping() {
180
181        public Object mapReturnValue(IceMapper mapper, Object value)
182                throws Ice.UserException {
183            if (value == null) {
184                return null;
185            } else {
186                if (!IceMapper.isNullablePrimitive(value.getClass())) {
187                    throw new RuntimeException(
188                            "Object not nullable primitive: " + value);
189                }
190                Object rv = mapper.findTarget(value);
191                return rv;
192            }
193        }
194    };
195
196    public final static ReturnMapping PRIMITIVE_MAP = new ReturnMapping() {
197
198        public Object mapReturnValue(IceMapper mapper, Object value)
199                throws Ice.UserException {
200            if (value == null) {
201                return null;
202            } else {
203                Map map = (Map) value;
204                Map rv = new HashMap();
205                for (Object k : map.keySet()) {
206                    Object v = map.get(k);
207                    if (k != null
208                            && !IceMapper.isNullablePrimitive(k.getClass())) {
209                        throw new RuntimeException(
210                                "Key not nullable primitive: " + k);
211                    }
212                    if (v != null
213                            && !IceMapper.isNullablePrimitive(v.getClass())) {
214                        throw new RuntimeException(
215                                "Object not nullable primitive: " + v);
216                    }
217                    Object kr = mapper.findTarget(k);
218                    Object vr = mapper.findTarget(v);
219                    rv.put(kr, vr);
220                }
221                return rv;
222            }
223        }
224    };
225
226    /**
227     * Returns true only if the current mapping is the {@link #VOID} mapping.
228     */
229    public boolean isVoid() {
230        return canMapReturnValue() && mapping == VOID;
231    }
232
233    /**
234     * True if this instance has a {@link ReturnMapping}
235     */
236    public boolean canMapReturnValue() {
237        return mapping != null;
238    }
239
240    /**
241     * Convert the given Object via the set {@link ReturnMapping}. Throws a
242     * {@link NullPointException} if no mapping is set.
243     */
244    public Object mapReturnValue(Object value) throws Ice.UserException {
245        return mapping.mapReturnValue(this, value);
246    }
247
248    // Exception handling
249    // =========================================================================
250
251    public static ServerError fillServerError(ServerError se, Throwable t) {
252        se.message = t.getMessage();
253        se.serverExceptionClass = t.getClass().getName();
254        se.serverStackTrace = stackAsString(t);
255        return se;
256    }
257
258    public static String stackAsString(Throwable t) {
259        StringWriter sw = new StringWriter();
260        PrintWriter pw = new PrintWriter(sw);
261        t.printStackTrace(pw);
262        Throwable cause = t.getCause();
263        while (cause != null && cause != t) {
264            cause.printStackTrace(pw);
265            t = cause;
266            cause = t.getCause();
267        }
268        pw.flush();
269        pw.close();
270
271        return sw.getBuffer().toString();
272    }
273
274    // Classes
275    // =========================================================================
276
277    private static Class<? extends IObject> _class(String className) {
278        Class k = null;
279        try {
280            k = Class.forName(className);
281        } catch (Exception e) {
282            // ok
283        }
284        return k;
285    }
286
287    public static Class<? extends IObject> omeroClass(String className,
288            boolean strict) throws ApiUsageException {
289
290        Class k = _class(className);
291
292        // If that didn't work, try to prefix with "omero.model"
293        if (k == null) {
294            k = _class("omero.model." + className);
295        }
296
297        // If either of those attempts worked, map it with IceMap
298        if (k != null) {
299            k = IceMap.OMEROtoOME.get(k);
300        }
301
302        // For whatever reason, it's not valid. Log it.
303        if (k == null) {
304            if (log.isDebugEnabled()) {
305                log.debug(className + " does not specify a valid class.");
306            }
307        }
308
309        if (k == null && strict) {
310            ApiUsageException aue = new ApiUsageException();
311            aue.message = className + " does not specify a valid class.";
312            throw aue;
313        }
314
315        // Return, even null.
316        return k;
317    }
318
319    // Conversions
320    // =========================================================================
321
322    public RType toRType(Object o) throws omero.ApiUsageException {
323        if (o == null) {
324            return null;
325        } else if (o instanceof RType) {
326            return (RType) o;
327        } else if (o instanceof Date) {
328            Date date = (Date) o;
329            omero.RTime time = rtime(date.getTime());
330            return time;
331        } else if (o instanceof Integer) {
332            Integer i = (Integer) o;
333            omero.RInt rint = rint(i);
334            return rint;
335        } else if (o instanceof Long) {
336            Long lng = (Long) o;
337            omero.RLong rlng = rlong(lng.longValue());
338            return rlng;
339        } else if (o instanceof Float) {
340            Float flt = (Float) o;
341            omero.RFloat rflt = rfloat(flt);
342            return rflt;
343        } else if (o instanceof Double) {
344            Double dbl = (Double) o;
345            omero.RDouble rdbl = rdouble(dbl.doubleValue());
346            return rdbl;
347        } else if (o instanceof String) {
348            String str = (String) o;
349            omero.RString rstr = rstring(str);
350            return rstr;
351        } else if (o instanceof IObject) {
352            IObject obj = (IObject) o;
353            omero.model.IObject om = (omero.model.IObject) map(obj);
354            omero.RObject robj = robject(om);
355            return robj;
356        } else if (o instanceof Collection) {
357            return rlist(map((Collection) o));
358        } else if (o instanceof Map) {
359            return rmap(map((Map) o));
360        } else if (o instanceof omero.Internal) {
361            return rinternal((omero.Internal) o);
362        } else {
363            throw new ApiUsageException(null, null,
364                    "Unsupported conversion to rtype from:" + o);
365        }
366    }
367
368    /**
369     * Uses the omero.rtypes hierarchy to properly convert any {@link RType} to
370     * its internal representation. This requires that the instance properly
371     * implement {@link omero.rtypes.Conversion} otherwise ApiUsageException
372     * will be thrown.
373     *
374     * @param rt
375     * @return
376     * @throws omero.ApiUsageException
377     */
378    public Object fromRType(RType rt) throws omero.ApiUsageException {
379
380        if (rt == null) {
381            return null;
382        }
383
384        if (rt instanceof omero.rtypes.Conversion) {
385            omero.rtypes.Conversion conv = (omero.rtypes.Conversion) rt;
386            return conv.convert(this);
387        } else {
388            omero.ApiUsageException aue = new omero.ApiUsageException();
389            aue.message = rt.getClass() + " is not a conversion type";
390            throw aue;
391        }
392
393    }
394
395    public static EventContext convert(ome.system.EventContext ctx) {
396        EventContext ec = new EventContext();
397        ec.sessionId = ctx.getCurrentSessionId();
398        ec.sessionUuid = ctx.getCurrentSessionUuid();
399        ec.eventId = ctx.getCurrentEventId();
400        ec.eventType = ctx.getCurrentEventType();
401        ec.groupId = ctx.getCurrentGroupId();
402        ec.groupName = ctx.getCurrentGroupName();
403        ec.userId = ctx.getCurrentUserId();
404        ec.userName = ctx.getCurrentUserName();
405        ec.leaderOfGroups = ctx.getLeaderOfGroupsList();
406        ec.memberOfGroups = ctx.getMemberOfGroupsList();
407        ec.isAdmin = ctx.isCurrentUserAdmin();
408        ec.isReadOnly = ctx.isReadOnly();
409        return ec;
410    }
411
412    public static omero.romio.RGBBuffer convert(RGBBuffer buffer) {
413        omero.romio.RGBBuffer b = new omero.romio.RGBBuffer();
414        b.bands = new byte[3][];
415        b.bands[RedBand.value] = buffer.getRedBand();
416        b.bands[GreenBand.value] = buffer.getGreenBand();
417        b.bands[BlueBand.value] = buffer.getBlueBand();
418        b.sizeX1 = buffer.getSizeX1();
419        b.sizeX2 = buffer.getSizeX2();
420        return b;
421    }
422
423    public static PlaneDef convert(omero.romio.PlaneDef def)
424            throws omero.ApiUsageException {
425        PlaneDef pd = new PlaneDef(def.slice, def.t);
426        switch (def.slice) {
427        case XY.value:
428            pd.setZ(def.z);
429            break;
430        case ZY.value:
431            pd.setX(def.x);
432            break;
433        case XZ.value:
434            pd.setY(def.y);
435            break;
436        default:
437            omero.ApiUsageException aue = new omero.ApiUsageException();
438            aue.message = "Unknown slice for " + def;
439            throw aue;
440        }
441
442        return pd;
443    }
444
445    public static Principal convert(omero.sys.Principal old) {
446        if (old == null) {
447            return null;
448        }
449        return new Principal(old.name, old.group, old.eventType);
450    }
451
452    public static omero.sys.Roles convert(Roles roles) {
453        omero.sys.Roles r = new omero.sys.Roles();
454        r.rootId = roles.getRootId();
455        r.rootName = roles.getRootName();
456        r.systemGroupId = roles.getSystemGroupId();
457        r.systemGroupName = roles.getSystemGroupName();
458        r.userGroupId = roles.getUserGroupId();
459        r.userGroupName = roles.getUserGroupName();
460        r.guestGroupName = roles.getGuestGroupName();
461        return r;
462    }
463
464    public static RTime convert(Date date) {
465        return rtime(date);
466    }
467
468    public static Timestamp convert(RTime time) {
469        if (time == null) {
470            return null;
471        }
472        return new Timestamp(time.getValue());
473    }
474
475    public ome.parameters.Parameters convert(Parameters params)
476            throws ApiUsageException {
477
478        if (params == null) {
479            return null;
480        }
481
482        ome.parameters.Parameters p = new ome.parameters.Parameters();
483        if (params.map != null) {
484            for (String name : params.map.keySet()) {
485                Object obj = params.map.get(name);
486                p.add(convert(name, obj));
487            }
488        }
489        if (params.theFilter != null) {
490            p.setFilter(convert(params.theFilter));
491        }
492        return p;
493    }
494
495    public ome.parameters.QueryParameter convert(String key, Object o)
496            throws ApiUsageException {
497
498        if (o == null) {
499            return null;
500        }
501
502        String name = key;
503        Class klass = o.getClass();
504        Object value = null;
505        if (RType.class.isAssignableFrom(klass)) {
506            value = fromRType((RType) o);
507            // If fromRType passes correctly, then we're sure that we
508            // can convert to rtypes.Conversion
509            klass = ((Conversion) o).type();
510        } else {
511            omero.ApiUsageException aue = new omero.ApiUsageException();
512            aue.message = "Query parameter must be a subclass of RType " + o;
513            throw aue;
514        }
515
516        ome.parameters.QueryParameter qp = new ome.parameters.QueryParameter(
517                name, klass, value);
518        return qp;
519    }
520
521    public static ome.parameters.Filter convert(Filter f) {
522
523        if (f == null) {
524            return null;
525        }
526
527        ome.parameters.Filter filter = new ome.parameters.Filter();
528
529        int offset = 0, limit = Integer.MAX_VALUE;
530        if (f.offset != null) {
531            offset = f.offset.getValue();
532        }
533        if (f.limit != null) {
534            limit = f.limit.getValue();
535        }
536        filter.page(offset, limit);
537
538        if (f.ownerId != null) {
539            filter.owner(f.ownerId.getValue());
540        }
541
542        if (f.groupId != null) {
543            filter.group(f.groupId.getValue());
544        }
545
546        if (f.unique) {
547            filter.unique();
548        }
549
550        return filter;
551    }
552
553    /**
554     * Overrides the findCollection logic of {@link ModelMapper}, since all
555     * {@link Collection}s should be {@link List}s in Ice.
556     *
557     * Originally necessitated by the Map<Long, Set<IObject>> return value of
558     * {@link IPojos#findAnnotations(Class, Set, Set, Map)}
559     */
560    @Override
561    public Collection findCollection(Collection source) {
562        if (source == null) {
563            return null;
564        }
565
566        Collection target = (Collection) model2target.get(source);
567        if (null == target) {
568            target = new ArrayList();
569            model2target.put(source, target);
570        }
571        return target;
572    }
573
574    public List map(Filterable[] array) {
575        if (array == null) {
576            return null;
577        } else if (array.length == 0) {
578            return new ArrayList();
579        } else {
580            List l = new ArrayList(array.length);
581            for (int i = 0; i < array.length; i++) {
582                l.add(map(array[i]));
583            }
584            return l;
585        }
586    }
587
588    // ~ For Reversing (omero->ome). Copied from ReverseModelMapper.
589    // =========================================================================
590
591    protected Map target2model = new IdentityHashMap();
592
593    public ome.model.internal.Permissions convert(omero.model.Permissions p) {
594        if (p == null) {
595            return null;
596        }
597        return Utils.toPermissions(p.getPerm1());
598    }
599
600    // TODO copied with ModelMapper
601    public boolean isImmutable(Object obj) {
602        if (null == obj || obj instanceof Number || obj instanceof Number[]
603                || obj instanceof String || obj instanceof String[]
604                || obj instanceof Boolean || obj instanceof Boolean[]) {
605            return true;
606        }
607        return false;
608    }
609
610    public Object reverse(Object source) throws ApiUsageException {
611        if (source == null) {
612            return null;
613        } else if (Collection.class.isAssignableFrom(source.getClass())) {
614            return reverse((Collection) source);
615        } else if (ModelBased.class.isAssignableFrom(source.getClass())) {
616            return reverse((ModelBased) source);
617        } else if (isImmutable(source)) {
618            return source;
619        } else if (RType.class.isAssignableFrom(source.getClass())) {
620            return fromRType((RType) source);
621        } else {
622            omero.ApiUsageException aue = new omero.ApiUsageException();
623            aue.message = "Don't know how to reverse " + source;
624            throw aue;
625        }
626
627    }
628
629    /**
630     * Copied from {@link ModelMapper#findCollection(Collection)} This could be
631     * unified in that a method findCollection(Collection, Map) was added with
632     * {@link ModelMapper} calling findCollection(source,model2target) and
633     * {@link #reverseCollection(Collection)} calling
634     * findCollection(source,target2model).
635     *
636     * @param collection
637     * @return
638     */
639    public Collection reverse(Collection source) { // FIXME throws
640        // omero.ApiUsageException {
641        return reverse(source, source == null ? null : source.getClass());
642    }
643
644    /**
645     * Creates a collection assignable to the given type. Currently only
646     * {@link Set} and {@link List} are supported, and {@link HashSet}s and
647     * {@link ArrayList}s will be returned. The need for this arose from the
648     * decision to have no {@link Set}s in the Ice Java mapping.
649     *
650     * @param source
651     * @param targetType
652     * @return
653     * @see ticket:684
654     */
655    public Collection reverse(Collection source, Class targetType) { // FIXME
656        // throws
657        // omero.ApiUsageException
658        // {
659
660        if (source == null) {
661            return null;
662        }
663
664        Collection target = (Collection) target2model.get(source);
665        if (null == target) {
666            if (Set.class.isAssignableFrom(targetType)) {
667                target = new HashSet();
668            } else if (List.class.isAssignableFrom(targetType)) {
669                target = new ArrayList();
670            } else {
671                // omero.ApiUsageException aue = new omero.ApiUsageException();
672                // aue.message = "Unknown collection type "+targetType;
673                // throw aue;
674                throw new InternalException("Unknown collection type "
675                        + targetType);
676            }
677            target2model.put(source, target);
678            try {
679                for (Object object : source) {
680                    target.add(reverse(object));
681                }
682            } catch (ApiUsageException aue) { // FIXME reverse can't throw
683                // ServerErrors!
684                convertAndThrow(aue);
685            }
686        }
687        return target;
688    }
689
690    /**
691     * Supports the separate case of reversing for arrays. See
692     * {@link #reverse(Collection, Class)} and {@link #map(Filterable[])}.
693     *
694     * @param list<