BUG-5280: validate FrontendType regular expression
[controller.git] / opendaylight / md-sal / cds-access-api / src / main / java / org / opendaylight / controller / cluster / access / concepts / FrontendType.java
1 /*
2  * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.controller.cluster.access.concepts;
9
10 import com.google.common.annotations.Beta;
11 import com.google.common.base.MoreObjects;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Strings;
14 import com.google.common.base.Verify;
15 import java.io.DataInput;
16 import java.io.DataOutput;
17 import java.io.Externalizable;
18 import java.io.IOException;
19 import java.io.ObjectInput;
20 import java.io.ObjectOutput;
21 import java.nio.charset.StandardCharsets;
22 import java.util.regex.Pattern;
23 import javax.annotation.RegEx;
24 import org.opendaylight.yangtools.concepts.Identifier;
25
26 /**
27  * An {@link Identifier} identifying a data store frontend type, which is able to access the data store backend.
28  * Frontend implementations need to define this identifier so that multiple clients existing on a member node can be
29  * discerned.
30  *
31  * @author Robert Varga
32  */
33 @Beta
34 public final class FrontendType implements Comparable<FrontendType>, Identifier, WritableObject {
35     private static final class Proxy implements Externalizable {
36         private static final long serialVersionUID = 1L;
37         private byte[] serialized;
38
39         public Proxy() {
40             // For Externalizable
41         }
42
43         Proxy(final byte[] serialized) {
44             this.serialized = Preconditions.checkNotNull(serialized);
45         }
46
47         @Override
48         public void writeExternal(final ObjectOutput out) throws IOException {
49             out.writeInt(serialized.length);
50             out.write(serialized);
51         }
52
53         @Override
54         public void readExternal(final ObjectInput in) throws IOException {
55             serialized = new byte[in.readInt()];
56             in.readFully(serialized);
57         }
58
59         private Object readResolve() {
60             // TODO: consider caching instances here
61             return new FrontendType(new String(serialized, StandardCharsets.UTF_8), serialized);
62         }
63     }
64
65     @RegEx
66     private static final String SIMPLE_STRING_REGEX = "^[a-zA-Z0-9-_.*+:=,!~';]+$";
67     private static final Pattern SIMPLE_STRING_PATTERN = Pattern.compile(SIMPLE_STRING_REGEX);
68     private static final long serialVersionUID = 1L;
69     private final String name;
70     private volatile byte[] serialized;
71
72     private FrontendType(final String name) {
73         this.name = Preconditions.checkNotNull(name);
74     }
75
76     FrontendType(final String name, final byte[] serialized) {
77         this(name);
78         this.serialized = Verify.verifyNotNull(serialized);
79     }
80
81     /**
82      * Return a {@link FrontendType} corresponding to a string representation. Input string has constraints
83      * on what characters it can contain. It may contain the following:
84      * - US-ASCII letters and numbers
85      * - special characters: -_.*+:=,!~';
86      *
87      * @return A {@link FrontendType} instance
88      * @throws IllegalArgumentException if the string is null, empty or contains invalid characters
89      */
90     public static FrontendType forName(final String name) {
91         Preconditions.checkArgument(!Strings.isNullOrEmpty(name));
92         Preconditions.checkArgument(SIMPLE_STRING_PATTERN.matcher(name).matches(),
93             "Supplied name %s does not patch pattern %s", name, SIMPLE_STRING_REGEX);
94         return new FrontendType(name);
95     }
96
97     public static FrontendType readFrom(final DataInput in) throws IOException {
98         final byte[] serialized = new byte[in.readInt()];
99         in.readFully(serialized);
100         return new FrontendType(new String(serialized, StandardCharsets.UTF_8));
101     }
102
103     @Override
104     public void writeTo(final DataOutput out) throws IOException {
105         final byte[] serialized = getSerialized();
106         out.writeInt(serialized.length);
107         out.write(serialized);
108     }
109
110     public String getName() {
111         return name;
112     }
113
114     @Override
115     public int hashCode() {
116         return name.hashCode();
117     }
118
119     @Override
120     public boolean equals(final Object o) {
121         return this == o || (o instanceof FrontendType && name.equals(((FrontendType)o).name));
122     }
123
124     @Override
125     public int compareTo(final FrontendType o) {
126         return this == o ? 0 : name.compareTo(o.name);
127     }
128
129     @Override
130     public String toString() {
131         return MoreObjects.toStringHelper(FrontendType.class).add("name", name).toString();
132     }
133
134     private byte[] getSerialized() {
135         byte[] local = serialized;
136         if (local == null) {
137             local = name.getBytes(StandardCharsets.UTF_8);
138             serialized = local;
139         }
140         return local;
141     }
142
143     Object writeReplace() {
144         return new Proxy(getSerialized());
145     }
146 }