/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.openjpa.persistence.graph; import java.io.Serializable; import java.util.Properties; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.ManyToMany; import javax.persistence.OneToOne; /** * Generic, directed, attributed Relation as a first-class, persistent entity. * <br> * A relation is * <ol> * <LI>generic because Relation type is parameterized with the type of vertices it links. * <LI>directed because it distinguishes the two vertices as source and target. * <LI>attributed because any arbitrary name-value pair can be associated with a relation. * </ol> * A relation is made persistence capable by annotating its generic source and target as persistent entity. * A relation is also a <em>first-class</em> entity having its own persistent identifier. * <br> * A relation is immutable in terms of its two vertices. The properties * attached to a relation, however, can change. * <br> * @param <V1> the type of <em>source</em> vertex linked by this relation. * @param <V2> the type of <em>target</em> vertex linked by this relation. * * @author Pinaki Poddar * */ @SuppressWarnings("serial") @Entity public class PersistentRelation<V1,V2> implements Relation<V1,V2>, Serializable { /** * Relation is a first class object with its own identifier. */ @Id @GeneratedValue private long id; /** * A Relation must have a non-null vertex as source. */ @OneToOne(optional=false, targetEntity=Entity.class, cascade={CascadeType.PERSIST,CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}) private V1 source; /** * A Relation may have a non-null vertex as target. */ @OneToOne(optional=true, targetEntity=Entity.class, cascade={CascadeType.PERSIST,CascadeType.MERGE, CascadeType.DETACH, CascadeType.REFRESH}) private V2 target; /** * The properties of a Relation is a set of key-value pairs and is declared as * <code>java.util.Properties</code>. * <br> * Declaring the key-value pairs as <code>java.util.Properties</code> makes OpenJPA * assume that both key and value will be stored in database as String. * This is not <em>strictly</em> correct because <code>java.util.Properties</code> * declares its key and value as <code>java.lang.Object</code>. Hence it is possible for an application * to insert key and/or value that are not a String but that type information will not be preserved in * the database. Subsequently, when loaded from database the key and value * both will appear as String and hence it becomes the application's responsibility to decode the * Strings back to the actual type. While this provision loses type information, it allows the * database record to be readable and more importantly supports query that are predicated on * (equality only) key-value pairs. * <br> * Another possibility to express key-value pair as * <br> * <code>Map<String,Serializable> attrs;</code> * <br> * This will serialize the values but preserve their types. The down-side is neither a query can be * predicated on value nor are the database records readable. * <br> * The third alternative is a Map where keys are String and values are Object * <br> * <code>Map<String,Object> attrs;</code> * This leads to the whole map being serialized as a single blob of data. */ @ManyToMany(cascade={CascadeType.ALL},fetch=FetchType.LAZY) private Properties attrs; /** * Special constructor for byte code enhancement. */ protected PersistentRelation() { } /** * A relation is immutable in terms of two vertices it connects. * * @param s source vertex must not be null. * @param t target vertex may or may not be null. */ public PersistentRelation(V1 s, V2 t) { if (s == null) throw new NullPointerException("Can not create relation from a null source vertex"); source = s; target = t; attrs = new Properties(); } /** * Gets generated persistent identity. */ public long getId() { return id; } /** * Gets the immutable source vertex. */ public V1 getSource() { return source; } /** * Gets the immutable target vertex. */ public V2 getTarget() { return target; } /** * Affirms if the given attribute is associated with this relation. */ public boolean hasAttribute(String attr) { return attrs.containsKey(attr); } /** * Gets the value of the given attribute. * * @return value of the given attribute. A null value does not distinguish whether * the attribute was set to a null value or the attribute was absent. */ public Object getAttribute(String attr) { return attrs.get(attr); } public Properties getAttributes() { return attrs; } /** * Adds the given key-value pair, overwriting any prior association to the same attribute. * * @return the same relation for fluent method-chaining */ public Relation<V1,V2> addAttribute(String attr, Object v) { attrs.put(attr, v); return this; } /** * Removes the given attribute. * * @return value of the given attribute that just has been removed. A null value does not * distinguish whether the attribute was set to a null value or the attribute was absent. */ public Relation<V1,V2> removeAttribute(String attr) { attrs.remove(attr); return this; } public String toString() { return source + "->" + target; } }