/******************************************************************************* * Copyright (c) 2007, 2010 Association for Decentralized Information Management * in Industry THTH ry. * 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: * VTT Technical Research Centre of Finland - initial API and implementation *******************************************************************************/ package org.simantics.project.management; import java.util.function.Supplier; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.simantics.databoard.binding.error.BindingException; import org.simantics.databoard.binding.error.RuntimeBindingException; import org.simantics.db.ReadGraph; import org.simantics.db.RequestProcessor; import org.simantics.db.Resource; import org.simantics.db.common.request.ResourceRead; import org.simantics.db.common.utils.Transaction; import org.simantics.db.exception.DatabaseException; import org.simantics.graph.representation.TransferableGraph1; import org.simantics.layer0.DatabaseManagementResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * GraphBundle represents a bundle graph that may exist in memory * in a OSGi Bundle Context, a P2 Bundle Pool, or in Simantics Database. * * The string representation of the version is in the following format: * / * * Here is what is said about osgi version numbers: * * Major - Differences in the major part indicate significant differences * such that backward compability is not guaranteed. * * Minor - Changes in the minor part indicate that the newer version of the * entity is backward compatible with the older version, but it * includes additional functionality and/or API. * * Service - The service part indicates the presence of bug fixes and minor * implementation (i.e., hidden) changes over previous versions. * * Qualifier - The qualifier is not interpreted by the system. Qualifiers are * compared using standard string comparison. Qualifier is determined * at build time by builder. It may be millisecond time or version * control revision number. The value is monotonically increasing. * * * The class is hash-equals-comparable. * * @author Toni Kalajainen */ public class GraphBundle implements Comparable { private static final Logger LOGGER = LoggerFactory.getLogger(GraphBundle.class); /** Versioned Id pattern */ static String ID_PATTERN_STRING = "[a-zA-Z_0-9\\-]+(?:\\.[a-zA-Z_0-9\\-]+)*"; static String VERSION_PATTERN_STRING = "(\\d+).(\\d+).(\\d+).([a-zA-Z_0-9\\-]+)"; static Pattern ID_PATTERN = Pattern.compile(ID_PATTERN_STRING); static Pattern VERSION_PATTERN = Pattern.compile(VERSION_PATTERN_STRING); static Pattern VERSIONED_ID_PATTERN = Pattern.compile("(" + ID_PATTERN_STRING + ")/" + VERSION_PATTERN_STRING + ""); /** User-friendly name */ String name; /** If {@link #graph} is null then this may be defined to fetch the data on-demand */ Supplier graphSource; /** Actual graph */ TransferableGraph1 graph; /** GraphBundle resource in database */ Resource resource; /** Graph hash code */ int hashcode; /** Id */ String id; // Version int major, minor, service; // Optional qualifier String qualifier; /** Database install Info, optional */ long[] resourceArray; /** Should this ontology be installed immutable **/ boolean immutable = true; GraphBundle() {} public GraphBundle(String name, TransferableGraph1 data, String versionedId) throws RuntimeBindingException { try { // Assert version id is correct Matcher m = VERSIONED_ID_PATTERN.matcher(versionedId); if (!m.matches()) { throw new IllegalArgumentException("Illegal VersionId \""+versionedId+"\", / is expected."); } this.name = name; this.graph = data; this.hashcode = hash(data); this.id = m.group(1); this.major = Integer.valueOf( m.group(2) ); this.minor = Integer.valueOf( m.group(3) ); if (m.group(4) != null) { this.service = Integer.valueOf( m.group(4) ); } this.qualifier = m.group(5); } catch (BindingException e) { // Unexpected throw new RuntimeBindingException(e); } } public GraphBundle(String name, TransferableGraph1 data, String id, String version) throws RuntimeBindingException { Matcher m = ID_PATTERN.matcher(id); if (!m.matches()) throw new IllegalArgumentException("Illegal Id, got \""+id+"\""); m = VERSION_PATTERN.matcher(version); if (!m.matches()) throw new IllegalArgumentException("Illegal Version, got \""+id+"\", / is expected."); try { this.name = name; this.graph = data; this.hashcode = hash(data); this.id = id; this.major = Integer.valueOf( m.group(1) ); this.minor = Integer.valueOf( m.group(2) ); this.service = Integer.valueOf( m.group(3) ); if (m.group(4) != null) { this.qualifier = m.group(4); } } catch (BindingException e) { // Unexpected throw new RuntimeBindingException(e); } } public GraphBundle(String name, Supplier source, int hashCode, String id, String version) { Matcher m = ID_PATTERN.matcher(id); if (!m.matches()) throw new IllegalArgumentException("Illegal Id, got \""+id+"\""); m = VERSION_PATTERN.matcher(version); if (!m.matches()) throw new IllegalArgumentException("Illegal Version, got \""+id+"\", / is expected."); this.name = name; this.graphSource = source; this.hashcode = hashCode; this.id = id; this.major = Integer.valueOf( m.group(1) ); this.minor = Integer.valueOf( m.group(2) ); this.service = Integer.valueOf( m.group(3) ); if (m.group(4) != null) { this.qualifier = m.group(4); } } private int hash(TransferableGraph1 data) throws BindingException { return data == null ? 0 : TransferableGraph1.BINDING.hashValue( data ); } public String getName() { return name; } @Override public int compareTo(GraphBundle o) { int cur = id.compareTo(o.id); if(cur != 0) return cur; cur = major - o.major; if(cur != 0) return cur; cur = minor - o.minor; if(cur != 0) return cur; cur = service - o.service; return cur; } /** * This method excepts {@link Transaction#readGraph()} to return a non-null * value, i.e. a database transaction must be in progress that has been * started with * {@link Transaction#startTransaction(RequestProcessor, boolean)}. * * @return * @see #getGraph(RequestProcessor) */ public TransferableGraph1 getGraph() { if (graph == null) { if (graphSource != null) { graph = graphSource.get(); } if (graph == null) { ReadGraph g = Transaction.readGraph(); if (g == null) throw new IllegalStateException("No read transaction available"); try { graph = readTg(g); } catch (DatabaseException e) { LOGGER.error("Failed to read transferable graph from " + resource, e); } } } return graph; } public TransferableGraph1 getGraph(RequestProcessor processor) { if (graph == null) { try { graph = processor.syncRequest(new ResourceRead(resource) { @Override public TransferableGraph1 perform(ReadGraph graph) throws DatabaseException { return readTg(graph); } }); } catch (DatabaseException e) { LOGGER.error("Failed to read transferable graph from " + resource, e); } } return graph; } private TransferableGraph1 readTg(ReadGraph graph) throws DatabaseException { DatabaseManagementResource DatabaseManagement = DatabaseManagementResource.getInstance(graph); return graph.getRelatedValue(resource, DatabaseManagement.HasFile, TransferableGraph1.BINDING); } public int getHashcode() { return hashcode; } @Override public int hashCode() { return 31*id.hashCode() + 7*major + 3*minor + 11*service + (qualifier!=null?13*qualifier.hashCode():0) + hashcode; } @Override public boolean equals(Object obj) { if (obj instanceof GraphBundle == false) return false; GraphBundle other = (GraphBundle) obj; if (other.hashcode != hashcode) return false; if (!other.id.equals(id)) return false; if (other.major != major) return false; if (other.minor != minor) return false; if (other.service != service) return false; if (!objectEquals(other.qualifier, qualifier )) return false; return true; } static boolean objectEquals(Object o1, Object o2) { if (o1 == o2) return true; if (o1 == null && o2 == null) return true; if (o1 == null || o2 == null) return false; return o1.equals(o2); } public boolean getImmutable() { return immutable; } public String getId() { return id; } public int getMajor() { return major; } public int getMinor() { return minor; } public int getService() { return service; } public String getQualifier() { return qualifier; } public String getVersionedId() { return id+"/"+major+"."+minor+"."+service+"."+qualifier; } @Override public String toString() { return name+", "+id+"/"+getVersionedId()+", hash="+hashcode; } public long[] getResourceArray() { return resourceArray; } public void setResourceArray(long[] resourceArray) { this.resourceArray = resourceArray; } public static void main(String[] args) { Matcher m = VERSIONED_ID_PATTERN.matcher("org.simantics.layer0/1.1.1.qualifier"); if (m.matches()) { System.out.println( m.groupCount() ); } m = VERSIONED_ID_PATTERN.matcher("org.simantics.layer0/1.1.1"); if (m.matches()) { System.out.println( m.groupCount() ); } m = VERSIONED_ID_PATTERN.matcher("org.simantics.layer0/1.1.1.200810101010"); if (m.matches()) { System.out.println( m.groupCount() ); } m = VERSIONED_ID_PATTERN.matcher("org.simantics.layer0/1.1"); if (m.matches()) { System.out.println( m.groupCount() ); } } }