/* Licensed 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.riotfamily.components.model;
import java.util.Collection;
import java.util.Map;
import java.util.Set;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;
import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;
import org.hibernate.annotations.Type;
import org.riotfamily.common.hibernate.ActiveRecordBeanSupport;
import org.riotfamily.common.util.Generics;
import org.riotfamily.common.web.cache.TagCacheItems;
import org.springframework.util.Assert;
/**
* Entity that stores objects of any kind in a map. The map is serialized using
* a ContentMapMarshaller.
*/
@Entity
@Table(name="riot_contents")
@Cache(usage=CacheConcurrencyStrategy.NONSTRICT_READ_WRITE, region="components")
@TagCacheItems
public class Content extends ActiveRecordBeanSupport implements ContentMap {
private ContentMapMarshaller marshaller;
private int version;
private ContentContainer container;
private boolean dirty;
private String xml;
private boolean xmlRequiresUpdate;
private boolean unmarshalling;
private transient ContentMap map;
private transient Map<String, ContentFragment> fragments = Generics.newHashMap();
private transient Set<Object> references;
public Content() {
}
public Content(ContentContainer container) {
this.container = container;
}
public Content(Content other) {
this(other.getContainer());
setXml(other.getXml());
}
@Transient
public void setMarshaller(ContentMapMarshaller marshaller) {
this.marshaller = marshaller;
}
@Version
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
@ManyToOne(fetch=FetchType.LAZY)
public ContentContainer getContainer() {
return container;
}
void setContainer(ContentContainer container) {
this.container = container;
}
@Type(type="text")
public String getXml() {
marshal();
return xml;
}
public void setXml(String xml) {
Assert.isTrue(!xmlRequiresUpdate, "setXml() must not be called if xmlRequiresUpdate");
this.xml = xml;
this.map = null;
}
private void marshal() {
if (xml == null || xmlRequiresUpdate) {
dirty |= xmlRequiresUpdate;
references = Generics.newHashSet();
xml = marshaller.marshal(getMap());
xmlRequiresUpdate = false;
}
}
private void unmarshal() {
if (map == null) {
if (xml == null) {
map = new ContentMapImpl(this);
}
else {
Assert.isTrue(!xmlRequiresUpdate, "xmlRequiresUpdate must be false if map is null");
unmarshalling = true;
fragments.clear();
references = Generics.newHashSet();
map = marshaller.unmarshal(this, xml);
xmlRequiresUpdate = false;
unmarshalling = false;
}
}
}
public boolean isDirty() {
return dirty || xmlRequiresUpdate;
}
void setDirty(boolean dirty) {
this.dirty = dirty;
}
/**
* ContentFragments invoke this method when they are modified.
*/
void fragmentModified() {
if (!unmarshalling) {
xmlRequiresUpdate = true;
}
}
public void addReference(Object ref) {
references.add(ref);
}
@Transient
public Set<Object> getReferences() {
unmarshal();
marshal();
return references;
}
@Transient
private ContentMap getMap() {
unmarshal();
return map;
}
// -----------------------------------------------------------------------
// Implementation of the ContentFragment interface
// -----------------------------------------------------------------------
@Transient
public String getCompositeId() {
return getFragmentId();
}
@Transient
public String getFragmentId() {
return String.valueOf(getId());
}
@Transient
public String getPath() {
return null;
}
@Transient
public Content getContent() {
return this;
}
// -----------------------------------------------------------------------
// Implementation of the Map interface (delegate methods)
// -----------------------------------------------------------------------
public void clear() {
getMap().clear();
}
public boolean containsKey(Object key) {
return getMap().containsKey(key);
}
public boolean containsValue(Object value) {
return getMap().containsValue(value);
}
public Set<java.util.Map.Entry<String, Object>> entrySet() {
return getMap().entrySet();
}
public Object get(Object key) {
return getMap().get(key);
}
@Transient
public boolean isEmpty() {
return getMap().isEmpty();
}
public Set<String> keySet() {
return getMap().keySet();
}
public Object put(String key, Object value) {
return getMap().put(key, value);
}
public void putAll(Map<? extends String, ? extends Object> m) {
getMap().putAll(m);
}
public Object remove(Object key) {
return getMap().remove(key);
}
public int size() {
return getMap().size();
}
public Collection<Object> values() {
return getMap().values();
}
// -----------------------------------------------------------------------
String nextFragmentId() {
return version + "_" + fragments.size();
}
String getCompositeId(ContentFragment part) {
return getId() + "_" + part.getFragmentId();
}
void registerFragment(ContentFragment fragment) {
String id = fragment.getFragmentId();
Assert.notNull(id);
Assert.isTrue(!fragments.containsKey(id));
fragments.put(id, fragment);
}
private ContentFragment getFragment(String id) {
int i = id.indexOf('_');
if (i == -1) {
return this;
}
unmarshal();
String fragmentId = id.substring(i + 1);
ContentFragment fragment = fragments.get(fragmentId);
Assert.notNull(fragment, "Fragment " + fragmentId
+ " does not exist in Content " + getId());
return fragment;
}
// -----------------------------------------------------------------------
public static Content load(Long id) {
return load(Content.class, id);
}
@SuppressWarnings("unchecked")
public static<T extends ContentFragment> T loadFragment(String id) {
Content content = loadByFragmentId(id);
return (T) content.getFragment(id);
}
public static Content loadByFragmentId(String id) {
int i = id.indexOf('_');
String contentId = (i != -1) ? id.substring(0, i) : id;
Content content = Content.load(Long.valueOf(contentId));
Assert.notNull(content, "Could not load content for part: " + id);
return content;
}
}