/******************************************************************************* * Copyright (c) 2015 IBH SYSTEMS GmbH. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBH SYSTEMS GmbH - initial API and implementation *******************************************************************************/ package org.eclipse.packagedrone.repo.channel.apm; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.nio.charset.StandardCharsets; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.time.Instant; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.packagedrone.repo.MetaKey; import org.eclipse.packagedrone.repo.Severity; import org.eclipse.packagedrone.repo.channel.ArtifactInformation; import org.eclipse.packagedrone.repo.channel.CacheEntryInformation; import org.eclipse.packagedrone.repo.channel.ChannelState; import org.eclipse.packagedrone.repo.channel.ValidationMessage; import org.eclipse.packagedrone.repo.channel.apm.store.BlobStore; import org.eclipse.packagedrone.repo.channel.apm.store.CacheStore; import org.osgi.service.event.EventAdmin; import com.google.gson.stream.JsonReader; import com.google.gson.stream.JsonToken; public class ChannelReader implements AutoCloseable { private final InputStream stream; private final String channelId; private final EventAdmin eventAdmin; private final BlobStore store; private final CacheStore cacheStore; private final DateFormat dateFormat; private long numberOfBytes; public ChannelReader ( final InputStream stream, final String channelId, final EventAdmin eventAdmin, final BlobStore store, final CacheStore cacheStore ) { this.stream = stream; this.channelId = channelId; this.eventAdmin = eventAdmin; this.store = store; this.cacheStore = cacheStore; this.dateFormat = new SimpleDateFormat ( ChannelModelProvider.DATE_FORMAT ); } @SuppressWarnings ( "resource" ) public ModifyContextImpl read () throws IOException { this.numberOfBytes = 0; final Reader reader = new InputStreamReader ( this.stream, StandardCharsets.UTF_8 ); final ChannelState.Builder state = new ChannelState.Builder (); Boolean locked = null; Map<MetaKey, CacheEntryInformation> cacheEntries = Collections.emptyMap (); Map<String, ArtifactInformation> artifacts = Collections.emptyMap (); Map<MetaKey, String> extractedMetaData = Collections.emptyMap (); Map<MetaKey, String> providedMetaData = Collections.emptyMap (); Map<String, String> aspects = new HashMap<> (); final JsonReader jr = new JsonReader ( reader ); jr.beginObject (); while ( jr.hasNext () ) { final String name = jr.nextName (); switch ( name ) { case "description": state.setDescription ( jr.nextString () ); break; case "locked": state.setLocked ( locked = jr.nextBoolean () ); break; case "creationTimestamp": state.setCreationTimestamp ( readTime ( jr ) ); break; case "modificationTimestamp": state.setModificationTimestamp ( readTime ( jr ) ); break; case "cacheEntries": cacheEntries = readCacheEntries ( jr ); break; case "artifacts": artifacts = readArtifacts ( jr ); break; case "extractedMetaData": extractedMetaData = readMetadata ( jr ); break; case "providedMetaData": providedMetaData = readMetadata ( jr ); break; case "validationMessages": state.setValidationMessages ( readValidationMessages ( jr ) ); break; case "aspects": aspects = readAspects ( jr ); break; default: jr.skipValue (); break; } } jr.endObject (); if ( locked == null ) { throw new IOException ( "Missing values for channel" ); } // transient information state.setNumberOfArtifacts ( artifacts.size () ); state.setNumberOfBytes ( this.numberOfBytes ); // create result return new ModifyContextImpl ( this.channelId, this.eventAdmin, this.store, this.cacheStore, state.build (), aspects, artifacts, cacheEntries, extractedMetaData, providedMetaData ); } private Map<String, String> readAspects ( final JsonReader jr ) throws IOException { final Map<String, String> result = new HashMap<> (); jr.beginObject (); while ( jr.hasNext () ) { switch ( jr.nextName () ) { case "map": jr.beginObject (); while ( jr.hasNext () ) { final String id = jr.nextName (); String value = null; if ( jr.peek () == JsonToken.STRING ) { value = jr.nextString (); } else { jr.skipValue (); } result.put ( id, value ); } jr.endObject (); break; } } jr.endObject (); return result; } private Map<String, ArtifactInformation> readArtifacts ( final JsonReader jr ) throws IOException { jr.beginObject (); final Map<String, ArtifactInformation> result = new HashMap<> (); while ( jr.hasNext () ) { final String id = jr.nextName (); jr.beginObject (); String name = null; Long size = null; Instant creationTimestamp = null; String parentId = null; Set<String> childIds = Collections.emptySet (); Set<String> facets = Collections.emptySet (); String virtualizerAspectId = null; List<ValidationMessage> validationMessages = Collections.emptyList (); Map<MetaKey, String> extractedMetaData = Collections.emptyMap (); Map<MetaKey, String> providedMetaData = Collections.emptyMap (); while ( jr.hasNext () ) { final String ele = jr.nextName (); switch ( ele ) { case "name": name = jr.nextString (); break; case "size": size = jr.nextLong (); break; case "date": creationTimestamp = readTime ( jr ); break; case "parentId": parentId = jr.nextString (); break; case "childIds": childIds = readSet ( jr ); break; case "facets": facets = readSet ( jr ); break; case "virtualizerAspectId": virtualizerAspectId = jr.nextString (); break; case "extractedMetaData": extractedMetaData = readMetadata ( jr ); break; case "providedMetaData": providedMetaData = readMetadata ( jr ); break; case "validationMessages": validationMessages = readValidationMessages ( jr ); break; default: jr.skipValue (); break; } } jr.endObject (); if ( id == null || name == null || size == null || creationTimestamp == null ) { throw new IOException ( "Missing values for artifact" ); } this.numberOfBytes += size; result.put ( id, new ArtifactInformation ( id, parentId, childIds, name, size, creationTimestamp, facets, validationMessages, providedMetaData, extractedMetaData, virtualizerAspectId ) ); } jr.endObject (); return result; } private Map<MetaKey, String> readMetadata ( final JsonReader jr ) throws IOException { final Map<MetaKey, String> result = new HashMap<> (); jr.beginObject (); while ( jr.hasNext () ) { final String name = jr.nextName (); if ( jr.peek () == JsonToken.NULL ) { jr.skipValue (); } else { final String value = jr.nextString (); result.put ( MetaKey.fromString ( name ), value ); } } jr.endObject (); return result; } private List<ValidationMessage> readValidationMessages ( final JsonReader jr ) throws IOException { final List<ValidationMessage> result = new LinkedList<> (); jr.beginArray (); while ( jr.hasNext () ) { result.add ( readValidationMessage ( jr ) ); } jr.endArray (); return result; } private ValidationMessage readValidationMessage ( final JsonReader jr ) throws IOException { String aspectId = null; Severity severity = null; String message = null; Set<String> artifactIds = Collections.emptySet (); jr.beginObject (); while ( jr.hasNext () ) { final String name = jr.nextName (); switch ( name ) { case "aspectId": aspectId = jr.nextString (); break; case "severity": severity = Severity.valueOf ( jr.nextString () ); break; case "message": message = jr.nextString (); break; case "artifactIds": artifactIds = readSet ( jr ); break; } } jr.endObject (); if ( aspectId == null || severity == null || message == null ) { throw new IOException ( "Missing values in validation message" ); } return new ValidationMessage ( aspectId, severity, message, artifactIds ); } private Set<String> readSet ( final JsonReader jr ) throws IOException { final Set<String> result = new HashSet<> (); jr.beginArray (); while ( jr.hasNext () ) { result.add ( jr.nextString () ); } jr.endArray (); return result; } private Map<MetaKey, CacheEntryInformation> readCacheEntries ( final JsonReader jr ) throws IOException { final Map<MetaKey, CacheEntryInformation> result = new HashMap<> (); jr.beginObject (); while ( jr.hasNext () ) { final String entryName = jr.nextName (); jr.beginObject (); String name = null; Long size = null; String mimeType = null; Instant timestamp = null; while ( jr.hasNext () ) { final String ele = jr.nextName (); switch ( ele ) { case "name": name = jr.nextString (); break; case "size": size = jr.nextLong (); break; case "mimeType": mimeType = jr.nextString (); break; case "timestamp": timestamp = readTime ( jr ); break; default: jr.skipValue (); break; } } if ( name == null || size == null | mimeType == null || timestamp == null ) { throw new IOException ( "Invalid format" ); } jr.endObject (); final MetaKey key = MetaKey.fromString ( entryName ); result.put ( key, new CacheEntryInformation ( key, name, size, mimeType, timestamp ) ); } jr.endObject (); return result; } private Instant readTime ( final JsonReader jr ) throws IOException { final JsonToken peek = jr.peek (); if ( peek == JsonToken.NUMBER ) { return Instant.ofEpochMilli ( jr.nextLong () ); } else if ( peek == JsonToken.NULL ) { jr.nextNull (); return null; } else if ( peek == JsonToken.STRING ) { final String str = jr.nextString (); try { return Instant.ofEpochMilli ( Long.parseLong ( str ) ); } catch ( final NumberFormatException e ) { try { return this.dateFormat.parse ( str ).toInstant (); } catch ( final ParseException e2 ) { throw new IOException ( e2 ); } } } else { throw new IOException ( String.format ( "Invalid timestamp token: %s", peek ) ); } } @Override public void close () throws IOException { this.stream.close (); } }