001/**
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.activemq.openwire;
018
019import java.io.DataInput;
020import java.io.DataOutput;
021import java.io.IOException;
022import java.lang.reflect.Method;
023import java.util.HashMap;
024import java.util.Map;
025
026import org.apache.activemq.command.CommandTypes;
027import org.apache.activemq.command.DataStructure;
028import org.apache.activemq.command.WireFormatInfo;
029import org.apache.activemq.util.ByteSequence;
030import org.apache.activemq.util.ByteSequenceData;
031import org.apache.activemq.util.DataByteArrayInputStream;
032import org.apache.activemq.util.DataByteArrayOutputStream;
033import org.apache.activemq.util.IOExceptionSupport;
034import org.apache.activemq.wireformat.WireFormat;
035
036/**
037 *
038 *
039 */
040public final class OpenWireFormat implements WireFormat {
041
042    public static final int DEFAULT_STORE_VERSION = CommandTypes.PROTOCOL_STORE_VERSION;
043    public static final int DEFAULT_WIRE_VERSION = CommandTypes.PROTOCOL_VERSION;
044    public static final int DEFAULT_LEGACY_VERSION = CommandTypes.PROTOCOL_LEGACY_STORE_VERSION;
045    public static final long DEFAULT_MAX_FRAME_SIZE = Long.MAX_VALUE;
046
047    static final byte NULL_TYPE = CommandTypes.NULL;
048    private static final int MARSHAL_CACHE_SIZE = Short.MAX_VALUE / 2;
049    private static final int MARSHAL_CACHE_FREE_SPACE = 100;
050
051    private DataStreamMarshaller dataMarshallers[];
052    private int version;
053    private boolean stackTraceEnabled;
054    private boolean tcpNoDelayEnabled;
055    private boolean cacheEnabled;
056    private boolean tightEncodingEnabled;
057    private boolean sizePrefixDisabled;
058    private boolean maxFrameSizeEnabled = true;
059    private long maxFrameSize = DEFAULT_MAX_FRAME_SIZE;
060
061    // The following fields are used for value caching
062    private short nextMarshallCacheIndex;
063    private short nextMarshallCacheEvictionIndex;
064    private Map<DataStructure, Short> marshallCacheMap = new HashMap<DataStructure, Short>();
065    private DataStructure marshallCache[] = null;
066    private DataStructure unmarshallCache[] = null;
067    private DataByteArrayOutputStream bytesOut = new DataByteArrayOutputStream();
068    private DataByteArrayInputStream bytesIn = new DataByteArrayInputStream();
069    private WireFormatInfo preferedWireFormatInfo;
070
071    public OpenWireFormat() {
072        this(DEFAULT_STORE_VERSION);
073    }
074
075    public OpenWireFormat(int i) {
076        setVersion(i);
077    }
078
079    @Override
080    public int hashCode() {
081        return version ^ (cacheEnabled ? 0x10000000 : 0x20000000)
082               ^ (stackTraceEnabled ? 0x01000000 : 0x02000000)
083               ^ (tightEncodingEnabled ? 0x00100000 : 0x00200000)
084               ^ (sizePrefixDisabled ? 0x00010000 : 0x00020000)
085               ^ (maxFrameSizeEnabled ? 0x00010000 : 0x00020000);
086    }
087
088    public OpenWireFormat copy() {
089        OpenWireFormat answer = new OpenWireFormat(version);
090        answer.stackTraceEnabled = stackTraceEnabled;
091        answer.tcpNoDelayEnabled = tcpNoDelayEnabled;
092        answer.cacheEnabled = cacheEnabled;
093        answer.tightEncodingEnabled = tightEncodingEnabled;
094        answer.sizePrefixDisabled = sizePrefixDisabled;
095        answer.preferedWireFormatInfo = preferedWireFormatInfo;
096        answer.maxFrameSizeEnabled = maxFrameSizeEnabled;
097        return answer;
098    }
099
100    @Override
101    public boolean equals(Object object) {
102        if (object == null) {
103            return false;
104        }
105        OpenWireFormat o = (OpenWireFormat)object;
106        return o.stackTraceEnabled == stackTraceEnabled && o.cacheEnabled == cacheEnabled
107               && o.version == version && o.tightEncodingEnabled == tightEncodingEnabled
108               && o.sizePrefixDisabled == sizePrefixDisabled
109               && o.maxFrameSizeEnabled == maxFrameSizeEnabled;
110    }
111
112
113    @Override
114    public String toString() {
115        return "OpenWireFormat{version=" + version + ", cacheEnabled=" + cacheEnabled + ", stackTraceEnabled=" + stackTraceEnabled + ", tightEncodingEnabled="
116               + tightEncodingEnabled + ", sizePrefixDisabled=" + sizePrefixDisabled +  ", maxFrameSize=" + maxFrameSize + ", maxFrameSizeEnabled=" + maxFrameSizeEnabled + "}";
117        // return "OpenWireFormat{id="+id+",
118        // tightEncodingEnabled="+tightEncodingEnabled+"}";
119    }
120
121    @Override
122    public int getVersion() {
123        return version;
124    }
125
126    @Override
127    public synchronized ByteSequence marshal(Object command) throws IOException {
128
129        if (cacheEnabled) {
130            runMarshallCacheEvictionSweep();
131        }
132
133        ByteSequence sequence = null;
134        int size = 1;
135        if (command != null) {
136
137            DataStructure c = (DataStructure)command;
138            byte type = c.getDataStructureType();
139            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
140            if (dsm == null) {
141                throw new IOException("Unknown data type: " + type);
142            }
143            if (tightEncodingEnabled) {
144
145                BooleanStream bs = new BooleanStream();
146                size += dsm.tightMarshal1(this, c, bs);
147                size += bs.marshalledSize();
148
149                if(maxFrameSizeEnabled && size > maxFrameSize) {
150                    throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
151                }
152
153                bytesOut.restart(size);
154                if (!sizePrefixDisabled) {
155                    bytesOut.writeInt(size);
156                }
157                bytesOut.writeByte(type);
158                bs.marshal(bytesOut);
159                dsm.tightMarshal2(this, c, bytesOut, bs);
160                sequence = bytesOut.toByteSequence();
161
162            } else {
163                bytesOut.restart();
164                if (!sizePrefixDisabled) {
165                    bytesOut.writeInt(0); // we don't know the final size
166                    // yet but write this here for
167                    // now.
168                }
169                bytesOut.writeByte(type);
170                dsm.looseMarshal(this, c, bytesOut);
171                sequence = bytesOut.toByteSequence();
172
173                if (!sizePrefixDisabled) {
174                    size = sequence.getLength() - 4;
175                    int pos = sequence.offset;
176                    ByteSequenceData.writeIntBig(sequence, size);
177                    sequence.offset = pos;
178                }
179            }
180
181        } else {
182            bytesOut.restart(5);
183            bytesOut.writeInt(size);
184            bytesOut.writeByte(NULL_TYPE);
185            sequence = bytesOut.toByteSequence();
186        }
187
188        return sequence;
189    }
190
191    @Override
192    public synchronized Object unmarshal(ByteSequence sequence) throws IOException {
193        bytesIn.restart(sequence);
194        // DataInputStream dis = new DataInputStream(new
195        // ByteArrayInputStream(sequence));
196
197        if (!sizePrefixDisabled) {
198            int size = bytesIn.readInt();
199            if (sequence.getLength() - 4 != size) {
200                // throw new IOException("Packet size does not match marshaled
201                // size");
202            }
203
204            if (maxFrameSizeEnabled && size > maxFrameSize) {
205                throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
206            }
207        }
208
209        Object command = doUnmarshal(bytesIn);
210        // if( !cacheEnabled && ((DataStructure)command).isMarshallAware() ) {
211        // ((MarshallAware) command).setCachedMarshalledForm(this, sequence);
212        // }
213        return command;
214    }
215
216    @Override
217    public synchronized void marshal(Object o, DataOutput dataOut) throws IOException {
218
219        if (cacheEnabled) {
220            runMarshallCacheEvictionSweep();
221        }
222
223        int size = 1;
224        if (o != null) {
225
226            DataStructure c = (DataStructure)o;
227            byte type = c.getDataStructureType();
228            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
229            if (dsm == null) {
230                throw new IOException("Unknown data type: " + type);
231            }
232            if (tightEncodingEnabled) {
233                BooleanStream bs = new BooleanStream();
234                size += dsm.tightMarshal1(this, c, bs);
235                size += bs.marshalledSize();
236
237                if(maxFrameSizeEnabled && size > maxFrameSize) {
238                    throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
239                }
240
241                if (!sizePrefixDisabled) {
242                    dataOut.writeInt(size);
243                }
244
245                dataOut.writeByte(type);
246                bs.marshal(dataOut);
247                dsm.tightMarshal2(this, c, dataOut, bs);
248
249            } else {
250                DataOutput looseOut = dataOut;
251
252                if (!sizePrefixDisabled) {
253                    bytesOut.restart();
254                    looseOut = bytesOut;
255                }
256
257                looseOut.writeByte(type);
258                dsm.looseMarshal(this, c, looseOut);
259
260                if (!sizePrefixDisabled) {
261                    ByteSequence sequence = bytesOut.toByteSequence();
262                    dataOut.writeInt(sequence.getLength());
263                    dataOut.write(sequence.getData(), sequence.getOffset(), sequence.getLength());
264                }
265
266            }
267
268        } else {
269            if (!sizePrefixDisabled) {
270                dataOut.writeInt(size);
271            }
272            dataOut.writeByte(NULL_TYPE);
273        }
274    }
275
276    @Override
277    public Object unmarshal(DataInput dis) throws IOException {
278        DataInput dataIn = dis;
279        if (!sizePrefixDisabled) {
280            int size = dis.readInt();
281            if (maxFrameSizeEnabled && size > maxFrameSize) {
282                throw IOExceptionSupport.createFrameSizeException(size, maxFrameSize);
283            }
284            // int size = dis.readInt();
285            // byte[] data = new byte[size];
286            // dis.readFully(data);
287            // bytesIn.restart(data);
288            // dataIn = bytesIn;
289        }
290        return doUnmarshal(dataIn);
291    }
292
293    /**
294     * Used by NIO or AIO transports
295     */
296    public int tightMarshal1(Object o, BooleanStream bs) throws IOException {
297        int size = 1;
298        if (o != null) {
299            DataStructure c = (DataStructure)o;
300            byte type = c.getDataStructureType();
301            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
302            if (dsm == null) {
303                throw new IOException("Unknown data type: " + type);
304            }
305
306            size += dsm.tightMarshal1(this, c, bs);
307            size += bs.marshalledSize();
308        }
309        return size;
310    }
311
312    /**
313     * Used by NIO or AIO transports; note that the size is not written as part
314     * of this method.
315     */
316    public void tightMarshal2(Object o, DataOutput ds, BooleanStream bs) throws IOException {
317        if (cacheEnabled) {
318            runMarshallCacheEvictionSweep();
319        }
320
321        if (o != null) {
322            DataStructure c = (DataStructure)o;
323            byte type = c.getDataStructureType();
324            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
325            if (dsm == null) {
326                throw new IOException("Unknown data type: " + type);
327            }
328            ds.writeByte(type);
329            bs.marshal(ds);
330            dsm.tightMarshal2(this, c, ds, bs);
331        }
332    }
333
334    /**
335     * Allows you to dynamically switch the version of the openwire protocol
336     * being used.
337     *
338     * @param version
339     */
340    @Override
341    public void setVersion(int version) {
342        String mfName = "org.apache.activemq.openwire.v" + version + ".MarshallerFactory";
343        Class mfClass;
344        try {
345            mfClass = Class.forName(mfName, false, getClass().getClassLoader());
346        } catch (ClassNotFoundException e) {
347            throw (IllegalArgumentException)new IllegalArgumentException("Invalid version: " + version
348                                                                         + ", could not load " + mfName)
349                .initCause(e);
350        }
351        try {
352            Method method = mfClass.getMethod("createMarshallerMap", new Class[] {OpenWireFormat.class});
353            dataMarshallers = (DataStreamMarshaller[])method.invoke(null, new Object[] {this});
354        } catch (Throwable e) {
355            throw (IllegalArgumentException)new IllegalArgumentException(
356                                                                         "Invalid version: "
357                                                                             + version
358                                                                             + ", "
359                                                                             + mfName
360                                                                             + " does not properly implement the createMarshallerMap method.")
361                .initCause(e);
362        }
363        this.version = version;
364    }
365
366    public Object doUnmarshal(DataInput dis) throws IOException {
367        byte dataType = dis.readByte();
368        if (dataType != NULL_TYPE) {
369            DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
370            if (dsm == null) {
371                throw new IOException("Unknown data type: " + dataType);
372            }
373            Object data = dsm.createObject();
374            if (this.tightEncodingEnabled) {
375                BooleanStream bs = new BooleanStream();
376                bs.unmarshal(dis);
377                dsm.tightUnmarshal(this, data, dis, bs);
378            } else {
379                dsm.looseUnmarshal(this, data, dis);
380            }
381            return data;
382        } else {
383            return null;
384        }
385    }
386
387    // public void debug(String msg) {
388    // String t = (Thread.currentThread().getName()+" ").substring(0, 40);
389    // System.out.println(t+": "+msg);
390    // }
391    public int tightMarshalNestedObject1(DataStructure o, BooleanStream bs) throws IOException {
392        bs.writeBoolean(o != null);
393        if (o == null) {
394            return 0;
395        }
396
397        if (o.isMarshallAware()) {
398            // MarshallAware ma = (MarshallAware)o;
399            ByteSequence sequence = null;
400            // sequence=ma.getCachedMarshalledForm(this);
401            bs.writeBoolean(sequence != null);
402            if (sequence != null) {
403                return 1 + sequence.getLength();
404            }
405        }
406
407        byte type = o.getDataStructureType();
408        DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
409        if (dsm == null) {
410            throw new IOException("Unknown data type: " + type);
411        }
412        return 1 + dsm.tightMarshal1(this, o, bs);
413    }
414
415    public void tightMarshalNestedObject2(DataStructure o, DataOutput ds, BooleanStream bs)
416        throws IOException {
417        if (!bs.readBoolean()) {
418            return;
419        }
420
421        byte type = o.getDataStructureType();
422        ds.writeByte(type);
423
424        if (o.isMarshallAware() && bs.readBoolean()) {
425
426            // We should not be doing any caching
427            throw new IOException("Corrupted stream");
428            // MarshallAware ma = (MarshallAware) o;
429            // ByteSequence sequence=ma.getCachedMarshalledForm(this);
430            // ds.write(sequence.getData(), sequence.getOffset(),
431            // sequence.getLength());
432
433        } else {
434
435            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
436            if (dsm == null) {
437                throw new IOException("Unknown data type: " + type);
438            }
439            dsm.tightMarshal2(this, o, ds, bs);
440
441        }
442    }
443
444    public DataStructure tightUnmarshalNestedObject(DataInput dis, BooleanStream bs) throws IOException {
445        if (bs.readBoolean()) {
446
447            byte dataType = dis.readByte();
448            DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
449            if (dsm == null) {
450                throw new IOException("Unknown data type: " + dataType);
451            }
452            DataStructure data = dsm.createObject();
453
454            if (data.isMarshallAware() && bs.readBoolean()) {
455
456                dis.readInt();
457                dis.readByte();
458
459                BooleanStream bs2 = new BooleanStream();
460                bs2.unmarshal(dis);
461                dsm.tightUnmarshal(this, data, dis, bs2);
462
463                // TODO: extract the sequence from the dis and associate it.
464                // MarshallAware ma = (MarshallAware)data
465                // ma.setCachedMarshalledForm(this, sequence);
466
467            } else {
468                dsm.tightUnmarshal(this, data, dis, bs);
469            }
470
471            return data;
472        } else {
473            return null;
474        }
475    }
476
477    public DataStructure looseUnmarshalNestedObject(DataInput dis) throws IOException {
478        if (dis.readBoolean()) {
479
480            byte dataType = dis.readByte();
481            DataStreamMarshaller dsm = dataMarshallers[dataType & 0xFF];
482            if (dsm == null) {
483                throw new IOException("Unknown data type: " + dataType);
484            }
485            DataStructure data = dsm.createObject();
486            dsm.looseUnmarshal(this, data, dis);
487            return data;
488
489        } else {
490            return null;
491        }
492    }
493
494    public void looseMarshalNestedObject(DataStructure o, DataOutput dataOut) throws IOException {
495        dataOut.writeBoolean(o != null);
496        if (o != null) {
497            byte type = o.getDataStructureType();
498            dataOut.writeByte(type);
499            DataStreamMarshaller dsm = dataMarshallers[type & 0xFF];
500            if (dsm == null) {
501                throw new IOException("Unknown data type: " + type);
502            }
503            dsm.looseMarshal(this, o, dataOut);
504        }
505    }
506
507    public void runMarshallCacheEvictionSweep() {
508        // Do we need to start evicting??
509        while (marshallCacheMap.size() > marshallCache.length - MARSHAL_CACHE_FREE_SPACE) {
510
511            marshallCacheMap.remove(marshallCache[nextMarshallCacheEvictionIndex]);
512            marshallCache[nextMarshallCacheEvictionIndex] = null;
513
514            nextMarshallCacheEvictionIndex++;
515            if (nextMarshallCacheEvictionIndex >= marshallCache.length) {
516                nextMarshallCacheEvictionIndex = 0;
517            }
518
519        }
520    }
521
522    public Short getMarshallCacheIndex(DataStructure o) {
523        return marshallCacheMap.get(o);
524    }
525
526    public Short addToMarshallCache(DataStructure o) {
527        short i = nextMarshallCacheIndex++;
528        if (nextMarshallCacheIndex >= marshallCache.length) {
529            nextMarshallCacheIndex = 0;
530        }
531
532        // We can only cache that item if there is space left.
533        if (marshallCacheMap.size() < marshallCache.length) {
534            marshallCache[i] = o;
535            Short index = new Short(i);
536            marshallCacheMap.put(o, index);
537            return index;
538        } else {
539            // Use -1 to indicate that the value was not cached due to cache
540            // being full.
541            return new Short((short)-1);
542        }
543    }
544
545    public void setInUnmarshallCache(short index, DataStructure o) {
546
547        // There was no space left in the cache, so we can't
548        // put this in the cache.
549        if (index == -1) {
550            return;
551        }
552
553        unmarshallCache[index] = o;
554    }
555
556    public DataStructure getFromUnmarshallCache(short index) {
557        return unmarshallCache[index];
558    }
559
560    public void setStackTraceEnabled(boolean b) {
561        stackTraceEnabled = b;
562    }
563
564    public boolean isStackTraceEnabled() {
565        return stackTraceEnabled;
566    }
567
568    public boolean isTcpNoDelayEnabled() {
569        return tcpNoDelayEnabled;
570    }
571
572    public void setTcpNoDelayEnabled(boolean tcpNoDelayEnabled) {
573        this.tcpNoDelayEnabled = tcpNoDelayEnabled;
574    }
575
576    public boolean isCacheEnabled() {
577        return cacheEnabled;
578    }
579
580    public void setCacheEnabled(boolean cacheEnabled) {
581        if(cacheEnabled){
582            marshallCache = new DataStructure[MARSHAL_CACHE_SIZE];
583            unmarshallCache = new DataStructure[MARSHAL_CACHE_SIZE];
584        }
585        this.cacheEnabled = cacheEnabled;
586    }
587
588    public boolean isTightEncodingEnabled() {
589        return tightEncodingEnabled;
590    }
591
592    public void setTightEncodingEnabled(boolean tightEncodingEnabled) {
593        this.tightEncodingEnabled = tightEncodingEnabled;
594    }
595
596    public boolean isSizePrefixDisabled() {
597        return sizePrefixDisabled;
598    }
599
600    public void setSizePrefixDisabled(boolean prefixPacketSize) {
601        this.sizePrefixDisabled = prefixPacketSize;
602    }
603
604    public void setPreferedWireFormatInfo(WireFormatInfo info) {
605        this.preferedWireFormatInfo = info;
606    }
607
608    public WireFormatInfo getPreferedWireFormatInfo() {
609        return preferedWireFormatInfo;
610    }
611
612    public long getMaxFrameSize() {
613        return maxFrameSize;
614    }
615
616    public void setMaxFrameSize(long maxFrameSize) {
617        this.maxFrameSize = maxFrameSize;
618    }
619
620    public boolean isMaxFrameSizeEnabled() {
621        return maxFrameSizeEnabled;
622    }
623
624    /**
625     * Set whether the maxFrameSize check will be enabled. Note this is only applied to this format
626     * and will NOT be negotiated
627     *
628     * @param maxFrameSizeEnabled
629     */
630    public void setMaxFrameSizeEnabled(boolean maxFrameSizeEnabled) {
631        this.maxFrameSizeEnabled = maxFrameSizeEnabled;
632    }
633
634    public void renegotiateWireFormat(WireFormatInfo info) throws IOException {
635
636        if (preferedWireFormatInfo == null) {
637            throw new IllegalStateException("Wireformat cannot not be renegotiated.");
638        }
639
640        this.setVersion(min(preferedWireFormatInfo.getVersion(), info.getVersion()));
641        info.setVersion(this.getVersion());
642
643        this.setMaxFrameSize(min(preferedWireFormatInfo.getMaxFrameSize(), info.getMaxFrameSize()));
644        info.setMaxFrameSize(this.getMaxFrameSize());
645        //Note: Don't negotiate maxFrameSizeEnabled so the client and server can set independently
646
647        this.stackTraceEnabled = info.isStackTraceEnabled() && preferedWireFormatInfo.isStackTraceEnabled();
648        info.setStackTraceEnabled(this.stackTraceEnabled);
649
650        this.tcpNoDelayEnabled = info.isTcpNoDelayEnabled() && preferedWireFormatInfo.isTcpNoDelayEnabled();
651        info.setTcpNoDelayEnabled(this.tcpNoDelayEnabled);
652
653        this.cacheEnabled = info.isCacheEnabled() && preferedWireFormatInfo.isCacheEnabled();
654        info.setCacheEnabled(this.cacheEnabled);
655
656        this.tightEncodingEnabled = info.isTightEncodingEnabled()
657                                    && preferedWireFormatInfo.isTightEncodingEnabled();
658        info.setTightEncodingEnabled(this.tightEncodingEnabled);
659
660        this.sizePrefixDisabled = info.isSizePrefixDisabled()
661                                  && preferedWireFormatInfo.isSizePrefixDisabled();
662        info.setSizePrefixDisabled(this.sizePrefixDisabled);
663
664        if (cacheEnabled) {
665
666            int size = Math.min(preferedWireFormatInfo.getCacheSize(), info.getCacheSize());
667            info.setCacheSize(size);
668
669            if (size == 0) {
670                size = MARSHAL_CACHE_SIZE;
671            }
672
673            marshallCache = new DataStructure[size];
674            unmarshallCache = new DataStructure[size];
675            nextMarshallCacheIndex = 0;
676            nextMarshallCacheEvictionIndex = 0;
677            marshallCacheMap = new HashMap<DataStructure, Short>();
678        } else {
679            marshallCache = null;
680            unmarshallCache = null;
681            nextMarshallCacheIndex = 0;
682            nextMarshallCacheEvictionIndex = 0;
683            marshallCacheMap = null;
684        }
685
686    }
687
688    protected int min(int version1, int version2) {
689        if (version1 < version2 && version1 > 0 || version2 <= 0) {
690            return version1;
691        }
692        return version2;
693    }
694
695    protected long min(long version1, long version2) {
696        if (version1 < version2 && version1 > 0 || version2 <= 0) {
697            return version1;
698        }
699        return version2;
700    }
701}