/* * Copyright (C) 2013 Glencoe Software, Inc. All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. */ package omero.cmd.fs; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Hashtable; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.TreeMap; import loci.formats.IFormatReader; import loci.formats.ImageReader; import ome.api.IQuery; import ome.io.nio.PixelsService; import ome.model.annotations.FileAnnotation; import ome.model.core.Image; import ome.model.core.Pixels; import ome.model.fs.Fileset; import ome.parameters.Parameters; import omero.RType; import omero.cmd.ERR; import omero.cmd.Helper; import omero.cmd.IRequest; import omero.cmd.OriginalMetadataRequest; import omero.cmd.OriginalMetadataResponse; import omero.cmd.Response; import omero.cmd.HandleI.Cancel; import omero.constants.annotation.file.ORIGINALMETADATA; import omero.constants.namespaces.NSCOMPANIONFILE; import omero.util.IceMapper; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Maps; /** * Original metadata loader, handling both pre-FS and post-FS data. * * @author Josh Moore, josh at glencoesoftware.com * @since 5.0.0 */ public class OriginalMetadataRequestI extends OriginalMetadataRequest implements IRequest { private static final long serialVersionUID = -1L; private final OriginalMetadataResponse rsp = new OriginalMetadataResponse(); private final PixelsService pixelsService; private Helper helper; public OriginalMetadataRequestI(PixelsService pixelsService) { this.pixelsService = pixelsService; } // // CMD API // public Map<String, String> getCallContext() { Map<String, String> all = new HashMap<String, String>(); all.put("omero.group", "-1"); return all; } public void init(Helper helper) { this.helper = helper; this.helper.setSteps(1); } public Object step(int step) { helper.assertStep(step); loadFileset(); if (rsp.filesetId == null) { loadFileAnnotation(); } return null; } @Override public void finish() throws Cancel { // no-op } public void buildResponse(int step, Object object) { helper.assertResponse(step); if (helper.isLast(step)) { helper.setResponseIfNull(rsp); } } public Response getResponse() { return helper.getResponse(); } // // LOADING & PARSING // /** * Searches for a {@link Fileset} attached to this {@link Image}, and if present, * uses Bio-Formats to parse the metadata into the {@link OriginalMetadataResponse} * instance. If no {@link Fileset} is present, then there <em>may</em> be a * {@link FileAnnotation} present which has a static version of the metadata. */ protected void loadFileset() { rsp.filesetId = firstIdOrNull("select i.fileset.id from Image i where i.id = :id"); if (rsp.filesetId != null) { final Image image = helper.getServiceFactory().getQueryService().get(Image.class, imageId); final Pixels pixels = image.getPrimaryPixels(); try { final IFormatReader reader = pixelsService.getBfReader(pixels); final Hashtable<String, Object> global = reader.getGlobalMetadata(); final Hashtable<String, Object> series = reader.getSeriesMetadata(); rsp.globalMetadata = wrap(global); rsp.seriesMetadata = wrap(series); } catch (Throwable t) { helper.cancel(new ERR(), t, "bf-reader-failure", "pixels", ""+pixels.getId()); } } } /** * Only called if {@link #loadFileset()} finds no {@link Fileset}. If any {@link FileAnnotation} * instances with the appropriate namespace and name are found, the first one is taken and * parsed into the {@link OriginalMetadataResponse}. */ protected void loadFileAnnotation() { rsp.fileAnnotationId = firstIdOrNull( "select a.id from Image i join i.annotationLinks l join l.child a " + "where i.id = :id and a.file.name = '" + ORIGINALMETADATA.value + "' " + "and a.ns = '" + NSCOMPANIONFILE.value + "'"); if (rsp.fileAnnotationId != null) { final IQuery iQuery = helper.getServiceFactory().getQueryService(); final FileAnnotation fileAnnotation = iQuery.get(FileAnnotation.class, rsp.fileAnnotationId.getValue()); final String filePath = pixelsService.getFilesPath(fileAnnotation.getFile().getId()); parseOriginalMetadataTxt(new File(filePath)); } } // // HELPERS // /** * Use {@link IQuery#projection(String, Parameters)} to load the first * long which matches the given query. This means that the first return * value in the select statement should likely be the id of an object. */ protected omero.RLong firstIdOrNull(String query) { List<Object[]> ids = helper.getServiceFactory().getQueryService() .projection(query, new Parameters().addId(imageId).page(0, 1)); if (ids != null && ids.size() > 0) { Object[] id = ids.get(0); if (id != null && id.length > 0) { return omero.rtypes.rlong((Long) id[0]); } } return null; } /** * Use {@link IceMapper} to convert from {@link Object} instances in * the given {@link Hashtable} to {@link RType} instances. This may * throw an exception on unknown types. */ protected Map<String, RType> wrap(Hashtable<String, Object> table) { final Map<String, RType> rv = new HashMap<String, RType>(); if (table == null || table.size() == 0) { return rv; } final IceMapper mapper = new IceMapper(); for (Entry<String, Object> entry : table.entrySet()) { String key = entry.getKey(); Object val = entry.getValue(); try { if (val instanceof Short) { // Likely could be handled in toRType rv.put(key, mapper.toRType(((Short) val).intValue())); } else { rv.put(key, mapper.toRType(val)); } } catch (Exception e) { String msg = String.format("Could not convert to rtype: " + "key=%s, value=%s, type=%s ", key, val, (val == null ? "null" : val.getClass())); if (helper == null) { // from command-line System.err.println(msg); } else { helper.warn(msg); } } } return rv; } /** * Split the given string at the rightmost '=' character among those that are the least enclosed by some kind of bracket. * @param keyValue the key = value string * @return the extracted key and value, or <code>null</code> if there is no '=' character */ private static Map.Entry<String, String> splitOnEquals(String keyValue) { Integer equalsIndex = null; Integer equalsSmallestDepth = null; int currentIndex = 0; int currentDepth = 0; while (currentIndex < keyValue.length()) { switch (keyValue.charAt(currentIndex)) { case '(': case '[': case '{': currentDepth++; break; case ')': case ']': case '}': currentDepth--; break; case '=': if (equalsSmallestDepth == null || currentDepth <= equalsSmallestDepth) { equalsIndex = currentIndex; equalsSmallestDepth = currentDepth; } break; } currentIndex++; } if (equalsIndex == null) { return null; } else { return Maps.immutableEntry(keyValue.substring(0, equalsIndex).trim(), keyValue.substring(equalsIndex + 1).trim()); } } /** * Read the given INI-style file and populate the maps with the properties from the corresponding sections. * @param file the file to read */ protected void parseOriginalMetadataTxt(File file) { final Pattern section = Pattern.compile("\\s*\\[\\s*(.+?)\\s*\\]\\s*"); rsp.globalMetadata = new TreeMap<String, RType>(); rsp.seriesMetadata = new TreeMap<String, RType>(); final ImmutableMap<String, Map<String, RType>> sections = ImmutableMap.of("GlobalMetadata", rsp.globalMetadata, "SeriesMetadata", rsp.seriesMetadata); Map<String, RType> currentSection = null; BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(new FileInputStream(file), "UTF-8")); while (true) { String line; line = in.readLine(); if (line == null) { break; } Matcher matcher; if ((matcher = section.matcher(line)).matches()) { currentSection = sections.get(matcher.group(1)); } else if (currentSection != null) { final Entry<String, String> keyValue = splitOnEquals(line); if (keyValue != null) { currentSection.put(keyValue.getKey(), omero.rtypes.rstring(keyValue.getValue())); } } } } catch (IOException e) { if (helper != null) { helper.cancel(new ERR(), e, "reader-failure", "original-metadata", file.getPath()); } } finally { if (in != null) { try { in.close(); } catch (IOException e) { } } } } public static void main(String[] args) throws Exception { OriginalMetadataRequestI omr = new OriginalMetadataRequestI(null); ImageReader reader = new ImageReader(); for (String file : args) { reader.setId(file); final Hashtable<String, Object> bfglobal = reader.getGlobalMetadata(); final Hashtable<String, Object> bfseries = reader.getSeriesMetadata(); Map<String, RType> global = omr.wrap(bfglobal); Map<String, RType> series = omr.wrap(bfseries); printMap("[GlobalMetadata]", global); printMap("[SeriesMetadata]", series); } reader.close(); } private static void printMap(String title, Map<String, RType> map) { System.out.println(title); for (Map.Entry<String, RType> entry : map.entrySet()) { System.out.print(entry.getKey()); System.out.print("="); System.out.print(entry.getValue()); System.out.print("\n"); } } }