001/*
002 *  Licensed to the Apache Software Foundation (ASF) under one
003 *  or more contributor license agreements.  See the NOTICE file
004 *  distributed with this work for additional information
005 *  regarding copyright ownership.  The ASF licenses this file
006 *  to you under the Apache License, Version 2.0 (the
007 *  "License"); you may not use this file except in compliance
008 *  with the License.  You may obtain a copy of the License at
009 *
010 *        http://www.apache.org/licenses/LICENSE-2.0
011 *
012 *  Unless required by applicable law or agreed to in writing,
013 *  software distributed under the License is distributed on an
014 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 *  KIND, either express or implied.  See the License for the
016 *  specific language governing permissions and limitations
017 *  under the License.
018 */
019
020package org.apache.isis.core.metamodel.adapter.oid;
021
022import static org.hamcrest.CoreMatchers.is;
023import static org.hamcrest.CoreMatchers.not;
024import static org.hamcrest.CoreMatchers.nullValue;
025
026import java.io.IOException;
027import java.io.Serializable;
028
029import com.google.common.base.Objects;
030
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import org.apache.isis.applib.services.bookmark.Bookmark;
035import org.apache.isis.core.commons.encoding.DataInputExtended;
036import org.apache.isis.core.commons.encoding.DataOutputExtended;
037import org.apache.isis.core.commons.ensure.Ensure;
038import org.apache.isis.core.commons.matchers.IsisMatchers;
039import org.apache.isis.core.commons.url.UrlEncodingUtils;
040import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
041import org.apache.isis.core.metamodel.adapter.version.Version;
042import org.apache.isis.core.metamodel.spec.ObjectSpecId;
043
044public final class RootOidDefault implements Serializable, RootOid {
045
046    private final static Logger LOG = LoggerFactory.getLogger(RootOidDefault.class);
047
048    private static final long serialVersionUID = 1L;
049
050    private final ObjectSpecId objectSpecId;
051    private final String identifier;
052    private final State state;
053    
054    // not part of equality check
055    private Version version;
056
057    private int cachedHashCode;
058
059    
060    
061    // ////////////////////////////////////////////
062    // Constructor, factory methods
063    // ////////////////////////////////////////////
064
065    public static RootOidDefault createTransient(ObjectSpecId objectSpecId, final String identifier) {
066        return new RootOidDefault(objectSpecId, identifier, State.TRANSIENT);
067    }
068
069    public static RootOid create(Bookmark bookmark) {
070        return create(ObjectSpecId.of(bookmark.getObjectType()), bookmark.getIdentifier());
071    }
072
073    public static RootOidDefault create(ObjectSpecId objectSpecId, final String identifier) {
074        return create(objectSpecId, identifier, null);
075    }
076
077    public static RootOidDefault create(ObjectSpecId objectSpecId, final String identifier, Long versionSequence) {
078        return create(objectSpecId, identifier, versionSequence, null, null);
079    }
080
081    public static RootOidDefault create(ObjectSpecId objectSpecId, final String identifier, Long versionSequence, String versionUser) {
082        return create(objectSpecId, identifier, versionSequence, versionUser, null);
083    }
084
085    public static RootOidDefault create(ObjectSpecId objectSpecId, final String identifier, Long versionSequence, Long versionUtcTimestamp) {
086        return create(objectSpecId, identifier, versionSequence, null, versionUtcTimestamp);
087    }
088
089    public static RootOidDefault create(ObjectSpecId objectSpecId, final String identifier, Long versionSequence, String versionUser, Long versionUtcTimestamp) {
090        return new RootOidDefault(objectSpecId, identifier, State.PERSISTENT, Version.create(versionSequence, versionUser, versionUtcTimestamp));
091    }
092
093
094    
095    public RootOidDefault(ObjectSpecId objectSpecId, final String identifier, final State state) {
096        this(objectSpecId, identifier, state, (Version)null);
097    }
098
099    public RootOidDefault(ObjectSpecId objectSpecId, final String identifier, final State state, Long versionSequence) {
100        this(objectSpecId, identifier, state, versionSequence, null, null);
101    }
102
103    /**
104     * If specify version sequence, can optionally specify the user that changed the object.  This is used for informational purposes only.
105     */
106    public RootOidDefault(ObjectSpecId objectSpecId, final String identifier, final State state, Long versionSequence, String versionUser) {
107        this(objectSpecId, identifier, state, versionSequence, versionUser, null);
108    }
109
110    /**
111     * If specify version sequence, can optionally specify utc timestamp that the oid was changed.  This is used for informational purposes only.
112     */
113    public RootOidDefault(ObjectSpecId objectSpecId, final String identifier, final State state, Long versionSequence, Long versionUtcTimestamp) {
114        this(objectSpecId, identifier, state, versionSequence, null, versionUtcTimestamp);
115    }
116    
117    /**
118     * If specify version sequence, can optionally specify user and/or utc timestamp that the oid was changed.  This is used for informational purposes only.
119     */
120    public RootOidDefault(ObjectSpecId objectSpecId, final String identifier, final State state, Long versionSequence, String versionUser, Long versionUtcTimestamp) {
121        this(objectSpecId, identifier, state, Version.create(versionSequence, versionUser, versionUtcTimestamp));
122    }
123
124    public RootOidDefault(ObjectSpecId objectSpecId, final String identifier, final State state, Version version) {
125        Ensure.ensureThatArg(objectSpecId, is(not(nullValue())));
126        Ensure.ensureThatArg(identifier, is(not(nullValue())));
127        Ensure.ensureThatArg(identifier, is(not(IsisMatchers.contains("#"))), "identifier '" + identifier + "' contains a '#' symbol");
128        Ensure.ensureThatArg(identifier, is(not(IsisMatchers.contains("@"))), "identifier '" + identifier + "' contains an '@' symbol");
129        Ensure.ensureThatArg(state, is(not(nullValue())));
130        
131        this.objectSpecId = objectSpecId;
132        this.identifier = identifier;
133        this.state = state;
134        this.version = version;
135        initialized();
136    }
137
138    private void initialized() {
139        cacheState();
140    }
141
142
143    // ////////////////////////////////////////////
144    // Encodeable
145    // ////////////////////////////////////////////
146
147    public RootOidDefault(final DataInputExtended input) throws IOException {
148        final String oidStr = input.readUTF();
149        final RootOidDefault oid = getEncodingMarshaller().unmarshal(oidStr, RootOidDefault.class);
150        this.objectSpecId = oid.objectSpecId;
151        this.identifier = oid.identifier;
152        this.state = oid.state;
153        this.version = oid.version;
154        cacheState();
155    }
156
157    @Override
158    public void encode(final DataOutputExtended output) throws IOException {
159        output.writeUTF(enString(getEncodingMarshaller()));
160    }
161
162
163    /**
164     * Cannot be a reference because Oid gets serialized by wicket viewer
165     */
166    private OidMarshaller getEncodingMarshaller() {
167        return new OidMarshaller();
168    }
169
170
171    // ////////////////////////////////////////////
172    // deString'able, enString
173    // ////////////////////////////////////////////
174
175    public static RootOid deStringEncoded(final String urlEncodedOidStr, OidMarshaller oidMarshaller) {
176        final String oidStr = UrlEncodingUtils.urlDecode(urlEncodedOidStr);
177        return deString(oidStr, oidMarshaller);
178    }
179
180    public static RootOidDefault deString(final String oidStr, OidMarshaller oidMarshaller) {
181                return oidMarshaller.unmarshal(oidStr, RootOidDefault.class);
182    }
183
184    @Override
185    public String enString(OidMarshaller oidMarshaller) {
186        return oidMarshaller.marshal(this);
187    }
188
189    @Override
190    public String enStringNoVersion(OidMarshaller oidMarshaller) {
191        return oidMarshaller.marshalNoVersion(this);
192    }
193
194    // ////////////////////////////////////////////
195    // Properties
196    // ////////////////////////////////////////////
197
198    @Override
199    public ObjectSpecId getObjectSpecId() {
200        return objectSpecId;
201    }
202
203    public String getIdentifier() {
204        return identifier;
205    }
206
207    @Override
208    public boolean isTransient() {
209        return state.isTransient();
210    }
211
212    @Override
213    public boolean isViewModel() {
214        return state.isViewModel();
215    }
216    
217    @Override
218    public boolean isPersistent() {
219        return state.isPersistent();
220    }
221    
222
223    // ////////////////////////////////////////////
224    // asPersistent
225    // ////////////////////////////////////////////
226    
227    @Override
228    public RootOidDefault asPersistent(String identifier) {
229        Ensure.ensureThatState(state.isTransient(), is(true));
230        Ensure.ensureThatArg(identifier, is(not(nullValue())));
231
232        return new RootOidDefault(objectSpecId, identifier, State.PERSISTENT);
233    }
234
235
236    // ////////////////////////////////////////////
237    // Version
238    // ////////////////////////////////////////////
239
240        public Version getVersion() {
241                return version;
242        }
243
244    @Override
245    public void setVersion(Version version) {
246        this.version = version;
247    }
248
249    @Override
250    public Comparison compareAgainst(RootOid other) {
251        if(!equals(other)) {
252            return Comparison.NOT_EQUIVALENT;
253        }
254        if(getVersion() == null || other.getVersion() == null) {
255            return Comparison.EQUIVALENT_BUT_NO_VERSION_INFO;
256        }
257        return getVersion().equals(other.getVersion()) 
258                ? Comparison.EQUIVALENT_AND_UNCHANGED
259                : Comparison.EQUIVALENT_BUT_CHANGED;
260    }
261
262    // ////////////////////////////////////////////
263    // bookmark
264    // ////////////////////////////////////////////
265
266    @Override
267    public Bookmark asBookmark() {
268        final String objectType = getObjectSpecId().asString();
269        final String identifier = getIdentifier();
270        return new Bookmark(objectType, identifier);
271    }
272
273    // ////////////////////////////////////////////
274    // equals, hashCode
275    // ////////////////////////////////////////////
276
277    private void cacheState() {
278        cachedHashCode = 17;
279        cachedHashCode = 37 * cachedHashCode + objectSpecId.hashCode();
280        cachedHashCode = 37 * cachedHashCode + identifier.hashCode();
281        cachedHashCode = 37 * cachedHashCode + (isTransient() ? 0 : 1);
282    }
283
284    @Override
285    public boolean equals(final Object other) {
286        if (other == null) {
287            return false;
288        }
289        if (other == this) {
290            return true;
291        }
292        if (getClass() != other.getClass()) {
293            return false;
294        }
295        return equals((RootOid) other);
296    }
297
298    public boolean equals(final RootOid other) {
299        return Objects.equal(objectSpecId, other.getObjectSpecId()) && Objects.equal(identifier, other.getIdentifier()) && Objects.equal(isTransient(), other.isTransient());
300    }
301
302    @Override
303    public int hashCode() {
304        return cachedHashCode;
305    }
306
307    
308    // /////////////////////////////////////////////////////////
309    // toString
310    // /////////////////////////////////////////////////////////
311
312    @Override
313    public String toString() {
314        return enString(new OidMarshaller());
315    }
316
317
318    
319}